Removed settings page mock data and replaced with api calls
This commit is contained in:
@@ -195,7 +195,7 @@ export const RulesSection: React.FC<RulesSectionProps> = ({
|
||||
programmeId: Number(rule.programmeId),
|
||||
name: rule.name || '',
|
||||
description: rule.description || '',
|
||||
version: Number(rule.version) || 1,
|
||||
version: Number(rule.version) > 0 ? Number(rule.version) : 1,
|
||||
status: Number(rule.status),
|
||||
});
|
||||
setIsModalOpen(true);
|
||||
@@ -577,19 +577,26 @@ export const RulesSection: React.FC<RulesSectionProps> = ({
|
||||
{/* Version & Status */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-1 mb-1">
|
||||
<label className="block text-xs font-semibold text-slate-300">Rule Version</label>
|
||||
<HelpTooltip
|
||||
title="Rule Version"
|
||||
content="Integer version tracking revisions of this statutory rule."
|
||||
size="sm"
|
||||
/>
|
||||
<div className="flex items-center justify-between mb-1">
|
||||
<div className="flex items-center gap-1">
|
||||
<label className="block text-xs font-semibold text-slate-300">Rule Version</label>
|
||||
<HelpTooltip
|
||||
title="Rule Version"
|
||||
content="System-managed version number tracking revisions of this statutory rule (read-only)."
|
||||
size="sm"
|
||||
/>
|
||||
</div>
|
||||
<span className="text-[10px] font-mono uppercase tracking-wider text-slate-500 bg-slate-800/60 px-1.5 py-0.5 rounded border border-slate-700/50">
|
||||
Read-Only
|
||||
</span>
|
||||
</div>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
readOnly
|
||||
tabIndex={-1}
|
||||
{...ruleForm.register('version')}
|
||||
className="w-full px-3 py-2 text-xs bg-[#090d16] border border-slate-700 rounded-xl text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 font-mono"
|
||||
className="w-full px-3 py-2 text-xs bg-slate-900/80 border border-slate-700/70 rounded-xl text-slate-400 font-mono cursor-not-allowed select-none focus:outline-none focus:ring-0 focus:border-slate-700/70"
|
||||
/>
|
||||
{ruleForm.formState.errors.version && (
|
||||
<p className="text-red-400 text-[11px] mt-1">
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
Paperclip,
|
||||
Check,
|
||||
ShieldCheck,
|
||||
RotateCcw,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
getContracts,
|
||||
@@ -39,6 +40,8 @@ import { toolsService } from '../../services/toolsService';
|
||||
import { Modal } from '../ui/Modal';
|
||||
import { useToast } from '../ui/Toast';
|
||||
import { ProblemDetailsAlert } from '../ui/ProblemDetailsAlert';
|
||||
import { ExportButton } from '../ui/ExportButton';
|
||||
import { ExportColumn } from '../../utils/exportUtils';
|
||||
import {
|
||||
Contract,
|
||||
Award,
|
||||
@@ -390,6 +393,19 @@ export const ContractsManagement: React.FC<ContractsManagementProps> = ({
|
||||
Number(c.status) === ContractStatus.PendingApproval
|
||||
).length;
|
||||
|
||||
const contractExportColumns: ExportColumn<Contract>[] = [
|
||||
{ header: 'Contract ID', key: 'id' },
|
||||
{ header: 'Description / Title', key: 'description' },
|
||||
{ header: 'Project', key: 'projectId', format: (val) => (val ? `Project #${val}` : '—') },
|
||||
{ header: 'Award ID', key: 'awardId', format: (val) => (val ? `Award #${val}` : '—') },
|
||||
{ header: 'Organisation ID', key: 'organisationId', format: (val) => (val ? `Org #${val}` : '—') },
|
||||
{ header: 'Status', key: 'status', format: (val) => getContractStatusBadge(val).label },
|
||||
{ header: 'Signed Date', key: 'signedDate', format: (val) => (val ? formatDate(val) : '—') },
|
||||
{ header: 'Expiry Date', key: 'expiryDate', format: (val) => (val ? formatDate(val) : '—') },
|
||||
{ header: 'Budget Allocated', key: 'budgetAllocated', format: (val) => (val ? formatZAR(val) : '—') },
|
||||
{ header: 'Document URI', key: 'fileUri', format: (val) => val || '—' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Summary Cards */}
|
||||
@@ -466,27 +482,30 @@ export const ContractsManagement: React.FC<ContractsManagementProps> = ({
|
||||
</div>
|
||||
|
||||
{/* Action and Filter Bar */}
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 bg-[#0f1523]/80 backdrop-blur-md p-4 rounded-2xl border border-slate-800/80 shadow-md">
|
||||
<div className="flex flex-wrap items-center gap-2 flex-1">
|
||||
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-3 bg-[#0d121f]/90 p-4 rounded-2xl border border-slate-800/80 backdrop-blur-sm">
|
||||
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3 flex-1 flex-wrap">
|
||||
{/* Search */}
|
||||
<div className="relative flex-1 min-w-[220px] max-w-sm">
|
||||
<Search className="w-4 h-4 absolute left-3 top-1/2 -translate-y-1/2 text-slate-400" />
|
||||
<div className="relative flex-1 min-w-[200px] max-w-md">
|
||||
<Search className="w-4 h-4 text-slate-500 absolute left-3 top-3" />
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search contracts, award, org, programme..."
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full bg-[#151b2c] border border-slate-700/80 rounded-xl pl-9 pr-3 py-1.5 text-xs text-white placeholder-slate-500 focus:outline-none focus:border-blue-500"
|
||||
className="w-full pl-9 pr-4 py-2.5 text-xs bg-[#090d16] border border-slate-700/80 rounded-xl focus:outline-none focus:ring-1 focus:ring-blue-500 text-slate-200 placeholder:text-slate-500 transition shadow-xs"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Status Filter */}
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Filter className="w-3.5 h-3.5 text-slate-400" />
|
||||
<div className="flex items-center gap-2">
|
||||
<label className="text-xs text-slate-400 whitespace-nowrap flex items-center gap-1 font-medium">
|
||||
<Filter className="w-3.5 h-3.5 text-slate-400" />
|
||||
Status:
|
||||
</label>
|
||||
<select
|
||||
value={statusFilter}
|
||||
onChange={(e) => setStatusFilter(e.target.value)}
|
||||
className="bg-[#151b2c] border border-slate-700/80 rounded-xl px-2.5 py-1.5 text-xs text-slate-200 focus:outline-none focus:border-blue-500"
|
||||
className="px-3 py-2 text-xs bg-[#090d16] border border-slate-700/80 rounded-xl text-slate-200 focus:outline-none focus:ring-1 focus:ring-blue-500 [&>option]:bg-slate-900"
|
||||
>
|
||||
<option value="all">All Statuses</option>
|
||||
{CONTRACT_STATUS_OPTIONS.map((opt) => (
|
||||
@@ -497,28 +516,57 @@ export const ContractsManagement: React.FC<ContractsManagementProps> = ({
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => refetchContracts()}
|
||||
disabled={refetchingContracts}
|
||||
className="p-2 rounded-xl bg-[#151b2c] border border-slate-700/80 text-slate-400 hover:text-white hover:bg-slate-800 transition"
|
||||
title="Refresh contracts"
|
||||
>
|
||||
<RefreshCw className={`w-3.5 h-3.5 ${refetchingContracts ? 'animate-spin' : ''}`} />
|
||||
</button>
|
||||
{(searchTerm || statusFilter !== 'all') && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setSearchTerm('');
|
||||
setStatusFilter('all');
|
||||
}}
|
||||
className="inline-flex items-center gap-1 text-[11px] font-semibold text-slate-400 hover:text-white px-2.5 py-2 bg-slate-800 hover:bg-slate-700 rounded-xl transition cursor-pointer"
|
||||
>
|
||||
<RotateCcw className="w-3 h-3" />
|
||||
<span>Reset</span>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
resetCreateForm();
|
||||
setCreateModalOpen(true);
|
||||
}}
|
||||
className="inline-flex items-center justify-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>
|
||||
{/* Action Buttons: Refresh, Export & Create */}
|
||||
<div className="flex items-center gap-2 self-end lg:self-center">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
refetchContracts();
|
||||
success('Refreshed', 'Contract registry and award linkages updated.');
|
||||
}}
|
||||
disabled={refetchingContracts}
|
||||
className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 text-xs font-semibold text-slate-300 bg-[#0d121f] hover:bg-slate-800 border border-slate-700/80 rounded-xl transition cursor-pointer shadow-xs disabled:opacity-50"
|
||||
title="Refresh contracts from API"
|
||||
>
|
||||
<RefreshCw className={`w-3.5 h-3.5 text-blue-400 ${refetchingContracts ? 'animate-spin' : ''}`} />
|
||||
<span>Refresh</span>
|
||||
</button>
|
||||
|
||||
<ExportButton
|
||||
title="Contract Registry & Agreements"
|
||||
subtitle="Authoritative repository of bilateral legal agreements and contract files"
|
||||
filename={`pfm-contracts-${new Date().toISOString().split('T')[0]}`}
|
||||
columns={contractExportColumns}
|
||||
data={filteredContracts}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
resetCreateForm();
|
||||
setCreateModalOpen(true);
|
||||
}}
|
||||
className="inline-flex items-center justify-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-md shadow-blue-500/20 transition cursor-pointer"
|
||||
>
|
||||
<Plus className="w-4 h-4" />
|
||||
Create Contract
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Contracts Table */}
|
||||
|
||||
@@ -23,6 +23,7 @@ import {
|
||||
Check,
|
||||
FileSpreadsheet,
|
||||
FileCheck,
|
||||
RefreshCw,
|
||||
} from 'lucide-react';
|
||||
import {
|
||||
getAllEvidence,
|
||||
@@ -53,6 +54,8 @@ import {
|
||||
} from '../../lib/formatters';
|
||||
import { Modal } from '../ui/Modal';
|
||||
import { useToast } from '../ui/Toast';
|
||||
import { ExportButton } from '../ui/ExportButton';
|
||||
import { ExportColumn } from '../../utils/exportUtils';
|
||||
|
||||
export interface EvidenceReviewQueueProps {
|
||||
initialProjectId?: string | number | 'all';
|
||||
@@ -154,7 +157,12 @@ export const EvidenceReviewQueue: React.FC<EvidenceReviewQueueProps> = ({
|
||||
});
|
||||
|
||||
// Query evidence: if a project is selected (e.g. the first project by default), fetch via /v1/api/evidence/{projectId}
|
||||
const { data: rawEvidenceList = [], isLoading: loadingEvidence } = useQuery<Evidence[]>({
|
||||
const {
|
||||
data: rawEvidenceList = [],
|
||||
isLoading: loadingEvidence,
|
||||
refetch: refetchEvidence,
|
||||
isFetching: isFetchingEvidence,
|
||||
} = useQuery<Evidence[]>({
|
||||
queryKey: ['evidence', selectedProjectId],
|
||||
queryFn: async () => {
|
||||
if (selectedProjectId && selectedProjectId !== 'all') {
|
||||
@@ -168,6 +176,19 @@ export const EvidenceReviewQueue: React.FC<EvidenceReviewQueueProps> = ({
|
||||
// UI depends entirely on the API response - no mock or demo data
|
||||
const allEvidence: Evidence[] = rawEvidenceList;
|
||||
|
||||
const evidenceExportColumns: ExportColumn<Evidence>[] = [
|
||||
{ header: 'Evidence ID', key: 'id' },
|
||||
{ header: 'File Name', key: 'fileName' },
|
||||
{ header: 'Project ID', key: 'projectId', format: (val) => `Project #${val}` },
|
||||
{ header: 'Milestone ID', key: 'milestoneId', format: (val) => (val ? `Milestone #${val}` : '—') },
|
||||
{ header: 'Activity ID', key: 'activityId', format: (val) => (val ? `Activity #${val}` : '—') },
|
||||
{ header: 'Document Type', key: 'evidenceType', format: (val) => getEvidenceTypeLabel(val) },
|
||||
{ header: 'Submitter', key: 'createdBy', format: (val) => getUserEmail(val) },
|
||||
{ header: 'Submission Date', key: 'createdAt', format: (val) => formatDate(val) },
|
||||
{ header: 'Verification Status', key: 'status', format: (val) => getApprovalStatusBadge(val).label },
|
||||
{ header: 'Review Remarks', key: 'remarks', format: (val) => val || '—' },
|
||||
];
|
||||
|
||||
// Review evidence mutation: Facilitates Approve & Reject decisions via PUT /v1/api/evidence
|
||||
const reviewMutation = useMutation({
|
||||
mutationFn: async ({
|
||||
@@ -408,14 +429,36 @@ export const EvidenceReviewQueue: React.FC<EvidenceReviewQueueProps> = ({
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2.5 shrink-0">
|
||||
<div className="flex items-center gap-2 self-end sm:self-center shrink-0 flex-wrap">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
refetchEvidence();
|
||||
success('Refreshed', 'Latest deliverable evidence records updated.');
|
||||
}}
|
||||
disabled={isFetchingEvidence}
|
||||
className="inline-flex items-center justify-center gap-1.5 px-3.5 py-2 text-xs font-semibold text-slate-300 bg-[#0d121f] hover:bg-slate-800 border border-slate-700/80 rounded-xl transition cursor-pointer shadow-xs disabled:opacity-50"
|
||||
title="Force refresh evidence from API"
|
||||
>
|
||||
<RefreshCw className={`w-3.5 h-3.5 text-blue-400 ${isFetchingEvidence ? 'animate-spin' : ''}`} />
|
||||
<span>Refresh</span>
|
||||
</button>
|
||||
|
||||
<ExportButton
|
||||
title="Deliverable Evidence & Verification Ledger"
|
||||
subtitle="Audited records of submitted milestone deliverables and verification decisions"
|
||||
filename={`pfm-evidence-review-${new Date().toISOString().split('T')[0]}`}
|
||||
columns={evidenceExportColumns}
|
||||
data={filteredEvidence}
|
||||
/>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setIsManualIngestModalOpen(true)}
|
||||
className="inline-flex items-center gap-1.5 text-xs font-semibold text-slate-200 hover:text-white bg-slate-900/90 hover:bg-slate-800 border border-slate-700/80 px-3.5 py-2 rounded-xl transition shadow-xs cursor-pointer"
|
||||
className="inline-flex items-center gap-1.5 text-xs font-semibold text-white bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 border border-transparent px-3.5 py-2 rounded-xl transition shadow-md shadow-blue-500/20 cursor-pointer"
|
||||
title="Upload document on behalf of awardee"
|
||||
>
|
||||
<Upload className="w-3.5 h-3.5 text-blue-400" />
|
||||
<Upload className="w-3.5 h-3.5" />
|
||||
<span>Ingest Evidence</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user