import { useQuery } from "@tanstack/react-query"; import { fetchShift, type ShiftStatus, type TillId } from "../api.js"; import { qk } from "./query.js"; // Shared shift status for the whole app — the header control, the booth screen's // per-shift log scope, and the pay/exit modal's gate all read this one Query so // they never disagree about whether a shift is open and whose it is. A shift is a // per-TILL single-open accountability period (at most one open per till). The // default till is the booth; the wash desk reads its own (`useShift("carwash")`). // The WS invalidates qk.shift (a prefix, so every till) on shift_open/shift_z_report/ // cash movements, so this stays live without polling. See wiki/concepts/shift.md. export interface ShiftState { /** Raw status from the server (null while loading / on error). */ status: ShiftStatus | undefined; /** Is a shift open on this till? */ isOpen: boolean; /** Is the open shift the logged-in operator's (so they may close it / operate)? */ isMine: boolean; /** A shift is open but belongs to someone else — this operator is blocked. */ blockedByOther: boolean; /** ISO start of the open shift, for scoping the per-shift log. */ startedAt: string | null; /** Whoever holds the open shift (for "held by X" messaging). */ heldBy: string | null; isLoading: boolean; } /** Query key of one till's shift status — under the qk.shift prefix so the WS * invalidation reaches every till. */ export const shiftKey = (till: TillId) => [...qk.shift, "current", till] as const; export function useShift(till: TillId = "booth"): ShiftState { const q = useQuery({ queryKey: shiftKey(till), queryFn: () => fetchShift(till) }); const s = q.data; const isOpen = s?.open != null; const isMine = s?.isMine ?? false; return { status: s, isOpen, isMine, blockedByOther: isOpen && !isMine, startedAt: s?.open?.startedAt ?? null, heldBy: s?.open?.operator ?? null, isLoading: q.isLoading, }; }