Verified flow: clock in on dashboard -> delete the record in /attendance/admin -> navigate back: 'Zaznamenat odchod', the Pritomni KPI and the presence card still showed the pre-delete state until F5. Root cause: every attendance mutation outside the dashboard invalidated only ["attendance"], never ["dashboard"], so within the dashboard query's 60s staleTime the remount was served from cache. - useAttendanceAdmin create/bulk/edit/delete, /attendance punch + leave-request, AttendanceCreate, LeaveApproval approve/reject now also invalidate ["dashboard"] (CLAUDE.md rule: mutations invalidate every domain that embeds their data) - systemic backstop: dashboardOptions uses refetchOnMount "always" - the dashboard aggregates attendance/offers/invoices/orders/projects/leave, and domain pages can't all be expected to invalidate it; returning to the dashboard must never show pre-mutation data Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
44 lines
1.4 KiB
TypeScript
44 lines
1.4 KiB
TypeScript
import { queryOptions } from "@tanstack/react-query";
|
|
import { jsonQuery } from "../apiAdapter";
|
|
|
|
export const dashboardOptions = () =>
|
|
queryOptions({
|
|
queryKey: ["dashboard"],
|
|
queryFn: () => jsonQuery<Record<string, unknown>>("/api/admin/dashboard"),
|
|
staleTime: 60_000,
|
|
// The dashboard aggregates MANY domains (attendance, offers, invoices,
|
|
// orders, projects, leave). Mutations in those domains can't all be
|
|
// expected to invalidate ["dashboard"], so always refetch on mount —
|
|
// navigating back to the dashboard must never show pre-mutation data.
|
|
refetchOnMount: "always",
|
|
});
|
|
|
|
// require2FAOptions lives in ./settings.ts (the single definition consumers
|
|
// import). The duplicate copy that used to be here was dead.
|
|
|
|
export interface Session {
|
|
id: number | string;
|
|
is_current: boolean;
|
|
device_info?: {
|
|
icon?: string;
|
|
browser?: string;
|
|
os?: string;
|
|
};
|
|
ip_address: string;
|
|
created_at: string;
|
|
}
|
|
|
|
export const sessionsOptions = () =>
|
|
queryOptions({
|
|
queryKey: ["sessions"],
|
|
queryFn: async (): Promise<Session[]> => {
|
|
// Route through jsonQuery for the shared response.ok / 401 / non-JSON
|
|
// guards, then normalize the two possible payload shapes (a bare array
|
|
// or `{ sessions: [...] }`).
|
|
const data = await jsonQuery<Session[] | { sessions?: Session[] }>(
|
|
"/api/admin/sessions",
|
|
);
|
|
return Array.isArray(data) ? data : (data.sessions ?? []);
|
|
},
|
|
});
|