Personalnummer im gedruckten Organigramm, wahlweise
All checks were successful
CI / Lint, Typen, Tests, Build (push) Successful in 11m23s
CI / Migrationen auf leerer Datenbank (push) Successful in 10m13s

Von Max gewuenscht, ausdruecklich mit Auswahl oben. Ein Haekchen in "Was soll
aufs Papier?" stellt die Nummer hinter den Namen -- nicht in eine eigene Zeile:
auf einem Blatt mit zweihundert Kaesten kostet jede zusaetzliche Zeile
Massstab, und der entscheidet darueber, ob das Blatt noch lesbar ist.

Angeboten auf jeder Tiefe, nicht nur bei "mit Personen". Die Leitung einer
Einheit steht auch auf den Blaettern, die sonst keine Namen fuehren; ein
Haekchen, das dort fehlte, saehe aus, als gaebe es dort keine Namen.

Als Kontext statt als Eigenschaft durch den Baum: die Angabe betrifft eine
einzige Zeile ganz unten, muesste aber sonst durch vier Signaturen der
Seitenerzeugung durchgereicht werden. Und die Kennung der Messung traegt den
Schalter mit -- die Zeilen werden breiter, also muss der Massstab neu gemessen
werden, sonst liefe das Blatt ueber den Rand.
This commit is contained in:
2026-09-29 21:03:50 +02:00
parent 0039ce5069
commit d9966c9623
2 changed files with 65 additions and 2 deletions

View File

