Sort from the column headers, all seven of them
The dropdown is gone; each column header is now a link that sorts by that column, with an arrow showing the direction. Clicking the column already sorted reverses it; clicking a different one starts ascending again — going from "Eintritt, newest first" to "Name" should give you names from A, not inherit the previous direction. Names sort by surname and then forename, as asked. Both parts reverse together: turning only the surname would give Z-A across surnames but A-Z within each one, which is visible immediately among the fifteen Aigner. Three of the seven columns are not on the employee row. Bereich and Team hang off the position, Standort off a lookup table, so they are fetched as correlated subqueries rather than joins. That is not a style preference: the same filter chain produces the page *and* the count, and a join onto position_assignments would double every person who has held more than one position over time — the line above the list would read 1,203 for 867 people. Bereich is the level below the company, so it needs to walk up from the unit. No recursion: org_unit_type has exactly four levels, so two hops up cover it. Everything sorts `nulls last`, otherwise reversing the direction floats every person without a position or location to the top. The expressions live in lib/employee-sort.ts rather than in the page so the generated SQL can be read in a test — the failure mode here is silent, the list still shows fifteen rows, just the wrong ones. Eighteen tests, and the rules are mutation-checked: dropping the forename, dropping the id tiebreaker, dropping `nulls last`, sorting the location by its uuid, and shortening the Bereich walk each turn them red. Not seen in a browser: login goes through the company account and the database is unreachable. Typecheck, lint, 458 tests and the build are clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -4,7 +4,6 @@ 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. */
|
||||
@@ -105,23 +104,9 @@ export function EmployeeFilters({ units, depthOf, locations }: EmployeeFiltersPr
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{/* 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. */}
|
||||
<select
|
||||
aria-label="Liste sortieren"
|
||||
defaultValue={searchParams.get("sort") ?? STANDARD_SORTIERUNG}
|
||||
onChange={(e) => updateParam("sort", e.target.value)}
|
||||
className={FILTER_SELECT_CLASS}
|
||||
>
|
||||
{SORTIERUNGEN.map((s) => (
|
||||
<option key={s.value} value={s.value}>
|
||||
{s.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
{/* Sortiert wird nicht hier, sondern an den Spaltenköpfen der Liste.
|
||||
Diese Leiste schränkt ein, *was* zu sehen ist; die Reihenfolge
|
||||
gehört an die Spalte, die sie bestimmt. */}
|
||||
{/* 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 —
|
||||
|
||||
Reference in New Issue
Block a user