diff --git a/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx b/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx index d2dc1a2..f0f7121 100644 --- a/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx +++ b/PostFundManagement.ConsoleUI/src/components/layout/AppLayout.tsx @@ -186,7 +186,6 @@ export const AppLayout: React.FC = () => { useQuery({ queryKey: ['users-directory'], queryFn: () => getUsers(1, 200), - staleTime: 5 * 60 * 1000, }); // Query tenant endpoint to resolve tenant name if not provided in claims or profile @@ -194,7 +193,6 @@ export const AppLayout: React.FC = () => { queryKey: ['active-tenant-header', tenantId], queryFn: () => getTenant(tenantId!), enabled: !!tenantId && !claims?.tenant && !user?.tenant && !authTenantName, - staleTime: 5 * 60 * 1000, }); // Ensure current logged-in user is registered in the email cache diff --git a/PostFundManagement.ConsoleUI/src/components/monitoring/ContractsManagement.tsx b/PostFundManagement.ConsoleUI/src/components/monitoring/ContractsManagement.tsx index cc0b558..b460cea 100644 --- a/PostFundManagement.ConsoleUI/src/components/monitoring/ContractsManagement.tsx +++ b/PostFundManagement.ConsoleUI/src/components/monitoring/ContractsManagement.tsx @@ -1247,7 +1247,7 @@ export const ContractsManagement: React.FC = ({ {Number(contractToUpdate?.status) === ContractStatus.Active ? (
- Active / Executed (Immutable - Cannot Be Changed) + Active / Executed
) : ( (() => { diff --git a/PostFundManagement.ConsoleUI/src/components/monitoring/EvidenceReviewQueue.tsx b/PostFundManagement.ConsoleUI/src/components/monitoring/EvidenceReviewQueue.tsx index 5643bd9..b5dfcd5 100644 --- a/PostFundManagement.ConsoleUI/src/components/monitoring/EvidenceReviewQueue.tsx +++ b/PostFundManagement.ConsoleUI/src/components/monitoring/EvidenceReviewQueue.tsx @@ -807,7 +807,7 @@ export const EvidenceReviewQueue: React.FC = ({ title="Approved evidence is immutable. Status cannot be modified." > - Approved (Immutable) + Approved ) : ( <> diff --git a/PostFundManagement.ConsoleUI/src/components/notifications/UserSearchPicker.tsx b/PostFundManagement.ConsoleUI/src/components/notifications/UserSearchPicker.tsx index 8048e40..77e98fa 100644 --- a/PostFundManagement.ConsoleUI/src/components/notifications/UserSearchPicker.tsx +++ b/PostFundManagement.ConsoleUI/src/components/notifications/UserSearchPicker.tsx @@ -58,7 +58,6 @@ export const UserSearchPicker: React.FC = ({ const { data: users = [], isLoading } = useQuery({ queryKey: ['users-directory'], queryFn: () => getUsers(1, 200), - staleTime: 5 * 60 * 1000, }); // Filter active users and exclude the current logged-in user so they can never message themselves diff --git a/PostFundManagement.ConsoleUI/src/hooks/useInAppNotifications.ts b/PostFundManagement.ConsoleUI/src/hooks/useInAppNotifications.ts index 150099e..79c457a 100644 --- a/PostFundManagement.ConsoleUI/src/hooks/useInAppNotifications.ts +++ b/PostFundManagement.ConsoleUI/src/hooks/useInAppNotifications.ts @@ -124,7 +124,6 @@ export function useInAppNotifications() { }, // Live polling every 30 seconds refetchInterval: 30000, - staleTime: 10000, }); // InApp notifications (strictly platform === InApp 5) for Bell & Counters diff --git a/PostFundManagement.ConsoleUI/src/lib/queryClient.ts b/PostFundManagement.ConsoleUI/src/lib/queryClient.ts index c769474..ff34250 100644 --- a/PostFundManagement.ConsoleUI/src/lib/queryClient.ts +++ b/PostFundManagement.ConsoleUI/src/lib/queryClient.ts @@ -4,15 +4,38 @@ import { bumpMutationEpoch } from '../services/httpClient'; export const queryClient = new QueryClient({ defaultOptions: { queries: { - staleTime: 1000 * 60 * 2, // 2 minutes fresh in memory - gcTime: 1000 * 60 * 10, // 10 minutes cache retention - refetchOnWindowFocus: false, - refetchOnReconnect: true, + // Remove caching from all pages: zero stale time and zero cache retention + // Whenever a component needs data, it must call its corresponding endpoint(s) + staleTime: 0, + gcTime: 0, + refetchOnMount: 'always', + refetchOnWindowFocus: 'always', + refetchOnReconnect: 'always', retry: 1, }, }, }); +// Retain caching ONLY for sync status, auth token, roles and grants +const RETAINED_CACHE_OPTIONS = { + staleTime: 5 * 60 * 1000, + gcTime: 10 * 60 * 1000, + refetchOnMount: false, + refetchOnWindowFocus: false, +}; + +queryClient.setQueryDefaults(['roles'], RETAINED_CACHE_OPTIONS); +queryClient.setQueryDefaults(['system-roles'], RETAINED_CACHE_OPTIONS); +queryClient.setQueryDefaults(['role-claims'], RETAINED_CACHE_OPTIONS); +queryClient.setQueryDefaults(['user-roles'], RETAINED_CACHE_OPTIONS); +queryClient.setQueryDefaults(['user-claims'], RETAINED_CACHE_OPTIONS); +queryClient.setQueryDefaults(['grants'], RETAINED_CACHE_OPTIONS); +queryClient.setQueryDefaults(['user-grants'], RETAINED_CACHE_OPTIONS); +queryClient.setQueryDefaults(['user-sync'], RETAINED_CACHE_OPTIONS); +queryClient.setQueryDefaults(['sync-status'], RETAINED_CACHE_OPTIONS); +queryClient.setQueryDefaults(['auth'], RETAINED_CACHE_OPTIONS); +queryClient.setQueryDefaults(['auth-token'], RETAINED_CACHE_OPTIONS); + /** * Invalidates queries while simultaneously bumping the mutation epoch. * This guarantees that the background refetch generates a fresh query parameter (_v) diff --git a/PostFundManagement.ConsoleUI/src/pages/ChangeManagementPage.tsx b/PostFundManagement.ConsoleUI/src/pages/ChangeManagementPage.tsx index 88389a2..74af4df 100644 --- a/PostFundManagement.ConsoleUI/src/pages/ChangeManagementPage.tsx +++ b/PostFundManagement.ConsoleUI/src/pages/ChangeManagementPage.tsx @@ -1020,7 +1020,7 @@ export const ChangeManagementPage: React.FC = () => { {Number(cr.status) === ApprovalStatus.Approved ? ( - Resolved & Enacted (Immutable) + Resolved & Enacted ) : ( <> @@ -1528,7 +1528,7 @@ export const ChangeManagementPage: React.FC = () => { title="Approved and enacted change requests are immutable." > - Approved & Enacted (Immutable - Cannot Be Changed) + Approved & Enacted ) : ( { Programme Title {programmeFreshDetail?.name || selectedProgrammeForDetail?.name} - - {getApprovalStatusBadge(programmeFreshDetail?.status ?? selectedProgrammeForDetail?.status ?? ApprovalStatus.Approved).label} - +
+ + {getApprovalStatusBadge(programmeFreshDetail?.status ?? selectedProgrammeForDetail?.status ?? ApprovalStatus.Approved).label} + + {selectedProgrammeForDetail && Number(programmeFreshDetail?.status ?? selectedProgrammeForDetail.status) !== ApprovalStatus.Approved && ( + canApproveRecord('governance', selectedProgrammeForDetail.createdBy) ? ( + + ) : ( + isMaker(selectedProgrammeForDetail.createdBy) && canApprove('governance') && ( + + Maker-Checker: Record creator cannot approve + + ) + ) + )} +
- {/* Core Details Grid: Owner & Creation Date */} -
+ {/* Core Details Grid: Owner, Maker, Creation Date */} +
{/* Creation Date */}
@@ -2745,6 +2842,17 @@ export const GovernancePage: React.FC = () => {
+ + {/* Maker */} +
+ +
+ Created By (Maker) + + {getUserEmail(programmeFreshDetail?.createdBy || selectedProgrammeForDetail?.createdBy)} + +
+
{/* Metrics: Total Rules and Total Instructions */} diff --git a/PostFundManagement.ConsoleUI/src/pages/GrantsPage.tsx b/PostFundManagement.ConsoleUI/src/pages/GrantsPage.tsx index 9f5e5dd..cdeb34f 100644 --- a/PostFundManagement.ConsoleUI/src/pages/GrantsPage.tsx +++ b/PostFundManagement.ConsoleUI/src/pages/GrantsPage.tsx @@ -47,6 +47,7 @@ import { ExportButton } from '../components/ui/ExportButton'; import { ExportColumn } from '../utils/exportUtils'; import { useTableSort, SortableTh } from '../utils/tableSort'; import { useRbac } from '../auth/rbac'; +import { bumpMutationEpoch } from '../services/httpClient'; import { Award, Budget, @@ -233,6 +234,7 @@ export const GrantsPage: React.FC = () => { const updateAwardMutation = useMutation({ mutationFn: (data: UpdateAwardRequest) => updateAward(data), onSuccess: () => { + bumpMutationEpoch(); queryClient.invalidateQueries({ queryKey: ['awards'] }); queryClient.invalidateQueries({ queryKey: ['dashboard'] }); success('Award Updated', 'Award details saved successfully.'); @@ -248,6 +250,7 @@ export const GrantsPage: React.FC = () => { const budgetMutation = useMutation({ mutationFn: (data: CreateBudgetRequest) => createBudget(data), onSuccess: () => { + bumpMutationEpoch(); queryClient.invalidateQueries({ queryKey: ['budgets'] }); success('Budget line established', 'Budget line created against project'); setIsBudgetModalOpen(false); @@ -261,6 +264,7 @@ export const GrantsPage: React.FC = () => { const updateBudgetMutation = useMutation({ mutationFn: (data: UpdateBudgetRequest) => updateBudget(data), onSuccess: () => { + bumpMutationEpoch(); queryClient.invalidateQueries({ queryKey: ['budgets'] }); success('Budget Updated', 'Budget compliance and approval status updated.'); setIsEditBudgetModalOpen(false); @@ -287,6 +291,7 @@ export const GrantsPage: React.FC = () => { }); }, onSuccess: () => { + bumpMutationEpoch(); queryClient.invalidateQueries({ queryKey: ['disbursements'] }); success( editingDisbursement ? 'Disbursement Updated' : 'Disbursement Scheduled', @@ -497,6 +502,7 @@ export const GrantsPage: React.FC = () => { const approveMutation = useMutation({ mutationFn: (id: string | number) => approveDisbursement(id), onSuccess: () => { + bumpMutationEpoch(); queryClient.invalidateQueries({ queryKey: ['disbursements'] }); queryClient.invalidateQueries({ queryKey: ['dashboard'] }); success( @@ -826,8 +832,10 @@ export const GrantsPage: React.FC = () => {
) : (