- components/orgchart/: 3-way segmented view sharing one server fetch
(switching tabs doesn't refetch):
- EmployeeTree: expand/collapse hierarchy from the CEO down, search with
auto-expand-to-match and highlighting, "Bereiche anzeigen" /
"Alles einklappen".
- PositionTree: models the org *structure* (GF -> Bereichsleitung ->
Abteilung -> Teamleitung -> grouped IC positions by title, expandable
to the actual holders) independent of who's currently in it, plus
dashed rows for open requisitions linking to /positions.
- ReorgWorkbench: batch multiple moves (employees / whole team / whole
department / whole division as source, always a specific team as
target), live headcount-impact table, apply via the existing
apply_reorg RPC, and an undo card wired to undo_reorg.
Bug found via live apply+undo testing: undo_reorg's cleanup DELETE on
employee_history silently matched zero rows, because that table has no
DELETE policy at all (by design, for audit immutability) - RLS filters
DELETE-eligible rows to none rather than erroring. Added
supabase/functions_3.sql: a policy scoped to hr_admin deleting only rows
that carry a reorg_scenario_id, so every other history event type stays
genuinely immutable. Verified live: apply moves an employee and updates
the headcount table correctly; undo reverts team/division/manager AND
now actually removes the Reorganisation history entries it created.
Simplification flagged here (not hidden): the spec's "Ganzes Team /
Ganze Abteilung / Ganzer Bereich" reorg moves the structural org unit
itself to a new division; this implementation resolves all four move
kinds down to individual employee moves against a specific target team,
since the schema's team->department->division chain doesn't support
freely reparenting a team object without also picking a department. The
workbench UI, headcount-impact math, and apply/undo all work correctly
under this model - only the exact "move the team as a unit" semantics
differs from the literal spec wording.
46 lines
1.6 KiB
TypeScript
46 lines
1.6 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { SegmentedControl } from "@/components/ui/SegmentedControl";
|
|
import type { OpenPositionResolved } from "@/lib/positions";
|
|
import { EmployeeTree } from "./EmployeeTree";
|
|
import { PositionTree } from "./PositionTree";
|
|
import { ReorgWorkbench } from "./ReorgWorkbench";
|
|
import type { OrgDepartment, OrgDivision, OrgEmployee, OrgTeam, ReorgScenarioSummary } from "./types";
|
|
|
|
type View = "ma" | "pos" | "reo";
|
|
|
|
type OrgChartClientProps = {
|
|
employees: OrgEmployee[];
|
|
divisions: OrgDivision[];
|
|
departments: OrgDepartment[];
|
|
teams: OrgTeam[];
|
|
openPositions: OpenPositionResolved[];
|
|
reorgScenarios: ReorgScenarioSummary[];
|
|
};
|
|
|
|
export function OrgChartClient({ employees, divisions, departments, teams, openPositions, reorgScenarios }: OrgChartClientProps) {
|
|
const [view, setView] = useState<View>("ma");
|
|
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
<SegmentedControl<View>
|
|
value={view}
|
|
onChange={setView}
|
|
options={[
|
|
{ value: "ma", label: "Mitarbeiter" },
|
|
{ value: "pos", label: "Positionen" },
|
|
{ value: "reo", label: "Reorganisation" },
|
|
]}
|
|
/>
|
|
{view === "ma" && <EmployeeTree employees={employees} />}
|
|
{view === "pos" && (
|
|
<PositionTree employees={employees} divisions={divisions} departments={departments} teams={teams} openPositions={openPositions} />
|
|
)}
|
|
{view === "reo" && (
|
|
<ReorgWorkbench employees={employees} divisions={divisions} departments={departments} teams={teams} reorgScenarios={reorgScenarios} />
|
|
)}
|
|
</div>
|
|
);
|
|
}
|