Added, beneficiaries, site visits and change management

This commit is contained in:
2026-09-27 11:40:28 +02:00
parent fb27cc8c92
commit 36e5d3d7c8
16 changed files with 3132 additions and 212 deletions
@@ -44,6 +44,7 @@ import { HelpTooltip } from '../components/ui/HelpTooltip';
import { useToast } from '../components/ui/Toast';
import { ExportButton } from '../components/ui/ExportButton';
import { ExportColumn } from '../utils/exportUtils';
import { useTableSort, SortableTh } from '../utils/tableSort';
import {
Award,
Budget,
@@ -592,6 +593,49 @@ export const GrantsPage: React.FC = () => {
return id.includes(q) || projId.includes(q) || projName.includes(q) || ref.includes(q) || msId.includes(q) || creator.includes(q);
});
// Table Sorting Hooks
const {
sortedData: sortedAwards,
sortColumn: awardSortCol,
sortDirection: awardSortDir,
handleSort: handleAwardSort,
} = useTableSort(filteredAwards, null, null, {
referenceNo: (a) => a.referenceNo,
organisationId: (a) => a.organisationName || `Org #${a.organisationId}`,
programmeId: (a) => a.programmeName || `Programme #${a.programmeId}`,
amount: (a) => a.amount,
status: (a) => getApprovalStatusBadge(a.status).label,
createdAt: (a) => a.createdAt,
});
const {
sortedData: sortedBudgets,
sortColumn: budgetSortCol,
sortDirection: budgetSortDir,
handleSort: handleBudgetSort,
} = useTableSort(filteredBudgets, null, null, {
name: (b) => b.name,
projectId: (b) => b.projectId,
amount: (b) => b.amount,
createdAt: (b) => b.createdAt,
status: (b) => getApprovalStatusBadge(b.status ?? ApprovalStatus.Approved).label,
createdBy: (b) => getUserEmail(b.createdBy),
});
const {
sortedData: sortedDisbursements,
sortColumn: disbSortCol,
sortDirection: disbSortDir,
handleSort: handleDisbSort,
} = useTableSort(filteredDisbursements, null, null, {
id: (d) => d.id,
projectId: (d) => d.projectId,
milestoneId: (d) => d.milestoneId || 0,
amount: (d) => d.amount,
paymentReference: (d) => d.paymentReference || '',
status: (d) => getApprovalStatusBadge(d.status).label,
});
return (
<div className="space-y-6 max-w-7xl mx-auto">
{/* Header & Execution Pipeline Summary */}
@@ -766,12 +810,24 @@ export const GrantsPage: React.FC = () => {
<table className="w-full text-left text-xs border-collapse">
<thead>
<tr className="bg-slate-900/60 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold">
<th className="py-3.5 px-5">Approval Reference #</th>
<th className="py-3.5 px-4">Recipient Organisation</th>
<th className="py-3.5 px-4">Funding Programme</th>
<th className="py-3.5 px-4 text-right">Approved Amount (ZAR)</th>
<th className="py-3.5 px-4">Status</th>
<th className="py-3.5 px-5 text-right">Date Registered</th>
<SortableTh column="referenceNo" currentColumn={awardSortCol} direction={awardSortDir} onSort={handleAwardSort} className="py-3.5 px-5">
Approval Reference #
</SortableTh>
<SortableTh column="organisationId" currentColumn={awardSortCol} direction={awardSortDir} onSort={handleAwardSort} className="py-3.5 px-4">
Recipient Organisation
</SortableTh>
<SortableTh column="programmeId" currentColumn={awardSortCol} direction={awardSortDir} onSort={handleAwardSort} className="py-3.5 px-4">
Funding Programme
</SortableTh>
<SortableTh column="amount" currentColumn={awardSortCol} direction={awardSortDir} onSort={handleAwardSort} align="right" className="py-3.5 px-4 text-right">
Approved Amount (ZAR)
</SortableTh>
<SortableTh column="status" currentColumn={awardSortCol} direction={awardSortDir} onSort={handleAwardSort} className="py-3.5 px-4">
Status
</SortableTh>
<SortableTh column="createdAt" currentColumn={awardSortCol} direction={awardSortDir} onSort={handleAwardSort} align="right" className="py-3.5 px-5 text-right">
Date Registered
</SortableTh>
</tr>
</thead>
<tbody className="divide-y divide-slate-800/50">
@@ -781,14 +837,14 @@ export const GrantsPage: React.FC = () => {
Loading registered awards...
</td>
</tr>
) : filteredAwards.length === 0 ? (
) : sortedAwards.length === 0 ? (
<tr>
<td colSpan={6} className="py-8 text-center text-slate-500">
{searchTerm ? 'No awards match your search query.' : 'No registered awards found.'}
</td>
</tr>
) : (
filteredAwards.map((awd) => {
sortedAwards.map((awd) => {
const statusBadge = getApprovalStatusBadge(awd.status);
return (
<tr key={awd.id} className="hover:bg-slate-800/40 transition-colors">
@@ -867,13 +923,25 @@ export const GrantsPage: React.FC = () => {
<table className="w-full text-left text-xs border-collapse">
<thead>
<tr className="bg-slate-900/60 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold">
<th className="py-3.5 px-5">Budget Line Category</th>
<th className="py-3.5 px-4">Project ID</th>
<th className="py-3.5 px-4 text-right">Approved Amount (ZAR)</th>
<th className="py-3.5 px-4">Date Established</th>
<th className="py-3.5 px-4">Status</th>
<th className="py-3.5 px-4">Created By</th>
<th className="py-3.5 px-5 text-right">Actions</th>
<SortableTh column="name" currentColumn={budgetSortCol} direction={budgetSortDir} onSort={handleBudgetSort} className="py-3.5 px-5">
Budget Line Category
</SortableTh>
<SortableTh column="projectId" currentColumn={budgetSortCol} direction={budgetSortDir} onSort={handleBudgetSort} className="py-3.5 px-4">
Project ID
</SortableTh>
<SortableTh column="amount" currentColumn={budgetSortCol} direction={budgetSortDir} onSort={handleBudgetSort} align="right" className="py-3.5 px-4 text-right">
Approved Amount (ZAR)
</SortableTh>
<SortableTh column="createdAt" currentColumn={budgetSortCol} direction={budgetSortDir} onSort={handleBudgetSort} className="py-3.5 px-4">
Date Established
</SortableTh>
<SortableTh column="status" currentColumn={budgetSortCol} direction={budgetSortDir} onSort={handleBudgetSort} className="py-3.5 px-4">
Status
</SortableTh>
<SortableTh column="createdBy" currentColumn={budgetSortCol} direction={budgetSortDir} onSort={handleBudgetSort} className="py-3.5 px-4">
Created By
</SortableTh>
<th className="py-3.5 px-5 text-right font-medium">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-800/50">
@@ -883,14 +951,14 @@ export const GrantsPage: React.FC = () => {
Loading budget lines...
</td>
</tr>
) : filteredBudgets.length === 0 ? (
) : sortedBudgets.length === 0 ? (
<tr>
<td colSpan={7} className="py-8 text-center text-slate-500">
{searchTerm ? 'No budget lines match your search query.' : 'No budget lines established yet.'}
</td>
</tr>
) : (
filteredBudgets.map((b) => {
sortedBudgets.map((b) => {
const statusBadge = getApprovalStatusBadge(b.status ?? ApprovalStatus.Approved);
return (
<tr key={b.id} className="hover:bg-slate-800/40 transition-colors">
@@ -1005,13 +1073,25 @@ export const GrantsPage: React.FC = () => {
<table className="w-full text-left text-xs border-collapse">
<thead>
<tr className="bg-slate-900/60 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold">
<th className="py-3.5 px-5">Disbursement ID</th>
<th className="py-3.5 px-4">Project ID</th>
<th className="py-3.5 px-4">Milestone Association</th>
<th className="py-3.5 px-4 text-right">Tranche Amount</th>
<th className="py-3.5 px-4">Payment Reference</th>
<th className="py-3.5 px-4">Approval Status</th>
<th className="py-3.5 px-5 text-right">Approval Action</th>
<SortableTh column="id" currentColumn={disbSortCol} direction={disbSortDir} onSort={handleDisbSort} className="py-3.5 px-5">
Disbursement ID
</SortableTh>
<SortableTh column="projectId" currentColumn={disbSortCol} direction={disbSortDir} onSort={handleDisbSort} className="py-3.5 px-4">
Project ID
</SortableTh>
<SortableTh column="milestoneId" currentColumn={disbSortCol} direction={disbSortDir} onSort={handleDisbSort} className="py-3.5 px-4">
Milestone Association
</SortableTh>
<SortableTh column="amount" currentColumn={disbSortCol} direction={disbSortDir} onSort={handleDisbSort} align="right" className="py-3.5 px-4 text-right">
Tranche Amount
</SortableTh>
<SortableTh column="paymentReference" currentColumn={disbSortCol} direction={disbSortDir} onSort={handleDisbSort} className="py-3.5 px-4">
Payment Reference
</SortableTh>
<SortableTh column="status" currentColumn={disbSortCol} direction={disbSortDir} onSort={handleDisbSort} className="py-3.5 px-4">
Approval Status
</SortableTh>
<th className="py-3.5 px-5 text-right font-medium">Approval Action</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-800/50">
@@ -1021,14 +1101,14 @@ export const GrantsPage: React.FC = () => {
Loading disbursements...
</td>
</tr>
) : filteredDisbursements.length === 0 ? (
) : sortedDisbursements.length === 0 ? (
<tr>
<td colSpan={7} className="py-8 text-center text-slate-500">
{searchTerm ? 'No disbursements match your search query.' : 'No disbursement tranches recorded yet.'}
</td>
</tr>
) : (
filteredDisbursements.map((d) => {
sortedDisbursements.map((d) => {
const statusBadge = getApprovalStatusBadge(d.status);
const isApproved = d.status === ApprovalStatus.Approved;
return (