"use client"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; import { FILTER_SELECT_CLASS } from "@/components/ui/Field"; import { SearchInput } from "@/components/ui/SearchInput"; import { SORTIERUNGEN, STANDARD_SORTIERUNG } from "@/lib/employee-sort"; type EmployeeFiltersProps = { /** Der ganze Baum, in Tiefensuche-Reihenfolge. */ units: { id: string; name: string; unit_type: string }[]; depthOf: Map; locations: { id: string; name: string }[]; }; // Aktiv and Langzeitabwesenheit are separate statuses — somebody on a // long-term absence is employed but not active. The combined entry is // offered explicitly, named after the two it selects rather than calling // the pair "aktiv". The stored value is still 'Karenz' (see lib/absence.ts). const STATUS_OPTIONS = [ { value: "Aktiv", label: "Aktiv" }, { value: "Karenz", label: "Langzeitabwesenheit" }, { value: "Aktiv,Karenz", label: "Aktiv + Langzeitabwesenheit (beschäftigt)" }, { value: "Geplant", label: "Geplant" }, { value: "Ausgetreten", label: "Ausgetreten" }, ] as const; export function EmployeeFilters({ units, depthOf, locations }: EmployeeFiltersProps) { const router = useRouter(); const pathname = usePathname(); const searchParams = useSearchParams(); const [q, setQ] = useState(searchParams.get("q") ?? ""); useEffect(() => { const handle = setTimeout(() => { const current = new URLSearchParams(searchParams.toString()); if (q) current.set("q", q); else current.delete("q"); current.delete("page"); const next = current.toString(); if (next !== searchParams.toString()) router.push(`${pathname}?${next}`); }, 300); return () => clearTimeout(handle); // eslint-disable-next-line react-hooks/exhaustive-deps }, [q]); function updateParam(key: string, value: string) { const params = new URLSearchParams(searchParams.toString()); if (value) params.set(key, value); else params.delete(key); params.delete("page"); router.push(`${pathname}?${params.toString()}`); } return (
{/* aria-label rather than a visible label: the filter bar is a single horizontal row, and each select's first option already names it on screen. */} {/* Der ganze Baum, nicht nur die oberste Ebene: die Auswahl greift jeweils auf die Einheit *und alles darunter*, weshalb sich damit auch nach einer einzelnen Abteilung oder einem Team filtern lässt. Eingerückt statt gruppiert, weil optgroup keine Verschachtelung kennt und die Tiefe hier beliebig ist. */} {/* Die Sortierung steht bewusst neben den Filtern und nicht in der Tabellenüberschrift: sortiert wird über den ganzen Bestand, nicht über die fünfzehn Zeilen dieser Seite. Ein anklickbarer Spaltenkopf verspräche das Gegenteil. Anders als die Filter hat sie keinen leeren Eintrag — irgendeine Reihenfolge hat die Liste immer. */} {/* Der Dienstwagen stand hier einmal als eigenes Auswahlfeld. Er ist jetzt eines von rund zwanzig Kriterien unter Berichte, zusammen mit Vertragsart, Kollektivvertrag, Eintrittszeitraum und dem Rest — dort lässt sich die Auswahl auch exportieren, was der eigentliche Zweck der Frage war. In dieser Leiste, die vor allem zum Suchen da ist, wäre er ein Sonderfall unter vielen gleichrangigen. */}
); }