Refactored UI endpoint usage
This commit is contained in:
@@ -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')}
|
||||
|
||||
Reference in New Issue
Block a user