Files
alpenwerk-hr/components/orgchart/OrgChartClient.tsx
Maximilian Stubhan 108da8d5e6 Phase 4: Org chart (Mitarbeiter/Positionen/Reorganisation) + one more RLS bugfix
- 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.
2026-07-13 22:57:49 +02:00

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>
);
}