Elf Punkte aus der Rueckmeldung, und die verlorene Anmerkung
All checks were successful
CI / Lint, Typen, Tests, Build (push) Successful in 11m23s
CI / Migrationen auf leerer Datenbank (push) Successful in 10m18s

Kleinigkeiten zuerst: "Gelaufen" heisst jetzt "Vergangen", die Kachel
"Langzeitabwesend" heisst "Langzeitabwesende", "Eintritte/Austritte
(Jahr)" heissen "(YTD)" -- gezaehlt wurde ohnehin seit Jahresbeginn.
"Personenkreis" heisst "Grund". Der Hinweis unter dem Grad der
Behinderung und der Erklaertext ueber dem Honestly-Report sind weg.
"Gehaltsanpassung" steht nicht mehr im Filter des Protokolls: das Gehalt
ist aus dem Funktionsumfang, keine Funktion schreibt die Art mehr, und
ein Filter, der immer leer ausgeht, sieht aus wie ein Fehler.

Im Fenster fuer den Notfallkontakt faellt "Wirksam ab" weg -- eine
Telefonnummer fuer den Ernstfall gilt ab sofort. In "Daten aendern"
wandert der Block unter die Angehoerigen, dieselbe Reihenfolge wie im
Reiter "Stammdaten". Auf der Uebersicht fuehren die Namen unter "Letzte
Aktivitaeten" in die Akte; vorher war die Karte eine Sackgasse.

"Eintrag berichtigen" bot fuer jedes Feld ein Textfeld an, auch fuer
"Notfallkontakt Verhaeltnis", wo die Erfassung sonst eine Liste fuehrt.
Wer dort "Gattin" statt "Gattin/Gatte" tippte, erzeugte einen Wert, den
keine Auswertung mehr findet -- die Berichtigung schreibt in dieselbe
Spalte wie das Formular, nur ohne dessen Pruefung. Welche Felder eine
Liste bekommen, steht in lib/historie-felder.ts; ein Test prueft jeden
Schluessel gegen app_feld_karte(), damit ein Tippfehler dort nicht still
auf ein Textfeld zurueckfaellt. Felder mit Aufzaehlungstyp bleiben
bewusst aussen vor: dort ist der gespeicherte Wert nicht die Anzeige.

Und die Antwort auf "wo sieht man die Anmerkung beim Austritt?": nirgends.
Das Formular sammelte sie ein, terminate_employee liess sie fallen. Bis
20260814100000 stand sie in der Beschreibung des Ereignisses; beim
Umschreiben fuer den Nichtantritt ging sie verloren, und die Migration
fuer die Austrittsart reichte die verkuerzte Fassung weiter. Sie steht
jetzt wieder in der Personalakte und im Protokoll, und die Selbstpruefung
faengt den naechsten Verlust ab.
This commit is contained in:
2026-09-23 16:01:05 +02:00
parent 5e35d6bf41
commit db02fb4762
12 changed files with 304 additions and 44 deletions

View File

@@ -9,6 +9,7 @@ import { TextField } from "@/components/ui/Field";
import { Modal } from "@/components/ui/Modal";
import { useToast } from "@/components/ui/Toast";
import { fmtDate } from "@/lib/format";
import { auswahlFuer } from "@/lib/historie-felder";
import type { AuditChange } from "@/lib/types";
// Berichtigen, nicht neu erfassen.
@@ -181,14 +182,37 @@ export function HistorieBearbeiten({
{c.vorher === null || c.vorher === "" ? <span className="italic">leer</span> : c.vorher}
</td>
<td className="py-2">
<input
type="text"
aria-label={`${c.feld} — neuer Wert`}
value={werte[c.feld] ?? ""}
onChange={(e) => setWerte((v) => ({ ...v, [c.feld]: e.target.value }))}
className="w-full rounded border border-border px-2 py-1 text-sm text-ink
focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-brand-500"
/>
{/* Führt das Feld eine feste Liste, wird sie auch hier
angeboten: getippt entstünde sonst ein Wert, den keine
Auswertung mehr findet — die Berichtigung schreibt in
dieselbe Spalte wie das Formular, nur ohne dessen
Prüfung. Welche Felder das sind, steht in
lib/historie-felder.ts. */}
{auswahlFuer(c.feld) ? (
<select
aria-label={`${c.feld} — neuer Wert`}
value={werte[c.feld] ?? ""}
onChange={(e) => setWerte((v) => ({ ...v, [c.feld]: e.target.value }))}
className="w-full rounded border border-border bg-white px-2 py-1 text-sm text-ink
focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-brand-500"
>
<option value="">leer</option>
{auswahlFuer(c.feld)!.map((w) => (
<option key={w} value={w}>
{w}
</option>
))}
</select>
) : (
<input
type="text"
aria-label={`${c.feld} — neuer Wert`}
value={werte[c.feld] ?? ""}
onChange={(e) => setWerte((v) => ({ ...v, [c.feld]: e.target.value }))}
className="w-full rounded border border-border px-2 py-1 text-sm text-ink
focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-brand-500"
/>
)}
</td>
</tr>
))}

View File

