import { queryOptions } from "@tanstack/react-query"; import apiFetch from "../../utils/api"; import { jsonQuery } from "../apiAdapter"; export interface PlanUser { id: number; full_name: string; username: string; role_name: string | null; is_active: boolean; has_attendance_record: boolean; } export interface ResolvedCell { source: "entry" | "override"; entryId: number | null; overrideId: number | null; user_id: number; shift_date: string; project_id: number | null; /** Project label resolved server-side (see resolveGrid). Lets the UI show * the project without depending on the capped/permission-gated projects * list. Null when the cell has no project. */ project_number: string | null; project_name: string | null; category: string; note: string; rangeFrom: string | null; rangeTo: string | null; } export interface PlanCategory { id: number; key: string; label: string; color: string; sort_order: number; is_active: boolean; } export const planCategoriesOptions = () => queryOptions({ queryKey: ["plan", "categories"], queryFn: () => jsonQuery("/api/admin/plan/categories"), }); /** Build a key → category lookup from the categories list. */ export function categoryMap( categories: PlanCategory[] | undefined, ): Record { const map: Record = {}; for (const c of categories ?? []) map[c.key] = c; return map; } /** Czech label for a plan category key, resolved from the loaded categories * map, falling back to the raw key. The `map` arg is optional so callers not * yet threading categories still compile (they show the raw key until wired). */ export function planCategoryLabel( key: string, map?: Record, ): string { return map?.[key]?.label ?? key; } /** Build the display label for a cell's project, preferring the server- * embedded fields. Returns null when the cell has no project. */ export function cellProjectLabel(cell: { project_number: string | null; project_name: string | null; }): string | null { if (!cell.project_number && !cell.project_name) return null; if (cell.project_number && cell.project_name) { return `${cell.project_number} — ${cell.project_name}`; } return cell.project_number ?? cell.project_name; } export interface GridData { view: "week" | "month"; date_from: string; date_to: string; users: PlanUser[]; cells: Record>; } export const planKeys = { all: ["plan"] as const, grid: (dateFrom: string, dateTo: string, view: "week" | "month") => ["plan", "grid", { dateFrom, dateTo, view }] as const, entries: (userId: number | null, dateFrom: string, dateTo: string) => ["plan", "entries", { userId, dateFrom, dateTo }] as const, overrides: (userId: number | null, dateFrom: string, dateTo: string) => ["plan", "overrides", { userId, dateFrom, dateTo }] as const, users: () => ["plan", "users"] as const, }; export const gridQuery = ( dateFrom: string, dateTo: string, view: "week" | "month", ) => queryOptions({ queryKey: planKeys.grid(dateFrom, dateTo, view), queryFn: () => jsonQuery( `/api/admin/plan/grid?date_from=${dateFrom}&date_to=${dateTo}&view=${view}`, ), }); export const usersQuery = () => queryOptions({ queryKey: planKeys.users(), queryFn: () => jsonQuery("/api/admin/plan/users"), });