Klick auf eine Einheit zeigt ihre Angaben
Die naheliegendste Geste in dieser Ansicht war ohne Wirkung: eine Karte im Organigramm liess sich anklicken und antwortete nicht. Jetzt oeffnet sie eine Leiste mit dem, was an der Einheit steht -- Orgnummer, Art, die Kette nach oben, Gueltigkeit, Leitung, Belegschaft, unbesetzte Planstellen, die direkt untergeordneten Einheiten und die Zahl der Personen im ganzen Teilbaum. Dazu dieselben zwei Handlungen wie auf der Karte, nur beschriftet statt als Symbol. Alles daraus stammt aus den Daten, die die Struktursicht ohnehin geladen hat. Kein zusaetzlicher Gang zur Datenbank und damit auch keine zweite Wahrheit -- was die Leiste zeigt, ist dasselbe, woraus der Baum daneben gezeichnet ist. Die Liste klickt sich genauso: beide Ansichten kommen aus einem Baum, und eine Handlung, die es nur in einer von beiden gibt, findet man in der anderen nie. org_units liefert dafuer zusaetzlich valid_from und valid_to, aber nur an die Struktursicht. Der Druck kommt ohne sie aus; auf OrgUnitNode stehen sie deshalb als optionale Felder und nicht als zwei weitere Spalten, die ueberall mitgeschleppt werden.
This commit is contained in:
@@ -9,6 +9,7 @@ import { SegmentedControl } from "@/components/ui/SegmentedControl";
|
||||
import { useToast } from "@/components/ui/Toast";
|
||||
import { CreateOrgUnitModal } from "./CreateOrgUnitModal";
|
||||
import { LazyGraphOrgChart } from "./LazyGraphOrgChart";
|
||||
import { OrgUnitPanel } from "./OrgUnitPanel";
|
||||
import type { ChartNode, OrgEmployee, OrgUnitNode, OrgVacancy } from "./types";
|
||||
import { fmtName } from "@/lib/format";
|
||||
|
||||
@@ -54,15 +55,24 @@ 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;
|
||||
|
||||
// Die Einheit, deren Angaben offen sind. Der Klick auf die Karte war bisher
|
||||
// ohne Wirkung — bei achtzig Einheiten die naheliegendste Geste ohne Antwort.
|
||||
const [gewaehlt, setGewaehlt] = useState<string | null>(null);
|
||||
const onSelectUnit = useCallback((unitId: string) => setGewaehlt(unitId), []);
|
||||
const einheit = gewaehlt ? (units.find((u) => u.id === gewaehlt) ?? 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 betroffen = units.find((u) => u.id === unitId);
|
||||
const result = await deleteOrgUnit(unitId);
|
||||
if (result.success) {
|
||||
showToast(`${einheit ? `${einheit.org_number} · ${einheit.name}` : "Einheit"} entfernt.`);
|
||||
showToast(`${betroffen ? `${betroffen.org_number} · ${betroffen.name}` : "Einheit"} entfernt.`);
|
||||
// Die Angaben zu einer Einheit, die es nicht mehr gibt, stünden sonst
|
||||
// offen da.
|
||||
setGewaehlt(null);
|
||||
router.refresh();
|
||||
} else {
|
||||
showToast(result.error ?? "Fehler beim Entfernen.", "error");
|
||||
@@ -92,6 +102,7 @@ export function PositionTree({ employees, units, vacancies }: PositionTreeProps)
|
||||
onToggle={toggle}
|
||||
onAddUnit={onAddUnit}
|
||||
onDeleteUnit={onDeleteUnit}
|
||||
onSelectUnit={onSelectUnit}
|
||||
/>
|
||||
) : (
|
||||
<div className="rounded border border-border bg-white p-4">
|
||||
@@ -104,12 +115,25 @@ export function PositionTree({ employees, units, vacancies }: PositionTreeProps)
|
||||
onToggle={toggle}
|
||||
onAddUnit={onAddUnit}
|
||||
onDeleteUnit={onDeleteUnit}
|
||||
onSelectUnit={onSelectUnit}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{eltern && <CreateOrgUnitModal parent={eltern} units={units} onClose={() => setAnlegenUnter(null)} />}
|
||||
|
||||
{einheit && (
|
||||
<OrgUnitPanel
|
||||
unit={einheit}
|
||||
units={units}
|
||||
employees={employees}
|
||||
vacancies={vacancies}
|
||||
onAddUnit={onAddUnit}
|
||||
onDeleteUnit={onDeleteUnit}
|
||||
onClose={() => setGewaehlt(null)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -121,6 +145,7 @@ function ListNode({
|
||||
onToggle,
|
||||
onAddUnit,
|
||||
onDeleteUnit,
|
||||
onSelectUnit,
|
||||
}: {
|
||||
node: ChartNode;
|
||||
depth: number;
|
||||
@@ -128,6 +153,7 @@ function ListNode({
|
||||
onToggle: (id: string) => void;
|
||||
onAddUnit: (unitId: string) => void;
|
||||
onDeleteUnit: (unitId: string) => void;
|
||||
onSelectUnit: (unitId: string) => void;
|
||||
}) {
|
||||
const expandable = node.children.length > 0;
|
||||
const open = expanded.has(node.id);
|
||||
@@ -158,6 +184,16 @@ function ListNode({
|
||||
>
|
||||
{node.label}
|
||||
</Link>
|
||||
) : node.unitId ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onSelectUnit(node.unitId!)}
|
||||
aria-label={`Angaben zu ${node.label}`}
|
||||
className="rounded text-left text-sm font-semibold text-ink hover:text-brand-700 hover:underline
|
||||
focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-brand-500"
|
||||
>
|
||||
{node.label}
|
||||
</button>
|
||||
) : (
|
||||
<span className={node.kind === "group" ? "text-sm text-ink" : "text-sm font-semibold text-ink"}>{node.label}</span>
|
||||
)}
|
||||
@@ -204,6 +240,7 @@ function ListNode({
|
||||
onToggle={onToggle}
|
||||
onAddUnit={onAddUnit}
|
||||
onDeleteUnit={onDeleteUnit}
|
||||
onSelectUnit={onSelectUnit}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user