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
@@ -57,6 +57,7 @@ import { useToast } from '../components/ui/Toast';
import { ExportButton } from '../components/ui/ExportButton';
import { ExportColumn } from '../utils/exportUtils';
import { useAuth } from '../auth/AuthProvider';
import { useTableSort, SortableTh } from '../utils/tableSort';
import { FundingInstrumentsSection, InstructionsSection } from '../components/governance/InstructionsSection';
import { RulesSection } from '../components/governance/RulesSection';
import {
@@ -636,6 +637,36 @@ export const GovernancePage: React.FC = () => {
return name.includes(q) || id.includes(q) || portName.includes(q) || owner.includes(q);
});
// Table Sorting Hooks
const {
sortedData: sortedOrgs,
sortColumn: orgSortCol,
sortDirection: orgSortDir,
handleSort: handleOrgSort,
} = useTableSort(filteredOrgs, null, null, {
name: (o) => o.name,
registrationNo: (o) => o.registrationNo,
type: (o) => getOrganisationTypeLabel(o.type),
email: (o) => o.email,
status: (o) => getOrganisationStatusBadge(o).label,
});
const {
sortedData: sortedProgrammes,
sortColumn: progSortCol,
sortDirection: progSortDir,
handleSort: handleProgSort,
} = useTableSort(filteredProgrammes, null, null, {
name: (p) => p.name,
portfolioId: (p) => {
const port = portfolios.find((po) => String(po.id) === String(p.portfolioId));
return port ? port.name : `Portfolio #${p.portfolioId}`;
},
status: (p) => getApprovalStatusBadge(p.status).label,
createdAt: (p) => p.createdAt,
ownedBy: (p) => getUserEmail(p.ownedBy),
});
// Export Columns Definitions
const orgExportColumns: ExportColumn<Organisation>[] = [
{ header: 'Organisation ID', key: 'id' },
@@ -891,12 +922,22 @@ export const GovernancePage: React.FC = () => {
<table className="w-full text-left text-xs border-collapse">
<thead>
<tr className="bg-[#0d121f]/95 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold text-[11px]">
<th className="py-3.5 px-5">Organisation Name</th>
<th className="py-3.5 px-4">Registration No</th>
<th className="py-3.5 px-4">Entity Type</th>
<th className="py-3.5 px-4">Contact Email</th>
<th className="py-3.5 px-4">Status</th>
<th className="py-3.5 px-5 text-right">Actions</th>
<SortableTh column="name" currentColumn={orgSortCol} direction={orgSortDir} onSort={handleOrgSort} className="py-3.5 px-5">
Organisation Name
</SortableTh>
<SortableTh column="registrationNo" currentColumn={orgSortCol} direction={orgSortDir} onSort={handleOrgSort} className="py-3.5 px-4">
Registration No
</SortableTh>
<SortableTh column="type" currentColumn={orgSortCol} direction={orgSortDir} onSort={handleOrgSort} className="py-3.5 px-4">
Entity Type
</SortableTh>
<SortableTh column="email" currentColumn={orgSortCol} direction={orgSortDir} onSort={handleOrgSort} className="py-3.5 px-4">
Contact Email
</SortableTh>
<SortableTh column="status" currentColumn={orgSortCol} direction={orgSortDir} onSort={handleOrgSort} className="py-3.5 px-4">
Status
</SortableTh>
<th className="py-3.5 px-5 text-right font-medium">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-800/60">
@@ -906,14 +947,14 @@ export const GovernancePage: React.FC = () => {
Loading registered organisations...
</td>
</tr>
) : filteredOrgs.length === 0 ? (
) : sortedOrgs.length === 0 ? (
<tr>
<td colSpan={6} className="py-8 text-center text-slate-500">
No organisations found matching query.
</td>
</tr>
) : (
filteredOrgs.map((org) => {
sortedOrgs.map((org) => {
const statusBadge = getOrganisationStatusBadge(org);
return (
<tr key={org.id} className="hover:bg-slate-800/40 transition-colors">
@@ -1147,12 +1188,22 @@ export const GovernancePage: React.FC = () => {
<table className="w-full text-left text-xs border-collapse">
<thead>
<tr className="bg-[#0d121f]/95 border-b border-slate-800/80 text-slate-400 uppercase tracking-wider font-semibold text-[11px]">
<th className="py-3.5 px-5">Programme Name</th>
<th className="py-3.5 px-4">Parent Portfolio</th>
<th className="py-3.5 px-4">Governance Status</th>
<th className="py-3.5 px-4">Creation Date</th>
<th className="py-3.5 px-4">Owner Email</th>
<th className="py-3.5 px-5 text-right">Actions</th>
<SortableTh column="name" currentColumn={progSortCol} direction={progSortDir} onSort={handleProgSort} className="py-3.5 px-5">
Programme Name
</SortableTh>
<SortableTh column="portfolioId" currentColumn={progSortCol} direction={progSortDir} onSort={handleProgSort} className="py-3.5 px-4">
Parent Portfolio
</SortableTh>
<SortableTh column="status" currentColumn={progSortCol} direction={progSortDir} onSort={handleProgSort} className="py-3.5 px-4">
Governance Status
</SortableTh>
<SortableTh column="createdAt" currentColumn={progSortCol} direction={progSortDir} onSort={handleProgSort} className="py-3.5 px-4">
Creation Date
</SortableTh>
<SortableTh column="ownedBy" currentColumn={progSortCol} direction={progSortDir} onSort={handleProgSort} className="py-3.5 px-4">
Owner Email
</SortableTh>
<th className="py-3.5 px-5 text-right font-medium">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-slate-800/60">
@@ -1162,14 +1213,14 @@ export const GovernancePage: React.FC = () => {
Loading programmes...
</td>
</tr>
) : filteredProgrammes.length === 0 ? (
) : sortedProgrammes.length === 0 ? (
<tr>
<td colSpan={6} className="py-8 text-center text-slate-500">
No programmes found matching query.
</td>
</tr>
) : (
filteredProgrammes.map((pr) => {
sortedProgrammes.map((pr) => {
const port = portfolios.find((p) => String(p.id) === String(pr.portfolioId));
const statusBadge = getApprovalStatusBadge(pr.status);
return (