@@ -2,7 +2,7 @@
import { ChevronRight, Printer } from "lucide-react";
import Link from "next/link";
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { Button } from "@/components/ui/Button";
import { fmtDate } from "@/lib/format";
import type { PrintModel, PrintPerson, PrintUnit } from "@/lib/orgchart-print";
@@ -40,6 +40,16 @@ const TIEFEN: { wert: Tiefe; label: string; hinweis: string }[] = [
{ wert: "personen", label: "mit Personen", hinweis: "je Abteilung ein Blatt, mit allen Namen" },
];
/**
* Ob die Personalnummer mitgedruckt wird.
*
* Als Kontext und nicht als Eigenschaft durch den Baum: die Angabe betrifft
* eine einzige Zeile ganz unten (PersonZeile), müsste aber sonst durch jede
* Ebene der Seitenerzeugung durchgereicht werden — vier Signaturen für ein
* Häkchen. Gewünscht von Max am 30.09., ausdrücklich mit Wahlmöglichkeit.
*/
const NummerImDruck = createContext(false);
type Format = "a4" | "a3";
// Die Satzfläche ist die Blattgrösse abzüglich der Ränder aus @page, ein
// wenig knapper gerechnet. Die Vorschau bekommt genau diese Masse fest
@@ -160,6 +170,7 @@ function seitenBauen(model: PrintModel, bereiche: PrintUnit[], tiefe: Tiefe, asO
export function PrintChart({ model, asOf, today }: { model: PrintModel; asOf: string; today: string }) {
const [format, setFormat] = useState<Format>("a4");
const [tiefe, setTiefe] = useState<Tiefe>("team");
const [mitNummer, setMitNummer] = useState(false);
const [gewaehlt, setGewaehlt] = useState<Set<string>>(() => new Set(model.divisions.map((d) => d.id)));
const [schritt, setSchritt] = useState<"auswahl" | "vorschau">("auswahl");
@@ -203,6 +214,7 @@ export function PrintChart({ model, asOf, today }: { model: PrintModel; asOf: st
}
return (
<NummerImDruck.Provider value={mitNummer}>
<div className="flex flex-col gap-6">
<style>{`
@page { size: ${FORMATE[format].css}; margin: 12mm 14mm 14mm; }
@@ -290,6 +302,15 @@ export function PrintChart({ model, asOf, today }: { model: PrintModel; asOf: st
</button>
))}
</div>
{/* Auf jeder Tiefe angeboten, nicht nur bei „mit Personen": die
Leitung einer Einheit steht auch auf den Blättern, die sonst
keine Namen führen. Ein Häkchen, das dort fehlte, sähe aus, als
gäbe es dort keine Namen. */}
<label className="mt-3 flex items-center gap-2 text-sm text-ink-body">
<input type="checkbox" checked={mitNummer} onChange={(e) => setMitNummer(e.target.checked)} />
Personalnummer hinter den Namen drucken
</label>
</div>
<div className="mt-5">
@@ -412,7 +433,14 @@ export function PrintChart({ model, asOf, today }: { model: PrintModel; asOf: st
unterzeile={seite.unterzeile}
seite={i === 0 ? "Übersicht" : `Seite ${i + 1} von ${seiten.length}`}
/>
<Skaliert kennung={`${seite.key}|${format}|${tiefe}`} onFaktor={(f) => meldeMassstab(seite.key, f)}>
{/* Die Kennung trägt `mitNummer` mit: die Zeilen werden
breiter, also muss der Massstab neu gemessen werden. Ohne
das bliebe der Wert des vorigen Laufs stehen, und das Blatt
liefe über den Rand. */}
<Skaliert
kennung={`${seite.key}|${format}|${tiefe}|${mitNummer}`}
onFaktor={(f) => meldeMassstab(seite.key, f)}
>
<Chart seite={seite} />
</Skaliert>
</Blatt>
@@ -421,6 +449,7 @@ export function PrintChart({ model, asOf, today }: { model: PrintModel; asOf: st
</>
)}
</div>
</NummerImDruck.Provider>
);
}
@@ -635,9 +664,15 @@ function Kasten({ unit, mitPersonen }: { unit: PrintUnit; mitPersonen: boolean }
}
function PersonZeile({ p, leitung }: { p: PrintPerson; leitung?: boolean }) {
const mitNummer = useContext(NummerImDruck);
return (
<p className="text-[10px] leading-tight">
<span className={leitung ? "font-bold text-ink" : "text-ink"}>{p.name}</span>
{/* Die Personalnummer hinter den Namen und nicht in eine eigene Zeile:
auf einem Blatt mit zweihundert Kästen kostet jede zusätzliche Zeile
Massstab, und der entscheidet darüber, ob das Blatt noch lesbar ist.
Tabellenziffern, damit die Nummern untereinander stehen. */}
{mitNummer && <span className="text-ink-muted tabular-nums"> · {p.personnelNumber}</span>}
{p.absent && <span className="text-ink-muted"> · abwesend</span>}
<br />
<span className="text-ink-muted">{p.jobTitle}</span>

View File

@@ -103,6 +103,34 @@ describe("PrintChart — Messung", () => {
});
});
// Von Max gewünscht (30.09.), ausdrücklich zum An- und Abwählen. Die Nummer
// steht hinter dem Namen und nicht in einer eigenen Zeile: auf einem Blatt mit
// zweihundert Kästen kostet jede Zeile Massstab.
describe("PrintChart — Personalnummer", () => {
/** Die Zeile, in der eine Person steht — nicht die Kopfzeile ihrer Einheit. */
function personZeile(nachname: string): string {
return screen.getAllByText(new RegExp(nachname))[0].closest("p")!.textContent ?? "";
}
it("druckt sie erst, wenn sie angehakt ist", async () => {
const user = userEvent.setup();
renderChart();
await zurVorschau(user);
// Wiesinger führt die Gesellschaft und steht damit auf jedem Blatt.
expect(personZeile("Wiesinger")).not.toContain("· 1");
});
it("stellt sie hinter den Namen, sobald sie angehakt ist", async () => {
const user = userEvent.setup();
renderChart();
await user.click(screen.getByLabelText(/Personalnummer/));
await zurVorschau(user);
// Die Leitung steht auch auf Blättern ohne Namensliste — deshalb ist das
// Häkchen auf jeder Tiefe zu haben.
expect(personZeile("Wiesinger")).toContain("· 1");
});
});
describe("PrintChart", () => {
it("fragt zuerst, was gedruckt wird, und zeigt vorher kein Blatt", () => {
renderChart();