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