import React, { useState, useMemo } from 'react'; import { useQuery, useQueryClient } from '@tanstack/react-query'; import { Link } from 'react-router-dom'; import { TrendingUp, AlertTriangle, Users, Layers, Coins, CheckCircle, ArrowUpRight, Filter, RefreshCw, Clock, ShieldAlert, RotateCcw, LayoutDashboard, ShieldCheck, } from 'lucide-react'; import { getDashboard, getProjects, getAllPortfolios, getAllProgrammes, getAwards, getBudgets, getDisbursements, getAllRisks, getAllIndicators, getProjectComplianceItems, } from '../services/api'; import { formatCompactZAR, formatZAR, getIndustrySectorLabel } from '../lib/formatters'; import { ApprovalStatus, ComplianceStatus } from '../types'; import { RiskHeatmapMatrix } from '../components/ui/RiskHeatmapMatrix'; import { HelpTooltip } from '../components/ui/HelpTooltip'; import { useTableSort, SortableTh } from '../utils/tableSort'; interface AttentionProject { id: number | string; name: string; programme: string; status: string; risk: string; complianceStatus: string; isCompliant: boolean; unmetCount: number; budgetAtRisk: number; financialVariance: string; varianceType: string; maxScore: number; } export const ExecutiveDashboard: React.FC = () => { const queryClient = useQueryClient(); const [selectedProgramme, setSelectedProgramme] = useState('All'); const [selectedSector, setSelectedSector] = useState('All'); const [selectedPeriod, setSelectedPeriod] = useState('FY 2026/27'); const isFiltered = selectedProgramme !== 'All' || selectedSector !== 'All' || selectedPeriod !== 'FY 2026/27'; const activeFilterCount = (selectedProgramme !== 'All' ? 1 : 0) + (selectedSector !== 'All' ? 1 : 0) + (selectedPeriod !== 'FY 2026/27' ? 1 : 0); const resetFilters = () => { setSelectedProgramme('All'); setSelectedSector('All'); setSelectedPeriod('FY 2026/27'); }; // Data queries const { data: dashboard, isLoading: loadingDashboard, refetch: refetchDashboard, isFetching } = useQuery({ queryKey: ['dashboard'], queryFn: getDashboard, }); const { data: projects = [] } = useQuery({ queryKey: ['projects'], queryFn: () => getProjects(), }); const { data: portfolios = [] } = useQuery({ queryKey: ['portfolios'], queryFn: () => getAllPortfolios(), }); const { data: programmes = [] } = useQuery({ queryKey: ['programmes'], queryFn: () => getAllProgrammes(), }); const { data: awards = [] } = useQuery({ queryKey: ['awards'], queryFn: () => getAwards(), }); const { data: budgets = [] } = useQuery({ queryKey: ['budgets'], queryFn: () => getBudgets(), }); const { data: disbursements = [] } = useQuery({ queryKey: ['disbursements'], queryFn: () => getDisbursements(), }); const { data: risks = [] } = useQuery({ queryKey: ['risks'], queryFn: () => getAllRisks(), }); const { data: indicators = [] } = useQuery({ queryKey: ['indicators'], queryFn: () => getAllIndicators(), }); // Filtered projects according to active Programme and Sector dropdown selections const filteredProjects = useMemo(() => { return projects.filter((p) => { if (selectedProgramme !== 'All' && String(p.programmeId) !== selectedProgramme) { return false; } if (selectedSector !== 'All') { const sectorLabel = getIndustrySectorLabel(p.sector); if (sectorLabel !== selectedSector) return false; } return true; }); }, [projects, selectedProgramme, selectedSector]); const { data: projectComplianceMap = new Map() } = useQuery({ queryKey: ['projectComplianceMap', filteredProjects.map((p) => p.id)], queryFn: async () => { const map = new Map(); await Promise.all( filteredProjects.map(async (p) => { try { const items = await getProjectComplianceItems(p.id); map.set(p.id, items); } catch { map.set(p.id, []); } }) ); return map; }, enabled: filteredProjects.length > 0, }); const refetchAll = () => { refetchDashboard(); queryClient.invalidateQueries(); }; // Filtered risks associated with the filtered project set const filteredRisks = useMemo(() => { if (selectedProgramme === 'All' && selectedSector === 'All') { return risks; } const projectIds = new Set(filteredProjects.map((p) => String(p.id))); return risks.filter((r) => projectIds.has(String(r.projectId))); }, [risks, filteredProjects, selectedProgramme, selectedSector]); // Headline portfolio metrics calculation const portfolioValue = useMemo(() => { if (selectedProgramme !== 'All' || selectedSector !== 'All') { const projectIds = new Set(filteredProjects.map((p) => String(p.id))); const filteredBudgets = budgets.filter((b) => projectIds.has(String(b.projectId))); const budgetTotal = filteredBudgets.reduce((sum, b) => sum + (Number(b.amount) || 0), 0); if (budgetTotal > 0) return budgetTotal; const filteredAwards = awards.filter((a) => projectIds.has(String(a.projectId))); const awardsTotal = filteredAwards.reduce((sum, a) => sum + (Number(a.amount) || 0), 0); return awardsTotal; } if (dashboard?.portfolioValue && dashboard.portfolioValue > 0) { return dashboard.portfolioValue; } const budgetTotal = budgets.reduce((sum, b) => sum + (Number(b.amount) || 0), 0); if (budgetTotal > 0) return budgetTotal; const awardsTotal = awards.reduce((sum, a) => sum + (Number(a.amount) || 0), 0); return awardsTotal; }, [dashboard, budgets, awards, filteredProjects, selectedProgramme, selectedSector]); const activeAwards = useMemo(() => { if (selectedProgramme !== 'All' || selectedSector !== 'All') { const projectIds = new Set(filteredProjects.map((p) => String(p.id))); const matched = awards.filter((a) => projectIds.has(String(a.projectId))); return matched.length || filteredProjects.length; } if (awards.length > 0) return awards.length; return dashboard?.activeAwards ?? projects.length; }, [dashboard, awards, filteredProjects, projects, selectedProgramme, selectedSector]); const disbursed = useMemo(() => { const relevantDisbursements = disbursements.filter((d) => { if (selectedProgramme !== 'All' || selectedSector !== 'All') { const projectIds = new Set(filteredProjects.map((p) => String(p.id))); return projectIds.has(String(d.projectId)); } return true; }); const approvedTotal = relevantDisbursements .filter((d) => d.status === ApprovalStatus.Approved || (d.status as number) === 3) .reduce((sum, d) => sum + (Number(d.amount) || 0), 0); if (approvedTotal > 0) return approvedTotal; const allTotal = relevantDisbursements.reduce((sum, d) => sum + (Number(d.amount) || 0), 0); if (allTotal > 0 && (!dashboard?.disbursed || dashboard.disbursed === 0)) { return allTotal; } return dashboard?.disbursed || 0; }, [dashboard, disbursements, filteredProjects, selectedProgramme, selectedSector]); const onTrackPercentage = useMemo(() => { const targetProjects = filteredProjects.length > 0 ? filteredProjects : projects; if (targetProjects.length === 0) return 0; const onTrack = targetProjects.filter( (p) => p.status === ApprovalStatus.Approved || (p.status as number) === 3 || (p.status as unknown as string) === 'Active' ).length; if (onTrack === 0 && dashboard?.onTrackPercentage && dashboard.onTrackPercentage > 0) { return dashboard.onTrackPercentage; } return Math.round((onTrack / targetProjects.length) * 100); }, [dashboard, filteredProjects, projects]); // Calculate at-risk projects count based on risk scores const atRiskCount = useMemo(() => { const targetRisks = filteredRisks; const highRisks = targetRisks.filter((r) => { const score = (Number(r.likelihood) || 1) * (Number(r.impact) || 1); return score >= 12 || ((Number(r.impact) || 1) >= 4 && (Number(r.likelihood) || 1) >= 3); }); if (targetRisks.length > 0) { return highRisks.length; } return dashboard?.atRiskCount ?? 0; }, [dashboard, filteredRisks]); // Compliance and Capital at Risk calculations const totalBudgetAtRisk = useMemo(() => { const targetProjects = filteredProjects.length > 0 ? filteredProjects : projects; return targetProjects.reduce((sum, p) => { const pComps = projectComplianceMap.get(p.id) || []; const hasUnmet = pComps.some((c) => Number(c.status) === ComplianceStatus.NotMet || Number(c.status) === 1); const isPendingOrRejected = p.status === ApprovalStatus.Rejected || p.status === ApprovalStatus.Pending; if (hasUnmet || isPendingOrRejected) { const pBudgets = budgets.filter((b) => String(b.projectId) === String(p.id)); const pApproved = pBudgets .filter((b) => Number(b.status) === ApprovalStatus.Approved || b.status === undefined) .reduce((bSum, b) => bSum + (Number(b.amount) || 0), 0); return sum + pApproved; } return sum; }, 0); }, [filteredProjects, projects, projectComplianceMap, budgets]); const complianceRate = useMemo(() => { const targetProjects = filteredProjects.length > 0 ? filteredProjects : projects; if (targetProjects.length === 0) return 100; const compliantCount = targetProjects.filter((p) => { const pComps = projectComplianceMap.get(p.id) || []; return !pComps.some((c) => Number(c.status) === ComplianceStatus.NotMet || Number(c.status) === 1); }).length; return Math.round((compliantCount / targetProjects.length) * 100); }, [filteredProjects, projects, projectComplianceMap]); const beneficiariesReached = useMemo(() => { if (dashboard?.beneficiariesReached && dashboard.beneficiariesReached > 0) { return dashboard.beneficiariesReached; } const sumActual = indicators.reduce((sum, ind) => sum + (Number(ind.actualAmount) || 0), 0); return sumActual > 0 ? sumActual : 0; }, [dashboard, indicators]); // Sector allocation breakdown const sectorData = useMemo(() => { const targetProjects = filteredProjects.length > 0 ? filteredProjects : projects; if (targetProjects.length === 0) return []; const counts: Record = {}; targetProjects.forEach((p) => { const label = getIndustrySectorLabel(p.sector); counts[label] = (counts[label] || 0) + 1; }); const colors = ['bg-emerald-500', 'bg-teal-500', 'bg-blue-500', 'bg-indigo-500', 'bg-purple-500', 'bg-amber-500']; const totalProjects = targetProjects.length; return Object.entries(counts).map(([name, count], index) => { const percentage = Math.round((count / totalProjects) * 100); const amount = portfolioValue > 0 ? Math.round((portfolioValue / totalProjects) * count) : 0; return { name, count, percentage, amount, color: colors[index % colors.length], }; }); }, [projects, filteredProjects, portfolioValue]); // Quarterly disbursements distribution const quarterlyTrend = useMemo(() => { if (!disbursements || disbursements.length === 0) { return [ { quarter: 'Q1', amount: 0, heightPct: 15 }, { quarter: 'Q2', amount: 0, heightPct: 15 }, { quarter: 'Q3', amount: 0, heightPct: 15 }, { quarter: 'Q4', amount: 0, heightPct: 15 }, ]; } const qTotals: Record = { Q1: 0, Q2: 0, Q3: 0, Q4: 0 }; disbursements.forEach((d) => { const dateStr = d.createdAt || d.updatedAt; if (dateStr) { const month = new Date(dateStr).getMonth(); // 0-11 if (month <= 2) qTotals.Q1 += d.amount || 0; else if (month <= 5) qTotals.Q2 += d.amount || 0; else if (month <= 8) qTotals.Q3 += d.amount || 0; else qTotals.Q4 += d.amount || 0; } else { qTotals.Q1 += (d.amount || 0) * 0.25; qTotals.Q2 += (d.amount || 0) * 0.25; qTotals.Q3 += (d.amount || 0) * 0.25; qTotals.Q4 += (d.amount || 0) * 0.25; } }); const maxQ = Math.max(...Object.values(qTotals), 1); return Object.entries(qTotals).map(([quarter, amount]) => ({ quarter, amount, heightPct: Math.max(15, Math.min(100, Math.round((amount / maxQ) * 100))), })); }, [disbursements]); // Projects requiring management attention based on risk profile const attentionProjects = useMemo(() => { const targetProjects = filteredProjects.length > 0 ? filteredProjects : projects; if (targetProjects.length === 0) return []; // Map each project to its highest residual risk score const projectMaxRisk: Record = {}; risks.forEach((r) => { const pid = String(r.projectId); const score = (Number(r.likelihood) || 1) * (Number(r.impact) || 1); if (!projectMaxRisk[pid] || score > projectMaxRisk[pid]) { projectMaxRisk[pid] = score; } }); return targetProjects .map((p) => { const maxScore = projectMaxRisk[String(p.id)] || 0; const isRejected = p.status === ApprovalStatus.Rejected; const isPending = p.status === ApprovalStatus.Pending || p.status === ApprovalStatus.Review; let riskLevel: 'RED' | 'AMBER' | 'GREEN' = 'GREEN'; let statusLabel = 'Active'; let variance = '0%'; let varianceType: 'negative' | 'positive' = 'positive'; if (isRejected || maxScore >= 15) { riskLevel = 'RED'; statusLabel = 'At Risk'; variance = '-15%'; varianceType = 'negative'; } else if (isPending || maxScore >= 10) { riskLevel = 'AMBER'; statusLabel = isPending ? 'Pending' : 'Review'; variance = '-5%'; varianceType = 'negative'; } const pComps = projectComplianceMap.get(p.id) || []; const unmetCount = pComps.filter( (c) => Number(c.status) === ComplianceStatus.NotMet || Number(c.status) === 1 ).length; const isCompliant = unmetCount === 0; const pBudgets = budgets.filter((b) => String(b.projectId) === String(p.id)); const pApprovedBudget = pBudgets .filter((b) => Number(b.status) === ApprovalStatus.Approved || b.status === undefined) .reduce((sum, b) => sum + (Number(b.amount) || 0), 0); const projectBudgetAtRisk = !isCompliant || riskLevel === 'RED' || riskLevel === 'AMBER' ? pApprovedBudget : 0; return { id: p.id, name: p.name, programme: p.programmeName || `Programme ${p.programmeId}`, status: statusLabel, risk: riskLevel, complianceStatus: isCompliant ? 'Compliant' : `Noncompliant (${unmetCount} Unmet)`, isCompliant, unmetCount, budgetAtRisk: projectBudgetAtRisk, financialVariance: variance, varianceType, maxScore, }; }) .filter((p) => p.risk === 'RED' || p.risk === 'AMBER' || !p.isCompliant) .sort((a, b) => b.budgetAtRisk - a.budgetAtRisk || b.maxScore - a.maxScore) .slice(0, 8); }, [projects, filteredProjects, risks, projectComplianceMap, budgets]); const { sortedData: sortedAttentionProjects, sortColumn: projectSortCol, sortDirection: projectSortDir, handleSort: handleProjectSort, } = useTableSort(attentionProjects, null, null, { name: (p) => p.name, programme: (p) => p.programme, status: (p) => p.status, risk: (p) => p.risk, financialVariance: (p) => p.financialVariance, }); return (
{/* Header Banner */}

Executive Portfolio Dashboard

Live Portfolio Feed

Real-time portfolio overview, disbursement tracking, risk heatmap, and projects requiring governance action.

{isFiltered && ( )}
{/* Filter Toolbar */}
Filters: {activeFilterCount > 0 && ( {activeFilterCount} Active )}
{/* Programme Filter */}
Programme
{/* Sector Filter */}
Sector
{/* Financial Period Filter */}
Period
{/* 8 Headline Portfolio KPI Cards with Compliance & Budget At Risk */}
{/* Card 1: Portfolio Value */}
Portfolio Value

{formatCompactZAR(portfolioValue)}

{formatZAR(portfolioValue)}

{/* Card 2: Active Awards */}
Active Awards

{activeAwards}

Across {portfolios.length} portfolio{portfolios.length === 1 ? '' : 's'}

{/* Card 3: Disbursed */}
Disbursed

{formatCompactZAR(disbursed)}

{portfolioValue > 0 ? `${((disbursed / portfolioValue) * 100).toFixed(1)}% of commitments` : '0.0% of commitments'}

{/* Card 4: On Track */}
On Track

{onTrackPercentage}%

Milestones verified

{/* Card 5: At Risk */}
At Risk

{atRiskCount}

Requires PM action

{/* Card 6: Beneficiaries Reached */}
Beneficiaries

{beneficiariesReached >= 1000 ? `${(beneficiariesReached / 1000).toFixed(0)}k` : beneficiariesReached}

Directly impacted

{/* Card 7: Budget At Risk */}
Budget At Risk

{formatCompactZAR(totalBudgetAtRisk)}

{formatZAR(totalBudgetAtRisk)}

{/* Card 8: Compliance Health */}
Compliance

{complianceRate}%

100% statutory gate

{/* Visual Models Section (Sector Breakdown & Disbursements) */}
{/* Panel 1: Funding by Sector */}

Funding by Sector

Live Portfolio
{/* Sector Donut Chart */}
{sectorData.length} {sectorData.length === 1 ? 'Sector' : 'Sectors'}
{/* Sector Progress Bars */}
{sectorData.length === 0 ? (

No active project sectors assigned yet.

) : ( sectorData.map((s) => (
{s.name} ({s.count}) {s.percentage}%
)) )}
Primary Focus: {sectorData[0]?.name || 'All Sectors'} View Portfolios
{/* Panel 2: Disbursement Distribution */}

Disbursement Distribution

{disbursements.length} Tranche{disbursements.length === 1 ? '' : 's'}

Quarterly release distribution across active portfolios and milestone verifications.

{/* Quarterly Disbursement Trend Chart */}
{quarterlyTrend.map((q) => (
{q.quarter}
))}
Commitment Allocation
Actual Disbursed
Total Disbursed: {formatCompactZAR(disbursed)} Financials
{/* Panel 3: Portfolio Risk Heatmap & Matrix */} {/* Projects Requiring Attention Table */}

Projects Flagged At Risk & Noncompliant

Prioritized by risk severity, delivery delay, or financial variance threshold breach.

Open Project Directory →
Project Programme Status Compliance Risk Budget At Risk {sortedAttentionProjects.length === 0 ? ( ) : ( sortedAttentionProjects.map((p) => ( )) )}
Action
No active projects currently flagged for executive attention. All projects are operating within target thresholds.
{p.name} ID: {p.id} {p.programme} {p.status} {p.isCompliant ? ( Compliant ) : ( Noncompliant ({p.unmetCount} Unmet) )} {p.risk} 0 ? 'text-rose-400' : 'text-emerald-400'}> {formatZAR(p.budgetAtRisk)} Review
); };