Eine leere Organisationseinheit wieder entfernen
Der Papierkorb neben dem Plus, im Organigramm wie in der Liste, und nur dort, wo nichts darunter haengt. Ein Knopf, der beim Klick eine Absage erteilt, ist schlechter als kein Knopf -- die Datenbank prueft es trotzdem, denn sie sieht auch geschlossene Planstellen, die im Baum gar nicht gezeichnet sind. Abgewiesen wird mit Grund: untergeordnete Einheiten, Planstellen (geschlossene zaehlen mit), Kostenstellen, die darauf verweisen, und die Wurzel selbst. Geloescht und nicht geschlossen, und das ist eine bewusste Einschraenkung. delete_position macht seit dem OM-Umbau den Unterschied vor: besetzt gewesen → valid_to, nie besetzt gewesen → weg. Fuer Einheiten laesst sich davon heute nur die zweite Haelfte umsetzen, weil ein valid_to an einer Einheit zwar eingetragen, aber nirgends gelesen wuerde -- weder Organigramm noch orgMapsAbfragen, Berichte, Druck oder die Auswahl beim Anlegen einer Planstelle schraenken org_units auf den Stichtag ein. Die geschlossene Einheit staende ueberall weiter da, nur mit einem Datum, das niemand sieht. Das Schliessen zum Stichtag kommt, wenn org_units gegen den Stichtag gelesen wird -- dieselbe Arbeit, die auch das Verschieben braucht.
This commit is contained in:
@@ -1,9 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { ChevronDown, ChevronRight, Plus } from "lucide-react";
|
||||
import { ChevronDown, ChevronRight, Plus, Trash2 } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { deleteOrgUnit } from "@/actions/org";
|
||||
import { SegmentedControl } from "@/components/ui/SegmentedControl";
|
||||
import { useToast } from "@/components/ui/Toast";
|
||||
import { CreateOrgUnitModal } from "./CreateOrgUnitModal";
|
||||
import { LazyGraphOrgChart } from "./LazyGraphOrgChart";
|
||||
import type { ChartNode, OrgEmployee, OrgUnitNode, OrgVacancy } from "./types";
|
||||
@@ -28,6 +31,8 @@ type PositionTreeProps = {
|
||||
export function PositionTree({ employees, units, vacancies }: PositionTreeProps) {
|
||||
const [expanded, setExpanded] = useState<Set<string>>(() => new Set(units.filter((u) => u.parent_id === null).map((u) => `unit-${u.id}`)));
|
||||
const [mode, setMode] = useState<ViewMode>("list");
|
||||
const { showToast } = useToast();
|
||||
const router = useRouter();
|
||||
|
||||
// useCallback-stabil: das Layout-Memo von GraphOrgChart hängt an diesen
|
||||
// Referenzen, instabile Funktionen erzwängen sonst bei jedem Re-Render ein
|
||||
@@ -49,6 +54,23 @@ export function PositionTree({ employees, units, vacancies }: PositionTreeProps)
|
||||
const onAddUnit = useCallback((unitId: string) => setAnlegenUnter(unitId), []);
|
||||
const eltern = anlegenUnter ? (units.find((u) => u.id === anlegenUnter) ?? null) : null;
|
||||
|
||||
// Ohne Rückfrage, wie beim Entfernen einer Planstelle: entfernbar ist nur,
|
||||
// was nie eine Planstelle getragen hat und nichts unter sich hat. Alles
|
||||
// andere weist die Datenbank mit dem Grund ab, und der steht dann im Toast.
|
||||
const onDeleteUnit = useCallback(
|
||||
async (unitId: string) => {
|
||||
const einheit = units.find((u) => u.id === unitId);
|
||||
const result = await deleteOrgUnit(unitId);
|
||||
if (result.success) {
|
||||
showToast(`${einheit ? `${einheit.org_number} · ${einheit.name}` : "Einheit"} entfernt.`);
|
||||
router.refresh();
|
||||
} else {
|
||||
showToast(result.error ?? "Fehler beim Entfernen.", "error");
|
||||
}
|
||||
},
|
||||
[units, showToast, router]
|
||||
);
|
||||
|
||||
const tree = useMemo(() => buildUnitTree(units, employees, vacancies), [units, employees, vacancies]);
|
||||
|
||||
return (
|
||||
@@ -64,11 +86,25 @@ export function PositionTree({ employees, units, vacancies }: PositionTreeProps)
|
||||
/>
|
||||
</div>
|
||||
{mode === "graph" ? (
|
||||
<LazyGraphOrgChart tree={tree} isExpanded={isExpanded} onToggle={toggle} onAddUnit={onAddUnit} />
|
||||
<LazyGraphOrgChart
|
||||
tree={tree}
|
||||
isExpanded={isExpanded}
|
||||
onToggle={toggle}
|
||||
onAddUnit={onAddUnit}
|
||||
onDeleteUnit={onDeleteUnit}
|
||||
/>
|
||||
) : (
|
||||
<div className="rounded border border-border bg-white p-4">
|
||||
{tree.map((node) => (
|
||||
<ListNode key={node.id} node={node} depth={0} expanded={expanded} onToggle={toggle} onAddUnit={onAddUnit} />
|
||||
<ListNode
|
||||
key={node.id}
|
||||
node={node}
|
||||
depth={0}
|
||||
expanded={expanded}
|
||||
onToggle={toggle}
|
||||
onAddUnit={onAddUnit}
|
||||
onDeleteUnit={onDeleteUnit}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
@@ -84,12 +120,14 @@ function ListNode({
|
||||
expanded,
|
||||
onToggle,
|
||||
onAddUnit,
|
||||
onDeleteUnit,
|
||||
}: {
|
||||
node: ChartNode;
|
||||
depth: number;
|
||||
expanded: Set<string>;
|
||||
onToggle: (id: string) => void;
|
||||
onAddUnit: (unitId: string) => void;
|
||||
onDeleteUnit: (unitId: string) => void;
|
||||
}) {
|
||||
const expandable = node.children.length > 0;
|
||||
const open = expanded.has(node.id);
|
||||
@@ -129,6 +167,19 @@ function ListNode({
|
||||
{/* Derselbe Knopf wie auf der Karte in der Grafik — beide Ansichten
|
||||
kommen aus einem Baum, und eine Handlung, die es nur in einer von
|
||||
beiden gibt, findet man in der anderen nie. */}
|
||||
{node.unitId && !expandable && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onDeleteUnit(node.unitId!)}
|
||||
aria-label={`Einheit ${node.label} entfernen`}
|
||||
title="Einheit entfernen"
|
||||
className="ml-1 shrink-0 rounded p-0.5 text-ink-muted opacity-0 transition-opacity hover:text-danger-solid
|
||||
focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-brand-500
|
||||
group-hover/zeile:opacity-100 [@media(hover:none)]:opacity-100"
|
||||
>
|
||||
<Trash2 className="h-3 w-3" />
|
||||
</button>
|
||||
)}
|
||||
{node.unitId && (
|
||||
<button
|
||||
type="button"
|
||||
@@ -145,7 +196,15 @@ function ListNode({
|
||||
</div>
|
||||
{open &&
|
||||
node.children.map((child) => (
|
||||
<ListNode key={child.id} node={child} depth={depth + 1} expanded={expanded} onToggle={onToggle} onAddUnit={onAddUnit} />
|
||||
<ListNode
|
||||
key={child.id}
|
||||
node={child}
|
||||
depth={depth + 1}
|
||||
expanded={expanded}
|
||||
onToggle={onToggle}
|
||||
onAddUnit={onAddUnit}
|
||||
onDeleteUnit={onDeleteUnit}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user