Reworks the app from a two-role (hr_admin/manager) model to a single HR-only role gated by profiles.is_active, fixes transfer/promote/karenz/ reorg RPCs to actually defer future-dated changes via a new pending_org_changes table instead of writing them immediately (applied by a daily Vercel Cron route), makes reorg undo append-only instead of deleting history, adds Karenz-return and history-date integrity guards, deprecates the salary column, and adds explicit schema grants + perf indexes needed to run against a fresh (non-hosted) Postgres instance. Adds vitest unit + integration test suites (the latter against a real local Supabase instance) covering all of the above, plus lint/typecheck/ build wiring (`npm run check`).
56 lines
2.2 KiB
TypeScript
56 lines
2.2 KiB
TypeScript
import { fmtDate } from "@/lib/format";
|
||
import type { OpenPositionResolved } from "@/lib/positions";
|
||
import type { HireDraftData } from "./types";
|
||
|
||
const PAYGRADE_LABELS: Record<string, string> = {
|
||
A: "A – Einstieg",
|
||
B: "B – Qualifiziert",
|
||
C: "C – Erfahren",
|
||
D: "D – Spezialist:in",
|
||
E: "E – Teamleitung",
|
||
F: "F – Bereichsleitung / GF",
|
||
};
|
||
|
||
type StepSummaryProps = {
|
||
draft: HireDraftData;
|
||
selectedPosition: OpenPositionResolved | null;
|
||
locations: { id: string; name: string; country: string }[];
|
||
};
|
||
|
||
export function StepSummary({ draft, selectedPosition, locations }: StepSummaryProps) {
|
||
const location = locations.find((l) => l.id === draft.locationId);
|
||
const rows: [string, string][] = [
|
||
["Name", `${draft.firstName} ${draft.lastName}`],
|
||
["Geschlecht", draft.gender === "m" ? "männlich" : "weiblich"],
|
||
["Geburtsdatum", fmtDate(draft.birthDate)],
|
||
["SV-Nummer", draft.svNummer || "–"],
|
||
["E-Mail (privat)", draft.email || "–"],
|
||
["Telefon", draft.phone || "–"],
|
||
["Standort", location ? `${location.name} (${location.country})` : "–"],
|
||
["Position", selectedPosition ? `${selectedPosition.title} (${selectedPosition.position_number})` : "–"],
|
||
["Organisationseinheit", selectedPosition?.orgLabel ?? "–"],
|
||
["Führungskraft", selectedPosition?.managerName ?? "–"],
|
||
["Besetzung", draft.besetzung],
|
||
["Eintrittsdatum", fmtDate(draft.entryDate)],
|
||
["Vertragsart", draft.contractType === "befristet" ? `befristet bis ${fmtDate(draft.contractEndDate)}` : "unbefristet"],
|
||
["Beschäftigungsausmaß", `${draft.employmentType} (${draft.weeklyHours} h)`],
|
||
["Paygrade", PAYGRADE_LABELS[draft.paygrade]],
|
||
];
|
||
|
||
return (
|
||
<div className="flex flex-col gap-4">
|
||
<dl className="grid grid-cols-2 gap-x-6 gap-y-3">
|
||
{rows.map(([label, value]) => (
|
||
<div key={label}>
|
||
<dt className="text-xs font-semibold uppercase tracking-wide text-ink-muted">{label}</dt>
|
||
<dd className="mt-0.5 text-sm text-ink">{value}</dd>
|
||
</div>
|
||
))}
|
||
</dl>
|
||
<p className="rounded bg-info-bg px-3 py-2 text-sm text-info-text">
|
||
Personalnummer und Firmen-E-Mail-Adresse werden automatisch vergeben.
|
||
</p>
|
||
</div>
|
||
);
|
||
}
|