import React, { useState, useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; import { ShieldCheck, ShieldAlert, AlertTriangle, Users, Search, Filter, RefreshCw, Eye, CheckCircle2, Coins, Building2, FileSpreadsheet, Download, AlertCircle, } from 'lucide-react'; import { getProjects, getAllProgrammes, getAllPortfolios, getBudgets, getProjectComplianceItems, getProjectBeneficiaries, getBeneficiaryOutcomes, getProjectRisks, } from '../services/api'; import { Project, Programme, Portfolio, ComplianceItem, Beneficiary, Outcome, ComplianceStatus, ApprovalStatus, } from '../types'; import { formatDate, formatZAR, formatCompactZAR, getIndustrySectorLabel, getProjectTypeLabel, getApprovalStatusBadge, } from '../lib/formatters'; import { HelpTooltip } from '../components/ui/HelpTooltip'; import { ComplianceStatusBadge } from '../components/ui/StatusBadge'; import { ProjectComplianceModal } from '../components/projects/ProjectComplianceModal'; import { ExportButton } from '../components/ui/ExportButton'; import { ExportColumn } from '../utils/exportUtils'; export const CompliancePage: React.FC = () => { const [searchTerm, setSearchTerm] = useState(''); const [selectedProgramme, setSelectedProgramme] = useState('All'); const [selectedComplianceFilter, setSelectedComplianceFilter] = useState<'All' | 'Compliant' | 'Noncompliant'>('All'); const [selectedProjectForAudit, setSelectedProjectForAudit] = useState(null); const [isAuditModalOpen, setIsAuditModalOpen] = useState(false); // Queries const { data: projects = [], isLoading: loadingProjects, refetch: refetchProjects, isFetching } = useQuery({ queryKey: ['projects'], queryFn: () => getProjects(), }); const { data: programmes = [] } = useQuery({ queryKey: ['programmes'], queryFn: () => getAllProgrammes(), }); const { data: portfolios = [] } = useQuery({ queryKey: ['portfolios'], queryFn: () => getAllPortfolios(), }); const { data: allBudgets = [] } = useQuery({ queryKey: ['budgets'], queryFn: () => getBudgets(1, 200), }); const { data: beneficiariesMap = new Map() } = useQuery({ queryKey: ['allProjectBeneficiaries', projects.map((p) => p.id)], queryFn: async () => { const map = new Map(); await Promise.all( projects.map(async (p) => { try { const bens = await getProjectBeneficiaries(p.id); map.set(p.id, bens); } catch { map.set(p.id, []); } }) ); return map; }, enabled: projects.length > 0, }); // Query compliance items for all projects const { data: complianceMap = new Map(), isLoading: loadingComps } = useQuery({ queryKey: ['allProjectCompliance', projects.map((p) => p.id)], queryFn: async () => { const map = new Map(); await Promise.all( projects.map(async (p) => { try { const items = await getProjectComplianceItems(p.id); map.set(p.id, items); } catch { map.set(p.id, []); } }) ); return map; }, enabled: projects.length > 0, }); // Query beneficiary outcomes const allBensList = useMemo(() => { const list: Beneficiary[] = []; beneficiariesMap.forEach((bList) => list.push(...bList)); return list; }, [beneficiariesMap]); const { data: outcomesMap = new Map() } = useQuery({ queryKey: ['allBeneficiaryOutcomes', allBensList.map((b) => b.id)], queryFn: async () => { const map = new Map(); await Promise.all( allBensList.map(async (b) => { try { const outs = await getBeneficiaryOutcomes(b.id); map.set(b.id, outs); } catch { map.set(b.id, []); } }) ); return map; }, enabled: allBensList.length > 0, }); // Lookups const programmeMap = useMemo(() => { const map = new Map(); programmes.forEach((pr) => map.set(pr.id, pr)); return map; }, [programmes]); // Aggregate project compliance records const projectComplianceStats = useMemo(() => { return projects.map((p) => { const prog = programmeMap.get(p.programmeId); const items = complianceMap.get(p.id) || []; const projectBens = beneficiariesMap.get(p.id) || []; let totalOutcomes = 0; let metOutcomes = 0; projectBens.forEach((b) => { const bOutcomes = outcomesMap.get(b.id) || []; totalOutcomes += bOutcomes.length; metOutcomes += bOutcomes.filter( (o) => Number(o.status) === ComplianceStatus.Met || Number(o.status) === ComplianceStatus.Exceeded ).length; }); const totalItems = items.length; const metItems = items.filter( (i) => Number(i.status) === ComplianceStatus.Met || Number(i.status) === ComplianceStatus.Exceeded ).length; const unmetItems = totalItems - metItems; const unmetOutcomes = totalOutcomes - metOutcomes; const isCompliant = unmetItems === 0 && unmetOutcomes === 0; const projectBudgets = allBudgets.filter((b) => String(b.projectId) === String(p.id)); const approvedBudget = projectBudgets .filter((b) => Number(b.status) === ApprovalStatus.Approved || b.status === undefined) .reduce((sum, b) => sum + (Number(b.amount) || 0), 0); const budgetAtRisk = isCompliant ? 0 : approvedBudget; return { project: p, programme: prog, totalItems, metItems, unmetItems, totalOutcomes, metOutcomes, unmetOutcomes, beneficiariesCount: projectBens.length, isCompliant, approvedBudget, budgetAtRisk, }; }); }, [projects, programmeMap, complianceMap, beneficiariesMap, outcomesMap, allBudgets]); // Filtered rows const filteredRows = useMemo(() => { return projectComplianceStats.filter((row) => { if (selectedProgramme !== 'All' && String(row.project.programmeId) !== selectedProgramme) { return false; } if (selectedComplianceFilter === 'Compliant' && !row.isCompliant) { return false; } if (selectedComplianceFilter === 'Noncompliant' && row.isCompliant) { return false; } if (searchTerm.trim()) { const q = searchTerm.toLowerCase(); const pName = (row.project.name || '').toLowerCase(); const prName = (row.programme?.name || '').toLowerCase(); const idStr = String(row.project.id); if (!pName.includes(q) && !prName.includes(q) && !idStr.includes(q)) { return false; } } return true; }); }, [projectComplianceStats, selectedProgramme, selectedComplianceFilter, searchTerm]); // Aggregate KPI figures const totalProjectsCount = projectComplianceStats.length; const compliantProjectsCount = projectComplianceStats.filter((r) => r.isCompliant).length; const noncompliantProjectsCount = totalProjectsCount - compliantProjectsCount; const overallComplianceRate = totalProjectsCount > 0 ? Math.round((compliantProjectsCount / totalProjectsCount) * 100) : 100; const totalBudgetAtRisk = projectComplianceStats.reduce((sum, r) => sum + r.budgetAtRisk, 0); const totalBeneficiaries = allBensList.length; const complianceExportColumns: ExportColumn[] = [ { header: 'Project ID', accessor: (r) => r.project.id }, { header: 'Project Name', accessor: (r) => r.project.name }, { header: 'Programme', accessor: (r) => r.programme?.name || `Prog #${r.project.programmeId}` }, { header: 'Sector', accessor: (r) => getIndustrySectorLabel(r.project.sector) }, { header: 'Compliance Status', accessor: (r) => (r.isCompliant ? '100% Compliant' : 'Noncompliant') }, { header: 'Requirements Met', accessor: (r) => `${r.metItems}/${r.totalItems}` }, { header: 'Outcomes Met', accessor: (r) => `${r.metOutcomes}/${r.totalOutcomes}` }, { header: 'Approved Budget', accessor: (r) => formatZAR(r.approvedBudget) }, { header: 'Budget At Risk', accessor: (r) => formatZAR(r.budgetAtRisk) }, ]; return (
{/* Header Banner */}
Statutory Governance & Compliance

Portfolio Compliance Dashboard

Enterprise-wide compliance audit across all funded projects, beneficiary impact outcomes, and capital at risk.

{/* 5 Headline KPI Cards */}
{/* Card 1: Overall Rate */}
Compliance Rate

{overallComplianceRate}%

100% threshold target

{/* Card 2: Compliant Projects */}
Compliant

{compliantProjectsCount}

Eligible for approval

{/* Card 3: Noncompliant Projects */}
Noncompliant

{noncompliantProjectsCount}

Approvals blocked

{/* Card 4: Total Capital At Risk */}
Capital At Risk

{formatCompactZAR(totalBudgetAtRisk)}

{formatZAR(totalBudgetAtRisk)}

{/* Card 5: Beneficiaries Monitored */}
Beneficiaries

{totalBeneficiaries}

Impact groups monitored

{/* Filter Toolbar */}
setSearchTerm(e.target.value)} className="pl-9 pr-3 py-1.5 bg-[#090d16] border border-slate-700/80 rounded-xl text-xs text-white placeholder-slate-500 focus:outline-none focus:border-blue-500 w-64 transition" />
Programme:
Compliance:
Showing {filteredRows.length} of {projectComplianceStats.length} projects
{/* Projects Compliance Register Table */}

Project Compliance Matrix & Audit Register

Invariant: 100% compliance required for formal project approval
{loadingProjects || loadingComps ? ( ) : filteredRows.length === 0 ? ( ) : ( filteredRows.map((r) => ( )) )}
Project Name & ID Programme Compliance Status Requirements Met Outcomes Met Approved Budget Capital At Risk Actions
Loading compliance matrix...
No projects matching active compliance filters.
{r.project.name} ID #{r.project.id} • {getIndustrySectorLabel(r.project.sector)} {r.programme?.name || `Prog #${r.project.programmeId}`} {r.isCompliant ? ( 100% Compliant ) : ( Noncompliant ({r.unmetItems + r.unmetOutcomes} Unmet) )} {r.metItems}/{r.totalItems} {r.metOutcomes}/{r.totalOutcomes} {formatZAR(r.approvedBudget)} 0 ? 'text-rose-400' : 'text-emerald-400'}> {formatZAR(r.budgetAtRisk)}
{/* Compliance Audit Modal */} { setIsAuditModalOpen(false); setSelectedProjectForAudit(null); }} project={selectedProjectForAudit} programme={selectedProjectForAudit ? programmeMap.get(selectedProjectForAudit.programmeId) || null : null} />
); };