Fixed error with compliance page

This commit is contained in:
2026-10-22 15:58:22 +02:00
parent 299d6ff4dc
commit efc63ca398
3 changed files with 38 additions and 49 deletions
@@ -5,22 +5,15 @@ import {
ShieldAlert,
AlertTriangle,
Users,
CheckCircle2,
TrendingUp,
Target,
Clock,
Coins,
AlertCircle,
Sparkles,
CheckCircle2,
} 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,
@@ -38,8 +31,6 @@ import {
formatDate,
formatZAR,
getRiskScoreColor,
getIndustrySectorLabel,
getProjectTypeLabel,
} from '../../lib/formatters';
interface ProjectComplianceModalProps {
@@ -94,8 +85,6 @@ export const ProjectComplianceModal: React.FC<ProjectComplianceModalProps> = ({
enabled: isOpen && !!projectId,
});
// Beneficiaries already filtered by project
// Beneficiary outcomes query
const { data: outcomes = [], isLoading: loadingOutcomes } = useQuery({
queryKey: ['beneficiaryOutcomes', projectBeneficiaries.map((b) => b.id)],
@@ -283,7 +272,9 @@ export const ProjectComplianceModal: React.FC<ProjectComplianceModalProps> = ({
({risks.filter((r) => (Number(r.likelihood) || 1) * (Number(r.impact) || 1) >= 12).length} High)
</span>
</div>
<span className="text-[10px] text-slate-500">ISO 31000 risk register</span>
<span className="text-[10px] text-slate-500 flex items-center gap-1">
<CheckCircle2 className="w-3 h-3 text-cyan-400 inline" /> Milestones: {milestoneProgress}%
</span>
</div>
</div>
@@ -326,14 +317,14 @@ export const ProjectComplianceModal: React.FC<ProjectComplianceModalProps> = ({
);
return (
<tr key={ci.id} className="hover:bg-slate-900/30 transition">
<td className="py-2.5 px-4 font-semibold text-white">{ci.title}</td>
<td className="py-2.5 px-4 font-semibold text-white">{ci.title || 'Untitled Item'}</td>
<td className="py-2.5 px-4 text-slate-300 max-w-sm truncate" title={ci.requirements || ''}>
{ci.requirements || 'N/A'}
</td>
<td className="py-2.5 px-4 text-slate-300">
{linkedBen ? (
<span className="px-2 py-0.5 rounded bg-blue-500/10 border border-blue-500/20 text-blue-300 text-[11px]">
{linkedBen.name}
{linkedBen.name || 'Beneficiary'}
</span>
) : (
<span className="text-slate-500 italic">Project-Wide</span>
@@ -386,18 +377,18 @@ export const ProjectComplianceModal: React.FC<ProjectComplianceModalProps> = ({
>
<div className="flex items-center justify-between flex-wrap gap-2">
<div>
<span className="font-bold text-white text-xs mr-2">{b.name}</span>
<span className="font-bold text-white text-xs mr-2">{b.name || 'Beneficiary Group'}</span>
<span className="text-[11px] text-slate-400 bg-slate-800 px-2 py-0.5 rounded border border-slate-700/60">
{b.category} • {b.location}
{b.category || 'General'}{b.location ? ` • ${b.location}` : ''}
</span>
</div>
<div className="flex items-center gap-3 text-xs font-mono">
<span className="text-slate-400">
Target: <strong className="text-white">{b.targetCount}</strong>
Target: <strong className="text-white">{b.targetCount ?? 0}</strong>
</span>
<span className="text-slate-600">&bull;</span>
<span className="text-slate-400">
Reached: <strong className="text-emerald-400">{b.reachedCount}</strong>
Reached: <strong className="text-emerald-400">{b.reachedCount ?? 0}</strong>
</span>
</div>
</div>
@@ -417,8 +408,8 @@ export const ProjectComplianceModal: React.FC<ProjectComplianceModalProps> = ({
className="p-2.5 rounded-lg bg-slate-950/60 border border-slate-800/80 flex items-start justify-between gap-2"
>
<div className="min-w-0">
<span className="font-semibold text-slate-200 text-xs block truncate">{o.name}</span>
<span className="text-[10px] text-slate-400 line-clamp-1">{o.description}</span>
<span className="font-semibold text-slate-200 text-xs block truncate">{o.name || 'Outcome'}</span>
<span className="text-[10px] text-slate-400 line-clamp-1">{o.description || 'No description'}</span>
</div>
<ComplianceStatusBadge status={o.status} />
</div>
@@ -456,8 +447,8 @@ export const ProjectComplianceModal: React.FC<ProjectComplianceModalProps> = ({
<table className="w-full text-left text-xs border-collapse">
<thead>
<tr className="bg-slate-900/60 border-b border-slate-800 text-slate-400 text-[11px] font-semibold">
<th className="py-2.5 px-4">Risk Title</th>
<th className="py-2.5 px-4">Category</th>
<th className="py-2.5 px-4">Risk Name</th>
<th className="py-2.5 px-4">Category / Status</th>
<th className="py-2.5 px-4 text-center">Score</th>
<th className="py-2.5 px-4">Mitigation Strategy</th>
</tr>
@@ -465,25 +456,20 @@ export const ProjectComplianceModal: React.FC<ProjectComplianceModalProps> = ({
<tbody className="divide-y divide-slate-800/60">
{risks.map((r) => {
const score = (Number(r.likelihood) || 1) * (Number(r.impact) || 1);
const scoreColor = getRiskScoreColor(r.likelihood, r.impact);
return (
<tr key={r.id} className="hover:bg-slate-900/30 transition">
<td className="py-2.5 px-4 font-semibold text-white">{r.title}</td>
<td className="py-2.5 px-4 text-slate-300">{r.category || 'Operational'}</td>
<td className="py-2.5 px-4 font-semibold text-white">{r.name || r.title || 'Unnamed Risk'}</td>
<td className="py-2.5 px-4 text-slate-300">{r.category || r.status || 'Operational'}</td>
<td className="py-2.5 px-4 text-center">
<span
className={`px-2 py-0.5 rounded text-[10px] font-bold ${
score >= 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'
}`}
className={`px-2 py-0.5 rounded text-[10px] font-bold ${scoreColor.badge}`}
>
Score: {score}
{scoreColor.level} ({score})
</span>
</td>
<td className="py-2.5 px-4 text-slate-300 max-w-sm truncate" title={r.mitigation || ''}>
{r.mitigation || 'No mitigation recorded'}
<td className="py-2.5 px-4 text-slate-300 max-w-sm truncate" title={r.mitigationAction || r.mitigation || r.description || ''}>
{r.mitigationAction || r.mitigation || r.description || 'No mitigation recorded'}
</td>
</tr>
);
@@ -114,6 +114,20 @@ export const ExecutiveDashboard: React.FC = () => {
queryFn: () => getAllIndicators(),
});
// Filtered projects according to active Programme and Sector dropdown selections
const filteredProjects = useMemo(() => {
return projects.filter((p) => {
if (selectedProgramme !== 'All' && String(p.programmeId) !== selectedProgramme) {
return false;
}
if (selectedSector !== 'All') {
const sectorLabel = getIndustrySectorLabel(p.sector);
if (sectorLabel !== selectedSector) return false;
}
return true;
});
}, [projects, selectedProgramme, selectedSector]);
const { data: projectComplianceMap = new Map<string | number, any[]>() } = useQuery({
queryKey: ['projectComplianceMap', filteredProjects.map((p) => p.id)],
queryFn: async () => {
@@ -138,20 +152,6 @@ export const ExecutiveDashboard: React.FC = () => {
queryClient.invalidateQueries();
};
// Filtered projects according to active Programme and Sector dropdown selections
const filteredProjects = useMemo(() => {
return projects.filter((p) => {
if (selectedProgramme !== 'All' && String(p.programmeId) !== selectedProgramme) {
return false;
}
if (selectedSector !== 'All') {
const sectorLabel = getIndustrySectorLabel(p.sector);
if (sectorLabel !== selectedSector) return false;
}
return true;
});
}, [projects, selectedProgramme, selectedSector]);
// Filtered risks associated with the filtered project set
const filteredRisks = useMemo(() => {
if (selectedProgramme === 'All' && selectedSector === 'All') {
@@ -782,7 +782,10 @@ export interface Risk {
createdAt: string;
owner?: string;
mitigationAction?: string;
mitigation?: string | null;
status?: string;
category?: string | null;
title?: string | null;
}
export interface CreateRiskRequest {