diff --git a/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx b/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx index ac1b976..54fd012 100644 --- a/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx +++ b/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx @@ -23,6 +23,7 @@ import { import { useAuth } from '../../auth/AuthProvider'; import { BrandLogo } from '../ui/BrandLogo'; import { HelpTooltip } from '../ui/HelpTooltip'; +import { ErrorBoundary } from '../ui/ErrorBoundary'; import { useQuery } from '@tanstack/react-query'; import { getUsers } from '../../services/api'; import { getUserEmail, registerUserEmail } from '../../lib/formatters'; @@ -395,7 +396,9 @@ export const AppLayout: React.FC = () => {
- + + +
diff --git a/PostFundManagement.ConsoleUI/src/components/ui/ErrorBoundary.tsx b/PostFundManagement.ConsoleUI/src/components/ui/ErrorBoundary.tsx new file mode 100644 index 0000000..03706f6 --- /dev/null +++ b/PostFundManagement.ConsoleUI/src/components/ui/ErrorBoundary.tsx @@ -0,0 +1,76 @@ +import React, { Component, ErrorInfo, ReactNode } from 'react'; +import { AlertTriangle, RefreshCw, Home } from 'lucide-react'; + +interface Props { + children: ReactNode; + fallbackTitle?: string; +} + +interface State { + hasError: boolean; + error: Error | null; +} + +export class ErrorBoundary extends React.Component { + declare props: Props; + declare state: State; + + constructor(props: Props) { + super(props); + this.state = { + hasError: false, + error: null, + }; + } + + public static getDerivedStateFromError(error: Error): State { + return { hasError: true, error }; + } + + public componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error('[PFM Error Boundary Caught]', error, errorInfo); + } + + public handleReset = () => { + (this as any).setState({ hasError: false, error: null }); + }; + + public render() { + if (this.state.hasError) { + return ( +
+
+
+ +
+

+ {this.props.fallbackTitle || 'Display Module Encountered an Issue'} +

+

+ An unexpected display error occurred while rendering this view. You can reload the view or return to the executive dashboard. +

+
+ + + + Dashboard + +
+
+
+ ); + } + + return this.props.children; + } +} diff --git a/PostFundManagement.ConsoleUI/src/components/ui/StatusBadge.tsx b/PostFundManagement.ConsoleUI/src/components/ui/StatusBadge.tsx new file mode 100644 index 0000000..ea872a3 --- /dev/null +++ b/PostFundManagement.ConsoleUI/src/components/ui/StatusBadge.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { ApprovalStatus, Priority, ActivityStatus } from '../../types'; +import { getApprovalStatusBadge, getPriorityBadge, getActivityStatusBadge } from '../../lib/formatters'; + +export const ApprovalStatusBadge: React.FC<{ status: ApprovalStatus | number | null | undefined }> = ({ status }) => { + const badge = getApprovalStatusBadge(status ?? ApprovalStatus.Pending); + return ( + + {badge.dot && } + {badge.label} + + ); +}; + +export const PriorityBadge: React.FC<{ priority: Priority | number | null | undefined }> = ({ priority }) => { + const badge = getPriorityBadge(priority ?? Priority.Medium); + return ( + + {badge.label} + + ); +}; + +export const ActivityStatusBadge: React.FC<{ status: ActivityStatus | number | string | boolean | null | undefined }> = ({ status }) => { + const badge = getActivityStatusBadge(status); + return ( + + {badge.label} + + ); +}; diff --git a/PostFundManagement.ConsoleUI/src/pages/BeneficiariesPage.tsx b/PostFundManagement.ConsoleUI/src/pages/BeneficiariesPage.tsx index 29d877a..eb3abea 100644 --- a/PostFundManagement.ConsoleUI/src/pages/BeneficiariesPage.tsx +++ b/PostFundManagement.ConsoleUI/src/pages/BeneficiariesPage.tsx @@ -38,6 +38,7 @@ import { HelpTooltip } from '../components/ui/HelpTooltip'; import { ExportButton } from '../components/ui/ExportButton'; import { ExportColumn } from '../utils/exportUtils'; import { useTableSort, SortableTh } from '../utils/tableSort'; +import { ApprovalStatusBadge } from '../components/ui/StatusBadge'; import { Beneficiary, ComplianceItem, Outcome, ApprovalStatus } from '../types'; import { getApprovalStatusBadge, formatDate, APPROVAL_STATUS_OPTIONS } from '../lib/formatters'; @@ -727,7 +728,9 @@ export const BeneficiariesPage: React.FC = () => { {c.requirements || 'None'} - {getApprovalStatusBadge(c.status)} + + + {formatDate(c.createdAt)}
-
{getApprovalStatusBadge(ci.status)}
+
+ +
))} diff --git a/PostFundManagement.ConsoleUI/src/pages/ChangeManagementPage.tsx b/PostFundManagement.ConsoleUI/src/pages/ChangeManagementPage.tsx index 505f8a8..51614ab 100644 --- a/PostFundManagement.ConsoleUI/src/pages/ChangeManagementPage.tsx +++ b/PostFundManagement.ConsoleUI/src/pages/ChangeManagementPage.tsx @@ -36,6 +36,7 @@ import { useToast } from '../components/ui/Toast'; import { ExportButton } from '../components/ui/ExportButton'; import { ExportColumn } from '../utils/exportUtils'; import { useTableSort, SortableTh } from '../utils/tableSort'; +import { ApprovalStatusBadge, PriorityBadge } from '../components/ui/StatusBadge'; import { Issue, ChangeRequest, ApprovalStatus, ActivityStatus, Priority } from '../types'; import { getApprovalStatusBadge, @@ -484,7 +485,7 @@ export const ChangeManagementPage: React.FC = () => {
setSelectedProjectId(e.target.value)} className="bg-slate-900 border border-slate-800 rounded-xl px-3 py-1.5 text-xs text-white focus:outline-none focus:border-amber-500 max-w-[180px] truncate" > @@ -784,7 +787,9 @@ export const ChangeManagementPage: React.FC = () => { {formatZAR(cr.revisedBudget || 0)} - {getApprovalStatusBadge(cr.status)} + + + {cr.status === ApprovalStatus.Approved ? ( diff --git a/PostFundManagement.ConsoleUI/src/pages/SiteVisitsPage.tsx b/PostFundManagement.ConsoleUI/src/pages/SiteVisitsPage.tsx index 239cab3..88786dd 100644 --- a/PostFundManagement.ConsoleUI/src/pages/SiteVisitsPage.tsx +++ b/PostFundManagement.ConsoleUI/src/pages/SiteVisitsPage.tsx @@ -31,6 +31,7 @@ import { useToast } from '../components/ui/Toast'; import { ExportButton } from '../components/ui/ExportButton'; import { ExportColumn } from '../utils/exportUtils'; import { useTableSort, SortableTh } from '../utils/tableSort'; +import { ApprovalStatusBadge } from '../components/ui/StatusBadge'; import { SiteVisit, ApprovalStatus } from '../types'; import { getApprovalStatusBadge, @@ -407,7 +408,9 @@ export const SiteVisitsPage: React.FC = () => { {v.findings || 'No findings recorded.'} - {getApprovalStatusBadge(v.status)} + + +
-
{getApprovalStatusBadge(v.status)}
+
+ +
@@ -690,7 +695,9 @@ export const SiteVisitsPage: React.FC = () => {
Status -
{getApprovalStatusBadge(selectedVisitForDetail.status)}
+
+ +
Inspector