Print a checklist without printing the application around it
Some checks failed
CI / Lint, Typen, Tests, Build (push) Failing after 6m6s
CI / Integrationstests (echtes Postgres) (push) Failing after 5m22s

The checklist gets a print view: one A4 page, header carrying personnel
number, name, date of extract, entry date and position, the items in two
flowing columns so twenty-five fit, and two signature lines at the foot.
It prints the *state*, dated — not a blank form.

The default filename in the save dialog comes from the document title,
which is set to the agreed convention:

  20260818_2884_Aigner-Manuel_Onboarding-Checklist

Surname first, like everywhere else in the application, so a folder of
these sorts by person and within a person by date. Umlauts are resolved
rather than stripped: the naive route (NFKD, then every non-ASCII to a
dash) turns "Müller" into "Mu-ller", because decomposition splits the
umlaut and the diaeresis becomes the dash. "Weiß" needs its own rule —
it has no decomposition and would otherwise vanish.

The reported defect: the printout carried the application's own top bar
— hamburger, bell, "Neueinstellung", sign-out. Those are controls; on
paper they are decoration, and on a checklist filed in a personnel
record, misleading. The rule now sits on AppShell rather than on this
one page, so the org-chart print view — which had the same problem —
gets it too, along with anything printed later. The shell's padding goes
with it: the type area is set by @page on the print page itself, and the
shell's would have been added on top.

The browser's own header line (date, title, URL) is separate — that is a
checkbox in the print dialog, not something CSS can reach.

440 tests pass, including 13 new ones pinning the filename convention.
Not yet seen in a browser.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-18 20:19:58 +02:00
parent 521e4ecf00
commit 19e3170b00
9 changed files with 528 additions and 9 deletions

View File

@@ -0,0 +1,54 @@
import { notFound } from "next/navigation";
import { PrintChecklist } from "@/components/employees/PrintChecklist";
import { currentUserId } from "@/lib/auth/session";
import { artTitel, checklistDateiname, parseArt } from "@/lib/checklist-print";
import { loadChecklistPrint } from "@/lib/checklist-print-data";
import { withUser } from "@/lib/db";
import { todayIso } from "@/lib/format";
import { OFFBOARDING_GRUPPEN } from "@/lib/offboarding";
import { ONBOARDING_GRUPPEN } from "@/lib/onboarding";
// Eigene Route statt eines Druckstils auf dem Reiter.
//
// Der Reiter ist zum Arbeiten gebaut: Haken zum Anklicken, Eingabefelder,
// Kommentarknöpfe. Auf Papier ist davon nichts nützlich. Hier entsteht aus
// denselben Daten ein Blatt, das den Stand festhält — mit Kopf, Fortschritt
// und Platz für zwei Unterschriften.
type Props = {
params: Promise<{ id: string }>;
searchParams: Promise<{ art?: string }>;
};
/**
* Der Titel ist zugleich der Dateivorschlag im Druckdialog. Er wird hier
* gesetzt *und* in der Komponente noch einmal — hier für den Tab und den Fall,
* dass jemand die Seite direkt druckt, dort, weil der Titel aus den geladenen
* Daten kommt und die Metadaten sonst ein zweites Mal laden müssten.
*/
export async function generateMetadata({ params, searchParams }: Props) {
const [{ id }, sp] = await Promise.all([params, searchParams]);
const art = parseArt(sp.art);
const daten = await withUser(await currentUserId(), (tx) => loadChecklistPrint(tx, id, art));
if (!daten) return { title: artTitel(art) };
return { title: checklistDateiname(art, daten.kopf, todayIso()) };
}
export default async function ChecklistPrintPage({ params, searchParams }: Props) {
const [{ id }, sp] = await Promise.all([params, searchParams]);
const art = parseArt(sp.art);
const today = todayIso();
const daten = await withUser(await currentUserId(), (tx) => loadChecklistPrint(tx, id, art, today));
if (!daten) notFound();
return (
<PrintChecklist
art={art}
kopf={daten.kopf}
gruppen={art === "offboarding" ? OFFBOARDING_GRUPPEN : ONBOARDING_GRUPPEN}
staende={daten.staende}
today={today}
/>
);
}