"use client"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { changeEmployeeData } from "@/actions/employees"; import { AngehoerigeSection } from "@/components/employees/AngehoerigeSection"; import { RoleEmploymentFields, type RoleEmploymentValue } from "@/components/employees/RoleEmploymentFields"; import { SvNummerField } from "@/components/employees/SvNummerField"; import { TitleFields, type TitleValue } from "@/components/employees/TitleFields"; import { Button } from "@/components/ui/Button"; import { CountryPicker } from "@/components/ui/CountryPicker"; import { Field, SelectField, TextField } from "@/components/ui/Field"; import { SlideOver } from "@/components/ui/SlideOver"; import { useToast } from "@/components/ui/Toast"; import { brauchtAufenthaltstitel, UN_COUNTRIES } from "@/lib/countries"; import { STUNDEN_GRUENDE } from "@/lib/absence"; import { GRUND_BEGUENSTIGT_BEHINDERT } from "@/lib/kuendigungsschutz"; import { MITARBEITERART_STANDARD } from "@/lib/mitarbeiterart"; import { fmtFullName, todayIso } from "@/lib/format"; import { isValidSvnr, requiresAustrianSvnr } from "@/lib/svnr"; import { NotfallkontaktSection } from "@/components/employees/NotfallkontaktSection"; import type { ContractType, Database, EmploymentType, GenderType } from "@/lib/types"; type EmployeeRow = Database["public"]["Tables"]["employees"]["Row"]; type Dependent = Database["public"]["Tables"]["employee_dependents"]["Row"]; export function DatenAendernPanel({ open, onClose, employee, dependents, locationCountry, }: { open: boolean; onClose: () => void; employee: EmployeeRow; dependents: Dependent[]; locationCountry: string | null | undefined; }) { const { showToast } = useToast(); const router = useRouter(); const [pending, setPending] = useState(false); // The DB trigger would reject a bad number anyway; catching it here keeps // the whole effective-dated change from being thrown away on submit. const [effectiveDate, setEffectiveDate] = useState(todayIso); const [firstName, setFirstName] = useState(employee.first_name); const [lastName, setLastName] = useState(employee.last_name); const [titles, setTitles] = useState({ titlePrefix: employee.title_prefix ?? [], titleSuffix: employee.title_suffix ?? [] }); function updateTitles(patch: Partial) { setTitles((prev) => ({ ...prev, ...patch })); } const [gender, setGender] = useState(employee.gender); const [birthDate, setBirthDate] = useState(employee.birth_date); const [svNummer, setSvNummer] = useState(employee.sv_nummer ?? ""); const svNummerOk = !svNummer.trim() || !requiresAustrianSvnr(locationCountry) || isValidSvnr(svNummer, birthDate || null); const [nationality, setNationality] = useState(employee.nationality); const [hatTitel, setHatTitel] = useState(employee.hat_aufenthaltstitel ?? false); const [titelBis, setTitelBis] = useState(employee.aufenthaltstitel_bis ?? ""); // Die Staatsbürgerschaft entscheidet, ob die Frage überhaupt gestellt wird. const titelNoetig = brauchtAufenthaltstitel(nationality); const [address, setAddress] = useState(employee.address ?? ""); const [postalCode, setPostalCode] = useState(employee.postal_code ?? ""); const [city, setCity] = useState(employee.city ?? ""); const [addressCountry, setAddressCountry] = useState(employee.address_country ?? "Österreich"); const [email, setEmail] = useState(employee.email ?? ""); const [companyEmail, setCompanyEmail] = useState(employee.company_email ?? ""); const [phone, setPhone] = useState(employee.phone ?? ""); const [employmentType, setEmploymentType] = useState(employee.employment_type); const [weeklyHours, setWeeklyHours] = useState(String(employee.weekly_hours)); // Der Grund wird erst gefragt, wenn sich die Stunden tatsächlich ändern — // sonst stünde bei jeder Adressänderung eine Frage im Weg, die niemand // gestellt hat. const [stundenGrund, setStundenGrund] = useState(STUNDEN_GRUENDE[0]); const stundenGeaendert = Number(weeklyHours) !== Number(employee.weekly_hours); const [teilzeitBis, setTeilzeitBis] = useState(employee.teilzeit_bis ?? ""); const [contractType, setContractType] = useState(employee.contract_type); const [contractEndDate, setContractEndDate] = useState(employee.contract_end_date ?? ""); const [role, setRole] = useState({ workerType: employee.worker_type ?? "Angestellte:r", mitarbeiterart: employee.mitarbeiterart ?? MITARBEITERART_STANDARD, // Hier gesetzt und im Assistenten nicht: dort steht die Besetzungsart im // Schritt „Position". Ihr Wechsel von extern auf intern ist eine // Übernahme und wird als eigenes Ereignis protokolliert. source: employee.source ?? "Extern", collectiveAgreement: employee.collective_agreement ?? "Handel", workDays: employee.work_days ?? ["Mo", "Di", "Mi", "Do", "Fr"], isBetriebsrat: employee.is_betriebsrat ?? false, hasDienstwagen: employee.has_dienstwagen ?? false, hasKuendigungsschutz: employee.has_kuendigungsschutz ?? false, kuendigungsschutzBis: employee.kuendigungsschutz_bis ?? "", kuendigungsschutzGrund: employee.kuendigungsschutz_grund ?? "", kuendigungsschutzAb: employee.kuendigungsschutz_ab ?? "", istBeguenstigtBehindert: employee.ist_beguenstigt_behindert ?? false, // Als Text und nicht als Zahl: ein leeres Zahlenfeld wäre 0, und 0 // bedeutet hier „kein Bescheid", nicht „Grad null". behinderungGrad: employee.behinderung_grad === null ? "" : String(employee.behinderung_grad), behinderungAb: employee.behinderung_ab ?? "", behinderungBis: employee.behinderung_bis ?? "", dienstwagenArt: employee.dienstwagen_art ?? "Verbrenner", isLateraleFuehrung: employee.is_laterale_fuehrung ?? false, isCLevel: employee.is_c_level ?? false, }); function updateRole(patch: Partial) { setRole((prev) => ({ ...prev, ...patch })); } // Dieselbe Regel wie in RoleEmploymentFields: der Personenkreis zieht das // Kennzeichen mit. Hier noch einmal, weil nicht das Formular speichert, // sondern diese Seite — und was gezeigt wird, muss auch abgeschickt werden. const istBehindert = role.istBeguenstigtBehindert || role.kuendigungsschutzGrund === GRUND_BEGUENSTIGT_BEHINDERT; function handleEmploymentTypeChange(value: EmploymentType) { setEmploymentType(value); if (value === "Vollzeit") setWeeklyHours("38.5"); } async function handleSubmit() { if (employmentType === "Teilzeit" && (Number(weeklyHours) <= 0 || Number(weeklyHours) >= 38.5)) { showToast("Wochenstunden bei Teilzeit müssen zwischen 0 und 38,5 liegen.", "error"); return; } if (contractType === "befristet" && !contractEndDate) { showToast("Bei befristetem Vertrag ist ein Enddatum erforderlich.", "error"); return; } if (role.workDays.length === 0) { showToast("Mindestens ein Arbeitstag muss ausgewählt sein.", "error"); return; } if (!effectiveDate) { showToast("Bitte ein Wirksam-ab-Datum angeben.", "error"); return; } setPending(true); const result = await changeEmployeeData({ employee_id: employee.id, effective_date: effectiveDate, person: { first_name: firstName, last_name: lastName, title_prefix: titles.titlePrefix, title_suffix: titles.titleSuffix, gender, birth_date: birthDate, sv_nummer: svNummer, nationality, // Wechselt die Staatsbürgerschaft in den Freizügigkeitsraum, fällt // der Titel weg — sonst bliebe er als Rest an einer Person hängen, // die ihn nicht mehr braucht. Die Datenbank prüft diese Kopplung // bewusst nicht (siehe Migration), also gehört sie hierher. hat_aufenthaltstitel: titelNoetig ? hatTitel : false, aufenthaltstitel_bis: titelNoetig && hatTitel ? titelBis : "", address, postal_code: postalCode, city, address_country: addressCountry, // Getrimmt und als leere Zeichenkette durchgereicht: die Datenbank // macht daraus null (nullif), damit sich eine Angabe auch wieder // entfernen lässt. Ungetrimmt käme ein Feld, in dem nur ein // Leerzeichen stehen geblieben ist, als Inhalt in der Spalte an. email: email.trim(), company_email: companyEmail.trim(), phone: phone.trim(), // Der Notfallkontakt läuft über seinen eigenen Abschnitt weiter unten // — wie die Angehörigen. Stünde er zusätzlich hier, schrieben zwei // Stellen dasselbe Feld, und welche gewinnt, hinge an der // Reihenfolge. }, contract: { employment_type: employmentType, weekly_hours: Number(weeklyHours), contract_type: contractType, contract_end_date: contractType === "befristet" ? contractEndDate : "", }, role: { worker_type: role.workerType, mitarbeiterart: role.mitarbeiterart, source: role.source, collective_agreement: role.collectiveAgreement, work_days: role.workDays, is_betriebsrat: role.isBetriebsrat, has_dienstwagen: role.hasDienstwagen, dienstwagen_art: role.hasDienstwagen ? role.dienstwagenArt : "", has_kuendigungsschutz: role.hasKuendigungsschutz, // Leer statt des Wertes, wenn das Kennzeichen weg ist: die Bedingung // in der Datenbank duldet keinen Rest ohne Bezug, und die Funktion // räumt ihn auf demselben Weg weg wie beim Dienstwagen. kuendigungsschutz_bis: role.hasKuendigungsschutz ? role.kuendigungsschutzBis : "", kuendigungsschutz_grund: role.hasKuendigungsschutz ? role.kuendigungsschutzGrund : "", kuendigungsschutz_ab: role.hasKuendigungsschutz ? role.kuendigungsschutzAb : "", ist_beguenstigt_behindert: istBehindert, behinderung_grad: istBehindert ? role.behinderungGrad : "", behinderung_ab: istBehindert ? role.behinderungAb : "", behinderung_bis: istBehindert ? role.behinderungBis : "", // Die Teilzeitvariante wird nur mitgeschickt, wenn sich die Stunden // tatsächlich ändern. Sonst schriebe jede Adressänderung den Wert // erneut — und setzte ihn bei „Vertragliche Stundenänderung" sogar // zurück, obwohl niemand die Stunden angefasst hat. ...(stundenGeaendert ? { teilzeit_art: stundenGrund === "Vertragliche Stundenänderung" ? "" : stundenGrund, teilzeit_bis: stundenGrund === "Vertragliche Stundenänderung" ? "" : teilzeitBis, } : {}), is_laterale_fuehrung: role.isLateraleFuehrung, is_c_level: role.isCLevel, }, }); setPending(false); if (result.success) { showToast("Daten aktualisiert."); router.refresh(); onClose(); } else { showToast(result.error ?? "Fehler beim Speichern.", "error"); } } return ( } >

Person

setGender(v as GenderType)} options={[ { value: "m", label: "männlich" }, { value: "w", label: "weiblich" }, ]} />
{(p) => ( )} {/* Nur wo er verlangt ist. Bei Freizügigkeit — EU, EWR, Schweiz — wäre die Frage gegenstandslos, und ein „Nein" im Formular sähe aus wie eine Auskunft. */} {titelNoetig && ( <> setHatTitel(v === "ja")} options={[ { value: "nein", label: "Nein" }, { value: "ja", label: "Ja" }, ]} hint="Bei Staatsbürgerschaften ausserhalb von EU, EWR und Schweiz." /> {hatTitel && ( )} )}
{(p) => }

Vertrag

handleEmploymentTypeChange(v as EmploymentType)} options={[ { value: "Vollzeit", label: "Vollzeit" }, { value: "Teilzeit", label: "Teilzeit" }, ]} /> {stundenGeaendert && ( ({ value: g, label: g }))} hint="Steht danach am Profil und lässt sich auswerten." /> )} {stundenGeaendert && stundenGrund !== "Vertragliche Stundenänderung" && ( )} setContractType(v as ContractType)} options={[ { value: "unbefristet", label: "unbefristet" }, { value: "befristet", label: "befristet" }, ]} /> {contractType === "befristet" && ( )}

Rolle & Anstellung

{/* Derselbe Abschnitt wie im Reiter „Stammdaten", unmittelbar unter den Angehörigen — beides Personen im Umfeld, beides dieselbe Tabelle mit denselben Knöpfen. Hier standen drei Eingabefelder: fachlich dasselbe, aber daneben eine zweite Bauform für denselben Zweck. Beide Abschnitte laufen jetzt über das „Wirksam ab" dieses Formulars. */}
); }