Refactored UI endpoint usage
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user