+ {/* Uncluttered Top Bar-Free Grantee Context Header */}
+
+
+
+
+ PFM Grantee Portal
+
+ {hashedTenantId && (
+
+ #{hashedTenantId}
+
+ )}
+
+
+ {user?.tenant || 'Grantee Portfolio Overview'}
+
+
+ Welcome, {user?.name || 'Grantee Partner'} ({user?.email || 'Authorized Grantee'}). Access historic awards, track deliverable milestones, and submit activity evidence dossiers.
+
+
+
+ {/* Minimalist Top Actions (Refresh & Sign Out) */}
+
+ loadDashboardData(true)}
+ disabled={isRefreshing || isLoading}
+ className="flex items-center gap-2 px-3.5 py-2 rounded-2xl bg-white/[0.04] hover:bg-white/[0.08] border border-white/10 text-xs font-semibold text-slate-200 hover:text-white transition-all cursor-pointer shadow-sm active:scale-95"
+ title="Refresh latest awards and project milestones"
+ >
+
+ {isRefreshing ? 'Refreshing...' : 'Sync Data'}
+
+
+
+
+ Sign Out
+
+
+
+
+ {/* Global Error Banner (Comma-delimited error display) */}
+ {errorMessages.length > 0 && (
+
+
+
+
Notice: API server returned validation issues:
+
+
+ {errorMessages.map((msg, idx) => (
+ {msg}
+ ))}
+
+
+ )}
+
+ {/* Search & Filter Controls */}
+
+
+
+
+ setSearchQuery(e.target.value)}
+ className="w-full pl-9 pr-4 py-2 rounded-2xl bg-slate-900/60 border border-white/10 text-xs text-slate-200 placeholder:text-slate-500 focus:outline-none focus:border-emerald-500/50 backdrop-blur-md"
+ />
+
+
+
+
+
+ setStatusFilter(e.target.value)}
+ aria-label="Filter projects by status"
+ className="pl-3.5 pr-8 py-2 rounded-2xl bg-slate-900/60 border border-white/10 text-xs text-slate-200 focus:outline-none focus:border-emerald-500/50 appearance-none backdrop-blur-md cursor-pointer"
+ >
+ All Project Statuses
+ Active
+ Approved
+ Pending
+ Completed
+
+
+
+
+
+
+ {/* Main Content Area */}
+ {isLoading ? (
+
+
+
Retrieving applicant awards and project deliverables...
+
+ ) : filteredProjects.length === 0 ? (
+
+
+
+
+
No Projects or Awards Found
+
+ {searchQuery
+ ? 'No project records matched your search query. Try clearing the filter.'
+ : 'There are currently no active or historical awards linked to this grantee account.'}
+
+ {searchQuery && (
+
{
+ setSearchQuery('');
+ setStatusFilter('all');
+ }}
+ className="mt-2 text-xs text-emerald-400 hover:text-emerald-300 font-semibold underline cursor-pointer"
+ >
+ Clear filters
+
+ )}
+
+ ) : (
+
+ {filteredProjects.map((project) => {
+ const pKey = String(project.id);
+ const projectAwards = awards.filter((a) => String(a.projectId) === pKey);
+ const projectMilestones = milestonesByProject[pKey] || [];
+ const projectActivities = activitiesByProject[pKey] || [];
+ const projectEvidence = evidenceByProject[pKey] || [];
+
+ return (
+
loadDashboardData(true)}
+ />
+ );
+ })}
+
+ )}
+
+ );
+};
diff --git a/PostFundManagement.PartnerUI/src/components/dashboard/EvidenceUploadModal.tsx b/PostFundManagement.PartnerUI/src/components/dashboard/EvidenceUploadModal.tsx
new file mode 100644
index 0000000..44c8c5c
--- /dev/null
+++ b/PostFundManagement.PartnerUI/src/components/dashboard/EvidenceUploadModal.tsx
@@ -0,0 +1,292 @@
+import React, { useState } from 'react';
+import { api, extractApiErrors } from '../../services/api';
+import { Activity, Milestone, Project, EvidenceType } from '../../services/api/models';
+import {
+ X,
+ UploadCloud,
+ FileText,
+ CheckCircle2,
+ AlertTriangle,
+ Loader2,
+ Plus,
+ Sparkles,
+ Link as LinkIcon
+} from 'lucide-react';
+
+interface EvidenceUploadModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ project: Project;
+ milestone?: Milestone;
+ activity: Activity;
+ onEvidenceUploaded: () => void;
+}
+
+export const EvidenceUploadModal: React.FC