"use client"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { terminateEmployee } from "@/actions/employees"; import { Button } from "@/components/ui/Button"; import { SelectField, TextField, TextareaField } from "@/components/ui/Field"; import { SlideOver } from "@/components/ui/SlideOver"; import { useToast } from "@/components/ui/Toast"; import { AUSTRITTSART_LABELS, BEENDIGUNG_NO_SHOW, BEENDIGUNGSART_WERTE, austrittsartVon, beendigungsartenZu, type Austrittsart, } from "@/lib/beendigung"; import type { Database } from "@/lib/types"; import { fmtDate, fmtName } from "@/lib/format"; type EmployeeRow = Database["public"]["Tables"]["employees"]["Row"]; // Die Liste steht in lib/beendigung.ts, nicht mehr hier: der Berichtemanager // braucht sie ebenso, und zwei Listen liefen auseinander. const NO_SHOW = BEENDIGUNG_NO_SHOW; type TerminatePanelProps = { open: boolean; onClose: () => void; employee: EmployeeRow; directReportCount: number; }; export function TerminatePanel({ open, onClose, employee, directReportCount }: TerminatePanelProps) { const { showToast } = useToast(); const router = useRouter(); const [exitDate, setExitDate] = useState(""); // Wer noch gar nicht angefangen hat, tritt fast nie aus einem anderen Grund // aus. Die Vorbelegung nimmt den wahrscheinlichen Fall vorweg, ohne die // übrigen zu verstellen. const [reason, setReason] = useState(employee.status === "Geplant" ? NO_SHOW : BEENDIGUNGSART_WERTE[0]); // „Alle" statt einer Vorauswahl: die Einschränkung ist eine Hilfe beim // Suchen, keine Pflichtangabe. Wer die gewünschte Art auswendig kennt, // soll sie unmittelbar wählen können. const [austrittsart, setAustrittsart] = useState(""); const waehlbar = beendigungsartenZu(austrittsart || null); const [note, setNote] = useState(""); const [pending, setPending] = useState(false); // Bei einem Nichtantritt ist das Datum nicht frei wählbar: es ist der Tag, // an dem die Person hätte anfangen sollen. Die Datenbank setzt es ohnehin // so; hier steht es sichtbar, damit niemand ein Datum eintippt, das dann // stillschweigend übergangen wird. const istNoShow = reason === NO_SHOW; const wirksamesDatum = istNoShow ? employee.entry_date : exitDate; async function handleSubmit() { if (!wirksamesDatum) { showToast("Bitte ein Austrittsdatum angeben.", "error"); return; } setPending(true); const result = await terminateEmployee({ employee_id: employee.id, exit_date: wirksamesDatum, exit_reason: reason, note, }); setPending(false); if (result.success) { showToast( istNoShow ? `${employee.first_name} ${employee.last_name} ist nicht angetreten.` : `Austritt für ${employee.first_name} ${employee.last_name} erfasst.` ); router.refresh(); onClose(); } else { showToast(result.error ?? "Fehler beim Speichern.", "error"); } } return ( } >
{/* Zuerst, und nicht zu übersehen: bei besonderem Kündigungsschutz gelten eigene Regeln, bevor beendet werden darf. Die Anwendung entscheidet das nicht — sie darf es aber auch nicht verschweigen, und im Vertragsblatt nachzusehen ist genau der Schritt, den man unter Zeitdruck auslässt. */} {employee.has_kuendigungsschutz && (
Achtung: besonderer Kündigungsschutz {employee.kuendigungsschutz_bis ? ` bis ${fmtDate(employee.kuendigungsschutz_bis)}` : " (Ende nicht erfasst)"}. Vor einer Beendigung ist zu prüfen, ob sie zulässig ist — je nach Grund braucht es eine Zustimmung des Betriebsrats oder des Gerichts.
)} {directReportCount > 0 && (
{directReportCount} direkte Berichte werden automatisch der nächsthöheren Führungskraft zugeordnet.
)} {/* Die Austrittsart wählt nicht mit, sie engt ein. Gespeichert wird allein die Beendigungsart; auf wessen Betreiben beendet wurde, leitet lib/beendigung.ts daraus ab. Als zweites freies Feld liesse sich „Entlassung, freiwillig" erfassen, und zwei Auswertungen über dieselbe Person kämen zu verschiedenen Ergebnissen. */} { const neu = (v || "") as Austrittsart | ""; setAustrittsart(neu); // Passt der bisher gewählte Grund nicht mehr zur Einschränkung, // wandert die Auswahl auf den ersten passenden. Stehenzulassen, // was in der Liste nicht mehr sichtbar ist, wäre ein Formular, // das etwas anderes absendet, als es zeigt. const erlaubt = beendigungsartenZu(neu || null); if (!erlaubt.includes(reason)) setReason(erlaubt[0]); }} options={[ { value: "", label: "Alle Beendigungsarten" }, ...(Object.keys(AUSTRITTSART_LABELS) as Austrittsart[]).map((a) => ({ value: a, label: AUSTRITTSART_LABELS[a] })), ]} hint="Schränkt die Auswahl darunter ein. Gespeichert wird die Beendigungsart." /> ({ value: r, label: r === NO_SHOW ? "No Show (nicht angetreten)" : r }))} hint={austrittsartVon(reason) ? AUSTRITTSART_LABELS[austrittsartVon(reason)!] : undefined} /> {istNoShow && (

Die Planstelle wird wieder frei und gilt als nie besetzt. In allen Auswertungen zählt die Person an keinem Stichtag als beschäftigt.

)} {/* Bei einem Nichtantritt wurde nichts ausgegeben, was zurückkäme — deshalb entsteht dort auch keine Offboarding-Checkliste. Für einen echten Austritt legt terminateEmployee sie in derselben Transaktion an; hier steht nur die Ankündigung, damit niemand sie hier sucht und nichts findet. */} {!istNoShow && (

Nach der Bestätigung steht im Reiter „Offboarding“ eine Checkliste zur Verfügung.

)}
); }