Files
alpenwerk-hr/app/(app)/employees/[id]/page.tsx
Maximilian Stubhan 82d07f0d95
Some checks failed
CI / Lint, Typen, Tests, Build (push) Failing after 6m24s
CI / Integrationstests (echtes Postgres) (push) Failing after 5m51s
Put the onboarding checklist where the file is
The list existed on paper: one printed sheet per entry, twenty-five
boxes. What is on it is known only to whoever holds the sheet — it
cannot be searched, cannot be covered for while someone is away, and
says nothing about who ticked what.

Not every box on the sheet is a checkbox, and the differences carry
meaning, so the field kind is derived from the thing rather than
flattened:

  Haken   — the normal case. The Meldezettel is there or it is not.
  Ja/Nein — Prämienanspruch had *two* boxes on the sheet, and that is
            not decoration: "nein" is a finding, "not asked yet" is not.
            One checkbox cannot say both.
  Text    — shoe, shirt and trouser size. The value is the point;
            ticked off it would be worthless.

Every item takes a comment, and every item records who last touched it
and when — the part the sheet could never do.

Saved on click, not on submit. A checklist is worked through over days,
between other things; a save button at the end is where half a morning
goes missing.

The items live in lib/onboarding.ts, not in a table: a checklist is a
company process, not a master record. Stored per person is only the
answer, under the item's key — so an item dropped later leaves its old
answers standing instead of taking them along, and a file from back then
stays readable.

A list is created by hire and rehire, in the same transaction as the
hire itself: a hire without a checklist would be a half-recorded hire.
Rehire only adds what is missing and never clears an old tick — what
genuinely has to be redone is HR's call, and a program deciding it would
be guessing. People hired before this feature have no list and get a
button to start one.

Checked against the real database end to end: hire creates 25 open
items; checkbox, ja/nein, size and comment all land; a comment-only edit
leaves the tick alone; rehire tops the list up and keeps what was done.
The probe employee was removed afterwards — audit rows first, since the
log has no delete policy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 15:40:02 +02:00

56 lines
2.0 KiB
TypeScript

import { notFound } from "next/navigation";
import { EmployeeDetail } from "@/components/employees/EmployeeDetail";
import { currentUserId } from "@/lib/auth/session";
import { withUser } from "@/lib/db";
import { todayIso } from "@/lib/format";
import { breadcrumbLabel } from "@/lib/org";
import { loadEmployeeDetail } from "@/lib/employee-detail-data";
type PageProps = { params: Promise<{ id: string }> };
export default async function EmployeeDetailPage({ params }: PageProps) {
const { id } = await params;
const today = todayIso();
// Was die Akte liest und in wie vielen Rundreisen, steht in
// lib/employee-detail-data.ts.
const data = await withUser(await currentUserId(), (tx) => loadEmployeeDetail(tx, id, today));
if (!data) notFound();
const { employee, line, reports, history, dependents, notes, orgMaps, placement, kostenstelle, onboarding, openPositions, byId } = data;
return (
<EmployeeDetail
employee={employee}
placement={
placement && {
positionNumber: placement.positionNumber,
jobTitle: placement.jobTitle,
isChief: placement.isChief,
current: placement.current,
}
}
breadcrumb={breadcrumbLabel(orgMaps, placement?.orgUnitId)}
kostenstelle={kostenstelle}
onboarding={onboarding}
manager={(line?.acting_manager_id ? byId.get(line.acting_manager_id) : null) ?? null}
// Nur wenn eine Vertretung im Spiel ist — sonst stünde dieselbe Person
// zweimal da.
formalManager={
line?.formal_manager_id && line.formal_manager_id !== line.acting_manager_id
? (byId.get(line.formal_manager_id) ?? null)
: null
}
directReports={reports.flatMap((r) => {
const e = byId.get(r.employee_id);
return e ? [e] : [];
})}
history={history ?? []}
dependents={dependents ?? []}
notes={notes ?? []}
locations={orgMaps.locationList}
openPositions={openPositions}
/>
);
}