Refactored UI endpoint usage

This commit is contained in:
2026-09-20 08:26:06 +02:00
parent b703b0c64d
commit 5829bffaa0
35 changed files with 17482 additions and 648 deletions
@@ -28,6 +28,7 @@ import {
import { formatCompactZAR, formatZAR, getIndustrySectorLabel } from '../lib/formatters';
import { ApprovalStatus } from '../types';
import { RiskHeatmapMatrix } from '../components/ui/RiskHeatmapMatrix';
import { HelpTooltip } from '../components/ui/HelpTooltip';
export const ExecutiveDashboard: React.FC = () => {
const queryClient = useQueryClient();
@@ -35,7 +36,7 @@ export const ExecutiveDashboard: React.FC = () => {
const [selectedSector, setSelectedSector] = useState('All');
const [selectedPeriod, setSelectedPeriod] = useState('FY 2026/27');
// Real Queries from OpenAPI Endpoints
// Data queries
const { data: dashboard, isLoading: loadingDashboard, refetch: refetchDashboard, isFetching } = useQuery({
queryKey: ['dashboard'],
queryFn: getDashboard,
@@ -109,7 +110,7 @@ export const ExecutiveDashboard: React.FC = () => {
return risks.filter((r) => projectIds.has(String(r.projectId)));
}, [risks, filteredProjects, selectedProgramme, selectedSector]);
// Headline metrics sourced directly from live API models with real filtering
// Headline portfolio metrics calculation
const portfolioValue = useMemo(() => {
if (selectedProgramme !== 'All' || selectedSector !== 'All') {
const projectIds = new Set(filteredProjects.map((p) => String(p.id)));
@@ -176,7 +177,7 @@ export const ExecutiveDashboard: React.FC = () => {
return Math.round((onTrack / targetProjects.length) * 100);
}, [dashboard, filteredProjects, projects]);
// Dynamically calculate At Risk count directly from live registered risks
// Calculate at-risk projects count based on risk scores
const atRiskCount = useMemo(() => {
const targetRisks = filteredRisks;
const highRisks = targetRisks.filter((r) => {
@@ -198,7 +199,7 @@ export const ExecutiveDashboard: React.FC = () => {
return sumActual > 0 ? sumActual : 0;
}, [dashboard, indicators]);
// Real sector breakdown calculated dynamically from actual project records
// Sector allocation breakdown
const sectorData = useMemo(() => {
const targetProjects = filteredProjects.length > 0 ? filteredProjects : projects;
if (targetProjects.length === 0) return [];
@@ -224,7 +225,7 @@ export const ExecutiveDashboard: React.FC = () => {
});
}, [projects, filteredProjects, portfolioValue]);
// Quarterly disbursements distribution computed from real disbursement items
// Quarterly disbursements distribution
const quarterlyTrend = useMemo(() => {
if (!disbursements || disbursements.length === 0) {
return [
@@ -260,7 +261,7 @@ export const ExecutiveDashboard: React.FC = () => {
}));
}, [disbursements]);
// Attention Projects derived directly from live project data & risk scores
// Projects requiring management attention based on risk profile
const attentionProjects = useMemo(() => {
const targetProjects = filteredProjects.length > 0 ? filteredProjects : projects;
if (targetProjects.length === 0) return [];
@@ -323,6 +324,12 @@ export const ExecutiveDashboard: React.FC = () => {
<h2 className="text-xl font-bold text-white tracking-tight">
Executive Portfolio Dashboard
</h2>
<HelpTooltip
title="Executive Dashboard Overview"
content="Macro-level command center showing aggregated national portfolio commitments, actual tranche disbursements, delivery pace, and critical project escalations."
size="sm"
placement="bottom"
/>
<span className="text-[11px] font-semibold text-blue-400 bg-blue-500/10 px-2.5 py-0.5 rounded-full border border-blue-500/30">
Live Portfolio Feed
</span>
@@ -339,41 +346,65 @@ export const ExecutiveDashboard: React.FC = () => {
<span>Filter:</span>
</div>
<select
value={selectedProgramme}
onChange={(e) => setSelectedProgramme(e.target.value)}
className="text-xs font-medium bg-[#090d16] border border-slate-700/80 rounded-xl px-3 py-1.5 text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 transition cursor-pointer"
>
<option value="All">Programme: All</option>
{programmes.map((pr) => (
<option key={pr.id} value={pr.id}>
{pr.name}
</option>
))}
</select>
<div className="flex items-center gap-1">
<select
value={selectedProgramme}
onChange={(e) => setSelectedProgramme(e.target.value)}
className="text-xs font-medium bg-[#090d16] border border-slate-700/80 rounded-xl px-3 py-1.5 text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 transition cursor-pointer"
>
<option value="All">Programme: All</option>
{programmes.map((pr) => (
<option key={pr.id} value={pr.id}>
{pr.name}
</option>
))}
</select>
<HelpTooltip
title="Programme Filter"
content="Isolates metrics and projects funded under a specific thematic programme mandate."
size="sm"
placement="bottom"
/>
</div>
<select
value={selectedSector}
onChange={(e) => setSelectedSector(e.target.value)}
className="text-xs font-medium bg-[#090d16] border border-slate-700/80 rounded-xl px-3 py-1.5 text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 transition cursor-pointer"
>
<option value="All">Sector: All</option>
{sectorData.map((s) => (
<option key={s.name} value={s.name}>
{s.name}
</option>
))}
</select>
<div className="flex items-center gap-1">
<select
value={selectedSector}
onChange={(e) => setSelectedSector(e.target.value)}
className="text-xs font-medium bg-[#090d16] border border-slate-700/80 rounded-xl px-3 py-1.5 text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 transition cursor-pointer"
>
<option value="All">Sector: All</option>
{sectorData.map((s) => (
<option key={s.name} value={s.name}>
{s.name}
</option>
))}
</select>
<HelpTooltip
title="Industry Sector Filter"
content="Filters analytics by designated economic industry sector (e.g. AgriTech, HealthTech, Renewable Energy)."
size="sm"
placement="bottom"
/>
</div>
<select
value={selectedPeriod}
onChange={(e) => setSelectedPeriod(e.target.value)}
className="text-xs font-medium bg-[#090d16] border border-slate-700/80 rounded-xl px-3 py-1.5 text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 transition cursor-pointer"
>
<option value="FY 2026/27">Period: FY 2026/27</option>
<option value="FY 2025/26">Period: FY 2025/26</option>
<option value="All-Time">All-Time Cumulative</option>
</select>
<div className="flex items-center gap-1">
<select
value={selectedPeriod}
onChange={(e) => setSelectedPeriod(e.target.value)}
className="text-xs font-medium bg-[#090d16] border border-slate-700/80 rounded-xl px-3 py-1.5 text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 transition cursor-pointer"
>
<option value="FY 2026/27">Period: FY 2026/27</option>
<option value="FY 2025/26">Period: FY 2025/26</option>
<option value="All-Time">All-Time Cumulative</option>
</select>
<HelpTooltip
title="Financial Period Filter"
content="Calculates disbursements and milestones according to the government fiscal year or cumulative life-of-programme."
size="sm"
placement="bottom"
/>
</div>
<button
onClick={refetchAll}
@@ -391,7 +422,15 @@ export const ExecutiveDashboard: React.FC = () => {
{/* Card 1: Portfolio Value */}
<div className="bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-blue-500/30 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm">
<div className="flex items-center justify-between text-slate-400">
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Portfolio Value</span>
<div className="flex items-center gap-1.5">
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Portfolio Value</span>
<HelpTooltip
title="Total Portfolio Value"
content="Aggregated statutory grant commitment amount approved across all active grant awards and contracts."
size="sm"
placement="top"
/>
</div>
<div className="p-1.5 rounded-lg bg-blue-500/10 text-blue-400 border border-blue-500/20 group-hover:scale-110 transition">
<Coins className="w-3.5 h-3.5" />
</div>
@@ -405,7 +444,15 @@ export const ExecutiveDashboard: React.FC = () => {
{/* Card 2: Active Awards */}
<div className="bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-blue-500/30 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm">
<div className="flex items-center justify-between text-slate-400">
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Active Awards</span>
<div className="flex items-center gap-1.5">
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Active Awards</span>
<HelpTooltip
title="Active Grant Awards"
content="Total legally executed statutory agreements currently undergoing active disbursement and implementation."
size="sm"
placement="top"
/>
</div>
<div className="p-1.5 rounded-lg bg-indigo-500/10 text-indigo-400 border border-indigo-500/20 group-hover:scale-110 transition">
<Layers className="w-3.5 h-3.5" />
</div>
@@ -421,7 +468,15 @@ export const ExecutiveDashboard: React.FC = () => {
{/* Card 3: Disbursed */}
<div className="bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-teal-500/30 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm">
<div className="flex items-center justify-between text-slate-400">
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Disbursed</span>
<div className="flex items-center gap-1.5">
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Disbursed</span>
<HelpTooltip
title="Disbursed Funds"
content="Actual funds paid out to implementing grantees following verified milestone proof-of-performance."
size="sm"
placement="top"
/>
</div>
<div className="p-1.5 rounded-lg bg-teal-500/10 text-teal-400 border border-teal-500/20 group-hover:scale-110 transition">
<TrendingUp className="w-3.5 h-3.5" />
</div>
@@ -437,7 +492,15 @@ export const ExecutiveDashboard: React.FC = () => {
{/* Card 4: On Track */}
<div className="bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-emerald-500/30 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm">
<div className="flex items-center justify-between text-slate-400">
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">On Track</span>
<div className="flex items-center gap-1.5">
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">On Track</span>
<HelpTooltip
title="On-Track Progress Ratio"
content="Percentage of active projects meeting schedule deadlines and deliverable acceptance criteria without critical deviations."
size="sm"
placement="top"
/>
</div>
<div className="p-1.5 rounded-lg bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 group-hover:scale-110 transition">
<CheckCircle className="w-3.5 h-3.5" />
</div>
@@ -451,7 +514,15 @@ export const ExecutiveDashboard: React.FC = () => {
{/* Card 5: At Risk */}
<div className="bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-amber-500/30 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm">
<div className="flex items-center justify-between text-slate-400">
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">At Risk</span>
<div className="flex items-center gap-1.5">
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">At Risk</span>
<HelpTooltip
title="At-Risk Projects"
content="Count of projects with high residual risk scores (≥12), schedule slippage >30 days, or adverse financial variance."
size="sm"
placement="top"
/>
</div>
<div className="p-1.5 rounded-lg bg-amber-500/10 text-amber-400 border border-amber-500/20 group-hover:scale-110 transition">
<AlertTriangle className="w-3.5 h-3.5" />
</div>
@@ -465,7 +536,15 @@ export const ExecutiveDashboard: React.FC = () => {
{/* Card 6: Beneficiaries Reached */}
<div className="bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-indigo-500/30 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm">
<div className="flex items-center justify-between text-slate-400">
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Beneficiaries</span>
<div className="flex items-center gap-1.5">
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Beneficiaries</span>
<HelpTooltip
title="Beneficiaries Impacted"
content="Cumulative count of direct beneficiaries reached through projects, verified against national M&E indicator targets."
size="sm"
placement="top"
/>
</div>
<div className="p-1.5 rounded-lg bg-indigo-500/10 text-indigo-400 border border-indigo-500/20 group-hover:scale-110 transition">
<Users className="w-3.5 h-3.5" />
</div>
@@ -485,13 +564,21 @@ export const ExecutiveDashboard: React.FC = () => {
<div className="bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 flex flex-col justify-between shadow-lg backdrop-blur-sm">
<div>
<div className="flex items-center justify-between border-b border-slate-800/80 pb-3">
<h3 className="text-sm font-semibold text-white">Funding by Sector</h3>
<div className="flex items-center gap-2">
<h3 className="text-sm font-semibold text-white">Funding by Sector</h3>
<HelpTooltip
title="Sector Funding Distribution"
content="Breakdown of active grant commitments distributed across designated industrial and socio-economic sectors."
size="sm"
placement="bottom"
/>
</div>
<span className="text-[11px] font-mono text-blue-400 bg-blue-500/10 px-2 py-0.5 rounded-md border border-blue-500/30">
Live Portfolio
</span>
</div>
{/* Circular Graphic Simulation */}
{/* Sector Donut Chart */}
<div className="py-6 flex items-center justify-center">
<div className="relative w-36 h-36 rounded-full border-8 border-slate-800 flex items-center justify-center">
<div className="w-24 h-24 rounded-full border-4 border-blue-500 flex flex-col items-center justify-center bg-[#090d16] shadow-inner">
@@ -535,7 +622,15 @@ export const ExecutiveDashboard: React.FC = () => {
<div className="bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 flex flex-col justify-between shadow-lg backdrop-blur-sm">
<div>
<div className="flex items-center justify-between border-b border-slate-800/80 pb-3">
<h3 className="text-sm font-semibold text-white">Disbursement Distribution</h3>
<div className="flex items-center gap-2">
<h3 className="text-sm font-semibold text-white">Disbursement Distribution</h3>
<HelpTooltip
title="Disbursement Timeline & Cadence"
content="Compares statutory multi-year grant tranche commitments against verified payouts released across calendar quarters."
size="sm"
placement="bottom"
/>
</div>
<span className="text-[11px] font-mono text-emerald-400 bg-emerald-500/10 px-2 py-0.5 rounded-md border border-emerald-500/30 font-semibold">
{disbursements.length} Tranche{disbursements.length === 1 ? '' : 's'}
</span>
@@ -545,7 +640,7 @@ export const ExecutiveDashboard: React.FC = () => {
Quarterly release distribution across active portfolios and milestone verifications.
</p>
{/* Graphic Trend Visualization from Live Disbursements */}
{/* Quarterly Disbursement Trend Chart */}
<div className="h-44 flex items-end gap-3 pt-6 pb-2 px-2 border-b border-slate-800/80">
{quarterlyTrend.map((q) => (
<div key={q.quarter} className="flex-1 flex flex-col items-center gap-1.5 h-full justify-end">
@@ -597,7 +692,15 @@ export const ExecutiveDashboard: React.FC = () => {
<div className="bg-[#0f1523]/80 rounded-2xl border border-slate-800/80 overflow-hidden shadow-lg backdrop-blur-sm">
<div className="p-5 border-b border-slate-800/80 flex flex-col sm:flex-row sm:items-center justify-between gap-2 bg-[#0d121f]/90">
<div>
<h3 className="text-base font-semibold text-white">Projects Requiring Attention</h3>
<div className="flex items-center gap-2">
<h3 className="text-base font-semibold text-white">Projects Requiring Attention</h3>
<HelpTooltip
title="Exception Escalations"
content="Identifies projects breaching risk thresholds, schedule deadlines, or financial variances (>10%) requiring executive steering committee action."
size="sm"
placement="bottom"
/>
</div>
<p className="text-xs text-slate-400 mt-0.5">
Prioritized by risk severity, delivery delay, or financial variance threshold breach.
</p>