Merge pull request 'Implemented markdown based notification message body formatting' (#156) from finishline into main
Reviewed-on: #156
This commit was merged in pull request #156.
This commit is contained in:
@@ -69,7 +69,7 @@ export const UserSearchPicker: React.FC<UserSearchPickerProps> = ({
|
||||
const filtered = activeUsers.filter(u => {
|
||||
if (!term) return true;
|
||||
const email = (u.email || '').toLowerCase();
|
||||
const role = (u.role || '').toLowerCase();
|
||||
const role = String(u.role ?? '').toLowerCase();
|
||||
return email.includes(term) || role.includes(term);
|
||||
});
|
||||
|
||||
@@ -80,7 +80,7 @@ export const UserSearchPicker: React.FC<UserSearchPickerProps> = ({
|
||||
const otherUsers: User[] = [];
|
||||
|
||||
filtered.forEach(u => {
|
||||
const r = (u.role || '').toLowerCase();
|
||||
const r = String(u.role ?? '').toLowerCase();
|
||||
if (r.includes('admin') || r.includes('executive') || r.includes('director')) {
|
||||
adminUsers.push(u);
|
||||
} else if (r.includes('programme') || r.includes('project') || r.includes('manager')) {
|
||||
@@ -178,7 +178,7 @@ export const UserSearchPicker: React.FC<UserSearchPickerProps> = ({
|
||||
id: u.id,
|
||||
email: u.email,
|
||||
name: formattedName,
|
||||
role: u.role || 'User',
|
||||
role: String(u.role ?? 'User'),
|
||||
});
|
||||
setIsDropdownOpen(false);
|
||||
} else {
|
||||
@@ -186,7 +186,7 @@ export const UserSearchPicker: React.FC<UserSearchPickerProps> = ({
|
||||
id: u.id,
|
||||
email: u.email,
|
||||
name: formattedName,
|
||||
role: u.role || 'User',
|
||||
role: String(u.role ?? 'User'),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,6 +214,11 @@ export const ExecutiveDashboard: React.FC = () => {
|
||||
return dashboard?.disbursed || 0;
|
||||
}, [dashboard, disbursements, filteredProjects, selectedProgramme, selectedSector]);
|
||||
|
||||
const disbursedPercentage = useMemo(() => {
|
||||
if (!portfolioValue || portfolioValue <= 0) return 0;
|
||||
return Math.min(100, Math.round((disbursed / portfolioValue) * 1000) / 10);
|
||||
}, [disbursed, portfolioValue]);
|
||||
|
||||
const onTrackPercentage = useMemo(() => {
|
||||
const targetProjects = filteredProjects.length > 0 ? filteredProjects : projects;
|
||||
if (targetProjects.length === 0) return 0;
|
||||
@@ -341,7 +346,7 @@ export const ExecutiveDashboard: React.FC = () => {
|
||||
}, [disbursements]);
|
||||
|
||||
// Projects requiring management attention based on risk profile
|
||||
const attentionProjects = useMemo(() => {
|
||||
const attentionProjects = useMemo<AttentionProject[]>(() => {
|
||||
const targetProjects = filteredProjects.length > 0 ? filteredProjects : projects;
|
||||
if (targetProjects.length === 0) return [];
|
||||
|
||||
@@ -356,7 +361,7 @@ export const ExecutiveDashboard: React.FC = () => {
|
||||
});
|
||||
|
||||
return targetProjects
|
||||
.map((p) => {
|
||||
.map((p): AttentionProject => {
|
||||
const maxScore = projectMaxRisk[String(p.id)] || 0;
|
||||
const isRejected = p.status === ApprovalStatus.Rejected;
|
||||
const isPending = p.status === ApprovalStatus.Pending || p.status === ApprovalStatus.Review;
|
||||
@@ -393,7 +398,7 @@ export const ExecutiveDashboard: React.FC = () => {
|
||||
|
||||
return {
|
||||
id: p.id,
|
||||
name: p.name,
|
||||
name: p.name || 'Unnamed Project',
|
||||
programme: p.programmeName || `Programme ${p.programmeId}`,
|
||||
status: statusLabel,
|
||||
risk: riskLevel,
|
||||
@@ -568,187 +573,328 @@ export const ExecutiveDashboard: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 8 Headline Portfolio KPI Cards with Compliance & Budget At Risk */}
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 xl:grid-cols-8 gap-3.5">
|
||||
{/* 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">
|
||||
<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" />
|
||||
{/* Executive Portfolio KPIs: Structured 2-Tier Balanced Grid */}
|
||||
<div className="space-y-6">
|
||||
{/* Tier 1: Capital Commitments & Deployment Scale */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between px-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[11px] font-bold uppercase tracking-widest text-slate-400">
|
||||
Capital Deployment & Scale
|
||||
</span>
|
||||
<span className="h-1 w-1 rounded-full bg-slate-600" />
|
||||
<span className="text-xs text-slate-500 hidden sm:inline">Portfolio commitments, disbursements & capital exposure</span>
|
||||
</div>
|
||||
<span className="text-[10px] font-semibold text-blue-400/90 bg-blue-500/10 px-2 py-0.5 rounded-full border border-blue-500/20">
|
||||
Financial Governance
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="text-2xl font-bold text-white tracking-tight">{formatCompactZAR(portfolioValue)}</p>
|
||||
<p className="text-[10px] text-slate-400 font-mono mt-1">{formatZAR(portfolioValue)}</p>
|
||||
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{/* Card 1: Portfolio Value */}
|
||||
<div className="bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-blue-500/40 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm relative overflow-hidden">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex items-center gap-1.5 min-w-0 flex-1">
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-slate-400 truncate">
|
||||
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="shrink-0 p-2 rounded-xl bg-blue-500/10 text-blue-400 border border-blue-500/20 group-hover:scale-105 transition-transform">
|
||||
<Coins className="w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="text-2xl sm:text-3xl font-bold text-white tracking-tight">
|
||||
{formatCompactZAR(portfolioValue)}
|
||||
</p>
|
||||
<div className="flex items-center justify-between mt-3 pt-2.5 border-t border-slate-800/70 text-xs">
|
||||
<span className="text-slate-400 font-mono text-[11px] truncate mr-2" title={formatZAR(portfolioValue)}>
|
||||
{formatZAR(portfolioValue)}
|
||||
</span>
|
||||
<span className="text-[10px] font-medium text-blue-400 bg-blue-500/10 px-1.5 py-0.5 rounded border border-blue-500/20 shrink-0">
|
||||
Statutory Total
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Card 2: Disbursed */}
|
||||
<div className="bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-teal-500/40 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm relative overflow-hidden">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex items-center gap-1.5 min-w-0 flex-1">
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-slate-400 truncate">
|
||||
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="shrink-0 p-2 rounded-xl bg-teal-500/10 text-teal-400 border border-teal-500/20 group-hover:scale-105 transition-transform">
|
||||
<TrendingUp className="w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<div className="flex items-baseline justify-between gap-2">
|
||||
<p className="text-2xl sm:text-3xl font-bold text-teal-400 tracking-tight">
|
||||
{formatCompactZAR(disbursed)}
|
||||
</p>
|
||||
<span className="text-xs font-semibold text-teal-400 bg-teal-500/10 px-2 py-0.5 rounded-full border border-teal-500/20 shrink-0 font-mono">
|
||||
{disbursedPercentage}%
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full h-1.5 bg-slate-800/90 rounded-full overflow-hidden mt-3">
|
||||
<div
|
||||
className="h-full bg-gradient-to-r from-teal-500 to-emerald-400 rounded-full transition-all duration-500"
|
||||
style={{ width: `${disbursedPercentage}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-2.5 pt-2 border-t border-slate-800/70 text-xs text-slate-400">
|
||||
<span className="text-[11px]">Tranches released</span>
|
||||
<span className="font-mono text-slate-300 text-[11px]">{disbursedPercentage}% deployed</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Card 3: Active Awards */}
|
||||
<div className="bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-indigo-500/40 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm relative overflow-hidden">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex items-center gap-1.5 min-w-0 flex-1">
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-slate-400 truncate">
|
||||
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="shrink-0 p-2 rounded-xl bg-indigo-500/10 text-indigo-400 border border-indigo-500/20 group-hover:scale-105 transition-transform">
|
||||
<Layers className="w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="text-2xl sm:text-3xl font-bold text-white tracking-tight">
|
||||
{activeAwards}
|
||||
</p>
|
||||
<div className="flex items-center justify-between mt-3 pt-2.5 border-t border-slate-800/70 text-xs">
|
||||
<span className="text-slate-400 text-[11px]">Under implementation</span>
|
||||
<span className="font-medium text-indigo-400 bg-indigo-500/10 px-1.5 py-0.5 rounded border border-indigo-500/20 shrink-0 text-[10px]">
|
||||
{portfolios.length} Portfolio{portfolios.length === 1 ? '' : 's'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Card 4: Budget At Risk */}
|
||||
<div className="bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-rose-500/40 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm relative overflow-hidden">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex items-center gap-1.5 min-w-0 flex-1">
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-slate-400 truncate">
|
||||
Budget At Risk
|
||||
</span>
|
||||
<HelpTooltip
|
||||
title="Total Budgeted Capital At Risk"
|
||||
content="Aggregated approved budget across all projects currently flagged as noncompliant or carrying unmitigated high risks."
|
||||
size="sm"
|
||||
placement="top"
|
||||
/>
|
||||
</div>
|
||||
<div className="shrink-0 p-2 rounded-xl bg-rose-500/10 text-rose-400 border border-rose-500/20 group-hover:scale-105 transition-transform">
|
||||
<ShieldAlert className="w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="text-2xl sm:text-3xl font-bold text-rose-400 tracking-tight">
|
||||
{formatCompactZAR(totalBudgetAtRisk)}
|
||||
</p>
|
||||
<div className="flex items-center justify-between mt-3 pt-2.5 border-t border-slate-800/70 text-xs">
|
||||
<span className="text-slate-400 font-mono text-[11px] truncate mr-2" title={formatZAR(totalBudgetAtRisk)}>
|
||||
{formatZAR(totalBudgetAtRisk)}
|
||||
</span>
|
||||
<span className={`text-[10px] font-medium px-1.5 py-0.5 rounded border shrink-0 ${
|
||||
totalBudgetAtRisk > 0
|
||||
? 'text-rose-400 bg-rose-500/10 border-rose-500/20'
|
||||
: 'text-emerald-400 bg-emerald-500/10 border-emerald-500/20'
|
||||
}`}>
|
||||
{totalBudgetAtRisk > 0 ? 'Exposed' : 'Clear'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 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">
|
||||
<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" />
|
||||
{/* Tier 2: Operational Health, Risk & Beneficiary Impact */}
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between px-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[11px] font-bold uppercase tracking-widest text-slate-400">
|
||||
Operational Performance & Impact
|
||||
</span>
|
||||
<span className="h-1 w-1 rounded-full bg-slate-600" />
|
||||
<span className="text-xs text-slate-500 hidden sm:inline">Milestone pace, compliance gate health & direct beneficiaries</span>
|
||||
</div>
|
||||
<span className="text-[10px] font-semibold text-emerald-400/90 bg-emerald-500/10 px-2 py-0.5 rounded-full border border-emerald-500/20">
|
||||
Delivery Health
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="text-2xl font-bold text-white tracking-tight">{activeAwards}</p>
|
||||
<p className="text-[10px] text-emerald-400 font-mono mt-1">
|
||||
Across {portfolios.length} portfolio{portfolios.length === 1 ? '' : 's'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 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">
|
||||
<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 className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{/* Card 5: On Track */}
|
||||
<div className="bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-emerald-500/40 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm relative overflow-hidden">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex items-center gap-1.5 min-w-0 flex-1">
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-slate-400 truncate">
|
||||
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="shrink-0 p-2 rounded-xl bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 group-hover:scale-105 transition-transform">
|
||||
<CheckCircle className="w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<div className="flex items-baseline justify-between gap-2">
|
||||
<p className="text-2xl sm:text-3xl font-bold text-emerald-400 tracking-tight">
|
||||
{onTrackPercentage}%
|
||||
</p>
|
||||
<span className="text-xs font-semibold text-emerald-400 bg-emerald-500/10 px-2 py-0.5 rounded-full border border-emerald-500/20 shrink-0 font-mono">
|
||||
{onTrackPercentage >= 80 ? 'Optimal' : onTrackPercentage >= 60 ? 'Moderate' : 'Critical'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full h-1.5 bg-slate-800/90 rounded-full overflow-hidden mt-3">
|
||||
<div
|
||||
className="h-full bg-gradient-to-r from-emerald-500 to-teal-400 rounded-full transition-all duration-500"
|
||||
style={{ width: `${Math.min(100, onTrackPercentage)}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-2.5 pt-2 border-t border-slate-800/70 text-xs text-slate-400">
|
||||
<span className="text-[11px]">Milestones verified</span>
|
||||
<span className="font-mono text-slate-300 text-[11px]">Pace index</span>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="text-2xl font-bold text-teal-400 tracking-tight">{formatCompactZAR(disbursed)}</p>
|
||||
<p className="text-[10px] text-slate-400 font-mono mt-1">
|
||||
{portfolioValue > 0 ? `${((disbursed / portfolioValue) * 100).toFixed(1)}% of commitments` : '0.0% of commitments'}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 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">
|
||||
<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"
|
||||
/>
|
||||
{/* Card 6: Compliance Health */}
|
||||
<div className="bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-emerald-500/40 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm relative overflow-hidden">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex items-center gap-1.5 min-w-0 flex-1">
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-slate-400 truncate">
|
||||
Compliance
|
||||
</span>
|
||||
<HelpTooltip
|
||||
title="Portfolio Compliance Health"
|
||||
content="Percentage of active projects meeting 100% statutory compliance and beneficiary outcome criteria."
|
||||
size="sm"
|
||||
placement="top"
|
||||
/>
|
||||
</div>
|
||||
<div className="shrink-0 p-2 rounded-xl bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 group-hover:scale-105 transition-transform">
|
||||
<ShieldCheck className="w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<div className="flex items-baseline justify-between gap-2">
|
||||
<p className="text-2xl sm:text-3xl font-bold text-emerald-400 tracking-tight">
|
||||
{complianceRate}%
|
||||
</p>
|
||||
<span className="text-xs font-semibold text-emerald-400 bg-emerald-500/10 px-2 py-0.5 rounded-full border border-emerald-500/20 shrink-0 font-mono">
|
||||
{complianceRate >= 90 ? 'Passed' : 'Gated'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="w-full h-1.5 bg-slate-800/90 rounded-full overflow-hidden mt-3">
|
||||
<div
|
||||
className="h-full bg-gradient-to-r from-emerald-500 to-cyan-400 rounded-full transition-all duration-500"
|
||||
style={{ width: `${Math.min(100, complianceRate)}%` }}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between mt-2.5 pt-2 border-t border-slate-800/70 text-xs text-slate-400">
|
||||
<span className="text-[11px]">Statutory gate check</span>
|
||||
<span className="font-mono text-slate-300 text-[11px]">100% requirement</span>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="text-2xl font-bold text-emerald-400 tracking-tight">{onTrackPercentage}%</p>
|
||||
<p className="text-[10px] text-slate-400 font-mono mt-1">Milestones verified</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 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">
|
||||
<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"
|
||||
/>
|
||||
{/* Card 7: At Risk */}
|
||||
<div className="bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-amber-500/40 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm relative overflow-hidden">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex items-center gap-1.5 min-w-0 flex-1">
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-slate-400 truncate">
|
||||
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="shrink-0 p-2 rounded-xl bg-amber-500/10 text-amber-400 border border-amber-500/20 group-hover:scale-105 transition-transform">
|
||||
<AlertTriangle className="w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="text-2xl sm:text-3xl font-bold text-amber-400 tracking-tight">
|
||||
{atRiskCount}
|
||||
</p>
|
||||
<div className="flex items-center justify-between mt-3 pt-2.5 border-t border-slate-800/70 text-xs">
|
||||
<span className="text-slate-400 text-[11px]">{atRiskCount === 1 ? 'Project flagged' : 'Projects flagged'}</span>
|
||||
<span className={`text-[10px] font-medium px-1.5 py-0.5 rounded border shrink-0 ${
|
||||
atRiskCount > 0
|
||||
? 'text-amber-400 bg-amber-500/10 border-amber-500/20'
|
||||
: 'text-emerald-400 bg-emerald-500/10 border-emerald-500/20'
|
||||
}`}>
|
||||
{atRiskCount > 0 ? 'Requires PM Action' : 'Nominal'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="text-2xl font-bold text-amber-400 tracking-tight">{atRiskCount}</p>
|
||||
<p className="text-[10px] text-rose-400 font-mono mt-1">Requires PM action</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 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">
|
||||
<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"
|
||||
/>
|
||||
{/* Card 8: Beneficiaries Reached */}
|
||||
<div className="bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-purple-500/40 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm relative overflow-hidden">
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="flex items-center gap-1.5 min-w-0 flex-1">
|
||||
<span className="text-xs font-semibold uppercase tracking-wider text-slate-400 truncate">
|
||||
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="shrink-0 p-2 rounded-xl bg-purple-500/10 text-purple-400 border border-purple-500/20 group-hover:scale-105 transition-transform">
|
||||
<Users className="w-4 h-4" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="text-2xl sm:text-3xl font-bold text-white tracking-tight">
|
||||
{beneficiariesReached >= 1000 ? `${(beneficiariesReached / 1000).toFixed(1)}k` : beneficiariesReached}
|
||||
</p>
|
||||
<div className="flex items-center justify-between mt-3 pt-2.5 border-t border-slate-800/70 text-xs">
|
||||
<span className="text-slate-400 text-[11px]">Directly impacted</span>
|
||||
<span className="text-[10px] font-medium text-purple-400 bg-purple-500/10 px-1.5 py-0.5 rounded border border-purple-500/20 shrink-0">
|
||||
M&E Verified
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="text-2xl font-bold text-white tracking-tight">
|
||||
{beneficiariesReached >= 1000 ? `${(beneficiariesReached / 1000).toFixed(0)}k` : beneficiariesReached}
|
||||
</p>
|
||||
<p className="text-[10px] text-slate-400 font-mono mt-1">Directly impacted</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Card 7: Budget At Risk */}
|
||||
<div className="bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-rose-500/40 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm">
|
||||
<div className="flex items-center justify-between text-slate-400">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Budget At Risk</span>
|
||||
<HelpTooltip
|
||||
title="Total Budgeted Capital At Risk"
|
||||
content="Aggregated approved budget across all projects currently flagged as noncompliant or carrying unmitigated high risks."
|
||||
size="sm"
|
||||
placement="top"
|
||||
/>
|
||||
</div>
|
||||
<div className="p-1.5 rounded-lg bg-rose-500/10 text-rose-400 border border-rose-500/20 group-hover:scale-110 transition">
|
||||
<AlertTriangle className="w-3.5 h-3.5" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="text-2xl font-bold text-rose-400 tracking-tight">{formatCompactZAR(totalBudgetAtRisk)}</p>
|
||||
<p className="text-[10px] text-slate-400 font-mono mt-1">{formatZAR(totalBudgetAtRisk)}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Card 8: Compliance Health */}
|
||||
<div className="bg-[#0f1523]/80 p-5 rounded-2xl border border-slate-800/80 hover:border-emerald-500/40 transition-all flex flex-col justify-between shadow-md group backdrop-blur-sm">
|
||||
<div className="flex items-center justify-between text-slate-400">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Compliance</span>
|
||||
<HelpTooltip
|
||||
title="Portfolio Compliance Health"
|
||||
content="Percentage of active projects meeting 100% statutory compliance and beneficiary outcome criteria."
|
||||
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">
|
||||
<ShieldCheck className="w-3.5 h-3.5" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<p className="text-2xl font-bold text-emerald-400 tracking-tight">{complianceRate}%</p>
|
||||
<p className="text-[10px] text-slate-400 font-mono mt-1">100% statutory gate</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user