Print the rough power balance in the kbp design

The print of the rough power balance was plain grey. It now follows the
kbp design of the print view: a title block with office logo, stand,
project, number, owner, address, work stage, date, editor and office;
header row in kbp blue, cost-group rows tinted with a lilac rule, bold
subtotals, the total including reserve as a blue band, colored
deviations in the comparison, Arial, exact print colors, and a footer
with project, stand and "Seite x von y" on every page.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Grovy311 2026-09-26 17:48:37 +02:00
parent 37a70b6b36
commit e06c7d91e2
3 changed files with 292 additions and 24 deletions

View file

@ -0,0 +1,104 @@
"use client";
import { useEffect, useState } from "react";
import { documentLogoUrl, getDocumentBranding } from "../utils/api";
import type { EstimateBalanceDto, OfficeProfileDto, ProjectDto } from "../types";
import { formatIsoDateGerman } from "./estimate-balance-format";
interface EstimatePrintHeaderProps {
project: ProjectDto;
balance: EstimateBalanceDto;
}
function cssString(value: string) {
return JSON.stringify(value.replace(/[<>\r\n]/g, " "));
}
/**
* Print-only title block and page footer of the rough power balance in the
* kbp corporate design of the print view (Seitenansicht): logo, blue title,
* meta data, "Seite x von y". Hidden on screen.
*/
export function EstimatePrintHeader({ project, balance }: EstimatePrintHeaderProps) {
const [office, setOffice] = useState<OfficeProfileDto | null>(null);
const [hasLogo, setHasLogo] = useState(false);
const [logoVersion, setLogoVersion] = useState("");
useEffect(() => {
getDocumentBranding()
.then((branding) => {
setOffice(branding.office);
setHasLogo(Boolean(branding.logo));
setLogoVersion(branding.logo ? branding.logo.updatedAtIso : "");
})
.catch(() => {
setOffice(null);
setHasLogo(false);
});
}, []);
const stand = [balance.name, balance.workStage].filter(Boolean).join(" · ");
const date = balance.dateIso ? formatIsoDateGerman(balance.dateIso) : null;
const footer = `
@page estimate-balance {
size: A4 landscape;
margin: 10mm 10mm 13mm;
@bottom-left { content: ${cssString(`${project.name} · Grobe Leistungsbilanz`)}; font: 7.5pt Arial, sans-serif; color: #6b7280; }
@bottom-center { content: ${cssString(`Stand ${stand}${date ? ` · ${date}` : ""}`)}; font: 7.5pt Arial, sans-serif; color: #6b7280; }
@bottom-right { content: "Seite " counter(page) " von " counter(pages); font: 7.5pt Arial, sans-serif; color: #6b7280; }
}`;
return (
<>
<style>{footer}</style>
<header className="estimate-print-titleblock" aria-hidden="true">
<div className="estimate-print-logo">
{hasLogo ? (
<img alt="" src={`${documentLogoUrl}?v=${encodeURIComponent(logoVersion)}`} />
) : (
<span>{office?.officeName ?? ""}</span>
)}
</div>
<div className="estimate-print-title">
<div className="estimate-print-kicker">Grobe Leistungsbilanz · KG 400</div>
<div className="estimate-print-heading">
<span className="estimate-print-stand">{balance.name}</span>
<span className="estimate-print-project">{project.name}</span>
</div>
</div>
<dl className="estimate-print-meta">
<div>
<dt>Projekt-Nr.</dt>
<dd>{project.internalProjectNumber ?? "–"}</dd>
</div>
<div>
<dt>Bauherr</dt>
<dd>{project.buildingOwner ?? "–"}</dd>
</div>
<div>
<dt>Anschrift</dt>
<dd>{project.siteAddress?.split("\n").join(", ") ?? "–"}</dd>
</div>
</dl>
<dl className="estimate-print-meta is-tinted">
<div>
<dt>Leistungsphase</dt>
<dd>{balance.workStage ?? "–"}</dd>
</div>
<div>
<dt>Datum</dt>
<dd>{date ?? "–"}</dd>
</div>
<div>
<dt>Bearbeitung</dt>
<dd>{project.projectEditor ?? "–"}</dd>
</div>
<div>
<dt>Büro</dt>
<dd>{office?.officeName ?? "–"}</dd>
</div>
</dl>
</header>
</>
);
}