Refactored UI endpoint usage

This commit is contained in:
2026-09-20 08:26:06 +02:00
parent b703b0c64d
commit 5829bffaa0
35 changed files with 17482 additions and 648 deletions
@@ -37,7 +37,9 @@ import {
getAllMilestones,
} from '../services/api';
import { Modal } from '../components/ui/Modal';
import { HelpTooltip } from '../components/ui/HelpTooltip';
import { useToast } from '../components/ui/Toast';
import { ContractsManagement } from '../components/monitoring/ContractsManagement';
import { RiskHeatmapMatrix } from '../components/ui/RiskHeatmapMatrix';
import {
Indicator,
@@ -87,7 +89,8 @@ export const MonitoringPage: React.FC = () => {
const queryClient = useQueryClient();
const { success, error: toastError } = useToast();
const [activeTab, setActiveTab] = useState<'indicators' | 'evidence' | 'risks'>('evidence');
const [activeTab, setActiveTab] = useState<'contracts' | 'evidence' | 'indicators' | 'risks'>('contracts');
const [isCreateContractModalOpen, setIsCreateContractModalOpen] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);
// Modals & States
@@ -98,7 +101,7 @@ export const MonitoringPage: React.FC = () => {
const [actualInput, setActualInput] = useState<number>(0);
const [uploadEvidenceType, setUploadEvidenceType] = useState<number>(EvidenceType.CompletionCertificate);
// Figure 10 Reviewer commentary
// Reviewer commentary
const [reviewerComment, setReviewerComment] = useState('');
const [selectedMilestoneEvidenceFilter, setSelectedMilestoneEvidenceFilter] = useState<string | number | 'all'>('all');
@@ -185,7 +188,7 @@ export const MonitoringPage: React.FC = () => {
mutationFn: (formData: FormData) => uploadEvidenceFile(formData),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['evidence'] });
success('Evidence uploaded', 'Supporting file registered for reviewer inspection (Figure 10 / M14)');
success('Evidence uploaded', 'Supporting file registered for reviewer inspection.');
},
onError: (err: unknown) => {
toastError('Upload failed', err);
@@ -302,7 +305,7 @@ export const MonitoringPage: React.FC = () => {
}
};
// Indicator thresholds from SRS 9.6:
// Performance indicator thresholds:
// Green >= 90%, Amber 70-89%, Red < 70%
const getIndicatorStatus = (actual: number, target: number) => {
if (!target) return { label: 'Pending', badge: 'bg-slate-800/60 text-slate-400 border-slate-700/60' };
@@ -318,7 +321,12 @@ export const MonitoringPage: React.FC = () => {
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 bg-[#0f1523]/80 backdrop-blur-md p-6 rounded-2xl border border-slate-800/80 shadow-xl">
<div>
<h2 className="text-2xl font-bold text-white tracking-tight flex items-center gap-2">
M&E, Evidence & Risk Management
<span>M&E, Evidence & Risk Management</span>
<HelpTooltip
title="Monitoring & Evaluation Hub"
content="Track performance indicators, verify tangible deliverable evidence against statutory milestones, and maintain proactive risk mitigation plans."
placement="right"
/>
</h2>
<p className="text-xs text-slate-400 mt-1">
Verification workflow: Evidence Upload & Review Queue, Indicator Actuals Recording, and Risk Register.
@@ -326,10 +334,19 @@ export const MonitoringPage: React.FC = () => {
</div>
<div className="flex items-center gap-2">
{activeTab === 'contracts' && (
<button
onClick={() => setIsCreateContractModalOpen(true)}
className="inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition cursor-pointer"
>
<Plus className="w-4 h-4" />
Create Contract & Upload PDF
</button>
)}
{activeTab === 'indicators' && (
<button
onClick={() => setIsIndicatorModalOpen(true)}
className="inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition"
className="inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition cursor-pointer"
>
<Plus className="w-4 h-4" />
Define Indicator
@@ -338,7 +355,7 @@ export const MonitoringPage: React.FC = () => {
{activeTab === 'risks' && (
<button
onClick={openCreateRisk}
className="inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition"
className="inline-flex items-center gap-1.5 px-4 py-2 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 rounded-xl shadow-lg shadow-blue-500/20 transition cursor-pointer"
>
<Plus className="w-4 h-4" />
Add Risk to Register
@@ -349,6 +366,23 @@ export const MonitoringPage: React.FC = () => {
{/* Tabs */}
<div className="flex gap-2 border-b border-slate-800/80 pb-3 overflow-x-auto">
<button
onClick={() => setActiveTab('contracts')}
className={`px-4 py-2 text-xs font-medium rounded-xl transition flex items-center gap-2 ${
activeTab === 'contracts'
? 'bg-blue-600 text-white shadow-lg shadow-blue-600/30'
: 'text-slate-400 hover:text-slate-200 hover:bg-slate-800/50'
}`}
>
<FileText className="w-4 h-4" />
<span>Contracts & PDF Documents</span>
<HelpTooltip
title="Executed Contracts & Vault"
content="Digitally executed bilateral agreements, legal riders, and authoritative PDF repository."
size="sm"
placement="top"
/>
</button>
<button
onClick={() => setActiveTab('evidence')}
className={`px-4 py-2 text-xs font-medium rounded-xl transition flex items-center gap-2 ${
@@ -358,7 +392,13 @@ export const MonitoringPage: React.FC = () => {
}`}
>
<Paperclip className="w-4 h-4" />
Milestone Evidence & Review Queue
<span>Milestone Evidence & Review Queue</span>
<HelpTooltip
title="Verification Workflow"
content="Evidence submission, file validation, and peer review sign-off gating milestone payments."
size="sm"
placement="top"
/>
</button>
<button
onClick={() => setActiveTab('indicators')}
@@ -369,7 +409,13 @@ export const MonitoringPage: React.FC = () => {
}`}
>
<TrendingUp className="w-4 h-4" />
Indicators & Actuals ({indicators.length})
<span>Indicators & Actuals ({indicators.length})</span>
<HelpTooltip
title="Quantitative Indicators"
content="Baseline vs. target metric progression with automated green/amber/red performance thresholds."
size="sm"
placement="top"
/>
</button>
<button
onClick={() => setActiveTab('risks')}
@@ -380,14 +426,28 @@ export const MonitoringPage: React.FC = () => {
}`}
>
<ShieldAlert className="w-4 h-4" />
Risk Register & Heatmap ({risks.length})
<span>Risk Register & Heatmap ({risks.length})</span>
<HelpTooltip
title="Risk Register & Heatmap"
content="5x5 severity matrix scoring likelihood against impact to safeguard public grant investments."
size="sm"
placement="top"
/>
</button>
</div>
{/* Tab 1: Milestone Evidence Submission & Review (Exact Implementation of Figure 10) */}
{/* Tab 0: Contracts & Legal Documents with PDF Upload */}
{activeTab === 'contracts' && (
<ContractsManagement
isCreateModalOpenExternal={isCreateContractModalOpen}
onCloseCreateModalExternal={() => setIsCreateContractModalOpen(false)}
/>
)}
{/* Tab 1: Milestone Evidence Submission & Review */}
{activeTab === 'evidence' && (
<div className="space-y-6">
{/* Wireframe Container from SRS Figure 10 */}
{/* Milestone Evidence Review Container */}
<div className="bg-[#0f1523]/80 backdrop-blur-md rounded-2xl border border-slate-800/80 shadow-xl overflow-hidden">
{/* Header Bar */}
<div className="bg-slate-900/90 text-white px-6 py-4 flex flex-col sm:flex-row sm:items-center justify-between gap-2 border-b border-slate-800/80">
@@ -423,7 +483,7 @@ export const MonitoringPage: React.FC = () => {
</div>
</div>
{/* Upload Evidence & Attached Evidence Panels (Matching Figure 10) */}
{/* Upload Evidence & Attached Evidence Panels */}
<div className="grid grid-cols-1 md:grid-cols-2 divide-y md:divide-y-0 md:divide-x divide-slate-800/80">
{/* Upload Box */}
<div className="p-6 space-y-4">
@@ -545,7 +605,7 @@ export const MonitoringPage: React.FC = () => {
</div>
</div>
{/* Programme Manager Review Section (Figure 10) */}
{/* Programme Manager Review Section */}
<div className="p-6 border-t border-slate-800/80 bg-slate-900/40 space-y-4">
<h4 className="text-sm font-bold text-white">Programme Manager Review</h4>
@@ -765,10 +825,19 @@ export const MonitoringPage: React.FC = () => {
onClose={() => setIsIndicatorModalOpen(false)}
title="Define M&E Indicator"
subtitle="Logical Framework & Theory of Change metric"
helpText="Quantitative or qualitative KPI tracked across reporting periods against target thresholds to assess project efficacy."
>
<form onSubmit={indicatorForm.handleSubmit((data) => indicatorMutation.mutate(data as any))} className="space-y-4">
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1.5">Project *</label>
<div className="flex items-center gap-1 mb-1.5">
<label className="block text-xs font-semibold text-slate-300">Project *</label>
<HelpTooltip
title="Target Project"
content="The project against which this indicator is tracked."
size="sm"
placement="right"
/>
</div>
<select
{...indicatorForm.register('projectId')}
className="w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition [&>option]:bg-slate-900 [&>option]:text-white"
@@ -782,7 +851,15 @@ export const MonitoringPage: React.FC = () => {
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1.5">Indicator Statement *</label>
<div className="flex items-center gap-1 mb-1.5">
<label className="block text-xs font-semibold text-slate-300">Indicator Statement *</label>
<HelpTooltip
title="Measurement Statement"
content="Precise description of the result or output being counted or verified."
size="sm"
placement="right"
/>
</div>
<input
type="text"
{...indicatorForm.register('name')}
@@ -793,7 +870,15 @@ export const MonitoringPage: React.FC = () => {
<div className="grid grid-cols-3 gap-3">
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1.5">Unit of Measure *</label>
<div className="flex items-center gap-1 mb-1.5">
<label className="block text-xs font-semibold text-slate-300">Unit of Measure *</label>
<HelpTooltip
title="Metric Dimension"
content="Counting unit (e.g. Number, Percentage, Currency ZAR, Rating Score)."
size="sm"
placement="top"
/>
</div>
<select
{...indicatorForm.register('unitOfMeasure')}
className="w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition [&>option]:bg-slate-900 [&>option]:text-white"
@@ -807,7 +892,15 @@ export const MonitoringPage: React.FC = () => {
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1.5">Baseline Value *</label>
<div className="flex items-center gap-1 mb-1.5">
<label className="block text-xs font-semibold text-slate-300">Baseline Value *</label>
<HelpTooltip
title="Pre-grant Baseline"
content="Observed initial starting value prior to project intervention."
size="sm"
placement="top"
/>
</div>
<input
type="number"
{...indicatorForm.register('baselineAmount')}
@@ -816,7 +909,15 @@ export const MonitoringPage: React.FC = () => {
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1.5">Target Value *</label>
<div className="flex items-center gap-1 mb-1.5">
<label className="block text-xs font-semibold text-slate-300">Target Value *</label>
<HelpTooltip
title="Target Benchmark"
content="Contracted goal to achieve before contract completion."
size="sm"
placement="top"
/>
</div>
<input
type="number"
{...indicatorForm.register('targetAmount')}
@@ -850,6 +951,7 @@ export const MonitoringPage: React.FC = () => {
onClose={() => setSelectedIndicatorForActual(null)}
title="Record Indicator Actual Value"
subtitle={selectedIndicatorForActual?.name || ''}
helpText="Logs a certified field or audited measurement against the target, recalculating the achievement percentage and RAG rating."
>
<div className="space-y-4">
<div className="p-3.5 bg-slate-900/60 border border-slate-800/80 rounded-xl text-xs flex justify-between text-slate-300">
@@ -858,7 +960,15 @@ export const MonitoringPage: React.FC = () => {
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1.5">Actual Value to Record *</label>
<div className="flex items-center gap-1 mb-1.5">
<label className="block text-xs font-semibold text-slate-300">Actual Value to Record *</label>
<HelpTooltip
title="Monitored Measurement"
content="Current realized value for this reporting period. Automatically updates Green (>=90%), Amber (70-89%), or Red (<70%) status."
size="sm"
placement="right"
/>
</div>
<input
type="number"
value={actualInput}
@@ -902,11 +1012,20 @@ export const MonitoringPage: React.FC = () => {
}}
title={editingRisk ? 'Edit Project Risk' : 'Add Risk to Risk Register'}
subtitle={editingRisk ? `Updating risk item #${editingRisk.id}` : 'Identify and score project delivery risks'}
helpText="Risks are scored by Likelihood (1-5) x Impact (1-5) to plot on the 5x5 Heatmap. High severity risks flag alerts for portfolio governance."
>
<form onSubmit={riskForm.handleSubmit((data) => riskMutation.mutate(data as any))} className="space-y-4">
{!editingRisk && (
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1.5">Associated Project *</label>
<div className="flex items-center gap-1 mb-1.5">
<label className="block text-xs font-semibold text-slate-300">Associated Project *</label>
<HelpTooltip
title="Context Project"
content="The project directly exposed to this potential threat or operational hazard."
size="sm"
placement="right"
/>
</div>
<select
{...riskForm.register('projectId')}
className="w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition [&>option]:bg-slate-900 [&>option]:text-white"
@@ -921,7 +1040,15 @@ export const MonitoringPage: React.FC = () => {
)}
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1.5">Risk Title *</label>
<div className="flex items-center gap-1 mb-1.5">
<label className="block text-xs font-semibold text-slate-300">Risk Title *</label>
<HelpTooltip
title="Event Name"
content="Succinct identifier of the uncertainty (e.g. Key Personnel Departure, Import Tariff Hike)."
size="sm"
placement="right"
/>
</div>
<input
type="text"
{...riskForm.register('name')}
@@ -932,7 +1059,15 @@ export const MonitoringPage: React.FC = () => {
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1.5">Likelihood (1–5) *</label>
<div className="flex items-center gap-1 mb-1.5">
<label className="block text-xs font-semibold text-slate-300">Likelihood (1–5) *</label>
<HelpTooltip
title="Occurrence Probability"
content="Rated 1 (Rare) through 5 (Almost Certain)."
size="sm"
placement="top"
/>
</div>
<select
{...riskForm.register('likelihood')}
className="w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition [&>option]:bg-slate-900 [&>option]:text-white"
@@ -946,7 +1081,15 @@ export const MonitoringPage: React.FC = () => {
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1.5">Impact (1–5) *</label>
<div className="flex items-center gap-1 mb-1.5">
<label className="block text-xs font-semibold text-slate-300">Impact (1–5) *</label>
<HelpTooltip
title="Consequence Severity"
content="Rated 1 (Insignificant) through 5 (Catastrophic to schedule/budget)."
size="sm"
placement="top"
/>
</div>
<select
{...riskForm.register('impact')}
className="w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 transition [&>option]:bg-slate-900 [&>option]:text-white"
@@ -961,7 +1104,15 @@ export const MonitoringPage: React.FC = () => {
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1.5">Risk Description *</label>
<div className="flex items-center gap-1 mb-1.5">
<label className="block text-xs font-semibold text-slate-300">Risk Description *</label>
<HelpTooltip
title="Root Cause & Mitigation"
content="Detail underlying triggers, preventative actions, and contingency response steps."
size="sm"
placement="right"
/>
</div>
<textarea
rows={2}
{...riskForm.register('description')}