import { keepPreviousData, queryOptions } from "@tanstack/react-query"; import { api } from "../api"; export const dashboardKeys = { all: (wsId: string) => ["dashboard", wsId] as const, daily: ( wsId: string, days: number, projectId: string | null, tz: string, ) => [...dashboardKeys.all(wsId), "daily", days, projectId, tz] as const, byAgent: ( wsId: string, days: number, projectId: string | null, tz: string, ) => [...dashboardKeys.all(wsId), "by-agent", days, projectId, tz] as const, agentRuntime: ( wsId: string, days: number, projectId: string | null, tz: string, ) => [...dashboardKeys.all(wsId), "agent-runtime", days, projectId, tz] as const, runTimeDaily: ( wsId: string, days: number, projectId: string | null, tz: string, ) => [...dashboardKeys.all(wsId), "runtime-daily", days, projectId, tz] as const, }; // 5-min rollup cadence on the server, 60s background refetch on the client. const STALE_TIME = 60 * 1000; // Range changes should keep the previous result mounted so KPI cards and // charts transition in place instead of falling back to a full-page skeleton. // Scope changes are deliberately excluded: carrying data across workspaces, // projects, report kinds, or timezones would briefly display the wrong data. function isSameDashboardScope( previousKey: readonly unknown[] | undefined, nextKey: readonly unknown[], ): boolean { if (!previousKey || previousKey.length !== nextKey.length) return false; return previousKey.every( (part, index) => index === 3 || Object.is(part, nextKey[index]), ); } // `tz` participates in every dashboard key so a Preferences change // repoints the cache. All four series — token rollups and the // atq.completed_at-based run-time series — slice their day boundary in // the viewer's tz, so the four dashboard tabs always agree. export function dashboardUsageDailyOptions( wsId: string, days: number, projectId: string | null, tz: string, ) { const queryKey = dashboardKeys.daily(wsId, days, projectId, tz); return queryOptions({ queryKey, queryFn: () => api.getDashboardUsageDaily({ days, project_id: projectId ?? undefined, tz, }), enabled: !!wsId, staleTime: STALE_TIME, placeholderData: (previousData, previousQuery) => isSameDashboardScope(previousQuery?.queryKey, queryKey) ? keepPreviousData(previousData) : undefined, }); } export function dashboardUsageByAgentOptions( wsId: string, days: number, projectId: string | null, tz: string, ) { const queryKey = dashboardKeys.byAgent(wsId, days, projectId, tz); return queryOptions({ queryKey, queryFn: () => api.getDashboardUsageByAgent({ days, project_id: projectId ?? undefined, tz, }), enabled: !!wsId, staleTime: STALE_TIME, placeholderData: (previousData, previousQuery) => isSameDashboardScope(previousQuery?.queryKey, queryKey) ? keepPreviousData(previousData) : undefined, }); } export function dashboardAgentRunTimeOptions( wsId: string, days: number, projectId: string | null, tz: string, ) { const queryKey = dashboardKeys.agentRuntime(wsId, days, projectId, tz); return queryOptions({ queryKey, queryFn: () => api.getDashboardAgentRunTime({ days, project_id: projectId ?? undefined, tz, }), enabled: !!wsId, staleTime: STALE_TIME, placeholderData: (previousData, previousQuery) => isSameDashboardScope(previousQuery?.queryKey, queryKey) ? keepPreviousData(previousData) : undefined, }); } export function dashboardRunTimeDailyOptions( wsId: string, days: number, projectId: string | null, tz: string, ) { const queryKey = dashboardKeys.runTimeDaily(wsId, days, projectId, tz); return queryOptions({ queryKey, queryFn: () => api.getDashboardRunTimeDaily({ days, project_id: projectId ?? undefined, tz, }), enabled: !!wsId, staleTime: STALE_TIME, placeholderData: (previousData, previousQuery) => isSameDashboardScope(previousQuery?.queryKey, queryKey) ? keepPreviousData(previousData) : undefined, }); }