From e6fdcf3d0d57eeed21389f193b7b8912ff5b6aa1 Mon Sep 17 00:00:00 2001 From: Hermes Agent Date: Sun, 27 Sep 2026 12:04:22 +0200 Subject: [PATCH] Fixed UI inconsistencies --- PostFundManagement.ConsoleUI/src/App.tsx | 1 + .../src/components/layout/AppLayout.tsx | 12 +- .../src/pages/BeneficiariesPage.tsx | 340 ++++++++---- .../src/pages/ChangeManagementPage.tsx | 355 ++++++++---- .../src/pages/ExecutiveDashboard.tsx | 106 ++-- .../src/pages/GovernancePage.tsx | 68 ++- .../src/pages/GrantsPage.tsx | 106 ++-- .../src/pages/MonitoringPage.tsx | 53 +- .../src/pages/ProjectsPage.tsx | 62 ++- .../src/pages/SettingsPage.tsx | 54 +- .../src/pages/SiteVisitsPage.tsx | 514 +++++++++++++----- 11 files changed, 1085 insertions(+), 586 deletions(-) diff --git a/PostFundManagement.ConsoleUI/src/App.tsx b/PostFundManagement.ConsoleUI/src/App.tsx index ac1d233..d8da8e6 100644 --- a/PostFundManagement.ConsoleUI/src/App.tsx +++ b/PostFundManagement.ConsoleUI/src/App.tsx @@ -53,6 +53,7 @@ export default function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx b/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx index 91d0722..ac1b976 100644 --- a/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx +++ b/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx @@ -73,14 +73,14 @@ const navItems: NavItem[] = [ icon: GitPullRequest, help: 'Track project issues, contractual change requests, and resolve changes in-place.', }, + { + name: 'Site Visits', + path: '/projects/site-visits', + icon: MapPin, + help: 'Log physical site visits, field observations, inspector findings, and project feedback.', + }, ], }, - { - name: 'Site Visits', - path: '/site-visits', - icon: MapPin, - help: 'Log physical site visits, field observations, inspector findings, and project feedback.', - }, { name: 'Grant & Financials', path: '/grants', diff --git a/PostFundManagement.ConsoleUI/src/pages/BeneficiariesPage.tsx b/PostFundManagement.ConsoleUI/src/pages/BeneficiariesPage.tsx index 391ff80..1b0934a 100644 --- a/PostFundManagement.ConsoleUI/src/pages/BeneficiariesPage.tsx +++ b/PostFundManagement.ConsoleUI/src/pages/BeneficiariesPage.tsx @@ -72,6 +72,7 @@ export const BeneficiariesPage: React.FC = () => { const [activeTab, setActiveTab] = useState<'beneficiaries' | 'compliance'>('beneficiaries'); const [searchTerm, setSearchTerm] = useState(''); const [categoryFilter, setCategoryFilter] = useState('all'); + const [compStatusFilter, setCompStatusFilter] = useState('all'); // Modal states const [isBenModalOpen, setIsBenModalOpen] = useState(false); @@ -144,7 +145,14 @@ export const BeneficiariesPage: React.FC = () => { const createBenMutation = useMutation({ mutationFn: (vals: BeneficiaryFormValues) => { if (!effectiveProjectId) throw new Error('No project selected'); - return createBeneficiary({ ...vals, projectId: effectiveProjectId }); + return createBeneficiary({ + projectId: effectiveProjectId, + name: vals.name, + location: vals.location, + category: vals.category, + targetCount: vals.targetCount, + reachedCount: vals.reachedCount, + }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['beneficiaries'] }); @@ -158,7 +166,14 @@ export const BeneficiariesPage: React.FC = () => { const updateBenMutation = useMutation({ mutationFn: (vals: BeneficiaryFormValues) => { if (!editingBen) throw new Error('No beneficiary selected'); - return updateBeneficiary({ ...vals, beneficiaryId: editingBen.id }); + return updateBeneficiary({ + beneficiaryId: editingBen.id, + name: vals.name, + location: vals.location, + category: vals.category, + targetCount: vals.targetCount, + reachedCount: vals.reachedCount, + }); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['beneficiaries'] }); @@ -236,13 +251,17 @@ export const BeneficiariesPage: React.FC = () => { // Filtered & Sorted Compliance Items const filteredComps = useMemo(() => { return complianceItems.filter((c) => { - return ( + const matchesSearch = !searchTerm.trim() || (c.title && c.title.toLowerCase().includes(searchTerm.toLowerCase())) || - (c.requirements && c.requirements.toLowerCase().includes(searchTerm.toLowerCase())) - ); + (c.requirements && c.requirements.toLowerCase().includes(searchTerm.toLowerCase())); + const matchesStatus = + compStatusFilter === 'all' || + (compStatusFilter === 'approved' && c.status === ApprovalStatus.Approved) || + (compStatusFilter === 'pending' && c.status !== ApprovalStatus.Approved); + return matchesSearch && matchesStatus; }); - }, [complianceItems, searchTerm]); + }, [complianceItems, searchTerm, compStatusFilter]); const { sortedData: sortedComps, sortColumn: compSortCol, sortDirection: compSortDir, handleSort: handleCompSort } = useTableSort(filteredComps); @@ -265,7 +284,7 @@ export const BeneficiariesPage: React.FC = () => { const compExportColumns: ExportColumn[] = [ { header: 'Compliance Requirement', key: 'title' }, { header: 'Requirements Detail', key: 'requirements' }, - { header: 'Status', accessor: (c) => (c.status === ApprovalStatus.Approved ? 'Approved' : 'Pending Review') }, + { header: 'Status', key: 'status', format: (val) => (val === ApprovalStatus.Approved ? 'Approved' : 'Pending Review') }, ]; const handleOpenEditBen = (b: Beneficiary) => { @@ -310,60 +329,6 @@ export const BeneficiariesPage: React.FC = () => { - -
- - - {activeTab === 'beneficiaries' ? ( - - ) : ( - - )} - - - - -
{/* KPI Cards */} @@ -413,67 +378,122 @@ export const BeneficiariesPage: React.FC = () => { - {/* Tabs & Filters */} + {/* Tabs */}
-
-
- +
+ - -
- -
-
- - Project: - -
- -
- - setSearchTerm(e.target.value)} - className="bg-slate-900 border border-slate-800 rounded-xl pl-8 pr-3 py-1.5 text-xs text-white focus:outline-none focus:border-blue-500 w-40 sm:w-56" - /> -
-
+
- {/* Tab 1: Beneficiaries Table */} + {/* Tab 1: Beneficiaries Tab Toolbar & Content */} {activeTab === 'beneficiaries' && ( +
+
+
+ {/* Search */} +
+ + setSearchTerm(e.target.value)} + className="w-full bg-slate-900 border border-slate-800 rounded-xl pl-8 pr-3 py-1.5 text-xs text-white focus:outline-none focus:border-blue-500 shadow-xs" + /> +
+ + {/* Category Filter */} +
+ +
+ + {/* Project Selector */} +
+ + +
+
+ + {/* Actions: Refresh, Export, Register */} +
+ + + + + +
+
@@ -572,11 +592,94 @@ export const BeneficiariesPage: React.FC = () => {
+
)} {/* Tab 2: Compliance Items Table */} {activeTab === 'compliance' && ( -
+
+
+
+ {/* Search */} +
+ + setSearchTerm(e.target.value)} + className="w-full bg-slate-900 border border-slate-800 rounded-xl pl-8 pr-3 py-1.5 text-xs text-white focus:outline-none focus:border-blue-500 shadow-xs" + /> +
+ + {/* Status Filter */} +
+ +
+ + {/* Project Selector */} +
+ + +
+
+ + {/* Actions: Refresh, Export, Add Compliance Item */} +
+ + + + + +
+
+ +
@@ -642,6 +745,7 @@ export const BeneficiariesPage: React.FC = () => {
+
)}
diff --git a/PostFundManagement.ConsoleUI/src/pages/ChangeManagementPage.tsx b/PostFundManagement.ConsoleUI/src/pages/ChangeManagementPage.tsx index 67bdbbc..197482f 100644 --- a/PostFundManagement.ConsoleUI/src/pages/ChangeManagementPage.tsx +++ b/PostFundManagement.ConsoleUI/src/pages/ChangeManagementPage.tsx @@ -83,6 +83,8 @@ export const ChangeManagementPage: React.FC = () => { const [activeTab, setActiveTab] = useState<'issues' | 'changes'>('issues'); const [searchTerm, setSearchTerm] = useState(''); const [priorityFilter, setPriorityFilter] = useState('all'); + const [issueStatusFilter, setIssueStatusFilter] = useState('all'); + const [changeStatusFilter, setChangeStatusFilter] = useState('all'); // Modals const [isIssueModalOpen, setIsIssueModalOpen] = useState(false); @@ -276,9 +278,14 @@ export const ChangeManagementPage: React.FC = () => { (iss.title && iss.title.toLowerCase().includes(searchTerm.toLowerCase())) || (iss.description && iss.description.toLowerCase().includes(searchTerm.toLowerCase())); const matchPrio = priorityFilter === 'all' || String(iss.priority) === String(priorityFilter); - return matchSearch && matchPrio; + const isResolved = iss.status === ActivityStatus.Inactive || !!iss.resolvedAt; + const matchStatus = + issueStatusFilter === 'all' || + (issueStatusFilter === 'open' && !isResolved) || + (issueStatusFilter === 'resolved' && isResolved); + return matchSearch && matchPrio && matchStatus; }); - }, [issues, searchTerm, priorityFilter]); + }, [issues, searchTerm, priorityFilter, issueStatusFilter]); const { sortedData: sortedIssues, sortColumn: issueSortCol, sortDirection: issueSortDir, handleSort: handleIssueSort } = useTableSort(filteredIssues); @@ -286,13 +293,18 @@ export const ChangeManagementPage: React.FC = () => { // Filtered & Sorted Change Requests const filteredChanges = useMemo(() => { return changeRequests.filter((cr) => { - return ( + const matchSearch = !searchTerm.trim() || (cr.title && cr.title.toLowerCase().includes(searchTerm.toLowerCase())) || - (cr.justification && cr.justification.toLowerCase().includes(searchTerm.toLowerCase())) - ); + (cr.justification && cr.justification.toLowerCase().includes(searchTerm.toLowerCase())); + const matchStatus = + changeStatusFilter === 'all' || + (changeStatusFilter === 'approved' && cr.status === ApprovalStatus.Approved) || + (changeStatusFilter === 'rejected' && cr.status === ApprovalStatus.Rejected) || + (changeStatusFilter === 'pending' && (cr.status === ApprovalStatus.Pending || cr.status === ApprovalStatus.Review)); + return matchSearch && matchStatus; }); - }, [changeRequests, searchTerm]); + }, [changeRequests, searchTerm, changeStatusFilter]); const { sortedData: sortedChanges, sortColumn: changeSortCol, sortDirection: changeSortDir, handleSort: handleChangeSort } = useTableSort(filteredChanges); @@ -301,16 +313,16 @@ export const ChangeManagementPage: React.FC = () => { const issueExportCols: ExportColumn[] = [ { header: 'Title', key: 'title' }, { header: 'Description', key: 'description' }, - { header: 'Priority', accessor: (i) => (i.priority === Priority.Critical ? 'Critical' : 'Standard') }, - { header: 'Assigned To', accessor: (i) => (i.assignedTo ? getUserEmail(i.assignedTo) : 'Unassigned') }, - { header: 'Status', accessor: (i) => (i.status === ActivityStatus.Active ? 'Active' : 'Resolved') }, + { header: 'Priority', key: 'priority', format: (val) => (val === Priority.Critical ? 'Critical' : 'Standard') }, + { header: 'Assigned To', key: 'assignedTo', format: (val) => (val ? getUserEmail(val) : 'Unassigned') }, + { header: 'Status', key: 'status', format: (val) => (val === ActivityStatus.Active ? 'Active' : 'Resolved') }, ]; const changeExportCols: ExportColumn[] = [ { header: 'Change Title', key: 'title' }, { header: 'Justification', key: 'justification' }, - { header: 'Revised Budget', accessor: (c) => formatZAR(c.revisedBudget || 0) }, - { header: 'Status', accessor: (c) => (c.status === ApprovalStatus.Approved ? 'Approved' : 'Pending') }, + { header: 'Revised Budget', key: 'revisedBudget', format: (val) => formatZAR(val || 0) }, + { header: 'Status', key: 'status', format: (val) => (val === ApprovalStatus.Approved ? 'Approved' : 'Pending') }, ]; const openResolveIssue = (iss: Issue) => { @@ -348,55 +360,6 @@ export const ChangeManagementPage: React.FC = () => {
- -
- - - {activeTab === 'issues' ? ( - - ) : ( - - )} - - - - -
{/* KPI Cards */} @@ -444,68 +407,134 @@ export const ChangeManagementPage: React.FC = () => { - {/* Tabs & Filters */} + {/* Tabs */}
-
-
- +
+ - -
- -
-
- - Project: - -
- -
- - setSearchTerm(e.target.value)} - className="bg-slate-900 border border-slate-800 rounded-xl pl-8 pr-3 py-1.5 text-xs text-white focus:outline-none focus:border-amber-500 w-40 sm:w-56" - /> -
-
+
- {/* Tab 1: Issues Table */} + {/* Tab 1: Issues Toolbar & Content */} {activeTab === 'issues' && ( -
+
+
+
+ {/* Search */} +
+ + setSearchTerm(e.target.value)} + className="w-full bg-slate-900 border border-slate-800 rounded-xl pl-8 pr-3 py-1.5 text-xs text-white focus:outline-none focus:border-amber-500 shadow-xs" + /> +
+ + {/* Priority Filter */} +
+ +
+ + {/* Status Filter */} +
+ +
+ + {/* Project Selector */} +
+ + +
+
+ + {/* Actions: Refresh, Export, Log Issue */} +
+ + + + + +
+
+ +
@@ -611,11 +640,100 @@ export const ChangeManagementPage: React.FC = () => {
+
)} {/* Tab 2: Change Requests Table */} {activeTab === 'changes' && ( -
+
+
+
+ {/* Search */} +
+ + setSearchTerm(e.target.value)} + className="w-full bg-slate-900 border border-slate-800 rounded-xl pl-8 pr-3 py-1.5 text-xs text-white focus:outline-none focus:border-amber-500 shadow-xs" + /> +
+ + {/* Status Filter */} +
+ +
+ + {/* Project Selector */} +
+ + +
+
+ + {/* Actions: Refresh, Export, Request Change */} +
+ + + + + +
+
+ +
@@ -696,6 +814,7 @@ export const ChangeManagementPage: React.FC = () => {
+
)}
diff --git a/PostFundManagement.ConsoleUI/src/pages/ExecutiveDashboard.tsx b/PostFundManagement.ConsoleUI/src/pages/ExecutiveDashboard.tsx index 9ff7da1..56d92c4 100644 --- a/PostFundManagement.ConsoleUI/src/pages/ExecutiveDashboard.tsx +++ b/PostFundManagement.ConsoleUI/src/pages/ExecutiveDashboard.tsx @@ -14,6 +14,7 @@ import { Clock, ShieldAlert, RotateCcw, + LayoutDashboard, } from 'lucide-react'; import { getDashboard, @@ -344,59 +345,65 @@ export const ExecutiveDashboard: React.FC = () => { return (
- {/* Page Title & Filter Bar */} -
- {/* Top Tier: Title, Status Badge, and Primary Actions */} -
-
-
-

- Executive Portfolio Dashboard -

- - - - Live Portfolio Feed - + {/* Header Banner */} +
+
+
+
+
+ +
+
+
+

+ Executive Portfolio Dashboard +

+ + + + Live Portfolio Feed + +
+

+ Real-time portfolio overview, disbursement tracking, risk heatmap, and projects requiring governance action. +

-

- Real-time portfolio overview, disbursement tracking, risk heatmap, and projects requiring governance action. -

-
- -
- {isFiltered && ( - - )} - -
- {/* Dedicated Filter Toolbar */} -
+
+ {isFiltered && ( + + )} + + +
+
+ + {/* Filter Toolbar */} +
Filters: @@ -479,7 +486,6 @@ export const ExecutiveDashboard: React.FC = () => { />
-
{/* 6 Headline Portfolio KPI Cards */}
diff --git a/PostFundManagement.ConsoleUI/src/pages/GovernancePage.tsx b/PostFundManagement.ConsoleUI/src/pages/GovernancePage.tsx index e60c69e..ea18858 100644 --- a/PostFundManagement.ConsoleUI/src/pages/GovernancePage.tsx +++ b/PostFundManagement.ConsoleUI/src/pages/GovernancePage.tsx @@ -717,72 +717,80 @@ export const GovernancePage: React.FC = () => { return (
- {/* Header */} -
-
-
-

- Governance & Structure -

- - Directory - - + {/* Header Banner */} +
+
+
+
+
+ +
+
+
+

+ Governance & Structure +

+ + Directory + + +
+

+ Maintain Master Organisation Registry, Funder Portfolios, Strategic Programmes, Funding Instruments, and Programme Rules. +

+
-

- Maintain Master Organisation Registry, Funder Portfolios, Strategic Programmes, Funding Instruments, and Programme Rules. -

- {/* Tab Switcher & CTA */} -
+ {/* Tab Actions */} +
{activeTab === 'organisations' && ( )} {activeTab === 'portfolios' && ( )} {activeTab === 'programmes' && ( )} {(activeTab === 'instruments' || activeTab === 'instructions') && ( )} {activeTab === 'rules' && ( )}
diff --git a/PostFundManagement.ConsoleUI/src/pages/GrantsPage.tsx b/PostFundManagement.ConsoleUI/src/pages/GrantsPage.tsx index 82ab3d9..9be3fc1 100644 --- a/PostFundManagement.ConsoleUI/src/pages/GrantsPage.tsx +++ b/PostFundManagement.ConsoleUI/src/pages/GrantsPage.tsx @@ -638,59 +638,66 @@ export const GrantsPage: React.FC = () => { return (
- {/* Header & Execution Pipeline Summary */} -
-
-
-
-

- Grant & Financial Management -

- + {/* Header Banner */} +
+
+
+
+
+ +
+
+
+

+ Grant & Financial Management +

+ +
+

+ End-to-end controlled execution thread: Funding Award Intake → Budgeting → Condition-Checked Disbursement. +

-

- End-to-end controlled execution thread: Funding Award Intake → Budgeting → Condition-Checked Disbursement. -

-
- -
- {activeTab === 'awards' && ( - - )} - {activeTab === 'budgets' && ( - - )} - {activeTab === 'disbursements' && ( - - )}
- {/* Pipeline Summary Bar */} -
+
+ {activeTab === 'awards' && ( + + )} + {activeTab === 'budgets' && ( + + )} + {activeTab === 'disbursements' && ( + + )} +
+
+ + {/* Pipeline Summary Bar */} +
Total Awards Registered @@ -728,7 +735,6 @@ export const GrantsPage: React.FC = () => { {formatCompactZAR(pendingDisbursementValue)}
-
{/* Tabs */}
diff --git a/PostFundManagement.ConsoleUI/src/pages/MonitoringPage.tsx b/PostFundManagement.ConsoleUI/src/pages/MonitoringPage.tsx index 8871d30..b1f2acf 100644 --- a/PostFundManagement.ConsoleUI/src/pages/MonitoringPage.tsx +++ b/PostFundManagement.ConsoleUI/src/pages/MonitoringPage.tsx @@ -25,6 +25,7 @@ import { RefreshCw, Search, RotateCcw, + Activity, } from 'lucide-react'; import { getAllIndicators, @@ -407,50 +408,58 @@ export const MonitoringPage: React.FC = () => { return (
- {/* Header */} -
-
-
-

- M&E, Evidence & Risk Management -

- + {/* Header Banner */} +
+
+
+
+
+ +
+
+
+

+ M&E, Evidence & Risk Management +

+ +
+

+ Verification workflow: Evidence Upload & Review Queue, Indicator Actuals Recording, and Risk Register. +

+
-

- Verification workflow: Evidence Upload & Review Queue, Indicator Actuals Recording, and Risk Register. -

-
+
{activeTab === 'contracts' && ( )} {activeTab === 'indicators' && ( )} {activeTab === 'risks' && ( )}
diff --git a/PostFundManagement.ConsoleUI/src/pages/ProjectsPage.tsx b/PostFundManagement.ConsoleUI/src/pages/ProjectsPage.tsx index b99f97d..145ed2b 100644 --- a/PostFundManagement.ConsoleUI/src/pages/ProjectsPage.tsx +++ b/PostFundManagement.ConsoleUI/src/pages/ProjectsPage.tsx @@ -408,35 +408,45 @@ export const ProjectsPage: React.FC = () => { return (
- {/* Page Header */} -
-
-
-

- Project & Milestone Management -

- - - Work Breakdown - + {/* Header Banner */} +
+
+
+
+
+ +
+
+
+

+ Project & Milestone Management +

+ + + Work Breakdown + +
+

+ Work breakdown structure, contractual deliverables, milestone evidence tracking, and progress calculation. +

+
-

- Work breakdown structure, contractual deliverables, milestone evidence tracking, and progress calculation. -

- +
+ +
diff --git a/PostFundManagement.ConsoleUI/src/pages/SettingsPage.tsx b/PostFundManagement.ConsoleUI/src/pages/SettingsPage.tsx index ab36928..339a0a7 100644 --- a/PostFundManagement.ConsoleUI/src/pages/SettingsPage.tsx +++ b/PostFundManagement.ConsoleUI/src/pages/SettingsPage.tsx @@ -468,25 +468,35 @@ export const SettingsPage: React.FC = () => { return (
- {/* Header */} -
-
-

- System Administration & Audit - -

-

- User access directory, statutory invitations, gateway broadcasts, and immutable system audit ledger. -

+ {/* Header Banner */} +
+
+
+
+
+ +
+
+
+

+ System Administration & Audit +

+ +
+

+ User access directory, statutory invitations, gateway broadcasts, and immutable system audit ledger. +

+
+
-
+
{activeTab === 'users' && ( -
+
{
)} @@ -509,10 +519,10 @@ export const SettingsPage: React.FC = () => { {activeTab === 'invites' && ( )} @@ -522,10 +532,10 @@ export const SettingsPage: React.FC = () => { setInviteOrgId(effectiveNotificationOrgId); setIsSendInviteModalOpen(true); }} - className="inline-flex items-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" + className="inline-flex items-center gap-1.5 px-4 py-2.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 rounded-xl shadow-lg shadow-blue-600/25 transition cursor-pointer" > - Add Notification + Add Notification )}
diff --git a/PostFundManagement.ConsoleUI/src/pages/SiteVisitsPage.tsx b/PostFundManagement.ConsoleUI/src/pages/SiteVisitsPage.tsx index c6ded53..1a7abda 100644 --- a/PostFundManagement.ConsoleUI/src/pages/SiteVisitsPage.tsx +++ b/PostFundManagement.ConsoleUI/src/pages/SiteVisitsPage.tsx @@ -52,7 +52,9 @@ export const SiteVisitsPage: React.FC = () => { const { success, error: toastError } = useToast(); const [selectedProjectId, setSelectedProjectId] = useState('all'); + const [activeTab, setActiveTab] = useState<'visits' | 'feedback'>('visits'); const [searchTerm, setSearchTerm] = useState(''); + const [statusFilter, setStatusFilter] = useState('all'); // Modals const [isVisitModalOpen, setIsVisitModalOpen] = useState(false); @@ -135,13 +137,17 @@ export const SiteVisitsPage: React.FC = () => { // Filtered & Sorted Visits const filteredVisits = useMemo(() => { return siteVisits.filter((v) => { - return ( + const matchSearch = !searchTerm.trim() || (v.inspectorName && v.inspectorName.toLowerCase().includes(searchTerm.toLowerCase())) || - (v.findings && v.findings.toLowerCase().includes(searchTerm.toLowerCase())) - ); + (v.findings && v.findings.toLowerCase().includes(searchTerm.toLowerCase())); + const matchStatus = + statusFilter === 'all' || + (statusFilter === 'approved' && v.status === ApprovalStatus.Approved) || + (statusFilter === 'pending' && v.status !== ApprovalStatus.Approved); + return matchSearch && matchStatus; }); - }, [siteVisits, searchTerm]); + }, [siteVisits, searchTerm, statusFilter]); const { sortedData: sortedVisits, sortColumn: visitSortCol, sortDirection: visitSortDir, handleSort: handleVisitSort } = useTableSort(filteredVisits); @@ -149,9 +155,9 @@ export const SiteVisitsPage: React.FC = () => { // Exports const visitExportCols: ExportColumn[] = [ { header: 'Inspector Name', key: 'inspectorName' }, - { header: 'Visit Date', accessor: (v) => formatDate(v.visitedAt) }, + { header: 'Visit Date', key: 'visitedAt', format: (val) => formatDate(val) }, { header: 'Findings', key: 'findings' }, - { header: 'Status', accessor: (v) => (v.status === ApprovalStatus.Approved ? 'Approved' : 'Pending') }, + { header: 'Status', key: 'status', format: (val) => (val === ApprovalStatus.Approved ? 'Approved' : 'Pending') }, ]; const handleOpenEdit = (v: SiteVisit) => { @@ -185,36 +191,6 @@ export const SiteVisitsPage: React.FC = () => {
- -
- - - - - -
{/* KPI Cards */} @@ -260,118 +236,368 @@ export const SiteVisitsPage: React.FC = () => {
- {/* Toolbar */} -
-
-
- - Project: - -
+ {/* Tabs */} +
+
+ -
- - setSearchTerm(e.target.value)} - className="bg-slate-900 border border-slate-800 rounded-xl pl-8 pr-3 py-1.5 text-xs text-white focus:outline-none focus:border-emerald-500 w-48 sm:w-64" - /> -
+
-
- {/* Table */} -
-
- - - - - Inspector Name - - - Inspection Date - - - Field Observations & Findings - - - Verification Status - - - - - - {loadingVisits ? ( - - - - ) : sortedVisits.length === 0 ? ( - - - - ) : ( - sortedVisits.map((v) => ( - - - - - - + {/* Tab 1: Visits Table View */} + {activeTab === 'visits' && ( +
+
+
+ {/* Search */} +
+ + setSearchTerm(e.target.value)} + className="w-full bg-slate-900 border border-slate-800 rounded-xl pl-8 pr-3 py-1.5 text-xs text-white focus:outline-none focus:border-emerald-500 shadow-xs" + /> +
+ + {/* Status Filter */} +
+ +
+ + {/* Project Selector */} +
+ + +
+
+ + {/* Actions: Refresh, Export, Log Site Visit */} +
+ + + + + +
+
+ +
+
+
Actions
-
-
- Loading site visit logs... -
-
- No site visits recorded for this project yet. -
-
- - {v.inspectorName || 'Field Officer'} -
-
-
- - {formatDate(v.visitedAt)} -
-
- {v.findings || 'No findings recorded.'} - {getApprovalStatusBadge(v.status)} - - -
+ + + + Inspector Name + + + Inspection Date + + + Field Observations & Findings + + + Verification Status + + - )) - )} - -
Actions
-
+ + + {loadingVisits ? ( + + +
+
+ Loading site visit logs... +
+ + + ) : sortedVisits.length === 0 ? ( + + + No site visits recorded for this project yet. + + + ) : ( + sortedVisits.map((v) => ( + + +
+ + {v.inspectorName || 'Field Officer'} +
+ + +
+ + {formatDate(v.visitedAt)} +
+ + + {v.findings || 'No findings recorded.'} + + {getApprovalStatusBadge(v.status)} + + + + + + )) + )} + + +
+
+
+ )} + + {/* Tab 2: Field Observations & Feedback Cards */} + {activeTab === 'feedback' && ( +
+
+
+ {/* Search */} +
+ + setSearchTerm(e.target.value)} + className="w-full bg-slate-900 border border-slate-800 rounded-xl pl-8 pr-3 py-1.5 text-xs text-white focus:outline-none focus:border-emerald-500 shadow-xs" + /> +
+ + {/* Status Filter */} +
+ +
+ + {/* Project Selector */} +
+ + +
+
+ + {/* Actions: Refresh, Export, Log Site Visit */} +
+ + + + + +
+
+ + {loadingVisits ? ( +
+
+
+ Loading observation cards... +
+
+ ) : sortedVisits.length === 0 ? ( +
+ No site visit observations or feedback found matching criteria. +
+ ) : ( +
+ {sortedVisits.map((v) => ( +
+
+
+
+
+ +
+
+

+ {v.inspectorName || 'Field Auditor'} +

+
+ + {formatDate(v.visitedAt)} +
+
+
+
{getApprovalStatusBadge(v.status)}
+
+ +
+

+ Field Observations & Findings +

+ {v.findings || 'No detailed findings recorded.'} +
+
+ +
+ ID #{v.id} +
+ + +
+
+
+ ))} +
+ )} +
+ )}
{/* Modal: Create/Edit Site Visit */}