The chart on screen is an infinite canvas: you zoom in, drag around, and look at one corner at a time. Paper has none of that. Printing the canvas means scaling 850 people onto one sheet, which yields boxes two millimetres wide — technically the whole company, practically nothing. So the print view is rebuilt rather than shrunk, and it does two things the canvas cannot. It asks before it prints. Depth (bereiche, abteilungen, teams, or teams with every name) and which divisions, each one selectable. Whoever needs Produktion for a meeting gets two sheets instead of forty, and the page count is on the button before anything reaches the printer. And it draws the hierarchy as a hierarchy: boxes joined by connecting lines, not a column of cards. Superior and subordinate are the entire point of an org chart; a tidy list of the same units simply does not say it. The lines come from borders on pseudo-elements, so the PDF keeps them as vectors and they stay sharp when someone zooms in. Header shading gets weaker with each level down, which survives the black-and- white printer that most of these end up on. Overview sheet first, then one sheet per selected division, each carrying its own heading and headcount so page seven is still readable on its own. A4 or A3, landscape. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
67 lines
2.1 KiB
TypeScript
67 lines
2.1 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
import { Printer } from "lucide-react";
|
|
import Link from "next/link";
|
|
import { SegmentedControl } from "@/components/ui/SegmentedControl";
|
|
import { AsOfPicker } from "./AsOfPicker";
|
|
import { EmployeeTree } from "./EmployeeTree";
|
|
import { PositionTree } from "./PositionTree";
|
|
import type { OrgEmployee, OrgUnitNode, OrgVacancy } from "./types";
|
|
|
|
type View = "ma" | "pos";
|
|
|
|
type OrgChartClientProps = {
|
|
employees: OrgEmployee[];
|
|
units: OrgUnitNode[];
|
|
vacancies: OrgVacancy[];
|
|
asOf: string;
|
|
today: string;
|
|
projectedCount: number;
|
|
historyStartsAt: string | null;
|
|
/** Arrived via "Im Organigramm anzeigen" — unfold and centre this person. */
|
|
focusId: string | null;
|
|
};
|
|
|
|
export function OrgChartClient({
|
|
employees,
|
|
units,
|
|
vacancies,
|
|
asOf,
|
|
today,
|
|
projectedCount,
|
|
historyStartsAt,
|
|
focusId,
|
|
}: OrgChartClientProps) {
|
|
const [view, setView] = useState<View>("ma");
|
|
|
|
return (
|
|
<div className="flex flex-col gap-4">
|
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
|
<SegmentedControl<View>
|
|
value={view}
|
|
onChange={setView}
|
|
options={[
|
|
{ value: "ma", label: "Mitarbeiter" },
|
|
{ value: "pos", label: "Organisation" },
|
|
]}
|
|
/>
|
|
{/* Der Stichtag wandert mit: wer eine vergangene Struktur ansieht,
|
|
druckt sie auch. */}
|
|
<Link
|
|
href={asOf === today ? "/orgchart/print" : `/orgchart/print?asOf=${asOf}`}
|
|
className="inline-flex items-center gap-2 rounded-md border border-border px-3 py-2 text-sm font-semibold text-ink hover:bg-brand-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-brand-500"
|
|
>
|
|
<Printer className="h-4 w-4" />
|
|
Als PDF
|
|
</Link>
|
|
</div>
|
|
|
|
<AsOfPicker asOf={asOf} today={today} projectedCount={projectedCount} historyStartsAt={historyStartsAt} />
|
|
|
|
{view === "ma" && <EmployeeTree employees={employees} focusId={focusId} />}
|
|
{view === "pos" && <PositionTree employees={employees} units={units} vacancies={vacancies} />}
|
|
</div>
|
|
);
|
|
}
|