"use client"; import { Plus, Trash2 } from "lucide-react"; import Link from "next/link"; import { Button } from "@/components/ui/Button"; import { SlideOver } from "@/components/ui/SlideOver"; import { fmtDate, fmtName } from "@/lib/format"; import type { OrgEmployee, OrgUnitNode, OrgVacancy } from "./types"; // Was an einer Organisationseinheit steht — beim Klick auf ihre Karte. // // Alles hier kommt aus den Daten, die die Struktursicht ohnehin geladen hat: // Einheiten, Personen und Vakanzen zum eingestellten Stichtag. Kein zusätzlicher // Gang zur Datenbank, und damit auch keine zweite Wahrheit — was die Karte // zeigt, ist dasselbe, woraus der Baum daneben gezeichnet ist. function Zeile({ label, children }: { label: string; children: React.ReactNode }) { return (
{label} {children}
); } export function OrgUnitPanel({ unit, units, employees, vacancies, onAddUnit, onDeleteUnit, onClose, }: { unit: OrgUnitNode; units: OrgUnitNode[]; employees: OrgEmployee[]; vacancies: OrgVacancy[]; onAddUnit: (unitId: string) => void; onDeleteUnit: (unitId: string) => void; onClose: () => void; }) { const kinder = units.filter((u) => u.parent_id === unit.id); // Die Kette nach oben, mit Schutz gegen einen Kreis in parent_id. Der ist // heute nicht herstellbar, aber diese Schleife wäre die erste Stelle, die // ihn ausbaden müsste — und ein hängender Browser sagt niemandem, woran es // liegt. const kette: OrgUnitNode[] = []; const gesehen = new Set(); let lauf = unit.parent_id ? units.find((u) => u.id === unit.parent_id) : undefined; while (lauf && !gesehen.has(lauf.id)) { gesehen.add(lauf.id); kette.unshift(lauf); lauf = lauf.parent_id ? units.find((u) => u.id === lauf!.parent_id) : undefined; } const eigene = employees.filter((e) => e.org_unit_id === unit.id); const leitung = eigene.find((e) => e.is_chief); const belegschaft = eigene.filter((e) => !e.is_chief); const offene = vacancies.filter((v) => v.org_unit_id === unit.id); // Alles darunter, nicht nur die direkten Kinder — die Frage hinter „wie // gross ist dieser Bereich" meint immer den ganzen Teilbaum. const teilbaum = new Set([unit.id]); for (let gewachsen = true; gewachsen; ) { gewachsen = false; for (const u of units) { if (u.parent_id && teilbaum.has(u.parent_id) && !teilbaum.has(u.id)) { teilbaum.add(u.id); gewachsen = true; } } } const imTeilbaum = employees.filter((e) => teilbaum.has(e.org_unit_id)).length; // Dieselbe Bedingung wie am Papierkorb auf der Karte: nur ein Blatt ohne // Planstellen lässt sich entfernen. Die Datenbank prüft strenger — sie sieht // auch geschlossene Planstellen, die hier nicht gezeichnet sind. const entfernbar = kinder.length === 0 && eigene.length === 0 && offene.length === 0 && unit.parent_id !== null; return (
{unit.org_number} {unit.unit_type} {kette.length > 0 ? kette.map((u) => u.name).join(" › ") : "—"} {unit.valid_from ? fmtDate(unit.valid_from) : "—"} {unit.valid_to ? fmtDate(unit.valid_to) : "unbefristet"}

Besetzung

{leitung ? ( {fmtName(leitung.first_name, leitung.last_name)} ) : ( vakant )} {belegschaft.length} {offene.length}

Struktur

{kinder.length} {imTeilbaum}
{kinder.length > 0 && (

Direkt darunter

    {kinder.map((k) => (
  • {k.org_number} · {k.name}{" "} ({k.unit_type})
  • ))}
)} {offene.length > 0 && (

Unbesetzt

    {offene.map((v) => (
  • {v.position_number} · {v.job_title} {v.is_chief && (Leitung)}
  • ))}
{/* Kontierung, Tätigkeit und Gültigkeit einer Planstelle stehen auf „Positionen"; von hier führt wenigstens der Weg dorthin. */} Auf Positionen bearbeiten
)}
{entfernbar && ( )}
); }