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:
@@ -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()}“. {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
|
||||
|
||||
Reference in New Issue
Block a user