import React from 'react'; import { useQuery } from '@tanstack/react-query'; import { ShieldCheck, ShieldAlert, AlertTriangle, Users, CheckCircle2, TrendingUp, Target, Clock, Coins, AlertCircle, Sparkles, } from 'lucide-react'; import { Modal } from '../ui/Modal'; import { HelpTooltip } from '../ui/HelpTooltip'; import { ComplianceStatusBadge } from '../ui/StatusBadge'; import { Project, Programme, ComplianceItem, Beneficiary, Outcome, ComplianceStatus, ApprovalStatus, } from '../../types'; import { getProjectComplianceItems, getProjectBeneficiaries, getBeneficiaryOutcomes, getProjectRisks, getMilestones, getActivities, getBudgets, } from '../../services/api'; import { formatDate, formatZAR, getRiskScoreColor, getIndustrySectorLabel, getProjectTypeLabel, } from '../../lib/formatters'; interface ProjectComplianceModalProps { isOpen: boolean; onClose: () => void; project: Project | null; programme: Programme | null; } export const ProjectComplianceModal: React.FC = ({ isOpen, onClose, project, programme, }) => { const projectId = project?.id; // Queries const { data: complianceItems = [], isLoading: loadingCompliance } = useQuery({ queryKey: ['complianceItems', projectId], queryFn: () => (projectId ? getProjectComplianceItems(projectId) : Promise.resolve([])), enabled: isOpen && !!projectId, }); const { data: projectBeneficiaries = [], isLoading: loadingBeneficiaries } = useQuery({ queryKey: ['projectBeneficiaries', projectId], queryFn: () => (projectId ? getProjectBeneficiaries(projectId) : Promise.resolve([])), enabled: isOpen && !!projectId, }); const { data: risks = [], isLoading: loadingRisks } = useQuery({ queryKey: ['risks', projectId], queryFn: () => (projectId ? getProjectRisks(projectId) : Promise.resolve([])), enabled: isOpen && !!projectId, }); const { data: milestones = [] } = useQuery({ queryKey: ['milestones', projectId], queryFn: () => (projectId ? getMilestones(projectId) : Promise.resolve([])), enabled: isOpen && !!projectId, }); const { data: activities = [] } = useQuery({ queryKey: ['activities', projectId], queryFn: () => (projectId ? getActivities(projectId) : Promise.resolve([])), enabled: isOpen && !!projectId, }); const { data: allBudgets = [] } = useQuery({ queryKey: ['budgets'], queryFn: () => getBudgets(1, 100), enabled: isOpen && !!projectId, }); // Beneficiaries already filtered by project // Beneficiary outcomes query const { data: outcomes = [], isLoading: loadingOutcomes } = useQuery({ queryKey: ['beneficiaryOutcomes', projectBeneficiaries.map((b) => b.id)], queryFn: async (): Promise => { if (projectBeneficiaries.length === 0) return []; const results = await Promise.all( projectBeneficiaries.map(async (b) => { try { return await getBeneficiaryOutcomes(b.id); } catch { return []; } }) ); return results.flat(); }, enabled: isOpen && projectBeneficiaries.length > 0, }); if (!project) return null; // Compliance calculations const totalCompCount = complianceItems.length; const metCompCount = complianceItems.filter( (c) => Number(c.status) === ComplianceStatus.Met || Number(c.status) === ComplianceStatus.Exceeded ).length; const unmetCompCount = totalCompCount - metCompCount; // Outcomes calculations const totalOutcomesCount = outcomes.length; const metOutcomesCount = outcomes.filter( (o) => Number(o.status) === ComplianceStatus.Met || Number(o.status) === ComplianceStatus.Exceeded ).length; const unmetOutcomesCount = totalOutcomesCount - metOutcomesCount; // Overall compliance verdict (Invariant: 100% met required for approval) const is100PercentCompliant = unmetCompCount === 0 && unmetOutcomesCount === 0; // Financial calculations const projectBudgets = allBudgets.filter((b) => String(b.projectId) === String(projectId)); const approvedBudgetTotal = projectBudgets .filter((b) => Number(b.status) === ApprovalStatus.Approved || b.status === undefined) .reduce((sum, b) => sum + (Number(b.amount) || 0), 0); const totalActivityBudget = activities.reduce((sum, a) => sum + (Number(a.amount) || 0), 0); const financialVariance = approvedBudgetTotal - totalActivityBudget; const isWithinBudget = financialVariance >= 0; // Milestone Progress const verifiedMilestonesCount = milestones.filter((m) => m.status === ApprovalStatus.Approved).length; const milestoneProgress = milestones.length > 0 ? Math.round((verifiedMilestonesCount / milestones.length) * 100) : 0; return (
{/* Top Verdict Banner */}
{is100PercentCompliant ? ( ) : ( )}

{is100PercentCompliant ? '100% Fully Compliant — Approved for Project Governance' : 'Action Required: Project is Noncompliant'}

{is100PercentCompliant ? 'Eligible for Approval' : 'Approvals Blocked'}

{is100PercentCompliant ? 'All statutory compliance requirements and beneficiary impact outcomes have met or exceeded threshold requirements.' : `This project has ${unmetCompCount} unmet compliance item(s) and ${unmetOutcomesCount} unmet beneficiary outcome(s). Per governance policy, projects cannot be approved until all compliance items and outcomes are 100% met.`}

Budget At Risk {is100PercentCompliant ? 'R 0.00' : formatZAR(approvedBudgetTotal)}
{/* 4 Summary Metric Cards */}
Compliance Requirements
{metCompCount}/{totalCompCount} ({totalCompCount > 0 ? Math.round((metCompCount / totalCompCount) * 100) : 100}%)
{unmetCompCount === 0 ? 'All items satisfied' : `${unmetCompCount} pending fulfillment`}
Beneficiary Outcomes
{metOutcomesCount}/{totalOutcomesCount} ({totalOutcomesCount > 0 ? Math.round((metOutcomesCount / totalOutcomesCount) * 100) : 100}%)
Across {projectBeneficiaries.length} beneficiary group{projectBeneficiaries.length === 1 ? '' : 's'}
Financial Variance
{isWithinBudget ? `+${formatZAR(financialVariance)}` : `-${formatZAR(Math.abs(financialVariance))}`}
Budget: {formatZAR(approvedBudgetTotal)}
Active Project Risks
{risks.length} ({risks.filter((r) => (Number(r.likelihood) || 1) * (Number(r.impact) || 1) >= 12).length} High)
ISO 31000 risk register
{/* Section 1: Statutory Compliance Requirements */}

Statutory Compliance Requirements ({complianceItems.length})

{metCompCount} of {complianceItems.length} Met / Exceeded
{loadingCompliance ? (
Loading compliance register...
) : complianceItems.length === 0 ? (
No specific statutory compliance requirements captured for this project yet.
) : (
{complianceItems.map((ci) => { const linkedBen = projectBeneficiaries.find( (b) => String(b.id) === String(ci.beneficiaryId) ); return ( ); })}
Title Requirements Specification Linked Beneficiary Status Created
{ci.title} {ci.requirements || 'N/A'} {linkedBen ? ( {linkedBen.name} ) : ( Project-Wide )} {formatDate(ci.createdAt)}
)}
{/* Section 2: Affected Beneficiaries & Mapped Outcomes */}

Affected Beneficiaries & Mapped Impact Outcomes ({projectBeneficiaries.length})

Total Target: {projectBeneficiaries.reduce((sum, b) => sum + (Number(b.targetCount) || 0), 0)} Reached:{' '} {projectBeneficiaries.reduce((sum, b) => sum + (Number(b.reachedCount) || 0), 0)}
{loadingBeneficiaries || loadingOutcomes ? (
Loading beneficiaries & outcomes...
) : projectBeneficiaries.length === 0 ? (
No target beneficiaries mapped to this project yet.
) : (
{projectBeneficiaries.map((b) => { const benOutcomes = outcomes.filter((o) => String(o.beneficiaryId) === String(b.id)); return (
{b.name} {b.category} • {b.location}
Target: {b.targetCount} • Reached: {b.reachedCount}
{/* Mapped Outcomes */}
Mapped Expected Outcomes ({benOutcomes.length}) {benOutcomes.length === 0 ? (

No expected outcomes recorded for this group.

) : (
{benOutcomes.map((o) => (
{o.name} {o.description}
))}
)}
); })}
)}
{/* Section 3: Risk Profile (ISO 31000) */}

Active Project Risks Register ({risks.length})

ISO 31000 Risk Management
{loadingRisks ? (
Loading risk register...
) : risks.length === 0 ? (
No active risks logged on this project register.
) : (
{risks.map((r) => { const score = (Number(r.likelihood) || 1) * (Number(r.impact) || 1); return ( ); })}
Risk Title Category Score Mitigation Strategy
{r.title} {r.category || 'Operational'} = 12 ? 'bg-rose-500/20 text-rose-300 border border-rose-500/30' : score >= 8 ? 'bg-amber-500/20 text-amber-300 border border-amber-500/30' : 'bg-emerald-500/20 text-emerald-300 border border-emerald-500/30' }`} > Score: {score} {r.mitigation || 'No mitigation recorded'}
)}
{/* Footer Actions */}
); };