Fixed partnerui hashing and over all theming
This commit is contained in:
@@ -63,6 +63,7 @@ import {
|
||||
import { formatZAR, formatCompactZAR } from '../../lib/formatters';
|
||||
import { MessageViewer } from '../notifications/MessageViewer';
|
||||
import { stripMarkdown } from '../../lib/markdownUtils';
|
||||
import { PfmPartnerLogo } from '../common/PfmPartnerLogo';
|
||||
import { ProjectCard, EnrichedPartnerProject } from './ProjectCard';
|
||||
import { EvidenceUploadModal } from './EvidenceUploadModal';
|
||||
|
||||
@@ -499,20 +500,25 @@ export function DashboardPage() {
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="min-h-screen w-full bg-[#0F0E17] text-[#F1F1F6] flex flex-col selection:bg-[#8B5CF6]/30 selection:text-[#F1F1F6]">
|
||||
<div className="relative min-h-screen w-full bg-slate-950 text-slate-100 flex flex-col selection:bg-emerald-500/30 selection:text-slate-100">
|
||||
{/* Ambient background glow from landing page */}
|
||||
<div className="pointer-events-none fixed inset-0 z-0 bg-[radial-gradient(ellipse_80%_80%_at_50%_-20%,rgba(16,185,129,0.08),rgba(2,6,23,0))]" />
|
||||
{/* =====================================================================
|
||||
TOP NAVIGATION BAR — 72PX, FULL-WIDTH EDGE-TO-EDGE
|
||||
===================================================================== */}
|
||||
<header className="sticky top-0 z-30 w-full bg-[#0F0E17]/95 backdrop-blur-md border-b border-white/[0.06] h-[72px] flex items-center transition-all">
|
||||
<header className="sticky top-0 z-30 w-full bg-slate-950/85 backdrop-blur-md border-b border-white/[0.08] h-[72px] flex items-center transition-all">
|
||||
<div className="max-w-[1440px] mx-auto px-6 h-full flex items-center justify-between gap-4 w-full">
|
||||
{/* Left: Brand Glyph & Horizontal Navigation Links */}
|
||||
<div className="flex items-center gap-6 sm:gap-10 h-full">
|
||||
<div
|
||||
className="w-8 h-8 rounded-lg bg-white flex items-center justify-center shrink-0 cursor-pointer shadow-sm hover:opacity-90 transition-opacity"
|
||||
className="flex items-center gap-2.5 cursor-pointer group shrink-0"
|
||||
onClick={() => setActiveTab('dashboard')}
|
||||
title="Dashboard Home"
|
||||
>
|
||||
<InfinityIcon className="w-5 h-5 text-black -rotate-[30deg]" strokeWidth={2.5} />
|
||||
<PfmPartnerLogo className="h-8 w-8 drop-shadow-[0_2px_10px_rgba(16,185,129,0.25)] transition-transform group-hover:scale-105 duration-200" />
|
||||
<span className="text-base sm:text-lg font-black tracking-tight bg-gradient-to-r from-emerald-400 via-teal-200 to-cyan-300 bg-clip-text text-transparent hidden sm:inline">
|
||||
PFM Partner
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Nav Tabs */}
|
||||
@@ -526,13 +532,13 @@ export function DashboardPage() {
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`relative h-full flex items-center text-sm transition-colors cursor-pointer ${
|
||||
isActive
|
||||
? 'font-bold text-[#F1F1F6]'
|
||||
: 'font-medium text-[#9A95B0] hover:text-[#F1F1F6]'
|
||||
? 'font-bold text-slate-100'
|
||||
: 'font-medium text-slate-400 hover:text-slate-100'
|
||||
}`}
|
||||
>
|
||||
<span>{tab.label}</span>
|
||||
{isActive && (
|
||||
<span className="absolute bottom-0 left-0 right-0 h-[3px] bg-[#8B5CF6] rounded-t-[2px]" />
|
||||
<span className="absolute bottom-0 left-0 right-0 h-[2.5px] bg-gradient-to-r from-emerald-400 to-teal-400 rounded-t-[2px] shadow-[0_0_12px_rgba(16,185,129,0.6)]" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
@@ -544,19 +550,19 @@ export function DashboardPage() {
|
||||
<div className="flex items-center gap-3 sm:gap-4">
|
||||
{/* Search Input */}
|
||||
<div className="relative hidden sm:block w-48 md:w-64">
|
||||
<Search className="w-4 h-4 text-[#9A95B0] absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none" />
|
||||
<Search className="w-4 h-4 text-slate-400 absolute left-3 top-1/2 -translate-y-1/2 pointer-events-none" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search projects..."
|
||||
value={searchQuery}
|
||||
onChange={e => setSearchQuery(e.target.value)}
|
||||
className="w-full bg-[#181622] border border-white/[0.06] focus:border-[#8B5CF6]/50 rounded-xl pl-9 pr-3 py-1.5 text-xs text-[#F1F1F6] placeholder-[#9A95B0] focus:outline-none transition-colors"
|
||||
className="w-full bg-slate-900/80 border border-white/10 focus:border-emerald-500/50 rounded-xl pl-9 pr-3 py-1.5 text-xs text-slate-100 placeholder-slate-400 focus:outline-none transition-colors"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSearchQuery('')}
|
||||
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-[#9A95B0] hover:text-white"
|
||||
className="absolute right-2.5 top-1/2 -translate-y-1/2 text-slate-400 hover:text-white"
|
||||
>
|
||||
<X className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
@@ -569,9 +575,9 @@ export function DashboardPage() {
|
||||
onClick={() => refetchApplicantData()}
|
||||
title="Refresh projects"
|
||||
disabled={isRefreshingApplicantData}
|
||||
className="p-2 rounded-xl bg-[#181622] border border-white/[0.06] text-[#9A95B0] hover:text-[#F1F1F6] transition-colors cursor-pointer disabled:opacity-50"
|
||||
className="p-2 rounded-xl bg-slate-900/80 border border-white/10 text-slate-400 hover:text-emerald-400 hover:border-emerald-500/30 transition-colors cursor-pointer disabled:opacity-50"
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 ${isRefreshingApplicantData ? 'animate-spin text-[#8B5CF6]' : ''}`} />
|
||||
<RefreshCw className={`w-4 h-4 ${isRefreshingApplicantData ? 'animate-spin text-emerald-400' : ''}`} />
|
||||
</button>
|
||||
|
||||
{/* Notifications Bell */}
|
||||
@@ -582,12 +588,12 @@ export function DashboardPage() {
|
||||
setShowBellDropdown(!showBellDropdown);
|
||||
setShowUserDropdown(false);
|
||||
}}
|
||||
className="relative p-2 rounded-xl bg-[#181622] border border-white/[0.06] text-[#9A95B0] hover:text-[#F1F1F6] transition-colors cursor-pointer"
|
||||
className="relative p-2 rounded-xl bg-slate-900/80 border border-white/10 text-slate-400 hover:text-emerald-400 hover:border-emerald-500/30 transition-colors cursor-pointer"
|
||||
title="Notifications"
|
||||
>
|
||||
<Bell className="w-4 h-4" />
|
||||
{unreadCount > 0 && (
|
||||
<span className="absolute -top-1 -right-1 flex h-4 min-w-[16px] px-1 items-center justify-center rounded-full bg-[#8B5CF6] text-white text-[10px] font-bold ring-2 ring-[#0F0E17]">
|
||||
<span className="absolute -top-1 -right-1 flex h-4 min-w-[16px] px-1 items-center justify-center rounded-full bg-emerald-500 text-slate-950 text-[10px] font-bold ring-2 ring-slate-950">
|
||||
{unreadCount > 9 ? '9+' : unreadCount}
|
||||
</span>
|
||||
)}
|
||||
@@ -595,14 +601,14 @@ export function DashboardPage() {
|
||||
|
||||
{/* Bell Dropdown */}
|
||||
{showBellDropdown && (
|
||||
<div className="absolute right-0 mt-2 w-80 rounded-2xl bg-[#181622] border border-white/[0.08] shadow-2xl py-3 z-50">
|
||||
<div className="absolute right-0 mt-2 w-80 rounded-2xl bg-slate-900/95 border border-white/10 shadow-2xl backdrop-blur-xl py-3 z-50">
|
||||
<div className="px-4 pb-2 border-b border-white/[0.06] flex items-center justify-between">
|
||||
<span className="text-xs font-semibold text-[#F1F1F6]">Notifications</span>
|
||||
<span className="text-xs font-semibold text-slate-100">Notifications</span>
|
||||
{unreadCount > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => markAllAsRead()}
|
||||
className="text-[11px] text-[#8B5CF6] hover:underline cursor-pointer"
|
||||
className="text-[11px] text-emerald-400 hover:underline cursor-pointer"
|
||||
>
|
||||
Mark all as read
|
||||
</button>
|
||||
@@ -610,7 +616,7 @@ export function DashboardPage() {
|
||||
</div>
|
||||
<div className="max-h-72 overflow-y-auto divide-y divide-white/[0.04]">
|
||||
{top5Unread.length === 0 ? (
|
||||
<div className="p-4 text-center text-xs text-[#9A95B0]">No unread notifications</div>
|
||||
<div className="p-4 text-center text-xs text-slate-400">No unread notifications</div>
|
||||
) : (
|
||||
top5Unread.map(item => (
|
||||
<div
|
||||
@@ -622,8 +628,8 @@ export function DashboardPage() {
|
||||
}}
|
||||
className="p-3 hover:bg-white/[0.03] transition-colors cursor-pointer text-left"
|
||||
>
|
||||
<p className="text-xs font-semibold text-[#F1F1F6] truncate">{item.subject}</p>
|
||||
<p className="text-[11px] text-[#9A95B0] line-clamp-2 mt-0.5">{stripMarkdown(item.message)}</p>
|
||||
<p className="text-xs font-semibold text-slate-100 truncate">{item.subject}</p>
|
||||
<p className="text-[11px] text-slate-400 line-clamp-2 mt-0.5">{stripMarkdown(item.message)}</p>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
@@ -635,7 +641,7 @@ export function DashboardPage() {
|
||||
setActiveTab('messages');
|
||||
setShowBellDropdown(false);
|
||||
}}
|
||||
className="text-xs text-[#8B5CF6] hover:underline font-medium cursor-pointer"
|
||||
className="text-xs text-emerald-400 hover:underline font-medium cursor-pointer"
|
||||
>
|
||||
View all messages
|
||||
</button>
|
||||
@@ -652,22 +658,22 @@ export function DashboardPage() {
|
||||
setShowUserDropdown(!showUserDropdown);
|
||||
setShowBellDropdown(false);
|
||||
}}
|
||||
className="flex items-center gap-2 p-1.5 pl-2 rounded-xl bg-[#181622] border border-white/[0.06] hover:border-white/[0.12] transition-colors cursor-pointer"
|
||||
className="flex items-center gap-2 p-1.5 pl-2 rounded-xl bg-slate-900/80 border border-white/[0.06] hover:border-white/[0.12] transition-colors cursor-pointer"
|
||||
>
|
||||
<div className="w-6 h-6 rounded-lg bg-gradient-to-tr from-[#8B5CF6] to-indigo-600 flex items-center justify-center text-white text-[11px] font-bold">
|
||||
<div className="w-6 h-6 rounded-lg bg-gradient-to-tr from-emerald-600 via-teal-600 to-cyan-600 flex items-center justify-center text-white text-[11px] font-bold">
|
||||
{getInitials(user?.profile?.name || user?.profile?.email || 'Partner')}
|
||||
</div>
|
||||
<span className="text-xs font-medium text-[#F1F1F6] hidden md:inline max-w-[120px] truncate">
|
||||
<span className="text-xs font-medium text-slate-100 hidden md:inline max-w-[120px] truncate">
|
||||
{user?.profile?.name || 'Partner Account'}
|
||||
</span>
|
||||
<ChevronDown className="w-3.5 h-3.5 text-[#9A95B0]" />
|
||||
<ChevronDown className="w-3.5 h-3.5 text-slate-400" />
|
||||
</button>
|
||||
|
||||
{showUserDropdown && (
|
||||
<div className="absolute right-0 mt-2 w-56 rounded-2xl bg-[#181622] border border-white/[0.08] shadow-2xl p-2 z-50 text-xs">
|
||||
<div className="absolute right-0 mt-2 w-56 rounded-2xl bg-slate-900/95 border border-white/10 shadow-2xl backdrop-blur-xl p-2 z-50 text-xs">
|
||||
<div className="px-3 py-2 border-b border-white/[0.06]">
|
||||
<p className="font-semibold text-[#F1F1F6] truncate">{user?.profile?.name || 'Partner User'}</p>
|
||||
<p className="text-[11px] text-[#9A95B0] truncate">{user?.profile?.email || ''}</p>
|
||||
<p className="font-semibold text-slate-100 truncate">{user?.profile?.name || 'Partner User'}</p>
|
||||
<p className="text-[11px] text-slate-400 truncate">{user?.profile?.email || ''}</p>
|
||||
{partnerSummary.primaryInvitedOrg && (
|
||||
<p className="text-[10px] text-emerald-400 mt-1 truncate">
|
||||
{partnerSummary.primaryInvitedOrg.name}
|
||||
@@ -696,8 +702,8 @@ export function DashboardPage() {
|
||||
<main className="flex-1 w-full max-w-[1440px] mx-auto px-6 py-8">
|
||||
{isLoadingApplicantData ? (
|
||||
<div className="py-24 flex flex-col items-center justify-center space-y-4">
|
||||
<RefreshCw className="w-8 h-8 text-[#8B5CF6] animate-spin" />
|
||||
<p className="text-sm text-[#9A95B0]">Loading your funding portfolio & projects...</p>
|
||||
<RefreshCw className="w-8 h-8 text-emerald-400 animate-spin" />
|
||||
<p className="text-sm text-slate-400">Loading your funding portfolio & projects...</p>
|
||||
</div>
|
||||
) : activeTab === 'dashboard' ? (
|
||||
/* ===================================================================
|
||||
@@ -707,10 +713,10 @@ export function DashboardPage() {
|
||||
{/* Top Bar: Title & Sort Options */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
|
||||
<div>
|
||||
<h2 className="text-[20px] leading-[28px] font-bold text-[#F1F1F6]">
|
||||
<h2 className="text-[20px] leading-[28px] font-bold text-slate-100">
|
||||
Ongoing Projects
|
||||
</h2>
|
||||
<p className="text-xs text-[#9A95B0] mt-0.5">
|
||||
<p className="text-xs text-slate-400 mt-0.5">
|
||||
Click “View Details” on any project to inspect its milestones, track progress, and fulfill activity evidence requirements.
|
||||
</p>
|
||||
</div>
|
||||
@@ -720,14 +726,14 @@ export function DashboardPage() {
|
||||
<select
|
||||
value={sortBy}
|
||||
onChange={e => setSortBy(e.target.value as SortOption)}
|
||||
className="appearance-none bg-[#181622] border border-white/[0.06] rounded-xl px-3 pr-8 py-2 text-xs font-medium text-[#F1F1F6] focus:outline-none cursor-pointer transition-colors"
|
||||
className="appearance-none bg-slate-900/80 border border-white/[0.06] rounded-xl px-3 pr-8 py-2 text-xs font-medium text-slate-100 focus:outline-none cursor-pointer transition-colors"
|
||||
>
|
||||
<option value="date_added" className="bg-[#181622] text-[#F1F1F6]">Sort by date added</option>
|
||||
<option value="grant_amount" className="bg-[#181622] text-[#F1F1F6]">Sort by award value</option>
|
||||
<option value="days_left" className="bg-[#181622] text-[#F1F1F6]">Sort by days remaining</option>
|
||||
<option value="name" className="bg-[#181622] text-[#F1F1F6]">Sort by project name</option>
|
||||
<option value="date_added" className="bg-slate-900/80 text-slate-100">Sort by date added</option>
|
||||
<option value="grant_amount" className="bg-slate-900/80 text-slate-100">Sort by award value</option>
|
||||
<option value="days_left" className="bg-slate-900/80 text-slate-100">Sort by days remaining</option>
|
||||
<option value="name" className="bg-slate-900/80 text-slate-100">Sort by project name</option>
|
||||
</select>
|
||||
<ChevronDown className="w-3.5 h-3.5 text-[#9A95B0] absolute right-2.5 top-1/2 -translate-y-1/2 pointer-events-none" />
|
||||
<ChevronDown className="w-3.5 h-3.5 text-slate-400 absolute right-2.5 top-1/2 -translate-y-1/2 pointer-events-none" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -747,18 +753,18 @@ export function DashboardPage() {
|
||||
))
|
||||
) : (
|
||||
/* Empty State */
|
||||
<div className="rounded-[16px] bg-[#181622] border border-white/[0.06] p-8 sm:p-12 shadow-sm flex flex-col items-center justify-center text-center relative overflow-hidden">
|
||||
<div className="rounded-[16px] bg-slate-900/80 border border-white/[0.06] p-8 sm:p-12 shadow-sm flex flex-col items-center justify-center text-center relative overflow-hidden">
|
||||
<div className="relative mb-5">
|
||||
<div className="w-14 h-14 rounded-2xl bg-[#8B5CF6]/15 border border-[#8B5CF6]/25 text-[#8B5CF6] flex items-center justify-center shadow-lg shadow-[#8B5CF6]/10">
|
||||
<div className="w-14 h-14 rounded-2xl bg-emerald-500/15 border border-emerald-500/25 text-emerald-400 flex items-center justify-center shadow-lg shadow-emerald-500/10">
|
||||
<Sparkles className="w-7 h-7" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 className="text-[20px] font-bold text-[#F1F1F6] tracking-tight">
|
||||
<h3 className="text-[20px] font-bold text-slate-100 tracking-tight">
|
||||
{searchQuery ? 'No Matching Projects Found' : 'No Active Projects Assigned Yet'}
|
||||
</h3>
|
||||
|
||||
<p className="text-[14px] leading-[22px] font-normal text-[#9A95B0] max-w-lg mt-2">
|
||||
<p className="text-[14px] leading-[22px] font-normal text-slate-400 max-w-lg mt-2">
|
||||
{searchQuery
|
||||
? `We couldn't find any ongoing projects matching "${searchQuery}". Clear your search query to see your full workspace portfolio.`
|
||||
: "Welcome to your Partner Workspace! Your funding organisation hasn't assigned any active project records to your profile just yet. If your funding award was recently approved or updated, click below to sync and retrieve your latest project schedule."}
|
||||
@@ -769,7 +775,7 @@ export function DashboardPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSearchQuery('')}
|
||||
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl bg-white/[0.06] hover:bg-white/[0.1] text-[#F1F1F6] text-xs font-semibold transition-all cursor-pointer"
|
||||
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl bg-white/[0.06] hover:bg-white/[0.1] text-slate-100 text-xs font-semibold transition-all cursor-pointer"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
<span>Clear Search</span>
|
||||
@@ -780,7 +786,7 @@ export function DashboardPage() {
|
||||
type="button"
|
||||
onClick={() => refetchApplicantData()}
|
||||
disabled={isRefreshingApplicantData}
|
||||
className="inline-flex items-center gap-2.5 px-6 py-2.5 rounded-xl bg-[#8B5CF6] hover:bg-[#7C3AED] text-white font-semibold text-xs tracking-wide shadow-lg shadow-[#8B5CF6]/25 transition-all cursor-pointer disabled:opacity-60"
|
||||
className="inline-flex items-center gap-2.5 px-6 py-2.5 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-semibold text-xs tracking-wide shadow-lg shadow-emerald-500/25 transition-all cursor-pointer disabled:opacity-60"
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 ${isRefreshingApplicantData ? 'animate-spin' : ''}`} />
|
||||
<span>{isRefreshingApplicantData ? 'Refreshing Workspace...' : 'Refresh Projects'}</span>
|
||||
@@ -797,8 +803,8 @@ export function DashboardPage() {
|
||||
<div className="space-y-6">
|
||||
<div className="flex flex-wrap items-center justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-[20px] font-bold text-[#F1F1F6]">Messages & Communications</h1>
|
||||
<p className="text-[12px] text-[#9A95B0] mt-0.5">
|
||||
<h1 className="text-[20px] font-bold text-slate-100">Messages & Communications</h1>
|
||||
<p className="text-[12px] text-slate-400 mt-0.5">
|
||||
Direct communication with your funding programme officers.
|
||||
</p>
|
||||
</div>
|
||||
@@ -812,16 +818,16 @@ export function DashboardPage() {
|
||||
setMessageSubject('');
|
||||
setMessageBody('');
|
||||
}}
|
||||
className="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl bg-[#8B5CF6] hover:bg-[#7C3AED] text-white font-semibold text-xs shadow-lg shadow-[#8B5CF6]/20 transition-all cursor-pointer"
|
||||
className="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white font-semibold text-xs shadow-lg shadow-emerald-500/20 transition-all cursor-pointer"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
<span>New Message</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="rounded-[16px] bg-[#181622] border border-white/[0.06] divide-y divide-white/[0.06] overflow-hidden">
|
||||
<div className="rounded-[16px] bg-slate-900/80 border border-white/[0.06] divide-y divide-white/[0.06] overflow-hidden">
|
||||
{inboxNotifications.length === 0 ? (
|
||||
<div className="p-12 text-center text-[#9A95B0] text-sm">
|
||||
<div className="p-12 text-center text-slate-400 text-sm">
|
||||
No new messages in your inbox yet. Messages sent by programme managers will be displayed here.
|
||||
</div>
|
||||
) : (
|
||||
@@ -837,12 +843,12 @@ export function DashboardPage() {
|
||||
className="p-5 hover:bg-white/[0.03] transition-colors flex items-start justify-between gap-4 cursor-pointer"
|
||||
>
|
||||
<div className="flex items-start gap-4 min-w-0">
|
||||
<div className="h-10 w-10 rounded-full bg-[#8B5CF6]/15 border border-[#8B5CF6]/30 text-[#8B5CF6] font-bold text-xs flex items-center justify-center shrink-0">
|
||||
<div className="h-10 w-10 rounded-full bg-emerald-500/15 border border-[#8B5CF6]/30 text-emerald-400 font-bold text-xs flex items-center justify-center shrink-0">
|
||||
{getInitials(msg.subject || 'PFM')}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-sm font-semibold text-[#F1F1F6] truncate">
|
||||
<h3 className="text-sm font-semibold text-slate-100 truncate">
|
||||
{msg.subject || 'Programme Notification'}
|
||||
</h3>
|
||||
{isUnread && (
|
||||
@@ -851,10 +857,10 @@ export function DashboardPage() {
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-[#9A95B0] mt-1 line-clamp-2">{stripMarkdown(msg.message || '')}</p>
|
||||
<p className="text-xs text-slate-400 mt-1 line-clamp-2">{stripMarkdown(msg.message || '')}</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-xs text-[#9A95B0]/70 shrink-0">
|
||||
<span className="text-xs text-slate-400/70 shrink-0">
|
||||
{formatFullDate(msg.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -869,15 +875,15 @@ export function DashboardPage() {
|
||||
=================================================================== */
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-[20px] font-bold text-[#F1F1F6]">Contracts & Approved Awards</h1>
|
||||
<p className="text-[12px] text-[#9A95B0] mt-0.5">
|
||||
<h1 className="text-[20px] font-bold text-slate-100">Contracts & Approved Awards</h1>
|
||||
<p className="text-[12px] text-slate-400 mt-0.5">
|
||||
Review your approved funding awards and download or sign grant agreements.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{partnerSummary.contractEntries.length === 0 ? (
|
||||
<div className="col-span-full rounded-[16px] bg-[#181622] border border-white/[0.06] p-10 text-center text-sm text-[#9A95B0]">
|
||||
<div className="col-span-full rounded-[16px] bg-slate-900/80 border border-white/[0.06] p-10 text-center text-sm text-slate-400">
|
||||
No contracts have been issued to your organisation yet.
|
||||
</div>
|
||||
) : (
|
||||
@@ -887,11 +893,11 @@ export function DashboardPage() {
|
||||
return (
|
||||
<div
|
||||
key={contract.id}
|
||||
className="rounded-[16px] bg-[#181622] border border-white/[0.06] p-6 flex flex-col justify-between gap-5"
|
||||
className="rounded-[16px] bg-slate-900/80 border border-white/[0.06] p-6 flex flex-col justify-between gap-5"
|
||||
>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-xs font-semibold text-[#8B5CF6] uppercase tracking-wider">
|
||||
<span className="text-xs font-semibold text-emerald-400 uppercase tracking-wider">
|
||||
Agreement v{contractVer}
|
||||
</span>
|
||||
<span
|
||||
@@ -904,18 +910,18 @@ export function DashboardPage() {
|
||||
{isExecuted ? 'Signed & Executed' : 'Action Required: Review & Sign'}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-[#F1F1F6]">
|
||||
<h3 className="text-lg font-bold text-slate-100">
|
||||
{fundingOrganisation?.name || 'Grant Funding Agreement'}
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-4 pt-2 text-xs">
|
||||
<div>
|
||||
<p className="text-[#9A95B0]">Contract Value</p>
|
||||
<p className="text-sm font-bold text-[#F1F1F6] mt-0.5">
|
||||
<p className="text-slate-400">Contract Value</p>
|
||||
<p className="text-sm font-bold text-slate-100 mt-0.5">
|
||||
{formatZAR(contract.totalValue || award?.amount || 0)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-[#9A95B0]">Valid Period</p>
|
||||
<p className="text-slate-400">Valid Period</p>
|
||||
<p className="text-[#D1CBDD] font-medium mt-0.5">
|
||||
{formatShortDate(contract.effectiveAt)} – {formatFullDate(contract.expiresAt)}
|
||||
</p>
|
||||
@@ -927,7 +933,7 @@ export function DashboardPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleOpenSecureFile('contract', contract.id)}
|
||||
className="flex-1 py-2.5 px-4 rounded-xl bg-white/[0.06] hover:bg-white/[0.1] text-[#F1F1F6] text-xs font-semibold flex items-center justify-center gap-2 transition-colors cursor-pointer"
|
||||
className="flex-1 py-2.5 px-4 rounded-xl bg-white/[0.06] hover:bg-white/[0.1] text-slate-100 text-xs font-semibold flex items-center justify-center gap-2 transition-colors cursor-pointer"
|
||||
>
|
||||
<ExternalLink className="w-3.5 h-3.5" />
|
||||
<span>Download PDF</span>
|
||||
@@ -937,7 +943,7 @@ export function DashboardPage() {
|
||||
type="button"
|
||||
onClick={() => signContractMutation.mutate(contract)}
|
||||
disabled={signContractMutation.isPending}
|
||||
className="flex-1 py-2.5 px-4 rounded-xl bg-[#8B5CF6] hover:bg-[#7C3AED] text-white text-xs font-semibold flex items-center justify-center gap-2 transition-colors cursor-pointer disabled:opacity-50"
|
||||
className="flex-1 py-2.5 px-4 rounded-xl bg-emerald-600 hover:bg-emerald-500 text-white text-xs font-semibold flex items-center justify-center gap-2 transition-colors cursor-pointer disabled:opacity-50"
|
||||
>
|
||||
<CheckCircle2 className="w-3.5 h-3.5" />
|
||||
<span>{signContractMutation.isPending ? 'Signing...' : 'Accept & Sign'}</span>
|
||||
@@ -956,43 +962,43 @@ export function DashboardPage() {
|
||||
=================================================================== */
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-[20px] font-bold text-[#F1F1F6]">Portfolio Analytics</h1>
|
||||
<p className="text-[12px] text-[#9A95B0] mt-0.5">
|
||||
<h1 className="text-[20px] font-bold text-slate-100">Portfolio Analytics</h1>
|
||||
<p className="text-[12px] text-slate-400 mt-0.5">
|
||||
Real-time performance metrics and deliverable fulfillment rates across your assigned projects.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-6">
|
||||
{/* Card 1: Milestone Fulfillment Rate */}
|
||||
<div className="p-6 rounded-[16px] bg-[#181622] border border-white/[0.06]">
|
||||
<div className="flex items-center justify-between text-[#8B5CF6] mb-2">
|
||||
<div className="p-6 rounded-[16px] bg-slate-900/80 border border-white/[0.06]">
|
||||
<div className="flex items-center justify-between text-emerald-400 mb-2">
|
||||
<TrendingUp className="w-5 h-5" />
|
||||
<span className="text-xs font-semibold text-emerald-400">
|
||||
{portfolioStats.completedMilestones} of {portfolioStats.totalMilestones} Milestones
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-[#F1F1F6]">{portfolioStats.milestoneRate}%</p>
|
||||
<p className="text-xs text-[#9A95B0] mt-1">Milestone Completion Rate</p>
|
||||
<p className="text-2xl font-bold text-slate-100">{portfolioStats.milestoneRate}%</p>
|
||||
<p className="text-xs text-slate-400 mt-1">Milestone Completion Rate</p>
|
||||
</div>
|
||||
|
||||
{/* Card 2: Active Projects */}
|
||||
<div className="p-6 rounded-[16px] bg-[#181622] border border-white/[0.06]">
|
||||
<div className="p-6 rounded-[16px] bg-slate-900/80 border border-white/[0.06]">
|
||||
<div className="flex items-center justify-between text-teal-400 mb-2">
|
||||
<Briefcase className="w-5 h-5" />
|
||||
<span className="text-xs font-semibold text-[#8B5CF6]">Active</span>
|
||||
<span className="text-xs font-semibold text-emerald-400">Active</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-[#F1F1F6]">{portfolioStats.totalProjects} Projects</p>
|
||||
<p className="text-xs text-[#9A95B0] mt-1">Assigned Funded Projects</p>
|
||||
<p className="text-2xl font-bold text-slate-100">{portfolioStats.totalProjects} Projects</p>
|
||||
<p className="text-xs text-slate-400 mt-1">Assigned Funded Projects</p>
|
||||
</div>
|
||||
|
||||
{/* Card 3: Total Supporting Proofs */}
|
||||
<div className="p-6 rounded-[16px] bg-[#181622] border border-white/[0.06]">
|
||||
<div className="p-6 rounded-[16px] bg-slate-900/80 border border-white/[0.06]">
|
||||
<div className="flex items-center justify-between text-amber-400 mb-2">
|
||||
<AwardIcon className="w-5 h-5" />
|
||||
<span className="text-xs font-semibold text-amber-400">Audited</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-[#F1F1F6]">{portfolioStats.totalEvidenceCount} Documents</p>
|
||||
<p className="text-xs text-[#9A95B0] mt-1">Uploaded Evidence Deliverables</p>
|
||||
<p className="text-2xl font-bold text-slate-100">{portfolioStats.totalEvidenceCount} Documents</p>
|
||||
<p className="text-xs text-slate-400 mt-1">Uploaded Evidence Deliverables</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1002,16 +1008,16 @@ export function DashboardPage() {
|
||||
=================================================================== */
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-[20px] font-bold text-[#F1F1F6]">Deliverables & Evidence Documents</h1>
|
||||
<p className="text-[12px] text-[#9A95B0] mt-0.5">
|
||||
<h1 className="text-[20px] font-bold text-slate-100">Deliverables & Evidence Documents</h1>
|
||||
<p className="text-[12px] text-slate-400 mt-0.5">
|
||||
Invoices, reports, and verified deliverable proofs submitted across projects.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-[16px] bg-[#181622] border border-white/[0.06] overflow-hidden">
|
||||
<div className="rounded-[16px] bg-slate-900/80 border border-white/[0.06] overflow-hidden">
|
||||
{enrichedProjects.flatMap(p => p.evidence.map(ev => ({ ev, project: p.project }))).length === 0 ? (
|
||||
<div className="p-12 text-center space-y-3">
|
||||
<p className="text-sm text-[#9A95B0]">
|
||||
<p className="text-sm text-slate-400">
|
||||
No documents uploaded yet. Expand any project on your Dashboard to upload proof files.
|
||||
</p>
|
||||
</div>
|
||||
@@ -1028,17 +1034,17 @@ export function DashboardPage() {
|
||||
className="p-4 sm:p-5 flex items-center justify-between gap-4 hover:bg-white/[0.02]"
|
||||
>
|
||||
<div className="flex items-center gap-3.5 min-w-0">
|
||||
<div className="p-2.5 rounded-xl bg-[#8B5CF6]/10 text-[#8B5CF6]">
|
||||
<div className="p-2.5 rounded-xl bg-[#8B5CF6]/10 text-emerald-400">
|
||||
<FileText className="w-5 h-5" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-semibold text-[#F1F1F6] truncate">
|
||||
<p className="text-sm font-semibold text-slate-100 truncate">
|
||||
{EVIDENCE_TYPE_LABELS[evType] || 'Supporting Document'}{' '}
|
||||
<span className="text-xs font-normal text-[#9A95B0]">
|
||||
<span className="text-xs font-normal text-slate-400">
|
||||
(v{ev.version || 1})
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xs text-[#9A95B0] truncate mt-0.5">
|
||||
<p className="text-xs text-slate-400 truncate mt-0.5">
|
||||
Project: {project.name} • Submitted {formatFullDate(evDate)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -1046,7 +1052,7 @@ export function DashboardPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleOpenSecureFile('evidence', ev.id)}
|
||||
className="px-3.5 py-2 rounded-xl bg-white/[0.06] hover:bg-white/[0.1] text-xs font-semibold text-[#8B5CF6] flex items-center gap-1.5 shrink-0 cursor-pointer"
|
||||
className="px-3.5 py-2 rounded-xl bg-white/[0.06] hover:bg-white/[0.1] text-xs font-semibold text-emerald-400 flex items-center gap-1.5 shrink-0 cursor-pointer"
|
||||
>
|
||||
<ExternalLink className="w-3.5 h-3.5" />
|
||||
<span>View File</span>
|
||||
@@ -1177,7 +1183,7 @@ export function DashboardPage() {
|
||||
</div>
|
||||
|
||||
<div className="space-y-3 text-xs">
|
||||
<div className="flex items-center justify-between text-[#9A95B0]">
|
||||
<div className="flex items-center justify-between text-slate-400">
|
||||
<span>From: {selectedNotification.destination?.split('|')[0] || 'Programme Management Office'}</span>
|
||||
<span>{formatFullDate(selectedNotification.createdAt)}</span>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user