Redesigned dashboard
This commit is contained in:
@@ -761,25 +761,25 @@ export function DashboardPage() {
|
||||
}, [inboxNotifications, partnerSummary]);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen w-full bg-[#090D16] text-slate-100 p-3 sm:p-6 lg:p-8 selection:bg-indigo-500/30 selection:text-indigo-200">
|
||||
<div className="min-h-screen w-full bg-[#0F0E17] text-[#F1F1F6] flex flex-col selection:bg-[#8B5CF6]/30 selection:text-[#F1F1F6]">
|
||||
{/* =====================================================================
|
||||
TOP NAVIGATION BAR — STANDALONE ELEVATED CONTAINER SEPARATED FROM BODY
|
||||
TOP NAVIGATION BAR — SNAPPED TO TOP, 72PX, FULL-WIDTH EDGE-TO-EDGE
|
||||
===================================================================== */}
|
||||
<header className="mx-auto max-w-[1460px] rounded-[24px] sm:rounded-[28px] bg-[#0E1526] border border-white/[0.08] shadow-[0_15px_45px_rgba(0,0,0,0.5)] px-5 sm:px-8 py-3.5 mb-6 sm:mb-8 transition-all">
|
||||
<div className="flex flex-wrap items-center justify-between gap-4">
|
||||
<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">
|
||||
<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">
|
||||
<div className="flex items-center gap-6 sm:gap-10 h-full">
|
||||
{/* Flat black infinity icon rotated with top lobe pointing to 2 o'clock */}
|
||||
<div
|
||||
className="w-8 h-8 rounded-xl bg-white flex items-center justify-center shrink-0 cursor-pointer shadow-sm hover:opacity-90 transition-opacity"
|
||||
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"
|
||||
onClick={() => setActiveTab('dashboard')}
|
||||
title="Dashboard Home"
|
||||
>
|
||||
<InfinityIcon className="w-5 h-5 text-black -rotate-[30deg]" strokeWidth={2.75} />
|
||||
<InfinityIcon className="w-5 h-5 text-black -rotate-[30deg]" strokeWidth={2.5} />
|
||||
</div>
|
||||
|
||||
{/* Nav Tabs with bottom underline on active */}
|
||||
<nav className="hidden md:flex items-center gap-6 lg:gap-8">
|
||||
{/* Nav Tabs with bottom indicator on active */}
|
||||
<nav className="hidden md:flex items-center gap-6 h-full">
|
||||
{navTabs.map(tab => {
|
||||
const isActive = activeTab === tab.id;
|
||||
return (
|
||||
@@ -787,15 +787,15 @@ export function DashboardPage() {
|
||||
key={tab.id}
|
||||
type="button"
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`relative py-1 text-sm transition-colors cursor-pointer ${
|
||||
className={`relative h-full flex items-center text-sm transition-colors cursor-pointer ${
|
||||
isActive
|
||||
? 'font-bold text-white'
|
||||
: 'font-medium text-slate-400 hover:text-slate-200'
|
||||
? 'font-bold text-[#F1F1F6]'
|
||||
: 'font-medium text-[#9A95B0] hover:text-[#F1F1F6]'
|
||||
}`}
|
||||
>
|
||||
<span>{tab.label}</span>
|
||||
{isActive && (
|
||||
<span className="absolute -bottom-2.5 left-0 right-0 h-[2.5px] rounded-full bg-slate-100" />
|
||||
<span className="absolute bottom-0 left-0 right-0 h-[3px] bg-[#8B5CF6] rounded-t-[2px]" />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
@@ -805,21 +805,21 @@ export function DashboardPage() {
|
||||
|
||||
{/* Right: Search Pill + Refresh + Notification Bell + Profile */}
|
||||
<div className="flex items-center gap-3 sm:gap-4 ml-auto">
|
||||
{/* Pill Search Input */}
|
||||
<div className="relative w-44 sm:w-56 lg:w-64">
|
||||
<Search className="w-3.5 h-3.5 text-slate-400 absolute left-3.5 top-1/2 -translate-y-1/2 pointer-events-none" />
|
||||
{/* Search Input: 280px x 38px */}
|
||||
<div className="relative w-44 sm:w-56 lg:w-[280px] h-[38px]">
|
||||
<Search className="w-3.5 h-3.5 text-[#9A95B0] absolute left-3.5 top-1/2 -translate-y-1/2 pointer-events-none" />
|
||||
<input
|
||||
type="text"
|
||||
value={searchQuery}
|
||||
onChange={e => setSearchQuery(e.target.value)}
|
||||
placeholder="Search ...."
|
||||
className="w-full pl-9 pr-3 py-1.5 rounded-full bg-[#182136] border border-white/[0.07] text-xs text-slate-200 placeholder:text-slate-500 focus:outline-none focus:border-indigo-400 transition-all"
|
||||
className="w-full h-full pl-9 pr-8 rounded-[8px] bg-white/[0.05] border border-white/[0.08] text-xs text-[#F1F1F6] placeholder:text-[#9A95B0]/70 focus:outline-none focus:border-[#8B5CF6] transition-all"
|
||||
/>
|
||||
{searchQuery && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSearchQuery('')}
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-500 hover:text-slate-300"
|
||||
className="absolute right-3 top-1/2 -translate-y-1/2 text-[#9A95B0] hover:text-[#F1F1F6]"
|
||||
>
|
||||
<X className="w-3 h-3" />
|
||||
</button>
|
||||
@@ -831,9 +831,9 @@ export function DashboardPage() {
|
||||
type="button"
|
||||
onClick={() => refetchApplicantData()}
|
||||
title="Refresh Partner Data"
|
||||
className="p-2 text-slate-400 hover:text-white transition-colors cursor-pointer"
|
||||
className="p-2 text-[#9A95B0] hover:text-[#F1F1F6] transition-colors cursor-pointer"
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 ${isRefreshingApplicantData ? 'animate-spin text-indigo-400' : ''}`} />
|
||||
<RefreshCw className={`w-4 h-4 ${isRefreshingApplicantData ? 'animate-spin text-[#8B5CF6]' : ''}`} />
|
||||
</button>
|
||||
|
||||
{/* Notification Bell */}
|
||||
@@ -841,22 +841,22 @@ export function DashboardPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowBellDropdown(prev => !prev)}
|
||||
className="relative p-2 text-slate-400 hover:text-white transition-colors cursor-pointer"
|
||||
className="relative p-2 text-[#9A95B0] hover:text-[#F1F1F6] transition-colors cursor-pointer"
|
||||
aria-label="Notifications"
|
||||
>
|
||||
<Bell className="w-4 h-4" />
|
||||
{unreadCount > 0 && (
|
||||
<span className="absolute top-1.5 right-1.5 w-2 h-2 rounded-full bg-rose-500 ring-2 ring-[#0E1526]" />
|
||||
<span className="absolute top-1.5 right-1.5 w-2 h-2 rounded-full bg-[#EF4444] ring-2 ring-[#0F0E17]" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
{showBellDropdown && (
|
||||
<div className="absolute right-0 mt-3 w-80 sm:w-96 rounded-2xl bg-[#141C30] border border-white/10 shadow-2xl p-4 z-50">
|
||||
<div className="absolute right-0 mt-3 w-80 sm:w-96 rounded-2xl bg-[#181622] border border-white/[0.08] shadow-2xl p-4 z-50 text-[#F1F1F6]">
|
||||
<div className="flex items-center justify-between pb-3 border-b border-white/[0.08]">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm font-semibold text-white">Notifications</span>
|
||||
<span className="text-sm font-semibold text-[#F1F1F6]">Notifications</span>
|
||||
{unreadCount > 0 && (
|
||||
<span className="px-2 py-0.5 text-[11px] font-semibold rounded-full bg-indigo-500/20 text-indigo-300">
|
||||
<span className="px-2 py-0.5 text-[11px] font-semibold rounded-full bg-[#8B5CF6]/20 text-[#8B5CF6]">
|
||||
{unreadCount} unread
|
||||
</span>
|
||||
)}
|
||||
@@ -865,7 +865,7 @@ export function DashboardPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => markAllAsRead()}
|
||||
className="text-xs text-indigo-400 hover:text-indigo-300 font-medium cursor-pointer"
|
||||
className="text-xs text-[#8B5CF6] hover:text-[#8B5CF6]/80 font-medium cursor-pointer"
|
||||
>
|
||||
Mark all read
|
||||
</button>
|
||||
@@ -873,7 +873,7 @@ export function DashboardPage() {
|
||||
</div>
|
||||
<div className="divide-y divide-white/[0.06] max-h-72 overflow-y-auto my-2">
|
||||
{top5Unread.length === 0 ? (
|
||||
<div className="py-6 text-center text-xs text-slate-400">
|
||||
<div className="py-6 text-center text-xs text-[#9A95B0]">
|
||||
You are all caught up! No unread notifications.
|
||||
</div>
|
||||
) : (
|
||||
@@ -888,12 +888,12 @@ export function DashboardPage() {
|
||||
}}
|
||||
className="w-full text-left py-3 px-2 hover:bg-white/[0.04] rounded-lg transition-colors flex items-start gap-3 cursor-pointer"
|
||||
>
|
||||
<span className="h-2 w-2 rounded-full bg-rose-500 mt-1.5 shrink-0" />
|
||||
<span className="h-2 w-2 rounded-full bg-[#EF4444] mt-1.5 shrink-0" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-xs font-semibold text-white truncate">
|
||||
<p className="text-xs font-semibold text-[#F1F1F6] truncate">
|
||||
{item.subject || 'Partner Notification'}
|
||||
</p>
|
||||
<p className="text-xs text-slate-400 line-clamp-2 mt-0.5">
|
||||
<p className="text-xs text-[#9A95B0] line-clamp-2 mt-0.5">
|
||||
{item.message}
|
||||
</p>
|
||||
</div>
|
||||
@@ -907,7 +907,7 @@ export function DashboardPage() {
|
||||
setShowBellDropdown(false);
|
||||
setActiveTab('messages');
|
||||
}}
|
||||
className="w-full py-2 text-center text-xs font-semibold text-indigo-300 hover:text-indigo-200 bg-indigo-500/10 hover:bg-indigo-500/15 rounded-xl transition-colors cursor-pointer"
|
||||
className="w-full py-2 text-center text-xs font-semibold text-[#8B5CF6] hover:text-white bg-[#8B5CF6]/10 hover:bg-[#8B5CF6]/20 rounded-xl transition-colors cursor-pointer"
|
||||
>
|
||||
See All Messages
|
||||
</button>
|
||||
@@ -922,24 +922,24 @@ export function DashboardPage() {
|
||||
onClick={() => setShowUserDropdown(prev => !prev)}
|
||||
className="flex items-center gap-2 text-left group cursor-pointer focus:outline-none"
|
||||
>
|
||||
<div className="w-8 h-8 rounded-full bg-gradient-to-tr from-amber-400 via-rose-400 to-indigo-500 p-0.5 flex items-center justify-center shrink-0">
|
||||
<div className="w-full h-full rounded-full bg-[#0E1526] flex items-center justify-center text-[11px] font-bold text-amber-200">
|
||||
<div className="w-8 h-8 rounded-full bg-gradient-to-tr from-amber-400 via-rose-400 to-[#8B5CF6] p-0.5 flex items-center justify-center shrink-0">
|
||||
<div className="w-full h-full rounded-full bg-[#181622] flex items-center justify-center text-[11px] font-bold text-amber-200">
|
||||
{getInitials(user?.name || 'Samia Afrin Mithila')}
|
||||
</div>
|
||||
</div>
|
||||
<span className="hidden sm:inline-block text-xs font-semibold text-slate-200 group-hover:text-white transition-colors">
|
||||
<span className="hidden sm:inline-block text-xs font-semibold text-[#F1F1F6] group-hover:text-white transition-colors">
|
||||
{user?.name || 'Samia Afrin Mithila'}
|
||||
</span>
|
||||
<ChevronDown className="w-3.5 h-3.5 text-slate-400 group-hover:text-white transition-colors" />
|
||||
<ChevronDown className="w-3.5 h-3.5 text-[#9A95B0] group-hover:text-[#F1F1F6] transition-colors" />
|
||||
</button>
|
||||
|
||||
{showUserDropdown && (
|
||||
<div className="absolute right-0 mt-3 w-56 rounded-2xl bg-[#141C30] border border-white/10 shadow-2xl p-2 z-50">
|
||||
<div className="absolute right-0 mt-3 w-56 rounded-2xl bg-[#181622] border border-white/[0.08] shadow-2xl p-2 z-50 text-[#F1F1F6]">
|
||||
<div className="px-3 py-2 border-b border-white/[0.06]">
|
||||
<p className="text-xs font-bold text-white truncate">{user?.name || 'Samia Afrin Mithila'}</p>
|
||||
<p className="text-[11px] text-slate-400 truncate">{user?.email || 'partner@grantee.org'}</p>
|
||||
<p className="text-xs font-bold text-[#F1F1F6] truncate">{user?.name || 'Samia Afrin Mithila'}</p>
|
||||
<p className="text-[11px] text-[#9A95B0] truncate">{user?.email || 'partner@grantee.org'}</p>
|
||||
{partnerSummary.primaryInvitedOrg && (
|
||||
<p className="text-[10px] text-indigo-400 mt-1 truncate">
|
||||
<p className="text-[10px] text-[#8B5CF6] mt-1 truncate">
|
||||
{partnerSummary.primaryInvitedOrg.name}
|
||||
</p>
|
||||
)}
|
||||
@@ -962,7 +962,7 @@ export function DashboardPage() {
|
||||
</div>
|
||||
|
||||
{/* Mobile Navigation bar for smaller screens */}
|
||||
<div className="flex md:hidden items-center gap-2 pt-3 border-t border-white/[0.06] mt-3 overflow-x-auto">
|
||||
<div className="flex md:hidden items-center gap-2 px-6 pb-2.5 border-t border-white/[0.06] overflow-x-auto bg-[#0F0E17] w-full">
|
||||
{navTabs.map(tab => (
|
||||
<button
|
||||
key={tab.id}
|
||||
@@ -970,8 +970,8 @@ export function DashboardPage() {
|
||||
onClick={() => setActiveTab(tab.id)}
|
||||
className={`px-3 py-1.5 rounded-lg text-xs font-medium whitespace-nowrap ${
|
||||
activeTab === tab.id
|
||||
? 'bg-indigo-500/20 text-indigo-300 border border-indigo-500/30 font-bold'
|
||||
: 'text-slate-400'
|
||||
? 'bg-[#8B5CF6]/20 text-[#8B5CF6] border border-[#8B5CF6]/30 font-bold'
|
||||
: 'text-[#9A95B0]'
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
@@ -981,31 +981,31 @@ export function DashboardPage() {
|
||||
</header>
|
||||
|
||||
{/* =====================================================================
|
||||
MAIN BODY SECTION (Separated from Top Bar)
|
||||
MAIN BODY SECTION (max-w-[1440px] centered with outer ambient margin)
|
||||
===================================================================== */}
|
||||
<main className="mx-auto max-w-[1460px]">
|
||||
<main className="w-full max-w-[1440px] mx-auto px-6 py-6 sm:py-8 flex-1">
|
||||
<div>
|
||||
{isLoadingApplicantData ? (
|
||||
<div className="py-24 flex flex-col items-center justify-center gap-4 text-center">
|
||||
<div className="h-10 w-10 animate-spin rounded-full border-2 border-indigo-400 border-t-transparent" />
|
||||
<div className="h-10 w-10 animate-spin rounded-full border-2 border-[#8B5CF6] border-t-transparent" />
|
||||
<div>
|
||||
<p className="text-base font-semibold text-white">Loading Workspace...</p>
|
||||
<p className="text-xs text-slate-400 mt-1">Retrieving ongoing projects and milestones.</p>
|
||||
<p className="text-base font-semibold text-[#F1F1F6]">Loading Workspace...</p>
|
||||
<p className="text-xs text-[#9A95B0] mt-1">Retrieving ongoing projects and milestones.</p>
|
||||
</div>
|
||||
</div>
|
||||
) : activeTab === 'dashboard' ? (
|
||||
/* =================================================================
|
||||
DASHBOARD: 2-COLUMN DRIBBBLE LAYOUT (Left ~68%, Right ~32%)
|
||||
DASHBOARD: ASYMMETRICAL 2-COLUMN GRID (1fr 340px, gap 24px)
|
||||
================================================================= */
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-7 items-start">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-[1fr_340px] gap-6 items-start">
|
||||
|
||||
{/* ===============================================================
|
||||
LEFT COLUMN: ONGOING PROJECTS (8 of 12 cols)
|
||||
LEFT MAIN SECTION: ONGOING PROJECTS (~70% width)
|
||||
=============================================================== */}
|
||||
<div className="lg:col-span-8 space-y-6">
|
||||
{/* Title + Sort Row */}
|
||||
<div className="space-y-5">
|
||||
{/* Title + Sort Row: 20px / 28px / font-weight 700 / #F1F1F6 */}
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="text-2xl font-bold text-white tracking-tight">
|
||||
<h2 className="text-[20px] leading-[28px] font-bold text-[#F1F1F6]">
|
||||
Ongoing Projects
|
||||
</h2>
|
||||
|
||||
@@ -1014,19 +1014,19 @@ export function DashboardPage() {
|
||||
<select
|
||||
value={sortBy}
|
||||
onChange={e => setSortBy(e.target.value as SortOption)}
|
||||
className="appearance-none bg-transparent hover:text-white pr-6 py-1 text-xs font-medium text-slate-300 focus:outline-none cursor-pointer transition-colors"
|
||||
className="appearance-none bg-transparent hover:text-[#F1F1F6] pr-6 py-1 text-xs font-medium text-[#9A95B0] focus:outline-none cursor-pointer transition-colors"
|
||||
>
|
||||
<option value="date_added" className="bg-[#141C30] text-slate-200">Sort by date added</option>
|
||||
<option value="grant_amount" className="bg-[#141C30] text-slate-200">Sort by spend / grant</option>
|
||||
<option value="days_left" className="bg-[#141C30] text-slate-200">Sort by days left</option>
|
||||
<option value="name" className="bg-[#141C30] text-slate-200">Sort by project name</option>
|
||||
<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 spend / grant</option>
|
||||
<option value="days_left" className="bg-[#181622] text-[#F1F1F6]">Sort by days left</option>
|
||||
<option value="name" className="bg-[#181622] text-[#F1F1F6]">Sort by project name</option>
|
||||
</select>
|
||||
<ChevronDown className="w-3.5 h-3.5 text-slate-400 absolute right-0 top-1/2 -translate-y-1/2 pointer-events-none" />
|
||||
<ChevronDown className="w-3.5 h-3.5 text-[#9A95B0] absolute right-0 top-1/2 -translate-y-1/2 pointer-events-none" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Render Real Projects if available, or Showcase Projects */}
|
||||
<div className="space-y-6">
|
||||
{/* Stacked Project Cards */}
|
||||
<div className="space-y-4">
|
||||
{filteredProjects.length > 0 ? (
|
||||
filteredProjects.map((item, pIdx) => {
|
||||
const { project, award, contract, fundingOrganisation, milestones, evidence, disbursements } = item;
|
||||
@@ -1074,101 +1074,97 @@ export function DashboardPage() {
|
||||
const leadRole = 'Programme Manager';
|
||||
const avatarColors = [
|
||||
'bg-gradient-to-tr from-sky-400 to-blue-600',
|
||||
'bg-gradient-to-tr from-amber-400 to-orange-500',
|
||||
'bg-gradient-to-tr from-purple-500 to-indigo-600',
|
||||
'bg-gradient-to-tr from-amber-400 to-[#F97316]',
|
||||
'bg-gradient-to-tr from-[#8B5CF6] to-indigo-600',
|
||||
];
|
||||
|
||||
return (
|
||||
<div
|
||||
key={project.id}
|
||||
className="rounded-[22px] bg-[#141C30] border border-white/[0.07] p-6 sm:p-7 shadow-lg hover:border-slate-700 transition-all"
|
||||
className="rounded-[16px] bg-[#181622] border border-white/[0.06] p-6 shadow-sm hover:border-white/[0.12] transition-all"
|
||||
>
|
||||
<div className="flex flex-col lg:flex-row gap-6 justify-between">
|
||||
{/* Left Sub-portion (~70%): Title, Description, 4 Metrics, Horizontal Timeline */}
|
||||
<div className="flex-1 flex flex-col justify-between gap-6 min-w-0">
|
||||
<div className="flex-1 flex flex-col justify-between gap-5 min-w-0">
|
||||
<div>
|
||||
<h3 className="text-lg font-bold text-white tracking-tight">
|
||||
<h3 className="text-[18px] leading-[24px] font-semibold text-[#F1F1F6]">
|
||||
{project.name || 'Funded Project'}
|
||||
</h3>
|
||||
<p className="text-xs text-slate-400 line-clamp-2 leading-relaxed mt-1.5 max-w-2xl">
|
||||
<p className="text-[14px] leading-[22px] font-normal text-[#9A95B0] line-clamp-2 mt-1.5 max-w-2xl">
|
||||
{project.description || 'Funding award execution, deliverable submissions, and post-fund milestone tracking.'}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* 4 Metrics in a horizontal inline row */}
|
||||
{/* 4 Metrics in a horizontal inline row: 13px font-weight 500 #D1CBDD */}
|
||||
<div className="flex flex-wrap items-center gap-4 sm:gap-6 py-1">
|
||||
<div className="flex items-center gap-1.5 text-xs">
|
||||
<div className="flex items-center gap-1.5 text-[13px] font-medium text-[#D1CBDD]">
|
||||
<span className="p-1 rounded-md bg-emerald-500/10 text-emerald-400 flex items-center justify-center">
|
||||
<Hourglass className="w-3.5 h-3.5" />
|
||||
</span>
|
||||
<span className="font-bold text-white">{formatCompactZAR(grantAmount)}</span>
|
||||
<span className="text-slate-400">Award</span>
|
||||
<span className="font-semibold text-[#F1F1F6]">{formatCompactZAR(grantAmount)}</span>
|
||||
<span className="text-[#9A95B0]">Award</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1.5 text-xs">
|
||||
<span className="p-1 rounded-md bg-rose-500/10 text-rose-400 flex items-center justify-center">
|
||||
<div className="flex items-center gap-1.5 text-[13px] font-medium text-[#D1CBDD]">
|
||||
<span className="p-1 rounded-md bg-[#F97316]/10 text-[#F97316] flex items-center justify-center">
|
||||
<DollarSign className="w-3.5 h-3.5" />
|
||||
</span>
|
||||
<span className="font-bold text-white">{formatCompactZAR(disbursedAmount)}</span>
|
||||
<span className="text-slate-400">Spend</span>
|
||||
<span className="font-semibold text-[#F1F1F6]">{formatCompactZAR(disbursedAmount)}</span>
|
||||
<span className="text-[#9A95B0]">Spend</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1.5 text-xs">
|
||||
<div className="flex items-center gap-1.5 text-[13px] font-medium text-[#D1CBDD]">
|
||||
<span className="p-1 rounded-md bg-amber-500/10 text-amber-400 flex items-center justify-center">
|
||||
<Calendar className="w-3.5 h-3.5" />
|
||||
</span>
|
||||
<span className="font-bold text-white">{daysLeft}</span>
|
||||
<span className="text-slate-400">Days left</span>
|
||||
<span className="font-semibold text-[#F1F1F6]">{daysLeft}</span>
|
||||
<span className="text-[#9A95B0]">Days left</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1.5 text-xs">
|
||||
<div className="flex items-center gap-1.5 text-[13px] font-medium text-[#D1CBDD]">
|
||||
<span className="p-1 rounded-md bg-cyan-500/10 text-cyan-400 flex items-center justify-center">
|
||||
<Paperclip className="w-3.5 h-3.5" />
|
||||
</span>
|
||||
<span className="font-bold text-white">{docsSubmitted}</span>
|
||||
<span className="text-slate-400">Docs submitted</span>
|
||||
<span className="font-semibold text-[#F1F1F6]">{docsSubmitted}</span>
|
||||
<span className="text-[#9A95B0]">Docs submitted</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Horizontal Timeline Stepper */}
|
||||
{/* Horizontal Timeline Stepper: height 2px, 10px nodes */}
|
||||
<div className="pt-2 overflow-x-auto">
|
||||
<div className="min-w-[460px] flex items-start justify-between relative">
|
||||
{stepperNodes.map((step, sIdx) => {
|
||||
const isLast = sIdx === stepperNodes.length - 1;
|
||||
const isDotted = sIdx === 2; // Exact connector style from Dribbble mock
|
||||
const isDotted = sIdx === 2; // Exact connector style from reference
|
||||
return (
|
||||
<div key={step.id} className="flex-1 flex flex-col items-start relative">
|
||||
<div className="w-full flex items-center">
|
||||
{step.type === 'done' ? (
|
||||
<div className="h-5 w-5 rounded-full bg-[#1E293B] border border-indigo-400/50 text-indigo-400 flex items-center justify-center shrink-0 z-10 shadow-sm">
|
||||
<Check className="w-3 h-3 stroke-[3]" />
|
||||
</div>
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[#8B5CF6] ring-4 ring-[#8B5CF6]/20 shrink-0 z-10 shadow-sm" />
|
||||
) : step.type === 'current' ? (
|
||||
<div className="h-5 w-5 rounded-full bg-slate-900 border-2 border-indigo-400 flex items-center justify-center shrink-0 z-10">
|
||||
<div className="h-1.5 w-1.5 rounded-full bg-indigo-400 animate-pulse" />
|
||||
</div>
|
||||
<div className="w-2.5 h-2.5 rounded-full bg-[#8B5CF6] ring-4 ring-[#8B5CF6]/30 animate-pulse shrink-0 z-10" />
|
||||
) : (
|
||||
<div className="h-5 w-5 rounded-full bg-slate-900 border-2 border-slate-700 shrink-0 z-10" />
|
||||
<div className="w-2.5 h-2.5 rounded-full border border-white/30 bg-[#181622] shrink-0 z-10" />
|
||||
)}
|
||||
|
||||
{!isLast && (
|
||||
<div
|
||||
className={`flex-1 mx-2 ${
|
||||
isDotted
|
||||
? 'border-t-2 border-dotted border-slate-600'
|
||||
? 'border-t-2 border-dotted border-white/20'
|
||||
: step.type === 'done'
|
||||
? 'h-[2px] bg-slate-600'
|
||||
: 'h-[2px] bg-slate-800'
|
||||
? 'h-[2px] bg-[#8B5CF6]'
|
||||
: 'h-[2px] bg-white/10'
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-2.5 pr-2">
|
||||
<p className="text-[11px] font-semibold text-slate-200 truncate max-w-[105px]">
|
||||
<p className="text-[11px] font-medium text-[#F1F1F6] truncate max-w-[105px]">
|
||||
{step.label}
|
||||
</p>
|
||||
<p className="text-[10px] text-slate-500 mt-0.5">
|
||||
<p className="text-[10px] text-[#9A95B0] mt-0.5">
|
||||
{step.date}
|
||||
</p>
|
||||
</div>
|
||||
@@ -1179,32 +1175,32 @@ export function DashboardPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Sub-portion (~30%): Contact Lead, 3 Meta Rows, 2 Stacked Action Buttons */}
|
||||
<div className="w-full lg:w-[220px] shrink-0 lg:pl-6 lg:border-l lg:border-white/[0.07] flex flex-col justify-between gap-5 pt-4 lg:pt-0 border-t lg:border-t-0 border-white/[0.07]">
|
||||
{/* Right Sub-portion (~30% / 220px): Contact Lead, 3 Meta Rows, 2 Stacked Action Buttons */}
|
||||
<div className="w-full lg:w-[220px] shrink-0 lg:pl-6 lg:border-l lg:border-white/[0.06] flex flex-col justify-between gap-4 pt-4 lg:pt-0 border-t lg:border-t-0 border-white/[0.06]">
|
||||
{/* Contact Person */}
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div>
|
||||
<h4 className="text-sm font-bold text-white leading-tight truncate max-w-[140px]">
|
||||
<h4 className="text-[14px] font-semibold text-[#F1F1F6] leading-tight truncate max-w-[140px]">
|
||||
{leadName}
|
||||
</h4>
|
||||
<p className="text-xs text-slate-400 mt-0.5">{leadRole}</p>
|
||||
<p className="text-[12px] text-[#9A95B0] mt-0.5">{leadRole}</p>
|
||||
</div>
|
||||
<div className="relative shrink-0">
|
||||
<div className={`w-10 h-10 rounded-full flex items-center justify-center font-bold text-xs ${avatarColors[pIdx % 3]} text-white shadow-sm`}>
|
||||
{getInitials(leadName)}
|
||||
</div>
|
||||
<span className="absolute bottom-0 right-0 w-2.5 h-2.5 rounded-full bg-emerald-400 ring-2 ring-[#141C30]" />
|
||||
<span className="absolute bottom-0 right-0 w-2.5 h-2.5 rounded-full bg-emerald-400 ring-2 ring-[#181622]" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 3 Meta Rows */}
|
||||
<div className="space-y-1.5 text-xs text-slate-300">
|
||||
{/* 3 Meta Rows: Location, Time, Star Rating */}
|
||||
<div className="space-y-1.5 text-[13px] font-medium text-[#D1CBDD]">
|
||||
<div className="flex items-center gap-2">
|
||||
<MapPin className="w-3.5 h-3.5 text-slate-400 shrink-0" />
|
||||
<MapPin className="w-3.5 h-3.5 text-[#9A95B0] shrink-0" />
|
||||
<span className="truncate">Gauteng, South Africa</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Clock className="w-3.5 h-3.5 text-slate-400 shrink-0" />
|
||||
<Clock className="w-3.5 h-3.5 text-[#9A95B0] shrink-0" />
|
||||
<span>01:23 pm</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -1218,7 +1214,7 @@ export function DashboardPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openContactModalForProject(item)}
|
||||
className="w-full py-2 px-3 rounded-xl border border-slate-600 hover:border-slate-400 hover:bg-white/[0.04] text-slate-200 text-xs font-semibold transition-all cursor-pointer"
|
||||
className="w-full py-2 px-3 rounded-xl border border-white/10 hover:border-white/20 hover:bg-white/[0.04] text-[#F1F1F6] text-xs font-semibold transition-all cursor-pointer"
|
||||
>
|
||||
Contact
|
||||
</button>
|
||||
@@ -1228,7 +1224,7 @@ export function DashboardPage() {
|
||||
setSelectedProjectItem(item);
|
||||
setProjectModalTab('milestones');
|
||||
}}
|
||||
className="w-full py-2 px-3 rounded-xl bg-[#222C44] hover:bg-[#2B3856] text-slate-100 text-xs font-semibold transition-all cursor-pointer shadow-sm"
|
||||
className="w-full py-2 px-3 rounded-xl bg-white/[0.06] hover:bg-white/[0.1] text-[#F1F1F6] text-xs font-semibold transition-all cursor-pointer shadow-sm"
|
||||
>
|
||||
View Details
|
||||
</button>
|
||||
@@ -1240,28 +1236,28 @@ export function DashboardPage() {
|
||||
})
|
||||
) : (
|
||||
/* Friendly Empty State with Refresh Button & Informative Section */
|
||||
<div className="rounded-[24px] bg-[#141C30] border border-white/[0.08] p-8 sm:p-12 shadow-xl flex flex-col items-center justify-center text-center relative overflow-hidden">
|
||||
<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">
|
||||
{/* Ambient background glow */}
|
||||
<div className="absolute -top-24 left-1/2 -translate-x-1/2 w-80 h-80 rounded-full bg-indigo-500/10 blur-[100px] pointer-events-none" />
|
||||
<div className="absolute -top-24 left-1/2 -translate-x-1/2 w-80 h-80 rounded-full bg-[#8B5CF6]/10 blur-[100px] pointer-events-none" />
|
||||
|
||||
{/* Friendly Icon Badge */}
|
||||
<div className="relative mb-5">
|
||||
<div className="w-16 h-16 rounded-2xl bg-indigo-500/15 border border-indigo-500/25 text-indigo-400 flex items-center justify-center shadow-lg shadow-indigo-500/10">
|
||||
<Sparkles className="w-8 h-8" />
|
||||
<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">
|
||||
<Sparkles className="w-7 h-7" />
|
||||
</div>
|
||||
<span className="absolute -bottom-1 -right-1 flex h-4 w-4">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-indigo-400 opacity-75" />
|
||||
<span className="relative inline-flex rounded-full h-4 w-4 bg-indigo-500" />
|
||||
<span className="absolute -bottom-1 -right-1 flex h-3.5 w-3.5">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-[#8B5CF6] opacity-75" />
|
||||
<span className="relative inline-flex rounded-full h-3.5 w-3.5 bg-[#8B5CF6]" />
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Friendly Heading */}
|
||||
<h3 className="text-xl sm:text-2xl font-bold text-white tracking-tight">
|
||||
{/* Friendly Heading: 20px font-bold #F1F1F6 */}
|
||||
<h3 className="text-[20px] font-bold text-[#F1F1F6] tracking-tight">
|
||||
{searchQuery ? 'No Matching Projects Found' : 'No Active Projects Assigned Yet'}
|
||||
</h3>
|
||||
|
||||
{/* Friendly Message */}
|
||||
<p className="text-xs sm:text-sm text-slate-400 max-w-lg mt-2.5 leading-relaxed">
|
||||
{/* Friendly Message: 14px / 22px / #9A95B0 */}
|
||||
<p className="text-[14px] leading-[22px] font-normal text-[#9A95B0] 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."}
|
||||
@@ -1273,7 +1269,7 @@ export function DashboardPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setSearchQuery('')}
|
||||
className="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-200 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-[#F1F1F6] text-xs font-semibold transition-all cursor-pointer"
|
||||
>
|
||||
<X className="w-4 h-4" />
|
||||
<span>Clear Search</span>
|
||||
@@ -1284,7 +1280,7 @@ export function DashboardPage() {
|
||||
type="button"
|
||||
onClick={() => refetchApplicantData()}
|
||||
disabled={isRefreshingApplicantData}
|
||||
className="inline-flex items-center gap-2.5 px-6 py-3 rounded-xl bg-indigo-500 hover:bg-indigo-400 text-white font-semibold text-xs tracking-wide shadow-lg shadow-indigo-500/25 active:scale-95 transition-all cursor-pointer disabled:opacity-60"
|
||||
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 active:scale-95 transition-all cursor-pointer disabled:opacity-60"
|
||||
>
|
||||
<RefreshCw className={`w-4 h-4 ${isRefreshingApplicantData ? 'animate-spin' : ''}`} />
|
||||
<span>{isRefreshingApplicantData ? 'Refreshing Workspace...' : 'Refresh Projects'}</span>
|
||||
@@ -1292,61 +1288,61 @@ export function DashboardPage() {
|
||||
</div>
|
||||
|
||||
{/* Elegantly placed informative text below all that */}
|
||||
<div className="mt-10 w-full max-w-xl text-left border-t border-white/[0.08] pt-8">
|
||||
<div className="mt-10 w-full max-w-xl text-left border-t border-white/[0.06] pt-8">
|
||||
<div className="flex items-center justify-between gap-2 mb-4">
|
||||
<div className="flex items-center gap-2 text-indigo-300">
|
||||
<div className="flex items-center gap-2 text-[#8B5CF6]">
|
||||
<Info className="w-4 h-4" />
|
||||
<span className="text-xs font-bold uppercase tracking-wider">How Project Activation Works</span>
|
||||
<span className="text-[11px] font-semibold uppercase tracking-[0.05em]">How Project Activation Works</span>
|
||||
</div>
|
||||
<span className="text-[11px] text-slate-500 font-medium">Post-Fund Lifecycle</span>
|
||||
<span className="text-[11px] text-[#9A95B0] font-medium">Post-Fund Lifecycle</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="p-4 rounded-2xl bg-[#0E1526]/80 border border-white/[0.05] hover:border-white/[0.1] transition-colors flex items-start gap-3.5">
|
||||
<div className="h-6 w-6 rounded-lg bg-indigo-500/20 border border-indigo-500/30 text-indigo-300 flex items-center justify-center text-xs font-bold shrink-0 mt-0.5">
|
||||
<div className="p-4 rounded-[14px] bg-[#0F0E17]/90 border border-white/[0.05] hover:border-white/[0.1] transition-colors flex items-start gap-3.5">
|
||||
<div className="h-6 w-6 rounded-lg bg-[#8B5CF6]/20 border border-[#8B5CF6]/30 text-[#8B5CF6] flex items-center justify-center text-xs font-bold shrink-0 mt-0.5">
|
||||
1
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="text-xs font-bold text-white">Award Handover & Formal Agreement</p>
|
||||
<p className="text-[11px] text-slate-400 mt-0.5 leading-relaxed">
|
||||
<p className="text-[13px] font-semibold text-[#F1F1F6]">Award Handover & Formal Agreement</p>
|
||||
<p className="text-[12px] leading-[18px] text-[#9A95B0] mt-0.5">
|
||||
Following sanction by the approval committee, your funding organisation registers the award. A legally binding grant agreement is automatically drafted for your review and digital execution.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 rounded-2xl bg-[#0E1526]/80 border border-white/[0.05] hover:border-white/[0.1] transition-colors flex items-start gap-3.5">
|
||||
<div className="h-6 w-6 rounded-lg bg-indigo-500/20 border border-indigo-500/30 text-indigo-300 flex items-center justify-center text-xs font-bold shrink-0 mt-0.5">
|
||||
<div className="p-4 rounded-[14px] bg-[#0F0E17]/90 border border-white/[0.05] hover:border-white/[0.1] transition-colors flex items-start gap-3.5">
|
||||
<div className="h-6 w-6 rounded-lg bg-[#8B5CF6]/20 border border-[#8B5CF6]/30 text-[#8B5CF6] flex items-center justify-center text-xs font-bold shrink-0 mt-0.5">
|
||||
2
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="text-xs font-bold text-white">Milestones & Evidence Submission</p>
|
||||
<p className="text-[11px] text-slate-400 mt-0.5 leading-relaxed">
|
||||
<p className="text-[13px] font-semibold text-[#F1F1F6]">Milestones & Evidence Submission</p>
|
||||
<p className="text-[12px] leading-[18px] text-[#9A95B0] mt-0.5">
|
||||
Once the project is active, your work breakdown structure, deliverable due dates, and mandatory evidence requirements appear here so you can easily upload progress reports, invoices, and certificates.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-4 rounded-2xl bg-[#0E1526]/80 border border-white/[0.05] hover:border-white/[0.1] transition-colors flex items-start gap-3.5">
|
||||
<div className="h-6 w-6 rounded-lg bg-indigo-500/20 border border-indigo-500/30 text-indigo-300 flex items-center justify-center text-xs font-bold shrink-0 mt-0.5">
|
||||
<div className="p-4 rounded-[14px] bg-[#0F0E17]/90 border border-white/[0.05] hover:border-white/[0.1] transition-colors flex items-start gap-3.5">
|
||||
<div className="h-6 w-6 rounded-lg bg-[#8B5CF6]/20 border border-[#8B5CF6]/30 text-[#8B5CF6] flex items-center justify-center text-xs font-bold shrink-0 mt-0.5">
|
||||
3
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="text-xs font-bold text-white">Payment Requests & Controlled Tranches</p>
|
||||
<p className="text-[11px] text-slate-400 mt-0.5 leading-relaxed">
|
||||
<p className="text-[13px] font-semibold text-[#F1F1F6]">Payment Requests & Controlled Tranches</p>
|
||||
<p className="text-[12px] leading-[18px] text-[#9A95B0] mt-0.5">
|
||||
Funds are released safely against verified milestone deliverables and satisfied conditions precedent. You can initiate drawdown requests directly from your project workspace.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="p-3.5 rounded-xl bg-indigo-950/30 border border-indigo-500/20 flex items-center justify-between gap-3 text-xs text-indigo-200">
|
||||
<div className="p-3.5 rounded-xl bg-[#8B5CF6]/10 border border-[#8B5CF6]/20 flex items-center justify-between gap-3 text-xs text-[#8B5CF6]">
|
||||
<div className="flex items-center gap-2">
|
||||
<MessageSquare className="w-4 h-4 text-indigo-400 shrink-0" />
|
||||
<MessageSquare className="w-4 h-4 text-[#8B5CF6] shrink-0" />
|
||||
<span>Need status clarification? Contact your assigned Programme Officer.</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab('messages')}
|
||||
className="text-xs font-semibold text-white underline hover:text-indigo-300 cursor-pointer shrink-0"
|
||||
className="text-xs font-semibold text-[#F1F1F6] underline hover:text-[#8B5CF6] cursor-pointer shrink-0"
|
||||
>
|
||||
Open Messages
|
||||
</button>
|
||||
@@ -1359,76 +1355,76 @@ export function DashboardPage() {
|
||||
</div>
|
||||
|
||||
{/* ===============================================================
|
||||
RIGHT COLUMN: OVERVIEW (2x2 Grid) + INBOX (4 of 12 cols)
|
||||
RIGHT COLUMN: SIDEBAR WIDGETS (Overview & Inbox, 340px)
|
||||
=============================================================== */}
|
||||
<div className="lg:col-span-4 space-y-6">
|
||||
<div className="w-full lg:w-[340px] space-y-5">
|
||||
|
||||
{/* 1. OVERVIEW CARD (2x2 Grid with vertical divider) */}
|
||||
<div className="rounded-[22px] bg-[#141C30] border border-white/[0.07] p-6 sm:p-7 shadow-lg">
|
||||
<h3 className="text-base sm:text-lg font-bold text-white tracking-tight mb-6">
|
||||
<div className="rounded-[16px] bg-[#181622] border border-white/[0.06] p-6 shadow-sm">
|
||||
<h3 className="text-[20px] leading-[28px] font-bold text-[#F1F1F6] mb-5">
|
||||
Overview
|
||||
</h3>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 relative">
|
||||
{/* Subtle vertical divider between the 2 columns */}
|
||||
<div className="absolute top-1 bottom-1 left-1/2 -translate-x-1/2 w-px bg-white/[0.07]" />
|
||||
<div className="absolute top-1 bottom-1 left-1/2 -translate-x-1/2 w-px bg-white/[0.06]" />
|
||||
|
||||
{/* Left Column: Running Project & Completed Project */}
|
||||
<div className="space-y-7 pr-3">
|
||||
<div className="space-y-6 pr-3">
|
||||
<div>
|
||||
<p className="text-3xl font-extrabold text-white tracking-tight leading-none">
|
||||
<p className="text-[24px] font-bold text-[#F1F1F6] leading-none">
|
||||
{overviewMetrics.runningProjects}
|
||||
</p>
|
||||
<p className="text-xs text-slate-400 mt-2 font-medium">Running Project</p>
|
||||
<p className="text-[12px] font-normal text-[#9A95B0] mt-1.5">Running Project</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-3xl font-extrabold text-white tracking-tight leading-none">
|
||||
<p className="text-[24px] font-bold text-[#F1F1F6] leading-none">
|
||||
{overviewMetrics.completedProjects}
|
||||
</p>
|
||||
<p className="text-xs text-slate-400 mt-2 font-medium">Completed Project</p>
|
||||
<p className="text-[12px] font-normal text-[#9A95B0] mt-1.5">Completed Project</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right Column: Have to Pay & Total Payment */}
|
||||
<div className="space-y-7 pl-4 sm:pl-6">
|
||||
<div className="space-y-6 pl-4 sm:pl-6">
|
||||
<div>
|
||||
<p className="text-3xl font-extrabold text-white tracking-tight leading-none">
|
||||
<p className="text-[24px] font-bold text-[#F1F1F6] leading-none">
|
||||
{overviewMetrics.pendingPayment}
|
||||
</p>
|
||||
<p className="text-xs text-slate-400 mt-2 font-medium">Have to Pay</p>
|
||||
<p className="text-[12px] font-normal text-[#9A95B0] mt-1.5">Have to Pay</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-3xl font-extrabold text-white tracking-tight leading-none">
|
||||
<p className="text-[24px] font-bold text-[#F1F1F6] leading-none">
|
||||
{overviewMetrics.totalPayment}
|
||||
</p>
|
||||
<p className="text-xs text-slate-400 mt-2 font-medium">Total Payment</p>
|
||||
<p className="text-[12px] font-normal text-[#9A95B0] mt-1.5">Total Payment</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 2. INBOX CARD (Matching Dribbble design: Inbox + badge, See All, and clean avatar list) */}
|
||||
<div className="rounded-[22px] bg-[#141C30] border border-white/[0.07] p-6 sm:p-7 shadow-lg">
|
||||
<div className="flex items-center justify-between mb-5">
|
||||
<div className="rounded-[16px] bg-[#181622] border border-white/[0.06] p-6 shadow-sm">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-base sm:text-lg font-bold text-white tracking-tight">Inbox</h3>
|
||||
<span className="h-5 min-w-[20px] px-1.5 rounded-full bg-rose-500 text-white text-[11px] font-bold flex items-center justify-center">
|
||||
<h3 className="text-[20px] leading-[28px] font-bold text-[#F1F1F6]">Inbox</h3>
|
||||
<span className="h-5 min-w-[20px] px-1.5 rounded-full bg-[#EF4444] text-white text-[11px] font-bold flex items-center justify-center">
|
||||
{unreadCount > 0 ? unreadCount : 3}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setActiveTab('messages')}
|
||||
className="text-xs font-medium text-slate-400 hover:text-white transition-colors cursor-pointer"
|
||||
className="text-[12px] font-medium text-[#9A95B0] hover:text-[#F1F1F6] transition-colors cursor-pointer"
|
||||
>
|
||||
See All
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* List of Contacts & Messages */}
|
||||
<div className="space-y-3.5 divide-y divide-white/[0.05]">
|
||||
{/* List of Contacts & Messages: Compact vertical list */}
|
||||
<div className="space-y-1 divide-y divide-white/[0.04]">
|
||||
{combinedInbox.map((item) => (
|
||||
<div
|
||||
key={item.id}
|
||||
@@ -1447,21 +1443,21 @@ export function DashboardPage() {
|
||||
setMessageBody('');
|
||||
}
|
||||
}}
|
||||
className="pt-3.5 first:pt-0 flex items-center gap-3.5 group cursor-pointer hover:bg-white/[0.02] p-1.5 rounded-xl transition-colors"
|
||||
className="flex items-center gap-3 py-2 px-1 hover:bg-white/[0.02] rounded-xl transition-colors cursor-pointer"
|
||||
>
|
||||
<div className={`w-10 h-10 rounded-full shrink-0 flex items-center justify-center font-bold text-xs ${item.avatarBg} text-white shadow-sm`}>
|
||||
<div className={`w-9 h-9 rounded-full shrink-0 flex items-center justify-center font-bold text-xs ${item.avatarBg} text-white shadow-sm`}>
|
||||
{item.avatarInitials}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-xs font-bold text-white truncate group-hover:text-indigo-300 transition-colors">
|
||||
<div className="flex items-center justify-between gap-1.5">
|
||||
<span className="text-[13px] font-semibold text-[#F1F1F6] truncate group-hover:text-[#8B5CF6] transition-colors">
|
||||
{item.name}
|
||||
</span>
|
||||
{item.unread && (
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-rose-500 shrink-0" />
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-[#EF4444] shrink-0" />
|
||||
)}
|
||||
</div>
|
||||
<p className="text-[11px] text-slate-400 truncate mt-0.5 leading-tight">
|
||||
<p className="text-[12px] font-normal text-[#9A95B0] truncate mt-0.5 leading-tight">
|
||||
{item.snippet}
|
||||
</p>
|
||||
</div>
|
||||
@@ -1479,8 +1475,8 @@ export function DashboardPage() {
|
||||
<div className="space-y-6">
|
||||
<div className="flex flex-wrap items-center justify-between gap-4">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white">Messages & Communications</h1>
|
||||
<p className="text-xs text-slate-400 mt-0.5">
|
||||
<h1 className="text-[20px] font-bold text-[#F1F1F6]">Messages & Communications</h1>
|
||||
<p className="text-[12px] text-[#9A95B0] mt-0.5">
|
||||
Direct communication with your funding programme officers.
|
||||
</p>
|
||||
</div>
|
||||
@@ -1494,16 +1490,16 @@ export function DashboardPage() {
|
||||
setMessageSubject('');
|
||||
setMessageBody('');
|
||||
}}
|
||||
className="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl bg-indigo-500 hover:bg-indigo-400 text-white font-semibold text-xs shadow-lg shadow-indigo-500/20 transition-all cursor-pointer"
|
||||
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"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
<span>New Message</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="rounded-[22px] bg-[#141C30] border border-white/[0.07] divide-y divide-white/[0.06] overflow-hidden">
|
||||
<div className="rounded-[16px] bg-[#181622] border border-white/[0.06] divide-y divide-white/[0.06] overflow-hidden">
|
||||
{inboxNotifications.length === 0 ? (
|
||||
<div className="p-12 text-center text-slate-400 text-sm">
|
||||
<div className="p-12 text-center text-[#9A95B0] text-sm">
|
||||
No new messages in your inbox yet. Messages sent by programme managers will be displayed here.
|
||||
</div>
|
||||
) : (
|
||||
@@ -1519,24 +1515,24 @@ 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-indigo-500/15 border border-indigo-500/30 text-indigo-300 font-bold text-xs flex items-center justify-center shrink-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">
|
||||
{getInitials(msg.subject || 'PFM')}
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-sm font-bold text-white truncate">
|
||||
<h3 className="text-sm font-semibold text-[#F1F1F6] truncate">
|
||||
{msg.subject || 'Programme Notification'}
|
||||
</h3>
|
||||
{isUnread && (
|
||||
<span className="px-2 py-0.5 rounded-full text-[10px] font-bold bg-rose-500 text-white">
|
||||
<span className="px-2 py-0.5 rounded-full text-[10px] font-bold bg-[#EF4444] text-white">
|
||||
New
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-slate-300 mt-1 line-clamp-2">{stripMarkdown(msg.message)}</p>
|
||||
<p className="text-xs text-[#9A95B0] mt-1 line-clamp-2">{stripMarkdown(msg.message)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-xs text-slate-500 shrink-0">
|
||||
<span className="text-xs text-[#9A95B0]/70 shrink-0">
|
||||
{formatFullDate(msg.createdAt)}
|
||||
</span>
|
||||
</div>
|
||||
@@ -1547,19 +1543,19 @@ export function DashboardPage() {
|
||||
</div>
|
||||
) : activeTab === 'contracts' ? (
|
||||
/* =================================================================
|
||||
CONTRACTS & ORDERS TAB
|
||||
CONTRACTS & AWARDS TAB
|
||||
================================================================= */
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white">Contracts & Approved Grants</h1>
|
||||
<p className="text-xs text-slate-400 mt-0.5">
|
||||
<h1 className="text-[20px] font-bold text-[#F1F1F6]">Contracts & Approved Awards</h1>
|
||||
<p className="text-[12px] text-[#9A95B0] 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-[22px] bg-[#141C30] border border-white/[0.07] p-10 text-center text-sm text-slate-400">
|
||||
<div className="col-span-full rounded-[16px] bg-[#181622] border border-white/[0.06] p-10 text-center text-sm text-[#9A95B0]">
|
||||
No contracts have been issued to your organisation yet.
|
||||
</div>
|
||||
) : (
|
||||
@@ -1569,47 +1565,47 @@ export function DashboardPage() {
|
||||
return (
|
||||
<div
|
||||
key={contract.id}
|
||||
className="rounded-[22px] bg-[#141C30] border border-white/[0.07] p-6 flex flex-col justify-between gap-5"
|
||||
className="rounded-[16px] bg-[#181622] 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-indigo-400 uppercase tracking-wider">
|
||||
<span className="text-xs font-semibold text-[#8B5CF6] uppercase tracking-wider">
|
||||
Agreement v{contractVer}
|
||||
</span>
|
||||
<span
|
||||
className={`px-2.5 py-0.5 rounded-full text-xs font-semibold ${
|
||||
isExecuted
|
||||
? 'bg-emerald-500/15 text-emerald-300 border border-emerald-500/30'
|
||||
: 'bg-amber-500/15 text-amber-300 border border-amber-500/30'
|
||||
: 'bg-[#F97316]/15 text-[#F97316] border border-[#F97316]/30'
|
||||
}`}
|
||||
>
|
||||
{isExecuted ? 'Signed & Executed' : 'Action Required: Review & Sign'}
|
||||
</span>
|
||||
</div>
|
||||
<h3 className="text-lg font-bold text-white">
|
||||
<h3 className="text-lg font-bold text-[#F1F1F6]">
|
||||
{fundingOrganisation?.name || 'Grant Funding Agreement'}
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 gap-4 pt-2 text-xs">
|
||||
<div>
|
||||
<p className="text-slate-400">Contract Value</p>
|
||||
<p className="text-sm font-bold text-white mt-0.5">
|
||||
<p className="text-[#9A95B0]">Contract Value</p>
|
||||
<p className="text-sm font-bold text-[#F1F1F6] mt-0.5">
|
||||
{formatZAR(contract.totalValue || award?.amount || 0)}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p className="text-slate-400">Valid Period</p>
|
||||
<p className="text-slate-200 font-medium mt-0.5">
|
||||
<p className="text-[#9A95B0]">Valid Period</p>
|
||||
<p className="text-[#D1CBDD] font-medium mt-0.5">
|
||||
{formatShortDate(contract.effectiveAt)} – {formatFullDate(contract.expiresAt)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-3 pt-4 border-t border-white/[0.07]">
|
||||
<div className="flex flex-wrap items-center gap-3 pt-4 border-t border-white/[0.06]">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleOpenSecureFile('contract', contract.id)}
|
||||
className="flex-1 py-2.5 px-4 rounded-xl bg-slate-800 hover:bg-slate-700 text-slate-200 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-[#F1F1F6] 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>
|
||||
@@ -1619,7 +1615,7 @@ export function DashboardPage() {
|
||||
type="button"
|
||||
onClick={() => signContractMutation.mutate(contract)}
|
||||
disabled={signContractMutation.isPending}
|
||||
className="flex-1 py-2.5 px-4 rounded-xl bg-indigo-500 hover:bg-indigo-400 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-[#8B5CF6] hover:bg-[#7C3AED] 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>
|
||||
@@ -1638,38 +1634,38 @@ export function DashboardPage() {
|
||||
================================================================= */
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white">Portfolio Analytics</h1>
|
||||
<p className="text-xs text-slate-400 mt-0.5">
|
||||
<h1 className="text-[20px] font-bold text-[#F1F1F6]">Portfolio Analytics</h1>
|
||||
<p className="text-[12px] text-[#9A95B0] mt-0.5">
|
||||
Performance metrics, milestone velocity, and financial drawdown burn rates.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-6">
|
||||
<div className="p-6 rounded-[22px] bg-[#141C30] border border-white/[0.07]">
|
||||
<div className="flex items-center justify-between text-indigo-400 mb-2">
|
||||
<div className="p-6 rounded-[16px] bg-[#181622] border border-white/[0.06]">
|
||||
<div className="flex items-center justify-between text-[#8B5CF6] mb-2">
|
||||
<TrendingUp className="w-5 h-5" />
|
||||
<span className="text-xs font-semibold text-emerald-400">+18% vs target</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-white">92.4%</p>
|
||||
<p className="text-xs text-slate-400 mt-1">Milestone Completion Rate</p>
|
||||
<p className="text-2xl font-bold text-[#F1F1F6]">92.4%</p>
|
||||
<p className="text-xs text-[#9A95B0] mt-1">Milestone Completion Rate</p>
|
||||
</div>
|
||||
|
||||
<div className="p-6 rounded-[22px] bg-[#141C30] border border-white/[0.07]">
|
||||
<div className="p-6 rounded-[16px] bg-[#181622] 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-indigo-400">On Track</span>
|
||||
<span className="text-xs font-semibold text-[#8B5CF6]">On Track</span>
|
||||
</div>
|
||||
<p className="text-2xl font-bold text-white">{overviewMetrics.runningProjects} Active</p>
|
||||
<p className="text-xs text-slate-400 mt-1">Funded Projects in Execution</p>
|
||||
<p className="text-2xl font-bold text-[#F1F1F6]">{overviewMetrics.runningProjects} Active</p>
|
||||
<p className="text-xs text-[#9A95B0] mt-1">Funded Projects in Execution</p>
|
||||
</div>
|
||||
|
||||
<div className="p-6 rounded-[22px] bg-[#141C30] border border-white/[0.07]">
|
||||
<div className="p-6 rounded-[16px] bg-[#181622] 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-white">100%</p>
|
||||
<p className="text-xs text-slate-400 mt-1">Compliance & Evidence Health</p>
|
||||
<p className="text-2xl font-bold text-[#F1F1F6]">100%</p>
|
||||
<p className="text-xs text-[#9A95B0] mt-1">Compliance & Evidence Health</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1679,16 +1675,16 @@ export function DashboardPage() {
|
||||
================================================================= */
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white">Deliverables & Evidence Documents</h1>
|
||||
<p className="text-xs text-slate-400 mt-0.5">
|
||||
<h1 className="text-[20px] font-bold text-[#F1F1F6]">Deliverables & Evidence Documents</h1>
|
||||
<p className="text-[12px] text-[#9A95B0] mt-0.5">
|
||||
Invoices, payment requests, and verified deliverable proofs submitted across projects.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="rounded-[22px] bg-[#141C30] border border-white/[0.07] overflow-hidden">
|
||||
<div className="rounded-[16px] bg-[#181622] 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-slate-400">
|
||||
<p className="text-sm text-[#9A95B0]">
|
||||
No documents uploaded yet. Open any project on your Dashboard and click "View Details" to submit proof files.
|
||||
</p>
|
||||
</div>
|
||||
@@ -1705,17 +1701,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-indigo-500/10 text-indigo-300">
|
||||
<div className="p-2.5 rounded-xl bg-[#8B5CF6]/10 text-[#8B5CF6]">
|
||||
<FileText className="w-5 h-5" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-semibold text-white truncate">
|
||||
<p className="text-sm font-semibold text-[#F1F1F6] truncate">
|
||||
{EVIDENCE_TYPE_LABELS[evType] || 'Supporting Document'}{' '}
|
||||
<span className="text-xs font-normal text-slate-400">
|
||||
<span className="text-xs font-normal text-[#9A95B0]">
|
||||
(v{ev.version || 1})
|
||||
</span>
|
||||
</p>
|
||||
<p className="text-xs text-slate-400 truncate mt-0.5">
|
||||
<p className="text-xs text-[#9A95B0] truncate mt-0.5">
|
||||
Project: {project.name} • Submitted {formatFullDate(evDate)}
|
||||
</p>
|
||||
</div>
|
||||
@@ -1723,7 +1719,7 @@ export function DashboardPage() {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => handleOpenSecureFile('evidence', ev.id)}
|
||||
className="px-3.5 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-xs font-semibold text-indigo-300 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-[#8B5CF6] flex items-center gap-1.5 shrink-0 cursor-pointer"
|
||||
>
|
||||
<ExternalLink className="w-3.5 h-3.5" />
|
||||
<span>View File</span>
|
||||
|
||||
Reference in New Issue
Block a user