From 1de209be481d7936a4e4d71d982a2551e81d88d5 Mon Sep 17 00:00:00 2001 From: Julian Cuni Date: Sun, 5 Jul 2026 15:23:29 +0200 Subject: [PATCH] =?UTF-8?q?fix(shifts):=20operator=20filter=20=E2=80=94=20?= =?UTF-8?q?select=20over=20real=20operators,=20no=20more=20focus=20loss?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The admin operator filter was a free-text input that broke three ways at once: its visibility hangs off the query response (scope === "all") and its value is part of the query key, so every keystroke started a new query, data went undefined for the round-trip, and the input UNMOUNTED mid-keystroke (lost focus, list blanking that read as a page reload). Filtering also silently failed — the server matches the operator by exact username, so partial text matched nothing. - keepPreviousData on the shifts query: previous data (and scope) stays live during refetch, so filter controls never unmount and the list never blanks on preset/filter changes. - The filter is now a setOperator(e.target.value)} placeholder={t("shifts.allOperators")} /> + {/* A select over operators that HAVE shifts — the server filter is an + exact username match, so free text could only miss. */} + )} diff --git a/apps/web/src/api.ts b/apps/web/src/api.ts index 5e36d46..0037c1e 100644 --- a/apps/web/src/api.ts +++ b/apps/web/src/api.ts @@ -1151,6 +1151,8 @@ export interface ShiftSummary extends ShiftSourceSplit { export function fetchShifts(params: { operator?: string; from?: string; to?: string } = {}): Promise<{ shifts: ShiftSummary[]; scope: "all" | "self"; + /** Admin scope only: every operator that has a shift — feeds the filter dropdown. */ + operators?: string[]; }> { const qs = new URLSearchParams(); if (params.operator) qs.set("operator", params.operator);