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
@@ -39,6 +39,7 @@ import {
} from '../services/api';
import { Modal } from '../components/ui/Modal';
import { useToast } from '../components/ui/Toast';
import { HelpTooltip } from '../components/ui/HelpTooltip';
import {
Project,
Activity,
@@ -383,7 +384,7 @@ export const ProjectsPage: React.FC = () => {
},
});
// Calculate project progress based on milestones (FR-M04-018)
// Calculate project progress percentage based on completed milestones
const completedMilestones = milestones.filter((m) => m.status === ApprovalStatus.Approved).length;
const milestoneProgress = milestones.length > 0 ? Math.round((completedMilestones / milestones.length) * 100) : 0;
@@ -396,6 +397,12 @@ export const ProjectsPage: React.FC = () => {
<h2 className="text-xl font-bold text-white tracking-tight flex items-center gap-2">
Project & Milestone Management
</h2>
<HelpTooltip
title="Work Breakdown Structure (WBS)"
content="Manage operational activities, verified contractual milestone deliverables, risk registers, and M&E performance indicator targets."
size="sm"
placement="bottom"
/>
<span className="text-[11px] font-semibold text-blue-400 bg-blue-500/10 px-2.5 py-0.5 rounded-full border border-blue-500/30">
Work Breakdown
</span>
@@ -418,21 +425,31 @@ export const ProjectsPage: React.FC = () => {
{/* Left Column: Project Directory List */}
<div className="lg:col-span-5 space-y-3">
<div className="flex items-center justify-between gap-2 px-1">
<h3 className="text-xs font-bold text-slate-400 uppercase tracking-wider">
Projects Directory ({projects.length})
</h3>
<select
value={selectedProgrammeFilter}
onChange={(e) => setSelectedProgrammeFilter(e.target.value === 'all' ? 'all' : e.target.value)}
className="text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl px-2.5 py-1 text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 max-w-[190px] [&>option]:bg-slate-900 [&>option]:text-white"
>
<option value="all">All Programmes</option>
{programmes.map((pr) => (
<option key={pr.id} value={pr.id}>
{pr.name}
</option>
))}
</select>
<div className="flex items-center gap-1.5">
<h3 className="text-xs font-bold text-slate-400 uppercase tracking-wider">
Projects Directory ({projects.length})
</h3>
<HelpTooltip
title="Project Registry"
content="Select any funded project to inspect its work breakdown structure, contractual deliverables, progress score, and M&E indicators."
size="sm"
placement="bottom"
/>
</div>
<div className="flex items-center gap-1">
<select
value={selectedProgrammeFilter}
onChange={(e) => setSelectedProgrammeFilter(e.target.value === 'all' ? 'all' : e.target.value)}
className="text-xs bg-slate-900/80 border border-slate-700/80 rounded-xl px-2.5 py-1 text-white focus:outline-none focus:ring-2 focus:ring-blue-500/50 max-w-[190px] [&>option]:bg-slate-900 [&>option]:text-white"
>
<option value="all">All Programmes</option>
{programmes.map((pr) => (
<option key={pr.id} value={pr.id}>
{pr.name}
</option>
))}
</select>
</div>
</div>
{loadingProjects ? (
@@ -513,7 +530,15 @@ export const ProjectsPage: React.FC = () => {
Edit Project
</button>
<div>
<label className="text-[10px] uppercase font-bold text-slate-400 block mb-1">Approval Status</label>
<div className="flex items-center gap-1 mb-1">
<label className="text-[10px] uppercase font-bold text-slate-400 block">Approval Status</label>
<HelpTooltip
title="Governance Approval Status"
content="Current statutory clearance stage of the project: Draft, In Review, Approved, or Active."
size="sm"
placement="bottom"
/>
</div>
<select
value={selectedProject.status}
onChange={(e) =>
@@ -534,12 +559,18 @@ export const ProjectsPage: React.FC = () => {
</div>
</div>
{/* Progress bar (FR-M04-018: calculated from approved milestones) */}
{/* Progress bar calculated from approved milestones */}
<div className="mt-5 pt-4 border-t border-slate-800/80">
<div className="flex items-center justify-between text-xs mb-1.5">
<span className="font-semibold text-slate-300 flex items-center gap-1.5">
<TrendingUp className="w-3.5 h-3.5 text-blue-400" />
Calculated Progress (Milestone-based)
<HelpTooltip
title="Calculated Progress Formula"
content="Computed dynamically as (Verified & Approved Milestones / Total Milestones) × 100%."
size="sm"
placement="top"
/>
</span>
<span className="font-bold text-blue-400">{milestoneProgress}%</span>
</div>
@@ -610,7 +641,15 @@ export const ProjectsPage: React.FC = () => {
{detailTab === 'activities' && (
<div className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-xs font-bold text-slate-300">Project Work Breakdown</span>
<div className="flex items-center gap-1.5">
<span className="text-xs font-bold text-slate-300">Project Work Breakdown</span>
<HelpTooltip
title="Work Breakdown Structure"
content="Granular operational tasks executed under this project. Activities can optionally be linked to contractual milestones."
size="sm"
placement="bottom"
/>
</div>
<button
onClick={openCreateActivity}
className="inline-flex items-center gap-1 text-xs font-semibold text-blue-400 bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/30 px-3 py-1 rounded-xl transition cursor-pointer"
@@ -671,7 +710,15 @@ export const ProjectsPage: React.FC = () => {
{detailTab === 'milestones' && (
<div className="space-y-4">
<div className="flex items-center justify-between">
<span className="text-xs font-bold text-slate-300">Contractual Milestones</span>
<div className="flex items-center gap-1.5">
<span className="text-xs font-bold text-slate-300">Contractual Milestones</span>
<HelpTooltip
title="Contractual Milestones"
content="Statutory deliverables tied to legal grant agreements. Verified completion triggers tranche disbursement releases."
size="sm"
placement="bottom"
/>
</div>
<button
onClick={openCreateMilestone}
className="inline-flex items-center gap-1 text-xs font-semibold text-blue-400 bg-blue-500/10 hover:bg-blue-500/20 border border-blue-500/30 px-3 py-1 rounded-xl transition cursor-pointer"
@@ -728,7 +775,15 @@ export const ProjectsPage: React.FC = () => {
{/* Indicators Sub-tab */}
{detailTab === 'indicators' && (
<div className="space-y-3">
<span className="text-xs font-bold text-slate-300 block">Assigned Project Indicators</span>
<div className="flex items-center gap-1.5">
<span className="text-xs font-bold text-slate-300 block">Assigned Project Indicators</span>
<HelpTooltip
title="M&E Indicator Tracking"
content="Monitors quantitative performance targets (e.g. jobs created, patents filed, rural SMMEs trained) against baseline values."
size="sm"
placement="bottom"
/>
</div>
{indicators.length === 0 ? (
<p className="text-xs text-slate-500 py-4 text-center">No indicators attached.</p>
) : (
@@ -755,7 +810,15 @@ export const ProjectsPage: React.FC = () => {
{/* Risks Sub-tab */}
{detailTab === 'risks' && (
<div className="space-y-3">
<span className="text-xs font-bold text-slate-300 block">Project Risk Register (M09)</span>
<div className="flex items-center gap-1.5">
<span className="text-xs font-bold text-slate-300 block">Project Risk Register (M09)</span>
<HelpTooltip
title="ISO 31000 Risk Register"
content="Assesses specific operational risks by Likelihood (1-5) and Consequence/Impact (1-5), specifying mitigation commitments."
size="sm"
placement="bottom"
/>
</div>
{risks.length === 0 ? (
<p className="text-xs text-slate-500 py-4 text-center">No project risks logged.</p>
) : (
@@ -799,10 +862,19 @@ export const ProjectsPage: React.FC = () => {
}}
title={editingProject ? 'Edit Funded Project' : 'Create Funded Project'}
subtitle={editingProject ? `Updating project #${editingProject.id}` : 'Operational entity under an approved Programme'}
helpText="Register or modify a funded project. Ensure the correct parent programme and industrial sector are assigned to preserve statutory reporting compliance."
>
<form onSubmit={projectForm.handleSubmit((data) => projectMutation.mutate(data as any))} className="space-y-4">
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">Project Name *</label>
<div className="flex items-center gap-1 mb-1">
<label className="block text-xs font-semibold text-slate-300">Project Name *</label>
<HelpTooltip
title="Project Title"
content="Clear, unique operational project title corresponding to the legal agreement."
size="sm"
placement="right"
/>
</div>
<input
type="text"
{...projectForm.register('name')}
@@ -816,7 +888,15 @@ export const ProjectsPage: React.FC = () => {
{!editingProject && (
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">Parent Programme *</label>
<div className="flex items-center gap-1 mb-1">
<label className="block text-xs font-semibold text-slate-300">Parent Programme *</label>
<HelpTooltip
title="Parent Programme"
content="The national statutory funding vehicle financing this initiative."
size="sm"
placement="right"
/>
</div>
<select
{...projectForm.register('programmeId')}
className="w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 bg-[#090d16] text-white font-medium"
@@ -832,7 +912,15 @@ export const ProjectsPage: React.FC = () => {
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">Industry Sector *</label>
<div className="flex items-center gap-1 mb-1">
<label className="block text-xs font-semibold text-slate-300">Industry Sector *</label>
<HelpTooltip
title="Industrial Sector"
content="Primary industrial or economic sector for national cluster analytics."
size="sm"
placement="top"
/>
</div>
<select
{...projectForm.register('sector')}
className="w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 bg-[#090d16] text-white font-medium"
@@ -846,7 +934,15 @@ export const ProjectsPage: React.FC = () => {
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">Project Type *</label>
<div className="flex items-center gap-1 mb-1">
<label className="block text-xs font-semibold text-slate-300">Project Type *</label>
<HelpTooltip
title="Project Type"
content="Category of execution: Grant, Commercialisation, Infrastructure, or Research."
size="sm"
placement="top"
/>
</div>
<select
{...projectForm.register('projectType')}
className="w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 bg-[#090d16] text-white font-medium"
@@ -901,13 +997,22 @@ export const ProjectsPage: React.FC = () => {
}}
title={editingActivity ? 'Edit Activity in Implementation Plan' : 'Add Activity to Implementation Plan'}
subtitle={editingActivity ? `Updating activity #${editingActivity.id}` : `Registering activity under Project #${activeProjectId}`}
helpText="Define operational tasks and assign them to contractual milestones. The allocated budget will be tracked against expenditure categories."
>
<form
onSubmit={activityForm.handleSubmit((data) => activityMutation.mutate(data as any))}
className="space-y-4"
>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">Activity Title *</label>
<div className="flex items-center gap-1 mb-1">
<label className="block text-xs font-semibold text-slate-300">Activity Title *</label>
<HelpTooltip
title="Activity Title"
content="Concise description of the specific work task to be undertaken by the project team."
size="sm"
placement="right"
/>
</div>
<input
type="text"
{...activityForm.register('title')}
@@ -916,11 +1021,19 @@ export const ProjectsPage: React.FC = () => {
/>
</div>
{/* Associated Milestone Dropdown (FR: Passing milestoneId to PUT/POST request) */}
{/* Associated Milestone Dropdown */}
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">
Associated Milestone (Deliverable Link)
</label>
<div className="flex items-center gap-1 mb-1">
<label className="block text-xs font-semibold text-slate-300">
Associated Milestone (Deliverable Link)
</label>
<HelpTooltip
title="Milestone Linkage"
content="Optionally bind this activity as a work package that contributes to a contractual milestone deliverable."
size="sm"
placement="right"
/>
</div>
<select
{...activityForm.register('milestoneId')}
className="w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 bg-[#090d16] text-white font-medium"
@@ -936,7 +1049,15 @@ export const ProjectsPage: React.FC = () => {
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">Budget Allocation (ZAR) *</label>
<div className="flex items-center gap-1 mb-1">
<label className="block text-xs font-semibold text-slate-300">Budget Allocation (ZAR) *</label>
<HelpTooltip
title="Activity Budget"
content="Estimated expenditure or cost required to complete this activity."
size="sm"
placement="top"
/>
</div>
<input
type="number"
{...activityForm.register('amount')}
@@ -944,7 +1065,15 @@ export const ProjectsPage: React.FC = () => {
/>
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">Cost Category *</label>
<div className="flex items-center gap-1 mb-1">
<label className="block text-xs font-semibold text-slate-300">Cost Category *</label>
<HelpTooltip
title="Cost Category"
content="Budget line category (e.g. Capital Equipment, Personnel, Field Trials, Operational)."
size="sm"
placement="top"
/>
</div>
<input
type="text"
{...activityForm.register('category')}
@@ -994,13 +1123,22 @@ export const ProjectsPage: React.FC = () => {
}}
title={editingMilestone ? 'Edit Contractual Milestone' : 'Define Contractual Milestone'}
subtitle={editingMilestone ? `Updating milestone #${editingMilestone.id}` : `Deliverable schedule for Project #${activeProjectId}`}
helpText="Contractual milestones represent formal deliverables specified in the grant contract. Upon verification and approval, they increment the calculated project progress score and unblock disbursement releases."
>
<form
onSubmit={milestoneForm.handleSubmit((data) => milestoneMutation.mutate(data as any))}
className="space-y-4"
>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">Milestone Deliverable Name *</label>
<div className="flex items-center gap-1 mb-1">
<label className="block text-xs font-semibold text-slate-300">Milestone Deliverable Name *</label>
<HelpTooltip
title="Deliverable Title"
content="Title of the agreed milestone (e.g. Inception Report, Working Prototype, Pilot Demonstration)."
size="sm"
placement="right"
/>
</div>
<input
type="text"
{...milestoneForm.register('name')}
@@ -1011,7 +1149,15 @@ export const ProjectsPage: React.FC = () => {
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">Contractual Due Date *</label>
<div className="flex items-center gap-1 mb-1">
<label className="block text-xs font-semibold text-slate-300">Contractual Due Date *</label>
<HelpTooltip
title="Due Date"
content="Contractual cutoff deadline by which evidence must be submitted for review."
size="sm"
placement="top"
/>
</div>
<input
type="date"
{...milestoneForm.register('dueAt')}
@@ -1019,7 +1165,15 @@ export const ProjectsPage: React.FC = () => {
/>
</div>
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">Priority Level *</label>
<div className="flex items-center gap-1 mb-1">
<label className="block text-xs font-semibold text-slate-300">Priority Level *</label>
<HelpTooltip
title="Milestone Priority"
content="Criticality of the milestone: Low, Medium, High, or Critical path."
size="sm"
placement="top"
/>
</div>
<select
{...milestoneForm.register('priority')}
className="w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 bg-[#090d16] text-white font-medium"
@@ -1035,7 +1189,15 @@ export const ProjectsPage: React.FC = () => {
{editingMilestone && (
<div>
<label className="block text-xs font-semibold text-slate-300 mb-1">Approval / Verification Status</label>
<div className="flex items-center gap-1 mb-1">
<label className="block text-xs font-semibold text-slate-300">Approval / Verification Status</label>
<HelpTooltip
title="Verification Status"
content="Only Approved/Verified milestones contribute to the calculated progress percentage."
size="sm"
placement="right"
/>
</div>
<select
{...milestoneForm.register('status')}
className="w-full px-3 py-2 text-xs border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 bg-[#090d16] text-white font-medium"