Files
alpenwerk-hr/lib/org.ts
Maximilian Stubhan 366731ec85 Phase 2/3: Employees list/detail + mutation RPCs + action panels
- supabase/functions.sql, functions_2.sql: Postgres RPCs for every
  employee/position/reorg mutation (hire, terminate, transfer, promote,
  start/adjust/return karenz, change data, rehire, create position, staff
  internally, apply/undo reorg). Each resolves manager_id server-side,
  writes history + audit atomically, and enforces hr_admin via
  require_hr_admin() (backed by the existing RLS policy).
- actions/employees.ts, positions.ts, reorg.ts: Server Actions wrapping
  the RPCs, returning success/error for client-side toast handling.
- Employees list (search/filter/pagination) and detail (4 tabs: Stammdaten,
  Vertrag & Gehalt, Organisation, Historie) reading from employees_directory.
- 6 action slide-over panels: Transfer, Promote, Karenz (start/adjust/
  return), Daten aendern (person+contract diffing), Terminate (with direct-
  report reparenting warning + offboarding checklist), Rehire.
- lib/org.ts: shared division/department/team/location lookups.

Verified live: promote mutation updates salary, writes history/audit, and
the detail page reflects it after refresh, no console errors.

Note: the spec's Karenz-verwalten panel only covers employees already on
Karenz; added a start-Karenz mode (Karenzbeginn/geplante Rueckkehr) to
cover the Aktiv-employee case implied by the header button but not
specified in the panel list.
2026-07-13 22:07:38 +02:00

49 lines
2.1 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { SupabaseClient } from "@supabase/supabase-js";
import type { Database } from "./supabase/types";
type Division = Database["public"]["Tables"]["divisions"]["Row"];
type Department = Database["public"]["Tables"]["departments"]["Row"];
type Team = Database["public"]["Tables"]["teams"]["Row"];
type Location = Database["public"]["Tables"]["locations"]["Row"];
export type OrgMaps = {
divisions: Map<string, Division>;
departments: Map<string, Department>;
teams: Map<string, Team>;
locations: Map<string, Location>;
divisionList: Division[];
locationList: Location[];
};
// Org reference data is tiny (9 divisions / 16 departments / 35 teams / 5
// locations) — fetched whole and joined client-side rather than per-row.
export async function loadOrgMaps(supabase: SupabaseClient<Database>): Promise<OrgMaps> {
const [{ data: divisions }, { data: departments }, { data: teams }, { data: locations }] = await Promise.all([
supabase.from("divisions").select("*").order("name"),
supabase.from("departments").select("*"),
supabase.from("teams").select("*"),
supabase.from("locations").select("*").order("name"),
]);
return {
divisions: new Map((divisions ?? []).map((d) => [d.id, d])),
departments: new Map((departments ?? []).map((d) => [d.id, d])),
teams: new Map((teams ?? []).map((t) => [t.id, t])),
locations: new Map((locations ?? []).map((l) => [l.id, l])),
divisionList: divisions ?? [],
locationList: locations ?? [],
};
}
export function breadcrumbFor(orgMaps: OrgMaps, divisionId: string | null, teamId: string | null) {
const division = divisionId ? orgMaps.divisions.get(divisionId) : undefined;
const team = teamId ? orgMaps.teams.get(teamId) : undefined;
const department = team ? orgMaps.departments.get(team.department_id) : undefined;
return { division, department, team };
}
export function breadcrumbLabel(orgMaps: OrgMaps, divisionId: string | null, teamId: string | null): string {
const { division, department, team } = breadcrumbFor(orgMaps, divisionId, teamId);
return [division?.name, department?.name, team?.name].filter(Boolean).join(" ") || "";
}