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`).
94 lines
4.3 KiB
TypeScript
94 lines
4.3 KiB
TypeScript
import type { PaygradeType } from "@/lib/supabase/types";
|
||
import type { HireDraftData } from "./types";
|
||
|
||
const PAYGRADES: { value: PaygradeType; label: string; description: string }[] = [
|
||
{ value: "A", label: "A – Einstieg", description: "Berufseinsteiger:innen ohne einschlägige Erfahrung" },
|
||
{ value: "B", label: "B – Qualifiziert", description: "Fachkräfte mit abgeschlossener Ausbildung" },
|
||
{ value: "C", label: "C – Erfahren", description: "Mehrjährige einschlägige Berufserfahrung" },
|
||
{ value: "D", label: "D – Spezialist:in", description: "Vertiefte Fachexpertise" },
|
||
{ value: "E", label: "E – Teamleitung", description: "Fachliche und disziplinäre Führung eines Teams" },
|
||
{ value: "F", label: "F – Bereichsleitung / GF", description: "Führung eines Bereichs bzw. Geschäftsführung" },
|
||
];
|
||
|
||
export function StepVertrag({ draft, update }: { draft: HireDraftData; update: (patch: Partial<HireDraftData>) => void }) {
|
||
function handleEmploymentTypeChange(value: HireDraftData["employmentType"]) {
|
||
update({
|
||
employmentType: value,
|
||
weeklyHours: value === "Vollzeit" ? "38.5" : draft.weeklyHours === "38.5" ? "20" : draft.weeklyHours,
|
||
});
|
||
}
|
||
|
||
return (
|
||
<div className="flex flex-col gap-4">
|
||
<div className="grid grid-cols-2 gap-3">
|
||
<div>
|
||
<label className="mb-1 block text-sm font-semibold text-ink">Eintrittsdatum*</label>
|
||
<input type="date" value={draft.entryDate} onChange={(e) => update({ entryDate: e.target.value })} className="w-full rounded border border-border px-3 py-2 text-sm" />
|
||
</div>
|
||
<div>
|
||
<label className="mb-1 block text-sm font-semibold text-ink">Vertragsart</label>
|
||
<select
|
||
value={draft.contractType}
|
||
onChange={(e) => update({ contractType: e.target.value as HireDraftData["contractType"] })}
|
||
className="w-full rounded border border-border px-3 py-2 text-sm"
|
||
>
|
||
<option value="unbefristet">unbefristet</option>
|
||
<option value="befristet">befristet</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
{draft.contractType === "befristet" && (
|
||
<div>
|
||
<label className="mb-1 block text-sm font-semibold text-ink">Befristet bis*</label>
|
||
<input
|
||
type="date"
|
||
value={draft.contractEndDate}
|
||
onChange={(e) => update({ contractEndDate: e.target.value })}
|
||
className="w-full rounded border border-border px-3 py-2 text-sm"
|
||
/>
|
||
</div>
|
||
)}
|
||
<div className="grid grid-cols-2 gap-3">
|
||
<div>
|
||
<label className="mb-1 block text-sm font-semibold text-ink">Beschäftigungsausmaß</label>
|
||
<select
|
||
value={draft.employmentType}
|
||
onChange={(e) => handleEmploymentTypeChange(e.target.value as HireDraftData["employmentType"])}
|
||
className="w-full rounded border border-border px-3 py-2 text-sm"
|
||
>
|
||
<option value="Vollzeit">Vollzeit</option>
|
||
<option value="Teilzeit">Teilzeit</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label className="mb-1 block text-sm font-semibold text-ink">Wochenstunden</label>
|
||
<input
|
||
type="number"
|
||
step="0.5"
|
||
value={draft.weeklyHours}
|
||
disabled={draft.employmentType === "Vollzeit"}
|
||
onChange={(e) => update({ weeklyHours: e.target.value })}
|
||
className="w-full rounded border border-border px-3 py-2 text-sm disabled:bg-surface disabled:text-ink-muted"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label className="mb-1 block text-sm font-semibold text-ink">Paygrade*</label>
|
||
<select
|
||
value={draft.paygrade}
|
||
onChange={(e) => update({ paygrade: e.target.value as PaygradeType })}
|
||
className="w-full rounded border border-border px-3 py-2 text-sm"
|
||
>
|
||
{PAYGRADES.map((p) => (
|
||
<option key={p.value} value={p.value}>
|
||
{p.label}
|
||
</option>
|
||
))}
|
||
</select>
|
||
<p className="mt-1 text-xs text-ink-muted">{PAYGRADES.find((p) => p.value === draft.paygrade)?.description}</p>
|
||
</div>
|
||
<p className="text-xs text-ink-muted">Es gilt eine Probezeit von 1 Monat gemäß Kollektivvertrag.</p>
|
||
</div>
|
||
);
|
||
}
|