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 => {
|
const filtered = activeUsers.filter(u => {
|
||||||
if (!term) return true;
|
if (!term) return true;
|
||||||
const email = (u.email || '').toLowerCase();
|
const email = (u.email || '').toLowerCase();
|
||||||
const role = (u.role || '').toLowerCase();
|
const role = String(u.role ?? '').toLowerCase();
|
||||||
return email.includes(term) || role.includes(term);
|
return email.includes(term) || role.includes(term);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -80,7 +80,7 @@ export const UserSearchPicker: React.FC<UserSearchPickerProps> = ({
|
|||||||
const otherUsers: User[] = [];
|
const otherUsers: User[] = [];
|
||||||
|
|
||||||
filtered.forEach(u => {
|
filtered.forEach(u => {
|
||||||
const r = (u.role || '').toLowerCase();
|
const r = String(u.role ?? '').toLowerCase();
|
||||||
if (r.includes('admin') || r.includes('executive') || r.includes('director')) {
|
if (r.includes('admin') || r.includes('executive') || r.includes('director')) {
|
||||||
adminUsers.push(u);
|
adminUsers.push(u);
|
||||||
} else if (r.includes('programme') || r.includes('project') || r.includes('manager')) {
|
} else if (r.includes('programme') || r.includes('project') || r.includes('manager')) {
|
||||||
@@ -178,7 +178,7 @@ export const UserSearchPicker: React.FC<UserSearchPickerProps> = ({
|
|||||||
id: u.id,
|
id: u.id,
|
||||||
email: u.email,
|
email: u.email,
|
||||||
name: formattedName,
|
name: formattedName,
|
||||||
role: u.role || 'User',
|
role: String(u.role ?? 'User'),
|
||||||
});
|
});
|
||||||
setIsDropdownOpen(false);
|
setIsDropdownOpen(false);
|
||||||
} else {
|
} else {
|
||||||
@@ -186,7 +186,7 @@ export const UserSearchPicker: React.FC<UserSearchPickerProps> = ({
|
|||||||
id: u.id,
|
id: u.id,
|
||||||
email: u.email,
|
email: u.email,
|
||||||
name: formattedName,
|
name: formattedName,
|
||||||
role: u.role || 'User',
|
role: String(u.role ?? 'User'),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,6 +214,11 @@ export const ExecutiveDashboard: React.FC = () => {
|
|||||||
return dashboard?.disbursed || 0;
|
return dashboard?.disbursed || 0;
|
||||||
}, [dashboard, disbursements, filteredProjects, selectedProgramme, selectedSector]);
|
}, [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 onTrackPercentage = useMemo(() => {
|
||||||
const targetProjects = filteredProjects.length > 0 ? filteredProjects : projects;
|
const targetProjects = filteredProjects.length > 0 ? filteredProjects : projects;
|
||||||
if (targetProjects.length === 0) return 0;
|
if (targetProjects.length === 0) return 0;
|
||||||
@@ -341,7 +346,7 @@ export const ExecutiveDashboard: React.FC = () => {
|
|||||||
}, [disbursements]);
|
}, [disbursements]);
|
||||||
|
|
||||||
// Projects requiring management attention based on risk profile
|
// Projects requiring management attention based on risk profile
|
||||||
const attentionProjects = useMemo(() => {
|
const attentionProjects = useMemo<AttentionProject[]>(() => {
|
||||||
const targetProjects = filteredProjects.length > 0 ? filteredProjects : projects;
|
const targetProjects = filteredProjects.length > 0 ? filteredProjects : projects;
|
||||||
if (targetProjects.length === 0) return [];
|
if (targetProjects.length === 0) return [];
|
||||||
|
|
||||||
@@ -356,7 +361,7 @@ export const ExecutiveDashboard: React.FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return targetProjects
|
return targetProjects
|
||||||
.map((p) => {
|
.map((p): AttentionProject => {
|
||||||
const maxScore = projectMaxRisk[String(p.id)] || 0;
|
const maxScore = projectMaxRisk[String(p.id)] || 0;
|
||||||
const isRejected = p.status === ApprovalStatus.Rejected;
|
const isRejected = p.status === ApprovalStatus.Rejected;
|
||||||
const isPending = p.status === ApprovalStatus.Pending || p.status === ApprovalStatus.Review;
|
const isPending = p.status === ApprovalStatus.Pending || p.status === ApprovalStatus.Review;
|
||||||
@@ -393,7 +398,7 @@ export const ExecutiveDashboard: React.FC = () => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
id: p.id,
|
id: p.id,
|
||||||
name: p.name,
|
name: p.name || 'Unnamed Project',
|
||||||
programme: p.programmeName || `Programme ${p.programmeId}`,
|
programme: p.programmeName || `Programme ${p.programmeId}`,
|
||||||
status: statusLabel,
|
status: statusLabel,
|
||||||
risk: riskLevel,
|
risk: riskLevel,
|
||||||
@@ -568,187 +573,328 @@ export const ExecutiveDashboard: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 8 Headline Portfolio KPI Cards with Compliance & Budget At Risk */}
|
{/* Executive Portfolio KPIs: Structured 2-Tier Balanced Grid */}
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-4 xl:grid-cols-8 gap-3.5">
|
<div className="space-y-6">
|
||||||
{/* Card 1: Portfolio Value */}
|
{/* Tier 1: Capital Commitments & Deployment Scale */}
|
||||||
<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="space-y-3">
|
||||||
<div className="flex items-center justify-between text-slate-400">
|
<div className="flex items-center justify-between px-1">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Portfolio Value</span>
|
<span className="text-[11px] font-bold uppercase tracking-widest text-slate-400">
|
||||||
<HelpTooltip
|
Capital Deployment & Scale
|
||||||
title="Total Portfolio Value"
|
</span>
|
||||||
content="Aggregated statutory grant commitment amount approved across all active grant awards and contracts."
|
<span className="h-1 w-1 rounded-full bg-slate-600" />
|
||||||
size="sm"
|
<span className="text-xs text-slate-500 hidden sm:inline">Portfolio commitments, disbursements & capital exposure</span>
|
||||||
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>
|
</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>
|
||||||
<div className="mt-3">
|
|
||||||
<p className="text-2xl font-bold text-white tracking-tight">{formatCompactZAR(portfolioValue)}</p>
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
<p className="text-[10px] text-slate-400 font-mono mt-1">{formatZAR(portfolioValue)}</p>
|
{/* 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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Card 2: Active Awards */}
|
{/* Tier 2: Operational Health, Risk & Beneficiary Impact */}
|
||||||
<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="space-y-3">
|
||||||
<div className="flex items-center justify-between text-slate-400">
|
<div className="flex items-center justify-between px-1">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Active Awards</span>
|
<span className="text-[11px] font-bold uppercase tracking-widest text-slate-400">
|
||||||
<HelpTooltip
|
Operational Performance & Impact
|
||||||
title="Active Grant Awards"
|
</span>
|
||||||
content="Total legally executed statutory agreements currently undergoing active disbursement and implementation."
|
<span className="h-1 w-1 rounded-full bg-slate-600" />
|
||||||
size="sm"
|
<span className="text-xs text-slate-500 hidden sm:inline">Milestone pace, compliance gate health & direct beneficiaries</span>
|
||||||
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>
|
</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>
|
||||||
<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="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||||
<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">
|
{/* Card 5: On Track */}
|
||||||
<div className="flex items-center justify-between text-slate-400">
|
<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-center gap-1.5">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Disbursed</span>
|
<div className="flex items-center gap-1.5 min-w-0 flex-1">
|
||||||
<HelpTooltip
|
<span className="text-xs font-semibold uppercase tracking-wider text-slate-400 truncate">
|
||||||
title="Disbursed Funds"
|
On Track
|
||||||
content="Actual funds paid out to implementing grantees following verified milestone proof-of-performance."
|
</span>
|
||||||
size="sm"
|
<HelpTooltip
|
||||||
placement="top"
|
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>
|
||||||
<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 */}
|
{/* Card 6: Compliance Health */}
|
||||||
<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="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-center justify-between text-slate-400">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5 min-w-0 flex-1">
|
||||||
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">On Track</span>
|
<span className="text-xs font-semibold uppercase tracking-wider text-slate-400 truncate">
|
||||||
<HelpTooltip
|
Compliance
|
||||||
title="On-Track Progress Ratio"
|
</span>
|
||||||
content="Percentage of active projects meeting schedule deadlines and deliverable acceptance criteria without critical deviations."
|
<HelpTooltip
|
||||||
size="sm"
|
title="Portfolio Compliance Health"
|
||||||
placement="top"
|
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>
|
||||||
<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 */}
|
{/* Card 7: 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="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-center justify-between text-slate-400">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5 min-w-0 flex-1">
|
||||||
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">At Risk</span>
|
<span className="text-xs font-semibold uppercase tracking-wider text-slate-400 truncate">
|
||||||
<HelpTooltip
|
At Risk
|
||||||
title="At-Risk Projects"
|
</span>
|
||||||
content="Count of projects with high residual risk scores (≥12), schedule slippage >30 days, or adverse financial variance."
|
<HelpTooltip
|
||||||
size="sm"
|
title="At-Risk Projects"
|
||||||
placement="top"
|
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>
|
||||||
<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 */}
|
{/* Card 8: 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="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-center justify-between text-slate-400">
|
<div className="flex items-start justify-between gap-3">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5 min-w-0 flex-1">
|
||||||
<span className="text-[11px] font-semibold uppercase tracking-wider text-slate-400">Beneficiaries</span>
|
<span className="text-xs font-semibold uppercase tracking-wider text-slate-400 truncate">
|
||||||
<HelpTooltip
|
Beneficiaries
|
||||||
title="Beneficiaries Impacted"
|
</span>
|
||||||
content="Cumulative count of direct beneficiaries reached through projects, verified against national M&E indicator targets."
|
<HelpTooltip
|
||||||
size="sm"
|
title="Beneficiaries Impacted"
|
||||||
placement="top"
|
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>
|
||||||
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user