@@ -42,7 +42,6 @@ export function NotfallkontaktModal({
const [name, setName] = useState(kontakt.name ?? "");
const [telefon, setTelefon] = useState(kontakt.telefon ?? "");
const [verhaeltnis, setVerhaeltnis] = useState(kontakt.verhaeltnis ?? "");
const [effectiveDate, setEffectiveDate] = useState(todayIso());
const [pending, setPending] = useState(false);
const vorhanden = Boolean(kontakt.name);
@@ -58,7 +57,7 @@ export function NotfallkontaktModal({
setPending(true);
const result = await changeEmployeeData({
employee_id: employeeId,
effective_date: effectiveDate,
effective_date: todayIso(),
person: {
emergency_contact_name: name.trim(),
emergency_contact_phone: telefon.trim(),
@@ -93,8 +92,11 @@ export function NotfallkontaktModal({
</>
}
>
{/* Kein „Wirksam ab": ein Notfallkontakt gilt ab sofort. Das Feld stand
hier, weil die Datenbankfunktion einen Stichtag kennt — bei einer
Telefonnummer, die im Ernstfall gewählt wird, ist ein Datum in der
Zukunft keine sinnvolle Angabe, sondern eine Frage zu viel. */}
<div className="flex flex-col gap-4">
<TextField label="Wirksam ab" required type="date" value={effectiveDate} onChange={setEffectiveDate} />
<TextField label="Name" required value={name} onChange={setName} />
<TextField label="Telefonnummer" required type="tel" value={telefon} onChange={setTelefon} />
{/* Dieselbe Liste wie im Einstellungsassistenten (EMERGENCY_RELATIONS)

View File

@@ -2,7 +2,6 @@
import { SelectField, TextField } from "@/components/ui/Field";
import {
BEGUENSTIGUNG_AB_GRAD,
GRUND_BEGUENSTIGT_BEHINDERT,
KUENDIGUNGSSCHUTZ_GRUENDE,
} from "@/lib/kuendigungsschutz";
@@ -222,7 +221,7 @@ export function RoleEmploymentFields({ value, onChange }: { value: RoleEmploymen
{value.hasKuendigungsschutz && (
<>
<SelectField
label="Personenkreis"
label="Grund"
dense
value={value.kuendigungsschutzGrund}
onChange={(v) => onChange({ kuendigungsschutzGrund: v })}
@@ -278,7 +277,6 @@ export function RoleEmploymentFields({ value, onChange }: { value: RoleEmploymen
type="number"
value={value.behinderungGrad}
onChange={(v) => onChange({ behinderungGrad: v })}
hint={`In Prozent laut Bescheid. Die Begünstigung beginnt bei ${BEGUENSTIGUNG_AB_GRAD}%.`}
/>
<TextField
label="Bescheid ab"

View File

@@ -319,23 +319,6 @@ export function DatenAendernPanel({
<TextField label="Telefon" dense type="tel" value={phone} onChange={setPhone} />
</div>
{/* Eigener Block: im Ernstfall greift jemand danach, und dann darf
er nicht zwischen den Adressfeldern der Person untergehen. */}
<fieldset className="mt-4 rounded-md border border-border-subtle p-3">
<legend className="px-1 text-xs font-semibold uppercase tracking-wide text-ink-muted">Notfallkontakt</legend>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
<TextField label="Name" dense value={notfallName} onChange={setNotfallName} />
<TextField label="Telefon" dense type="tel" value={notfallTelefon} onChange={setNotfallTelefon} />
<SelectField
label="Verhältnis"
dense
value={notfallVerhaeltnis}
onChange={setNotfallVerhaeltnis}
placeholder="Bitte wählen…"
options={EMERGENCY_RELATIONS.map((r) => ({ value: r, label: r }))}
/>
</div>
</fieldset>
</div>
<div>
@@ -402,6 +385,26 @@ export function DatenAendernPanel({
</div>
<AngehoerigeSection employeeId={employee.id} dependents={dependents} effectiveDate={effectiveDate} />
{/* Unterhalb der Angehörigen und ganz am Schluss — dieselbe Reihenfolge
wie im Reiter „Stammdaten". Vorher stand der Block mitten im
Abschnitt „Person", zwischen den Adressfeldern; beides sind
Personen im Umfeld, und die beiden gehören nebeneinander. */}
<fieldset className="rounded-md border border-border-subtle p-3">
<legend className="px-1 text-xs font-semibold uppercase tracking-wide text-ink-muted">Notfallkontakt</legend>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
<TextField label="Name" dense value={notfallName} onChange={setNotfallName} />
<TextField label="Telefon" dense type="tel" value={notfallTelefon} onChange={setNotfallTelefon} />
<SelectField
label="Verhältnis"
dense
value={notfallVerhaeltnis}
onChange={setNotfallVerhaeltnis}
placeholder="Bitte wählen…"
options={EMERGENCY_RELATIONS.map((r) => ({ value: r, label: r }))}
/>
</div>
</fieldset>
</div>
</SlideOver>
);

View File

@@ -36,7 +36,7 @@ type Sicht = "alle" | "anstehend" | "erledigt";
const SICHTEN: { value: Sicht; label: string }[] = [
{ value: "alle", label: "Alle" },
{ value: "anstehend", label: "Anstehend" },
{ value: "erledigt", label: "Gelaufen" },
{ value: "erledigt", label: "Vergangen" },
];
export function HistorieTab({ history, employeeId }: { history: HistoryRow[]; employeeId: string }) {