Workshop-Anforderungen, erster Teil: was ohne Migration geht

Anforderung 1 — Freiwilliger vs unfreiwilliger Austritt. Die Liste der
Beendigungsarten zieht aus TerminatePanel.tsx nach lib/beendigung.ts um: der
Berichtemanager braucht sie ebenso, und zwei Listen liefen auseinander. Zwei
neue Arten (Beendigung in der Probezeit, je Seite). Auf wessen Betreiben
beendet wurde, wird aus der Art **abgeleitet** und nicht daneben gespeichert
— als zweites freies Feld liesse sich "Entlassung, freiwillig" erfassen. Das
Dropdown im Formular schraenkt die Auswahl darunter ein.

Drei Gruppen statt zwei: Befristungsablauf geschieht auf niemandes
Betreiben, ein Nichtantritt ist kein Austritt. Beide einer Seite
zuzuschlagen wuerde jede Fluktuationsquote verfaelschen.

Anforderung 2 — Namensfilter in "Anstehend", ab neun Eintraegen.

Anforderung 3 — die zwei Unterschriftenfelder im gedruckten Blatt sind weg;
"Firmenfahrzeug" steht in beiden Checklisten. has_dienstwagen sagt, ob eines
zusteht, nicht ob es uebergeben wurde.

Anforderung 4 — "+794 weitere" ist ein Knopf geworden; die Namen waren
vorher nur ueber den Export erreichbar. Stammdatenaenderung und
Gehaltsanpassung stehen nicht mehr zur Auswahl: die eine entsteht bei jeder
geaenderten Telefonnummer, die andere ist ein totes Ereignis, seit das
Gehalt in Loga liegt. Neu ist der Untertyp — Beendigungsart beim Austritt,
Art der Abwesenheit bei der Langzeitabwesenheit, im Bericht und im Export.

Anforderung 10 — zwei Kacheln. "Aktives Dienstverhaeltnis" ist nicht
dasselbe wie "Aktive Mitarbeiter:innen": dort steht, wer heute arbeitet,
hier, mit wem ein Vertrag laeuft. Sichtbar waren 806 und 10, addieren musste
man selbst.
This commit is contained in:
2026-09-15 22:35:47 +02:00
parent 1cbed1a8f5
commit 8d0c9b4b65
15 changed files with 426 additions and 48 deletions

View File

@@ -47,11 +47,40 @@ export function AnstehendListe({
leerText: string;
}) {
const [alle, setAlle] = useState(false);
const sichtbar = alle ? eintraege : eintraege.slice(0, VORSCHAU);
const weitere = eintraege.length - sichtbar.length;
// Namensfilter aus dem Workshop (Anforderung 2). Im Browser und nicht über
// die Adresse, aus demselben Grund wie das Aufklappen: die Einträge sind
// vollständig geladen, es gibt nichts nachzuholen.
const [suche, setSuche] = useState("");
const begriff = suche.trim().toLowerCase();
// Wortweise und am Wortanfang — dieselbe Regel wie in der Mitarbeiterliste,
// damit „gru" Gruber findet und nicht auch jede Notiz mit „Arbeitsgruppe".
// Der Hinweis wird bewusst nicht durchsucht: gesucht wird nach Personen.
const gefiltert = begriff
? eintraege.filter((e) =>
e.label
.toLowerCase()
.split(/[\s,;/-]+/)
.some((wort) => wort.startsWith(begriff))
)
: eintraege;
const sichtbar = alle ? gefiltert : gefiltert.slice(0, VORSCHAU);
const weitere = gefiltert.length - sichtbar.length;
return (
<>
{/* Erst ab neun Einträgen: darunter sieht man die ganze Liste ohnehin,
und ein Suchfeld über acht Zeilen ist mehr Bedienelement als Hilfe. */}
{eintraege.length > VORSCHAU && (
<input
type="search"
value={suche}
onChange={(e) => setSuche(e.target.value)}
placeholder="Name filtern…"
aria-label="Anstehende Ereignisse nach Name filtern"
className="mb-2 w-full rounded border border-border-strong bg-white px-2.5 py-1.5 text-sm text-ink
placeholder:text-ink-muted focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-brand-500"
/>
)}
<ul className="flex flex-col divide-y divide-border-subtle">
{sichtbar.map((item) => {
// Überfällig gibt es nur bei Wiedervorlagen: die anderen Arten
@@ -83,6 +112,14 @@ export function AnstehendListe({
);
})}
{eintraege.length === 0 && <p className="py-2 text-sm text-ink-muted">{leerText}</p>}
{/* Ein leeres Ergebnis mit Suchbegriff ist etwas anderes als eine
leere Karte: der Zeitraum stimmt, nur der Name passt nicht. */}
{eintraege.length > 0 && gefiltert.length === 0 && (
<p className="py-2 text-sm text-ink-muted">
Kein Name passt zu „{suche.trim()}&ldquo;. {eintraege.length}{" "}
{eintraege.length === 1 ? "Ereignis steht" : "Ereignisse stehen"} an.
</p>
)}
</ul>
{/* Der Knopf steht nur da, wenn es etwas zu holen gibt. Ist die Liste