Fixed error with compliance page
This commit is contained in:
@@ -5,22 +5,15 @@ import {
|
|||||||
ShieldAlert,
|
ShieldAlert,
|
||||||
AlertTriangle,
|
AlertTriangle,
|
||||||
Users,
|
Users,
|
||||||
CheckCircle2,
|
|
||||||
TrendingUp,
|
|
||||||
Target,
|
Target,
|
||||||
Clock,
|
|
||||||
Coins,
|
Coins,
|
||||||
AlertCircle,
|
CheckCircle2,
|
||||||
Sparkles,
|
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Modal } from '../ui/Modal';
|
import { Modal } from '../ui/Modal';
|
||||||
import { HelpTooltip } from '../ui/HelpTooltip';
|
|
||||||
import { ComplianceStatusBadge } from '../ui/StatusBadge';
|
import { ComplianceStatusBadge } from '../ui/StatusBadge';
|
||||||
import {
|
import {
|
||||||
Project,
|
Project,
|
||||||
Programme,
|
Programme,
|
||||||
ComplianceItem,
|
|
||||||
Beneficiary,
|
|
||||||
Outcome,
|
Outcome,
|
||||||
ComplianceStatus,
|
ComplianceStatus,
|
||||||
ApprovalStatus,
|
ApprovalStatus,
|
||||||
@@ -38,8 +31,6 @@ import {
|
|||||||
formatDate,
|
formatDate,
|
||||||
formatZAR,
|
formatZAR,
|
||||||
getRiskScoreColor,
|
getRiskScoreColor,
|
||||||
getIndustrySectorLabel,
|
|
||||||
getProjectTypeLabel,
|
|
||||||
} from '../../lib/formatters';
|
} from '../../lib/formatters';
|
||||||
|
|
||||||
interface ProjectComplianceModalProps {
|
interface ProjectComplianceModalProps {
|
||||||
@@ -94,8 +85,6 @@ export const ProjectComplianceModal: React.FC<ProjectComplianceModalProps> = ({
|
|||||||
enabled: isOpen && !!projectId,
|
enabled: isOpen && !!projectId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Beneficiaries already filtered by project
|
|
||||||
|
|
||||||
// Beneficiary outcomes query
|
// Beneficiary outcomes query
|
||||||
const { data: outcomes = [], isLoading: loadingOutcomes } = useQuery({
|
const { data: outcomes = [], isLoading: loadingOutcomes } = useQuery({
|
||||||
queryKey: ['beneficiaryOutcomes', projectBeneficiaries.map((b) => b.id)],
|
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)
|
({risks.filter((r) => (Number(r.likelihood) || 1) * (Number(r.impact) || 1) >= 12).length} High)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -326,14 +317,14 @@ export const ProjectComplianceModal: React.FC<ProjectComplianceModalProps> = ({
|
|||||||
);
|
);
|
||||||
return (
|
return (
|
||||||
<tr key={ci.id} className="hover:bg-slate-900/30 transition">
|
<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 || ''}>
|
<td className="py-2.5 px-4 text-slate-300 max-w-sm truncate" title={ci.requirements || ''}>
|
||||||
{ci.requirements || 'N/A'}
|
{ci.requirements || 'N/A'}
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2.5 px-4 text-slate-300">
|
<td className="py-2.5 px-4 text-slate-300">
|
||||||
{linkedBen ? (
|
{linkedBen ? (
|
||||||
<span className="px-2 py-0.5 rounded bg-blue-500/10 border border-blue-500/20 text-blue-300 text-[11px]">
|
<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>
|
||||||
) : (
|
) : (
|
||||||
<span className="text-slate-500 italic">Project-Wide</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 className="flex items-center justify-between flex-wrap gap-2">
|
||||||
<div>
|
<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">
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-3 text-xs font-mono">
|
<div className="flex items-center gap-3 text-xs font-mono">
|
||||||
<span className="text-slate-400">
|
<span className="text-slate-400">
|
||||||
Target: <strong className="text-white">{b.targetCount}</strong>
|
Target: <strong className="text-white">{b.targetCount ?? 0}</strong>
|
||||||
</span>
|
</span>
|
||||||
<span className="text-slate-600">•</span>
|
<span className="text-slate-600">•</span>
|
||||||
<span className="text-slate-400">
|
<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>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</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"
|
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">
|
<div className="min-w-0">
|
||||||
<span className="font-semibold text-slate-200 text-xs block truncate">{o.name}</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}</span>
|
<span className="text-[10px] text-slate-400 line-clamp-1">{o.description || 'No description'}</span>
|
||||||
</div>
|
</div>
|
||||||
<ComplianceStatusBadge status={o.status} />
|
<ComplianceStatusBadge status={o.status} />
|
||||||
</div>
|
</div>
|
||||||
@@ -456,8 +447,8 @@ export const ProjectComplianceModal: React.FC<ProjectComplianceModalProps> = ({
|
|||||||
<table className="w-full text-left text-xs border-collapse">
|
<table className="w-full text-left text-xs border-collapse">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="bg-slate-900/60 border-b border-slate-800 text-slate-400 text-[11px] font-semibold">
|
<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">Risk Name</th>
|
||||||
<th className="py-2.5 px-4">Category</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 text-center">Score</th>
|
||||||
<th className="py-2.5 px-4">Mitigation Strategy</th>
|
<th className="py-2.5 px-4">Mitigation Strategy</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -465,25 +456,20 @@ export const ProjectComplianceModal: React.FC<ProjectComplianceModalProps> = ({
|
|||||||
<tbody className="divide-y divide-slate-800/60">
|
<tbody className="divide-y divide-slate-800/60">
|
||||||
{risks.map((r) => {
|
{risks.map((r) => {
|
||||||
const score = (Number(r.likelihood) || 1) * (Number(r.impact) || 1);
|
const score = (Number(r.likelihood) || 1) * (Number(r.impact) || 1);
|
||||||
|
const scoreColor = getRiskScoreColor(r.likelihood, r.impact);
|
||||||
return (
|
return (
|
||||||
<tr key={r.id} className="hover:bg-slate-900/30 transition">
|
<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 font-semibold text-white">{r.name || r.title || 'Unnamed Risk'}</td>
|
||||||
<td className="py-2.5 px-4 text-slate-300">{r.category || 'Operational'}</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">
|
<td className="py-2.5 px-4 text-center">
|
||||||
<span
|
<span
|
||||||
className={`px-2 py-0.5 rounded text-[10px] font-bold ${
|
className={`px-2 py-0.5 rounded text-[10px] font-bold ${scoreColor.badge}`}
|
||||||
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'
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
Score: {score}
|
{scoreColor.level} ({score})
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td className="py-2.5 px-4 text-slate-300 max-w-sm truncate" title={r.mitigation || ''}>
|
<td className="py-2.5 px-4 text-slate-300 max-w-sm truncate" title={r.mitigationAction || r.mitigation || r.description || ''}>
|
||||||
{r.mitigation || 'No mitigation recorded'}
|
{r.mitigationAction || r.mitigation || r.description || 'No mitigation recorded'}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -114,6 +114,20 @@ export const ExecutiveDashboard: React.FC = () => {
|
|||||||
queryFn: () => getAllIndicators(),
|
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({
|
const { data: projectComplianceMap = new Map<string | number, any[]>() } = useQuery({
|
||||||
queryKey: ['projectComplianceMap', filteredProjects.map((p) => p.id)],
|
queryKey: ['projectComplianceMap', filteredProjects.map((p) => p.id)],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
@@ -138,20 +152,6 @@ export const ExecutiveDashboard: React.FC = () => {
|
|||||||
queryClient.invalidateQueries();
|
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
|
// Filtered risks associated with the filtered project set
|
||||||
const filteredRisks = useMemo(() => {
|
const filteredRisks = useMemo(() => {
|
||||||
if (selectedProgramme === 'All' && selectedSector === 'All') {
|
if (selectedProgramme === 'All' && selectedSector === 'All') {
|
||||||
|
|||||||
@@ -782,7 +782,10 @@ export interface Risk {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
owner?: string;
|
owner?: string;
|
||||||
mitigationAction?: string;
|
mitigationAction?: string;
|
||||||
|
mitigation?: string | null;
|
||||||
status?: string;
|
status?: string;
|
||||||
|
category?: string | null;
|
||||||
|
title?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreateRiskRequest {
|
export interface CreateRiskRequest {
|
||||||
|
|||||||
Reference in New Issue
Block a user