+ {/* Subtle Ambient Background Glow honoring PartnerUI's emerald/teal/cyan palette */}
+
+
+ {/* =====================================================================
+ TOP NAVIGATION BAR (Matches Uploaded Template + Dark Emerald Theme)
+ ===================================================================== */}
+
+
+ {/* Left: Brand Logo & Navigation Links */}
+
+
setActiveTab('dashboard')}
+ className="flex items-center gap-3 text-left group focus:outline-none cursor-pointer"
+ >
+
+
+
+
+ PFM Partner
+
+
+ Grantee Portal
+
+
+
+ {partnerSummary.primaryInvitedOrg?.name || user?.name || 'Partner Workspace'}
+
+
+
+
+
+ {(
+ [
+ { id: 'dashboard', label: 'Dashboard' },
+ { id: 'messages', label: 'Messages', badge: unreadCount },
+ { id: 'contracts', label: 'Contracts & Grants' },
+ { id: 'documents', label: 'Deliverables & Documents' },
+ ] as { id: NavTab; label: string; badge?: number }[]
+ ).map(tab => {
+ const isActive = activeTab === tab.id;
+ return (
+ setActiveTab(tab.id)}
+ className={`relative px-4 py-2.5 text-sm font-medium transition-colors rounded-lg flex items-center gap-2 cursor-pointer ${
+ isActive
+ ? 'text-emerald-300 bg-emerald-500/10'
+ : 'text-slate-400 hover:text-slate-200 hover:bg-white/[0.03]'
+ }`}
+ >
+ {tab.label}
+ {tab.badge !== undefined && tab.badge > 0 && (
+
+ {tab.badge}
+
+ )}
+ {isActive && (
+
+ )}
+
+ );
+ })}
+
+
+
+ {/* Right: Search Input, Notification Bell, Partner Profile & Sign Out */}
+
+
+
+ setSearchQuery(e.target.value)}
+ placeholder="Search projects..."
+ className="w-full pl-10 pr-4 py-2 rounded-xl bg-slate-900/90 border border-white/[0.08] text-sm text-slate-100 placeholder:text-slate-500 focus:outline-none focus:border-emerald-500/50 focus:ring-1 focus:ring-emerald-500/30 transition-all"
+ />
+ {searchQuery && (
+ setSearchQuery('')}
+ className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-500 hover:text-slate-300"
+ >
+
+
+ )}
+
+
+
refetchApplicantData()}
+ title="Refresh Partner Data"
+ className="p-2.5 rounded-xl bg-slate-900/80 border border-white/[0.08] text-slate-400 hover:text-emerald-300 hover:border-emerald-500/30 transition-all cursor-pointer"
+ >
+
+
+
+ {/* Notification Bell */}
+
+
setShowBellDropdown(prev => !prev)}
+ className="relative p-2.5 rounded-xl bg-slate-900/80 border border-white/[0.08] text-slate-300 hover:text-white hover:border-emerald-500/30 transition-all cursor-pointer"
+ aria-label="Notifications"
+ >
+
+ {unreadCount > 0 && (
+
+ {unreadCount}
+
+ )}
+
+
+ {showBellDropdown && (
+
+
+
+ Notifications
+ {unreadCount > 0 && (
+
+ {unreadCount} unread
+
+ )}
+
+ {unreadCount > 0 && (
+
markAllAsRead()}
+ className="text-xs text-emerald-400 hover:text-emerald-300 font-medium cursor-pointer"
+ >
+ Mark all read
+
+ )}
+
+
+ {top5Unread.length === 0 ? (
+
+ You are all caught up! No new notifications.
+
+ ) : (
+ top5Unread.map(item => (
+
{
+ markAsRead(item.id);
+ setSelectedNotification(item);
+ setShowBellDropdown(false);
+ }}
+ 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"
+ >
+
+
+
+ {item.subject || 'New Partner Update'}
+
+
+ {item.message}
+
+
+
+ ))
+ )}
+
+
{
+ setShowBellDropdown(false);
+ setActiveTab('messages');
+ }}
+ className="w-full py-2 text-center text-xs font-semibold text-emerald-300 hover:text-emerald-200 bg-emerald-500/10 hover:bg-emerald-500/15 rounded-xl transition-colors cursor-pointer"
+ >
+ See All Messages
+
+
+ )}
+
+
+ {/* User Avatar & Sign Out */}
+
+
+ {getInitials(user?.name || user?.email)}
+
+
+
+ {user?.name || user?.email || 'Partner User'}
+
+
Partner Role
+
+
logout()}
+ title="Sign Out"
+ className="p-2 rounded-xl text-slate-400 hover:text-rose-300 hover:bg-rose-500/10 transition-colors cursor-pointer"
+ >
+
+
+
+
+
+
+ {/* Mobile Navigation Bar */}
+
+ {(
+ [
+ { id: 'dashboard', label: 'Dashboard' },
+ { id: 'messages', label: `Messages (${unreadCount})` },
+ { id: 'contracts', label: 'Contracts' },
+ { id: 'documents', label: 'Documents' },
+ ] as { id: NavTab; label: string }[]
+ ).map(tab => (
+ setActiveTab(tab.id)}
+ className={`px-3 py-1.5 rounded-lg text-xs font-medium whitespace-nowrap ${
+ activeTab === tab.id
+ ? 'bg-emerald-500/20 text-emerald-300 border border-emerald-500/30'
+ : 'text-slate-400'
+ }`}
+ >
+ {tab.label}
+
+ ))}
+
+
+
+ {/* =====================================================================
+ MAIN CONTENT AREA
+ ===================================================================== */}
+
+ {isLoadingApplicantData ? (
+
+
+
+
Loading your Partner Workspace...
+
+ Retrieving your projects, funding awards, contracts, and milestones.
+
+
+
+ ) : activeTab === 'dashboard' ? (
+ /* =================================================================
+ DASHBOARD VIEW — 2-COLUMN LAYOUT BASED ON UPLOADED TEMPLATE
+ Left (~2/3): "Ongoing Projects" + "Sort by date added" + Cards
+ Right (~1/3): "Overview" (2x2 Grid) + "Inbox"
+ ================================================================= */
+
+ {/* LEFT COLUMN: ONGOING PROJECTS (8 cols on xl) */}
+
+ {/* Section Header */}
+
+
+
+ Ongoing Projects
+
+
+ Track your grant milestones, submit proof documents, and request payments simply.
+
+
+
+ {/* Sort Selector matching "Sort by date added v" in template */}
+
+
+ setSortBy(e.target.value as SortOption)}
+ className="appearance-none bg-[#0d1322] border border-white/10 hover:border-emerald-500/40 rounded-xl pl-3.5 pr-9 py-2 text-xs font-medium text-slate-300 focus:outline-none focus:border-emerald-500 transition-colors cursor-pointer"
+ >
+ Sort by date added
+ Sort by grant amount
+ Sort by days left
+ Sort by project name
+
+
+
+
+
+
+ {/* Project Cards List */}
+ {filteredProjects.length === 0 ? (
+
+
+
+
+
+
+ {searchQuery ? 'No matching projects found' : 'No active projects assigned yet'}
+
+
+ {searchQuery
+ ? 'Try clearing your search filter to see all your funded projects.'
+ : 'Once your funding organisation registers your approved grant award and project, it will appear right here automatically.'}
+
+
+ {partnerSummary.contractEntries.length > 0 && (
+
setActiveTab('contracts')}
+ className="inline-flex items-center gap-2 px-4 py-2 rounded-xl bg-emerald-500 text-slate-950 font-semibold text-xs hover:bg-emerald-400 transition-colors cursor-pointer"
+ >
+
+ View Your Funding Contracts ({partnerSummary.contractEntries.length})
+
+ )}
+
+ ) : (
+
+ {filteredProjects.map(item => {
+ const {
+ project,
+ award,
+ contract,
+ fundingOrganisation,
+ milestones,
+ evidence,
+ disbursements,
+ } = item;
+
+ const grantAmount = contract?.totalValue || award?.amount || 0;
+ const disbursedAmount = disbursements
+ .filter(d => Number(d.status) === ApprovalStatus.Approved)
+ .reduce((sum, d) => sum + Number(d.amount || 0), 0);
+ const daysLeft = calculateDaysLeft(project.endedAt || contract?.expiresAt);
+ const docsSubmitted = evidence.length;
+
+ // Build 5-node stepper matching the template:
+ // [Project start] --- [Milestone 1] --- [Milestone 2] --- [Milestone 3] --- [Milestone 4]
+ const sortedMilestones = [...milestones].sort(
+ (a, b) => new Date(a.dueAt || 0).getTime() - new Date(b.dueAt || 0).getTime()
+ );
+
+ const stepperNodes = [
+ {
+ id: 'start',
+ label: 'Project start',
+ dateLabel: formatShortDate(project.startedAt || contract?.effectiveAt || project.createdAt),
+ state: 'completed' as 'completed' | 'current' | 'upcoming',
+ },
+ ...[0, 1, 2, 3].map(idx => {
+ const m = sortedMilestones[idx];
+ if (!m) {
+ return {
+ id: `m-placeholder-${idx}`,
+ label: `Milestone ${idx + 1}`,
+ dateLabel: 'Planned',
+ state: 'upcoming' as const,
+ };
+ }
+ const mStatus = Number(m.status);
+ const isDone = mStatus === ApprovalStatus.Approved;
+ const isCurrent =
+ !isDone &&
+ (mStatus === ApprovalStatus.Review ||
+ idx === 0 ||
+ Number(sortedMilestones[idx - 1]?.status) === ApprovalStatus.Approved);
+
+ return {
+ id: String(m.id),
+ label: m.name || `Milestone ${idx + 1}`,
+ dateLabel: formatShortDate(m.dueAt),
+ state: isDone ? ('completed' as const) : isCurrent ? ('current' as const) : ('upcoming' as const),
+ };
+ }),
+ ];
+
+ const funderName =
+ fundingOrganisation?.name ||
+ partnerSummary.primaryFundingOrg?.name ||
+ 'Funding Organisation';
+
+ return (
+
+
+ {/* LEFT PART OF CARD (9 cols): Title, 4 Metrics Row, Horizontal Milestone Stepper */}
+
+ {/* Project Title & Description */}
+
+
+
+ {project.name || 'Funded Project'}
+
+
+ {Number(project.status) === ActivityStatus.Inactive ? 'Completed' : 'Active Grant'}
+
+
+ {project.description && (
+
+ {project.description}
+
+ )}
+
+
+ {/* 4 Metric Items Row (Matching template's 4 icon+value+sublabel columns) */}
+
+ {/* Metric 1: Total Grant */}
+
+
+
+
+
+
+ {formatZAR(grantAmount)}
+
+
Awarded Grant
+
+
+
+ {/* Metric 2: Paid / Disbursed */}
+
+
+
+
+ {formatZAR(disbursedAmount)}
+
+
Funds Received
+
+
+
+ {/* Metric 3: Days Left */}
+
+
+
+
+
+
+ {daysLeft} days
+
+
Days left
+
+
+
+ {/* Metric 4: Documents / Deliverables Submitted */}
+
+
+
+
+
+
+ {docsSubmitted} docs
+
+
Proof uploaded
+
+
+
+
+ {/* Horizontal Milestone Stepper (Matching template's Project start -> Milestone 1..4) */}
+
+
+ {stepperNodes.map((node, idx) => {
+ const isLast = idx === stepperNodes.length - 1;
+ const nextNode = stepperNodes[idx + 1];
+ const lineCompleted =
+ node.state === 'completed' &&
+ (nextNode?.state === 'completed' || nextNode?.state === 'current');
+
+ return (
+
+ {/* Node Circle + Connecting Line */}
+
+ {node.state === 'completed' ? (
+
+
+
+ ) : node.state === 'current' ? (
+
+ ) : (
+
+ )}
+
+ {!isLast && (
+
+ )}
+
+
+ {/* Node Title & Date */}
+
+
+ {node.label}
+
+
+ {node.dateLabel}
+
+
+
+ );
+ })}
+
+
+
+
+ {/* RIGHT PART OF CARD (3 cols): Funder Info + "Contact" & "View Details" Buttons */}
+
+ {/* Funder Profile Block */}
+
+
+ {getInitials(funderName)}
+
+
+
+ {funderName}
+
+
+ {fundingOrganisation?.email || 'Funding Partner'}
+
+
+
+ Verified Grantor
+
+
+
+
+ {/* Stacked Action Buttons: Contact & View Details */}
+
+ openContactModalForProject(item)}
+ className="w-full py-2.5 px-4 rounded-xl bg-slate-800/90 hover:bg-slate-800 text-slate-200 border border-white/10 text-xs font-semibold transition-all cursor-pointer"
+ >
+ Contact
+
+ {
+ setSelectedProjectItem(item);
+ setProjectModalTab('milestones');
+ }}
+ className="w-full py-2.5 px-4 rounded-xl bg-emerald-500 hover:bg-emerald-400 text-slate-950 font-semibold text-xs shadow-lg shadow-emerald-500/15 transition-all cursor-pointer"
+ >
+ View Details
+
+
+
+
+
+ );
+ })}
+
+ )}
+
+
+ {/* RIGHT COLUMN: OVERVIEW (2x2 GRID) + INBOX (4 cols on xl) */}
+
+ {/* 1. OVERVIEW CARD */}
+
+
Overview
+
+
+ {/* Top-Left: Running Projects */}
+
+
+ {overviewMetrics.runningProjects}
+
+
Running Projects
+
+
+ {/* Top-Right: Pending Payment */}
+
+
+ {formatZAR(overviewMetrics.pendingPayment)}
+
+
Pending Payment
+
+
+
+
+ {/* Bottom-Left: Completed Projects */}
+
+
+ {overviewMetrics.completedProjects}
+
+
Completed Projects
+
+
+ {/* Bottom-Right: Total Payment */}
+
+
+ {formatZAR(overviewMetrics.totalPayment)}
+
+
Total Payment
+
+
+
+
+
+ {/* 2. INBOX CARD */}
+
+
+
+
Inbox
+ {unreadCount > 0 && (
+
+ {unreadCount}
+
+ )}
+
+
setActiveTab('messages')}
+ className="text-xs font-semibold text-slate-400 hover:text-emerald-300 transition-colors cursor-pointer"
+ >
+ See All
+
+
+
+
+ {inboxNotifications.length === 0 ? (
+
+
+
+
+
+
Your inbox is empty
+
+ Messages from your funding administrator will appear here.
+
+
+
+ setContactTarget({
+ organisation: partnerSummary.primaryFundingOrg,
+ projectTitle: 'General Partner Enquiry',
+ })
+ }
+ className="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-xl bg-emerald-500/15 hover:bg-emerald-500/25 text-emerald-300 text-xs font-semibold transition-colors cursor-pointer"
+ >
+
+ Send a Message
+
+
+ ) : (
+ inboxNotifications.slice(0, 6).map(msg => {
+ const isUnread = Number(msg.status) !== NotificationStatus.Read;
+ const senderLabel =
+ msg.destination?.split('|')[0]?.replace('To:', '').trim() ||
+ partnerSummary.primaryFundingOrg?.name ||
+ 'Funding Office';
+
+ return (
+
{
+ markAsRead(msg.id);
+ setSelectedNotification(msg);
+ }}
+ className="w-full p-4 text-left hover:bg-white/[0.03] transition-colors flex items-start gap-3.5 cursor-pointer"
+ >
+
+ {getInitials(msg.subject || senderLabel)}
+
+
+
+
+
+ {msg.subject || senderLabel}
+
+ {isUnread && (
+
+ )}
+
+
+ {formatShortDate(msg.createdAt)}
+
+
+
+ {msg.message}
+
+
+
+ );
+ })
+ )}
+
+
+
+
+ ) : activeTab === 'messages' ? (
+ /* =================================================================
+ MESSAGES TAB — Simple Non-Technical Partner Inbox & Compose
+ ================================================================= */
+
+
+
+
Messages & Communications
+
+ Direct communication with your funding programme officers.
+
+
+
{
+ setContactTarget({
+ organisation: partnerSummary.primaryFundingOrg,
+ projectTitle: 'Partner Support',
+ });
+ setMessageSubject('');
+ setMessageBody('');
+ }}
+ className="inline-flex items-center gap-2 px-4 py-2.5 rounded-xl bg-emerald-500 hover:bg-emerald-400 text-slate-950 font-semibold text-xs shadow-lg shadow-emerald-500/15 transition-all cursor-pointer"
+ >
+
+ New Message
+
+
+
+
+ {inboxNotifications.length === 0 ? (
+
+ No messages in your inbox yet.
+
+ ) : (
+ inboxNotifications.map(msg => {
+ const isUnread = Number(msg.status) !== NotificationStatus.Read;
+ return (
+
{
+ markAsRead(msg.id);
+ setSelectedNotification(msg);
+ }}
+ className="p-5 hover:bg-white/[0.03] transition-colors flex items-start justify-between gap-4 cursor-pointer"
+ >
+
+
+ {getInitials(msg.subject || 'PFM')}
+
+
+
+
+ {msg.subject || 'Programme Notification'}
+
+ {isUnread && (
+
+ New
+
+ )}
+
+
{msg.message}
+
+
+
+ {formatFullDate(msg.createdAt)}
+
+
+ );
+ })
+ )}
+
+
+ ) : activeTab === 'contracts' ? (
+ /* =================================================================
+ CONTRACTS & GRANTS TAB — View & Sign Contracts Simply
+ ================================================================= */
+
+
+
Contracts & Approved Grants
+
+ Review your approved funding awards and sign or download your grant agreements.
+
+
+
+
+ {partnerSummary.contractEntries.length === 0 ? (
+
+ No contracts have been issued to your organisation yet.
+
+ ) : (
+ partnerSummary.contractEntries.map(({ contract, award, fundingOrganisation }) => {
+ const isExecuted = Number(contract.status) === ContractStatus.Executed;
+ const contractVer = (contract as any).version || 1;
+ return (
+
+
+
+
+ Agreement v{contractVer}
+
+
+ {isExecuted ? 'Signed & Executed' : 'Action Required: Review & Sign'}
+
+
+
+ {fundingOrganisation?.name || 'Grant Funding Agreement'}
+
+
+
+
Contract Value
+
+ {formatZAR(contract.totalValue || award?.amount || 0)}
+
+
+
+
Valid Period
+
+ {formatShortDate(contract.effectiveAt)} – {formatFullDate(contract.expiresAt)}
+
+
+
+
+
+
+ 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"
+ >
+
+ Download PDF
+
+ {!isExecuted && canAdd('grant_financials') && (
+ signContractMutation.mutate(contract)}
+ disabled={signContractMutation.isPending}
+ className="flex-1 py-2.5 px-4 rounded-xl bg-emerald-500 hover:bg-emerald-400 text-slate-950 text-xs font-semibold flex items-center justify-center gap-2 transition-colors cursor-pointer disabled:opacity-50"
+ >
+
+ {signContractMutation.isPending ? 'Signing...' : 'Accept & Sign'}
+
+ )}
+
+
+ );
+ })
+ )}
+
+
+ ) : (
+ /* =================================================================
+ DELIVERABLES & DOCUMENTS TAB — All Uploaded Evidence Across Projects
+ ================================================================= */
+
+
+
Deliverables & Proof Documents
+
+ All reports, invoices, and milestone proof files submitted across your projects.
+
+
+
+
+ {enrichedProjects.flatMap(p =>
+ p.evidence.map(ev => ({ ev, project: p.project }))
+ ).length === 0 ? (
+
+
+ No documents uploaded yet. Open any project on your Dashboard and click "View Details" to upload proof documents.
+
+
+ ) : (
+
+ {enrichedProjects
+ .flatMap(p => p.evidence.map(ev => ({ ev, project: p.project })))
+ .map(({ ev, project }) => {
+ const evType = Number((ev as any).evidenceType ?? ev.type);
+ const evDate = (ev as any).submittedAt ?? ev.createdAt;
+ return (
+
+
+
+
+
+
+
+ {EVIDENCE_TYPE_LABELS[evType] || 'Supporting Document'}{' '}
+
+ (v{ev.version || 1})
+
+
+
+ Project: {project.name} • Submitted {formatFullDate(evDate)}
+
+
+
+
handleOpenSecureFile('evidence', ev.id)}
+ className="px-3.5 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-xs font-semibold text-emerald-300 flex items-center gap-1.5 shrink-0 cursor-pointer"
+ >
+
+ View File
+
+
+ );
+ })}
+
+ )}
+
+
+ )}
+
+
+ {/* =====================================================================
+ MODAL 1: PROJECT WORKSPACE ("View Details" button on Project Card)
+ Simple, non-technical tabs for Milestones, Proof Upload, Payments, Updates
+ ===================================================================== */}
+ {activeProjectModalItem && (
+
+
+ {/* Modal Header */}
+
+
+
+
+ Project Workspace
+
+
+ Funder: {activeProjectModalItem.fundingOrganisation?.name || 'Funding Partner'}
+
+
+
+ {activeProjectModalItem.project.name}
+
+ {activeProjectModalItem.project.description && (
+
+ {activeProjectModalItem.project.description}
+
+ )}
+
+
setSelectedProjectItem(null)}
+ className="p-2 rounded-xl bg-slate-800/80 text-slate-400 hover:text-white cursor-pointer"
+ >
+
+
+
+
+ {/* Simple Step-by-Step Tabs */}
+
+ {(
+ [
+ { id: 'milestones', label: `1. Milestones (${activeProjectModalItem.milestones.length})` },
+ { id: 'evidence', label: `2. Upload Proof (${activeProjectModalItem.evidence.length})` },
+ { id: 'payments', label: `3. Request Payment (${activeProjectModalItem.disbursements.length})` },
+ { id: 'activities', label: `4. Progress Updates (${activeProjectModalItem.activities.length})` },
+ { id: 'contract', label: '5. Grant Agreement' },
+ ] as const
+ ).map(t => (
+ setProjectModalTab(t.id)}
+ className={`px-4 py-2.5 text-xs font-semibold border-b-2 transition-colors whitespace-nowrap cursor-pointer ${
+ projectModalTab === t.id
+ ? 'border-emerald-400 text-emerald-300'
+ : 'border-transparent text-slate-400 hover:text-slate-200'
+ }`}
+ >
+ {t.label}
+
+ ))}
+
+
+ {/* Modal Body */}
+
+ {projectModalTab === 'milestones' && (
+
+
+
Project Milestones
+ {activeProjectModalItem.milestones.length === 0 ? (
+
+ No milestones have been added to this project yet. You can add one below.
+
+ ) : (
+
+ {activeProjectModalItem.milestones.map((m, idx) => {
+ const isVerified = Number(m.status) === ApprovalStatus.Approved;
+ return (
+
+
+
+ {isVerified ? : idx + 1}
+
+
+
{m.name}
+
+ Due: {formatFullDate(m.dueAt)}
+
+
+
+
+
+ {isVerified ? 'Verified' : 'In Progress'}
+
+ {
+ setSelectedMilestoneForUpload(String(m.id));
+ setProjectModalTab('evidence');
+ }}
+ className="px-3 py-1.5 rounded-lg bg-emerald-500/15 hover:bg-emerald-500/25 text-emerald-300 text-xs font-semibold transition-colors cursor-pointer"
+ >
+ Upload Proof
+
+
+
+ );
+ })}
+
+ )}
+
+
+ {canAdd('projects_milestones') && (
+
+ )}
+
+ )}
+
+ {projectModalTab === 'evidence' && (
+
+ {/* Simple Upload Box */}
+
+
+
+
+ Upload Supporting Document or Proof
+
+
+
+ Attach invoices, progress reports, photos, or certificates to prove milestone completion.
+
+
+
+
+ Which Milestone is this for? (Optional)
+
+ setSelectedMilestoneForUpload(e.target.value)}
+ className="w-full px-3.5 py-2 rounded-xl bg-slate-950 border border-white/10 text-xs text-white"
+ >
+ General Project Document
+ {activeProjectModalItem.milestones.map(m => (
+
+ {m.name}
+
+ ))}
+
+
+
+
+ Document Type
+
+ setSelectedEvidenceType(Number(e.target.value))}
+ className="w-full px-3.5 py-2 rounded-xl bg-slate-950 border border-white/10 text-xs text-white"
+ >
+ {Object.entries(EVIDENCE_TYPE_LABELS).map(([val, label]) => (
+
+ {label}
+
+ ))}
+
+
+
+
+
+ setEvidenceFile(e.target.files?.[0] || null)}
+ className="text-xs text-slate-300 file:mr-3 file:py-2 file:px-4 file:rounded-xl file:border-0 file:text-xs file:font-semibold file:bg-emerald-500/15 file:text-emerald-300 hover:file:bg-emerald-500/25"
+ />
+ uploadEvidenceMutation.mutate()}
+ disabled={!evidenceFile || uploadingEvidence}
+ className="px-5 py-2 rounded-xl bg-emerald-500 hover:bg-emerald-400 text-slate-950 font-semibold text-xs disabled:opacity-50 cursor-pointer"
+ >
+ {uploadingEvidence ? 'Uploading...' : 'Submit Document'}
+
+
+
+
+ {/* Existing Evidence List */}
+
+
+ Submitted Documents ({activeProjectModalItem.evidence.length})
+
+ {activeProjectModalItem.evidence.length === 0 ? (
+
+ No documents uploaded for this project yet.
+
+ ) : (
+ activeProjectModalItem.evidence.map(ev => {
+ const evType = Number((ev as any).evidenceType ?? ev.type);
+ const evDate = (ev as any).submittedAt ?? ev.createdAt;
+ return (
+
+
+
+
+
+ {EVIDENCE_TYPE_LABELS[evType] || 'Document'} (v{ev.version || 1})
+
+
+ Uploaded {formatFullDate(evDate)}
+
+
+
+
handleOpenSecureFile('evidence', ev.id)}
+ className="text-xs font-semibold text-emerald-300 hover:text-emerald-200 flex items-center gap-1 cursor-pointer"
+ >
+ Open
+
+
+
+ );
+ })
+ )}
+
+
+ )}
+
+ {projectModalTab === 'payments' && (
+
+ {/* Request Payment Box */}
+
+
Request a Grant Payment Drawdown
+
+ Select the completed milestone and enter the amount you are requesting.
+
+
+
+ Amount (ZAR)
+ setPaymentAmount(e.target.value)}
+ placeholder="e.g. 50000"
+ className="w-full px-3.5 py-2 rounded-xl bg-slate-950 border border-white/10 text-xs text-white"
+ />
+
+
+ Linked Milestone
+ setPaymentMilestoneId(e.target.value)}
+ className="w-full px-3.5 py-2 rounded-xl bg-slate-950 border border-white/10 text-xs text-white"
+ >
+ Select milestone (optional)
+ {activeProjectModalItem.milestones.map(m => (
+
+ {m.name}
+
+ ))}
+
+
+
+
requestPaymentMutation.mutate()}
+ disabled={requestPaymentMutation.isPending}
+ className="px-5 py-2 rounded-xl bg-emerald-500 hover:bg-emerald-400 text-slate-950 font-semibold text-xs cursor-pointer disabled:opacity-50"
+ >
+ {requestPaymentMutation.isPending ? 'Submitting...' : 'Submit Payment Request'}
+
+
+
+ {/* Payment History */}
+
+
+ Payment Requests & Disbursements
+
+ {activeProjectModalItem.disbursements.length === 0 ? (
+
+ No payment requests recorded for this project yet.
+
+ ) : (
+ activeProjectModalItem.disbursements.map(d => {
+ const isApproved = Number(d.status) === ApprovalStatus.Approved;
+ return (
+
+
+
+ {formatZAR(d.amount)}
+
+
+ Requested {formatFullDate(d.createdAt)}
+
+
+
+ {isApproved ? 'Approved & Paid' : 'Pending Review'}
+
+
+ );
+ })
+ )}
+
+
+ )}
+
+ {projectModalTab === 'activities' && (
+
+
+
+
+ {activeProjectModalItem.activities.map(act => (
+
+
+
{act.title}
+ {act.description && (
+
{act.description}
+ )}
+
+ {act.amount > 0 && (
+
+ {formatZAR(act.amount)}
+
+ )}
+
+ ))}
+
+
+ )}
+
+ {projectModalTab === 'contract' && (
+
+ {activeProjectModalItem.contract ? (
+
+
+
+
+ Grant Contract v{(activeProjectModalItem.contract as any).version || 1}
+
+
+ Total Value: {formatZAR(activeProjectModalItem.contract.totalValue)}
+
+
+
+ handleOpenSecureFile('contract', activeProjectModalItem.contract!.id)
+ }
+ className="px-4 py-2 rounded-xl bg-slate-800 hover:bg-slate-700 text-xs font-semibold text-emerald-300 flex items-center gap-2 cursor-pointer"
+ >
+
+ Download Contract
+
+
+
+ {Number(activeProjectModalItem.contract.status) !== ContractStatus.Executed && (
+
+ )}
+
+ ) : (
+
+ No contract linked to this project yet.
+
+ )}
+
+ )}
+
+
+
+ )}
+
+ {/* =====================================================================
+ MODAL 2: CONTACT FUNDER ("Contact" button on Project Card)
+ ===================================================================== */}
+ {contactTarget && (
+
+
+
+
+
+ Contact {contactTarget.organisation?.name || 'Funding Team'}
+
+ {contactTarget.projectTitle && (
+
{contactTarget.projectTitle}
+ )}
+
+
setContactTarget(null)}
+ className="p-1.5 rounded-lg text-slate-400 hover:text-white cursor-pointer"
+ >
+
+
+
+
+
+
+
+ )}
+
+ {/* =====================================================================
+ MODAL 3: MESSAGE READER (Clicking an Inbox item)
+ ===================================================================== */}
+ {selectedNotification && (
+
+
+
+
+
+ {selectedNotification.subject || 'Message'}
+
+
+ Received {formatFullDate(selectedNotification.createdAt)}
+
+
+
setSelectedNotification(null)}
+ className="p-1.5 rounded-lg text-slate-400 hover:text-white cursor-pointer"
+ >
+
+
+
+
+
+ {selectedNotification.message}
+
+
+ setSelectedNotification(null)}
+ className="px-4 py-2 rounded-xl bg-slate-800 text-slate-300 text-xs font-semibold cursor-pointer"
+ >
+ Close
+
+ {
+ const subj = selectedNotification.subject || 'Notification';
+ setSelectedNotification(null);
+ setContactTarget({
+ organisation: partnerSummary.primaryFundingOrg,
+ projectTitle: 'Reply',
+ });
+ setMessageSubject(subj.startsWith('Re:') ? subj : `Re: ${subj}`);
+ setMessageBody('');
+ }}
+ className="px-4 py-2 rounded-xl bg-emerald-500 hover:bg-emerald-400 text-slate-950 font-semibold text-xs flex items-center gap-1.5 cursor-pointer"
+ >
+
+ Reply
+
+
+
+
+
+ )}
+
+ );
+}
+export default DashboardPage;
diff --git a/PostFundManagement.PartnerUI/src/components/dashboard/ProjectCard.tsx b/PostFundManagement.PartnerUI/src/components/dashboard/ProjectCard.tsx
index 43948eb..f4d145d 100644
--- a/PostFundManagement.PartnerUI/src/components/dashboard/ProjectCard.tsx
+++ b/PostFundManagement.PartnerUI/src/components/dashboard/ProjectCard.tsx
@@ -138,9 +138,9 @@ export const ProjectCard: React.FC