Add print cover fields and office profile

- Project settings gain siteAddress, workStage, projectLead and
  projectEditor (project.update-settings schema version 3, migration 0009);
  versions 2 and 1 stay executable and keep the current cover values.
  Snapshot schema 6 carries the fields; older versions upgrade with null.
- Global office profile (office name/address) next to the document logo:
  table document_branding_settings, PUT /api/document-branding/office,
  editable from the print view toolbar.
- Cover page reads all fields; empty values show the "[ergänzen]" marker.
  The Planstände editor column shows the project editor.
- Shared logo constants moved to a dependency-free module so the Next.js
  bundler can import them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Grovy311 2026-09-25 20:42:44 +02:00
parent 94688d7a8c
commit bf58296ba5
35 changed files with 3571 additions and 106 deletions

View file

@ -5,6 +5,7 @@ import type {
CircuitTreeCircuitDto,
CircuitTreeComponentDto,
CircuitTreeDeviceRowDto,
OfficeProfileDto,
ProjectDto,
ProjectSnapshotMetadataDto,
} from "../types";
@ -18,17 +19,23 @@ import {
type PrintBoardHierarchyEntry,
} from "./print-document-model";
// Provisional office data until the project/cover fields are added.
export const printOfficeName = "kbp köhler beraten + planen GmbH";
export interface PrintDocumentProps {
project: ProjectDto;
office: OfficeProfileDto;
entries: PrintBoardHierarchyEntry[];
namedSnapshots: ProjectSnapshotMetadataDto[];
logoSrc: string | null;
printedAt: Date;
}
/** Shows a value or the orange "[ergänzen]" marker for missing data. */
function Value({ value, multiline = false }: { value: string | null; multiline?: boolean }) {
if (!value) {
return <Placeholder />;
}
return multiline ? <span className="pd-multiline">{value}</span> : <>{value}</>;
}
const dateFormat = new Intl.DateTimeFormat("de-DE", {
day: "2-digit",
month: "2-digit",
@ -129,6 +136,7 @@ function boardLabel(entry: PrintBoardHierarchyEntry) {
function CoverSheet({
project,
office,
entries,
namedSnapshots,
logoSrc,
@ -149,11 +157,11 @@ function CoverSheet({
<caption>Projekt</caption>
<tbody>
<tr><th>Bauvorhaben</th><td>{project.name}</td></tr>
<tr><th>Anschrift</th><td><Placeholder /></td></tr>
<tr><th>Bauherr</th><td>{project.buildingOwner ?? <Placeholder />}</td></tr>
<tr><th>Projektnummer intern</th><td>{project.internalProjectNumber ?? <Placeholder />}</td></tr>
<tr><th>Projektnummer extern</th><td>{project.externalProjectNumber ?? <Placeholder />}</td></tr>
<tr><th>Leistungsphase</th><td><Placeholder /></td></tr>
<tr><th>Anschrift</th><td><Value multiline value={project.siteAddress} /></td></tr>
<tr><th>Bauherr</th><td><Value value={project.buildingOwner} /></td></tr>
<tr><th>Projektnummer intern</th><td><Value value={project.internalProjectNumber} /></td></tr>
<tr><th>Projektnummer extern</th><td><Value value={project.externalProjectNumber} /></td></tr>
<tr><th>Leistungsphase</th><td><Value value={project.workStage} /></td></tr>
<tr><th>Gewerk</th><td>KG 440 – Starkstromanlagen</td></tr>
<tr><th>Beschreibung</th><td>{project.description ?? "–"}</td></tr>
</tbody>
@ -163,10 +171,10 @@ function CoverSheet({
<table className="pd-kv">
<caption>Planverfasser</caption>
<tbody>
<tr><th>Büro</th><td>{printOfficeName}</td></tr>
<tr><th>Anschrift</th><td><Placeholder /></td></tr>
<tr><th>Projektleitung</th><td><Placeholder /></td></tr>
<tr><th>Bearbeitung</th><td><Placeholder /></td></tr>
<tr><th>Büro</th><td><Value value={office.officeName} /></td></tr>
<tr><th>Anschrift</th><td><Value multiline value={office.officeAddress} /></td></tr>
<tr><th>Projektleitung</th><td><Value value={project.projectLead} /></td></tr>
<tr><th>Bearbeitung</th><td><Value value={project.projectEditor} /></td></tr>
</tbody>
</table>
<table className="pd-kv">
@ -192,7 +200,9 @@ function CoverSheet({
<td>{String.fromCharCode(97 + (index % 26))}</td>
<td>{dateFormat.format(new Date(snapshot.createdAtIso))}</td>
<td>{snapshot.name}</td>
<td><Placeholder /></td>
{/* Snapshots do not record an editor yet; the project's
current editor is shown for every entry. */}
<td><Value value={project.projectEditor} /></td>
</tr>
))
) : (
@ -582,6 +592,7 @@ function SectionRows({
export function PrintDocument({
project,
office,
entries,
namedSnapshots,
logoSrc,
@ -594,6 +605,7 @@ export function PrintDocument({
entries={entries}
logoSrc={logoSrc}
namedSnapshots={namedSnapshots}
office={office}
project={project}
stand={stand}
/>