Add print view, board info card and document logo upload
- Seitenansicht (/projects/:projectId/print): tabular cover page, board overview with supply structure and power sums, one plan per board with a repeating title block; PDF via the browser print dialog with named @page rules (A4 portrait cover, A4/A3 landscape plans, page x of y). - Distribution-board info card (board number, location, fed-from, remark) in the board edit dialog, persisted through the additively extended distribution-board.update command (migration 0008, snapshot schema 6, legacy subtree commands normalized). - Global document logo upload (PNG/JPEG/SVG, max 2 MB) outside project revisions and snapshots. - fed-from stays free text; the overview hierarchy and cumulated sums are preliminary until the feed link is modeled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
2742b26b87
commit
94688d7a8c
38 changed files with 5301 additions and 32 deletions
612
src/frontend/print/print-document.tsx
Normal file
612
src/frontend/print/print-document.tsx
Normal file
|
|
@ -0,0 +1,612 @@
|
|||
"use client";
|
||||
|
||||
import type { ReactNode } from "react";
|
||||
import type {
|
||||
CircuitTreeCircuitDto,
|
||||
CircuitTreeComponentDto,
|
||||
CircuitTreeDeviceRowDto,
|
||||
ProjectDto,
|
||||
ProjectSnapshotMetadataDto,
|
||||
} from "../types";
|
||||
import {
|
||||
formatCircuitCable,
|
||||
formatCurrent,
|
||||
formatFactor,
|
||||
formatKilowatt,
|
||||
formatProtectionDevice,
|
||||
formatUnitPower,
|
||||
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;
|
||||
entries: PrintBoardHierarchyEntry[];
|
||||
namedSnapshots: ProjectSnapshotMetadataDto[];
|
||||
logoSrc: string | null;
|
||||
printedAt: Date;
|
||||
}
|
||||
|
||||
const dateFormat = new Intl.DateTimeFormat("de-DE", {
|
||||
day: "2-digit",
|
||||
month: "2-digit",
|
||||
year: "numeric",
|
||||
});
|
||||
|
||||
function Placeholder({ label = "ergänzen" }: { label?: string }) {
|
||||
return <span className="pd-placeholder">[{label}]</span>;
|
||||
}
|
||||
|
||||
function Open() {
|
||||
return <span className="pd-open">offen</span>;
|
||||
}
|
||||
|
||||
function Logo({ src, className }: { src: string | null; className: string }) {
|
||||
return src ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img alt="Logo" className={`pd-logo ${className}`} src={src} />
|
||||
) : (
|
||||
<span className={`pd-logo-missing ${className}`}>Logo</span>
|
||||
);
|
||||
}
|
||||
|
||||
function SheetFooter({ project, stand }: { project: ProjectDto; stand: string }) {
|
||||
return (
|
||||
<div className="pd-sheet-footer" aria-hidden="true">
|
||||
<span>{project.name} · Leistungsbilanz</span>
|
||||
<span>
|
||||
Stand {stand} · Datenstand Rev. {project.currentRevision}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TitleBlock({
|
||||
kicker,
|
||||
number,
|
||||
name,
|
||||
rows,
|
||||
project,
|
||||
stand,
|
||||
logoSrc,
|
||||
}: {
|
||||
kicker: string;
|
||||
number?: string | null;
|
||||
name: string;
|
||||
rows: Array<[ReactNode, ReactNode]>;
|
||||
project: ProjectDto;
|
||||
stand: string;
|
||||
logoSrc: string | null;
|
||||
}) {
|
||||
return (
|
||||
<div className="pd-titleblock">
|
||||
<div className="pd-tb-logo">
|
||||
<Logo className="pd-tb-logo-image" src={logoSrc} />
|
||||
</div>
|
||||
<div className="pd-tb-main">
|
||||
<div className="pd-tb-kicker">{kicker}</div>
|
||||
<div className="pd-tb-title">
|
||||
{number ? <span className="pd-tb-number">{number}</span> : null}
|
||||
<span className="pd-tb-name">{name}</span>
|
||||
</div>
|
||||
</div>
|
||||
<dl className="pd-tb-grid">
|
||||
{rows.map(([label, value], index) => (
|
||||
<div key={index}>
|
||||
<dt>{label}</dt>
|
||||
<dd>{value}</dd>
|
||||
</div>
|
||||
))}
|
||||
</dl>
|
||||
<dl className="pd-tb-grid pd-tb-meta">
|
||||
<div>
|
||||
<dt>Projekt</dt>
|
||||
<dd>{project.name}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Projekt-Nr.</dt>
|
||||
<dd>{project.internalProjectNumber ?? "–"}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Stand</dt>
|
||||
<dd>{stand}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Datenstand</dt>
|
||||
<dd>Rev. {project.currentRevision}</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function boardLabel(entry: PrintBoardHierarchyEntry) {
|
||||
const { board } = entry.summary;
|
||||
return board.boardNumber ? `${board.boardNumber} – ${board.name}` : board.name;
|
||||
}
|
||||
|
||||
function CoverSheet({
|
||||
project,
|
||||
entries,
|
||||
namedSnapshots,
|
||||
logoSrc,
|
||||
stand,
|
||||
}: Omit<PrintDocumentProps, "printedAt"> & { stand: string }) {
|
||||
return (
|
||||
<section className="pd-sheet pd-portrait pd-cover">
|
||||
<div className="pd-cover-head">
|
||||
<div className="pd-cover-kicker">Projektdokumentation Elektrotechnik</div>
|
||||
<Logo className="pd-cover-logo" src={logoSrc} />
|
||||
</div>
|
||||
<div className="pd-cover-title">
|
||||
<h1>Leistungsbilanz</h1>
|
||||
<p>Verteilerübersicht und Verteilerpläne</p>
|
||||
</div>
|
||||
|
||||
<table className="pd-kv">
|
||||
<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>Gewerk</th><td>KG 440 – Starkstromanlagen</td></tr>
|
||||
<tr><th>Beschreibung</th><td>{project.description ?? "–"}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div className="pd-cover-cols">
|
||||
<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>
|
||||
</tbody>
|
||||
</table>
|
||||
<table className="pd-kv">
|
||||
<caption>Dokument</caption>
|
||||
<tbody>
|
||||
<tr><th>Dokumentart</th><td>Leistungsbilanz</td></tr>
|
||||
<tr><th>Stand</th><td>{stand}</td></tr>
|
||||
<tr><th>Datenstand</th><td>Revision {project.currentRevision}</td></tr>
|
||||
<tr><th>Verteiler</th><td>{entries.length}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<table className="pd-grid pd-block">
|
||||
<caption>Planstände</caption>
|
||||
<thead>
|
||||
<tr><th className="pd-col-index">Index</th><th>Datum</th><th>Änderung</th><th>Bearbeitung</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{namedSnapshots.length ? (
|
||||
namedSnapshots.map((snapshot, index) => (
|
||||
<tr key={snapshot.id}>
|
||||
<td>{String.fromCharCode(97 + (index % 26))}</td>
|
||||
<td>{dateFormat.format(new Date(snapshot.createdAtIso))}</td>
|
||||
<td>{snapshot.name}</td>
|
||||
<td><Placeholder /></td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr><td colSpan={4}>–</td></tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table className="pd-grid pd-block">
|
||||
<caption>Inhalt</caption>
|
||||
<tbody>
|
||||
<tr><td className="pd-col-index">1</td><td>Verteilerübersicht und Leistungssummen</td></tr>
|
||||
{entries.map((entry, index) => (
|
||||
<tr key={entry.summary.board.id}>
|
||||
<td className="pd-col-index">2.{index + 1}</td>
|
||||
<td>Verteilerplan {boardLabel(entry)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<SheetFooter project={project} stand={stand} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function StructureNode({
|
||||
entry,
|
||||
entries,
|
||||
}: {
|
||||
entry: PrintBoardHierarchyEntry;
|
||||
entries: PrintBoardHierarchyEntry[];
|
||||
}) {
|
||||
const children = entries.filter(
|
||||
(candidate) => candidate.parentBoardId === entry.summary.board.id
|
||||
);
|
||||
const { board } = entry.summary;
|
||||
return (
|
||||
<li>
|
||||
<div className="pd-node">
|
||||
<strong>{board.boardNumber ?? board.name}</strong>
|
||||
{board.boardNumber ? <span>{board.name}</span> : null}
|
||||
<em>{formatKilowatt(entry.summary.demandPowerKw)} kW</em>
|
||||
</div>
|
||||
{children.length ? (
|
||||
<ul>
|
||||
{children.map((child) => (
|
||||
<StructureNode entries={entries} entry={child} key={child.summary.board.id} />
|
||||
))}
|
||||
</ul>
|
||||
) : null}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
function OverviewSheet({
|
||||
project,
|
||||
entries,
|
||||
logoSrc,
|
||||
stand,
|
||||
}: {
|
||||
project: ProjectDto;
|
||||
entries: PrintBoardHierarchyEntry[];
|
||||
logoSrc: string | null;
|
||||
stand: string;
|
||||
}) {
|
||||
const roots = entries.filter((entry) => entry.parentBoardId === null);
|
||||
// Top-level boards whose "fed from" text names no other board are grouped
|
||||
// below that text as an external feed (e.g. "HAK / Zähleranlage").
|
||||
const feedGroups = new Map<string, PrintBoardHierarchyEntry[]>();
|
||||
for (const root of roots) {
|
||||
const feed = root.summary.board.fedFrom ?? "";
|
||||
feedGroups.set(feed, [...(feedGroups.get(feed) ?? []), root]);
|
||||
}
|
||||
const sum =(pick: (entry: PrintBoardHierarchyEntry) => number) =>
|
||||
entries.reduce((total, entry) => total + pick(entry), 0);
|
||||
return (
|
||||
<section className="pd-sheet pd-landscape">
|
||||
<TitleBlock
|
||||
kicker="Übersicht"
|
||||
logoSrc={logoSrc}
|
||||
name="Verteilerübersicht und Leistungssummen"
|
||||
project={project}
|
||||
rows={[
|
||||
["Verteiler", String(entries.length)],
|
||||
["Netz", `${project.threePhaseVoltageV} / ${project.singlePhaseVoltageV} V`],
|
||||
]}
|
||||
stand={stand}
|
||||
/>
|
||||
<div className="pd-overview">
|
||||
<div className="pd-structure">
|
||||
<h2>Versorgungsstruktur</h2>
|
||||
<ul className="pd-tree">
|
||||
{[...feedGroups].map(([feed, feedRoots]) => {
|
||||
const nodes = feedRoots.map((root) => (
|
||||
<StructureNode entries={entries} entry={root} key={root.summary.board.id} />
|
||||
));
|
||||
return feed ? (
|
||||
<li key={`feed:${feed}`}>
|
||||
<div className="pd-node pd-feed">
|
||||
<strong>Einspeisung</strong>
|
||||
<span>{feed}</span>
|
||||
</div>
|
||||
<ul>{nodes}</ul>
|
||||
</li>
|
||||
) : (
|
||||
nodes
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<table className="pd-grid pd-plan">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Nr.</th><th>Bezeichnung</th><th>Örtlichkeit</th><th>Etage</th><th>Netz</th><th>Gespeist von</th>
|
||||
<th className="pd-r">SK</th>
|
||||
<th className="pd-r">P<sub>inst</sub><br />[kW]</th>
|
||||
<th className="pd-r">P<sub>Geräte</sub><br />[kW]</th>
|
||||
<th className="pd-r">GZF<br />Vert.</th>
|
||||
<th className="pd-r">P<sub>gl</sub><br />[kW]</th>
|
||||
<th className="pd-r">I<sub>B</sub><br />[A]</th>
|
||||
<th className="pd-r">Σ inkl.<br />unterlagert [kW]</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{entries.map((entry) => {
|
||||
const { board } = entry.summary;
|
||||
return (
|
||||
<tr key={board.id}>
|
||||
<td style={{ paddingLeft: `${1.6 + entry.depth * 3.5}mm` }}>
|
||||
{entry.depth ? "└ " : ""}
|
||||
<strong>{board.boardNumber ?? "–"}</strong>
|
||||
</td>
|
||||
<td>{board.name}</td>
|
||||
<td>{board.location ?? "–"}</td>
|
||||
<td>{entry.summary.floorName ?? "–"}</td>
|
||||
<td>{board.supplyType ?? "–"}</td>
|
||||
<td>{board.fedFrom ?? "–"}</td>
|
||||
<td className="pd-r">{entry.summary.circuitCount}</td>
|
||||
<td className="pd-r">{formatKilowatt(entry.summary.installedPowerKw)}</td>
|
||||
<td className="pd-r">{formatKilowatt(entry.summary.devicePowerKw)}</td>
|
||||
<td className="pd-r">{formatFactor(entry.summary.simultaneityFactor)}</td>
|
||||
<td className="pd-r"><strong>{formatKilowatt(entry.summary.demandPowerKw)}</strong></td>
|
||||
<td className="pd-r">{formatCurrent(entry.summary.designCurrentA)}</td>
|
||||
<td className="pd-r">{formatKilowatt(entry.cumulatedDemandPowerKw)}</td>
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colSpan={7}>Summe aller Verteiler (ohne übergeordneten GZF)</td>
|
||||
<td className="pd-r">{formatKilowatt(sum((entry) => entry.summary.installedPowerKw))}</td>
|
||||
<td className="pd-r">{formatKilowatt(sum((entry) => entry.summary.devicePowerKw))}</td>
|
||||
<td />
|
||||
<td className="pd-r">{formatKilowatt(sum((entry) => entry.summary.demandPowerKw))}</td>
|
||||
<td />
|
||||
<td />
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<p className="pd-note">
|
||||
<strong>Vorläufig:</strong> „Gespeist von“ ist noch ein Freitextfeld. Struktur und
|
||||
„Σ inkl. unterlagert“ werden nur über gleichlautende Verteilernummern bzw.
|
||||
-bezeichnungen zugeordnet und einfach addiert (kein übergeordneter GZF). Die fachliche
|
||||
Verknüpfung und Summierung folgt.
|
||||
</p>
|
||||
<p className="pd-legend">
|
||||
P<sub>inst</sub> = Anzahl × Leistung je Stück · P<sub>Geräte</sub> = mit Geräte-GZF ·
|
||||
P<sub>gl</sub> = mit Verteiler-GZF · I<sub>B</sub> = S / (√3 · {project.threePhaseVoltageV} V),
|
||||
S aus cos φ je Gerätezeile
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<SheetFooter project={project} stand={stand} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
const planColumnCount = 11;
|
||||
|
||||
function roomLabel(row: CircuitTreeDeviceRowDto) {
|
||||
return row.roomNumberSnapshot
|
||||
? `${row.roomNumberSnapshot} ${row.roomNameSnapshot ?? ""}`.trim()
|
||||
: "–";
|
||||
}
|
||||
|
||||
function ComponentRow({ component }: { component: CircuitTreeComponentDto }) {
|
||||
return (
|
||||
<tr className="pd-component">
|
||||
<td className="pd-bmk">{component.equipmentIdentifier}</td>
|
||||
<td colSpan={6}>{component.name}</td>
|
||||
<td>{formatProtectionDevice(component.protectionDevice) ?? "–"}</td>
|
||||
<td colSpan={3} />
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
function CircuitRows({ circuit }: { circuit: CircuitTreeCircuitDto }) {
|
||||
const rows = circuit.deviceRows;
|
||||
const circuitCells = (
|
||||
<>
|
||||
<td>{formatProtectionDevice(circuit.protectionDevice) ?? <Open />}</td>
|
||||
<td>{formatCircuitCable(circuit) ?? (circuit.isReserve ? "–" : <Open />)}</td>
|
||||
<td className="pd-r">
|
||||
{circuit.cableLength ?? (circuit.isReserve ? "–" : <Open />)}
|
||||
</td>
|
||||
<td>{circuit.remark ?? ""}</td>
|
||||
</>
|
||||
);
|
||||
if (rows.length === 0) {
|
||||
return (
|
||||
<tr className="pd-circuit pd-reserve">
|
||||
<td className="pd-bmk">{circuit.equipmentIdentifier}</td>
|
||||
<td>{circuit.displayName ?? "Reserve"}</td>
|
||||
<td>–</td>
|
||||
<td /><td /><td />
|
||||
<td className="pd-r">–</td>
|
||||
{circuitCells}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
if (rows.length === 1) {
|
||||
const row = rows[0];
|
||||
const title = circuit.displayName ?? row.displayName;
|
||||
return (
|
||||
<tr className="pd-circuit">
|
||||
<td className="pd-bmk">{circuit.equipmentIdentifier}</td>
|
||||
<td>
|
||||
{title}
|
||||
{title !== row.displayName ? (
|
||||
<span className="pd-sub"> · {row.displayName}</span>
|
||||
) : null}
|
||||
</td>
|
||||
<td>{roomLabel(row)}</td>
|
||||
<td className="pd-r">{row.quantity}</td>
|
||||
<td className="pd-r">{formatUnitPower(row.powerPerUnit)}</td>
|
||||
<td className="pd-r">{formatFactor(row.simultaneityFactor)}</td>
|
||||
<td className="pd-r"><strong>{formatKilowatt(circuit.circuitTotalPower)}</strong></td>
|
||||
{circuitCells}
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<tr className="pd-circuit">
|
||||
<td className="pd-bmk">{circuit.equipmentIdentifier}</td>
|
||||
<td>{circuit.displayName ?? ""}</td>
|
||||
<td /><td /><td /><td />
|
||||
<td className="pd-r"><strong>{formatKilowatt(circuit.circuitTotalPower)}</strong></td>
|
||||
{circuitCells}
|
||||
</tr>
|
||||
{rows.map((row) => (
|
||||
<tr className="pd-device" key={row.id}>
|
||||
<td />
|
||||
<td>{row.displayName}</td>
|
||||
<td>{roomLabel(row)}</td>
|
||||
<td className="pd-r">{row.quantity}</td>
|
||||
<td className="pd-r">{formatUnitPower(row.powerPerUnit)}</td>
|
||||
<td className="pd-r">{formatFactor(row.simultaneityFactor)}</td>
|
||||
<td className="pd-r">{formatKilowatt(row.rowTotalPower)}</td>
|
||||
<td colSpan={4} />
|
||||
</tr>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function BoardPlanSheet({
|
||||
entry,
|
||||
project,
|
||||
logoSrc,
|
||||
stand,
|
||||
}: {
|
||||
entry: PrintBoardHierarchyEntry;
|
||||
project: ProjectDto;
|
||||
logoSrc: string | null;
|
||||
stand: string;
|
||||
}) {
|
||||
const { summary } = entry;
|
||||
const { board, tree } = summary;
|
||||
return (
|
||||
<section className="pd-sheet pd-landscape">
|
||||
<table className="pd-grid pd-plan">
|
||||
{/* The title block lives in <thead> so it repeats on every printed page. */}
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="pd-tb-cell" colSpan={planColumnCount}>
|
||||
<TitleBlock
|
||||
kicker="Verteilerplan"
|
||||
logoSrc={logoSrc}
|
||||
name={board.name}
|
||||
number={board.boardNumber}
|
||||
project={project}
|
||||
rows={[
|
||||
["Örtlichkeit", [board.location, summary.floorName].filter(Boolean).join(" · ") || "–"],
|
||||
["Netzart", board.supplyType ?? "–"],
|
||||
["Gespeist von", board.fedFrom ?? "–"],
|
||||
[
|
||||
<>P<sub>gl</sub></>,
|
||||
<>
|
||||
{formatKilowatt(summary.demandPowerKw)} kW · I<sub>B</sub>{" "}
|
||||
{formatCurrent(summary.designCurrentA)} A
|
||||
</>,
|
||||
],
|
||||
]}
|
||||
stand={stand}
|
||||
/>
|
||||
</th>
|
||||
</tr>
|
||||
<tr className="pd-head">
|
||||
<th>SK-Nr.</th><th>Bezeichnung</th><th>Raum</th>
|
||||
<th className="pd-r">Anz.</th>
|
||||
<th className="pd-r">P/Stk.<br />[kW]</th>
|
||||
<th className="pd-r">GZF</th>
|
||||
<th className="pd-r">P<br />[kW]</th>
|
||||
<th>Schutz</th><th>Kabel</th>
|
||||
<th className="pd-r">Länge<br />[m]</th>
|
||||
<th>Bemerkung</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{tree.headerComponents.map((component) => (
|
||||
<ComponentRow component={component} key={component.id} />
|
||||
))}
|
||||
{tree.sections.map((section) => (
|
||||
<SectionRows key={section.id} section={section} />
|
||||
))}
|
||||
{tree.footerComponents.map((component) => (
|
||||
<ComponentRow component={component} key={component.id} />
|
||||
))}
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td />
|
||||
<td colSpan={5}>
|
||||
Summe Verteiler (P<sub>inst</sub> {formatKilowatt(summary.installedPowerKw)} kW)
|
||||
</td>
|
||||
<td className="pd-r">{formatKilowatt(summary.devicePowerKw)}</td>
|
||||
<td colSpan={4} />
|
||||
</tr>
|
||||
<tr>
|
||||
<td />
|
||||
<td colSpan={5}>Gleichzeitigkeitsfaktor Verteiler</td>
|
||||
<td className="pd-r">{formatFactor(summary.simultaneityFactor)}</td>
|
||||
<td colSpan={4} />
|
||||
</tr>
|
||||
<tr className="pd-grand">
|
||||
<td />
|
||||
<td colSpan={5}>Gleichzeitige Leistung P<sub>gl</sub></td>
|
||||
<td className="pd-r">{formatKilowatt(summary.demandPowerKw)}</td>
|
||||
<td colSpan={4}>I<sub>B</sub> ≈ {formatCurrent(summary.designCurrentA)} A</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
<SheetFooter project={project} stand={stand} />
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function SectionRows({
|
||||
section,
|
||||
}: {
|
||||
section: PrintBoardHierarchyEntry["summary"]["tree"]["sections"][number];
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<tr className="pd-group">
|
||||
<td className="pd-bmk">{section.prefix}</td>
|
||||
<td colSpan={planColumnCount - 1}>{section.displayName}</td>
|
||||
</tr>
|
||||
{section.components.map((component) => (
|
||||
<ComponentRow component={component} key={component.id} />
|
||||
))}
|
||||
{section.circuits.map((circuit) => (
|
||||
<CircuitRows circuit={circuit} key={circuit.id} />
|
||||
))}
|
||||
<tr className="pd-subtotal">
|
||||
<td />
|
||||
<td colSpan={5}>Summe {section.displayName}</td>
|
||||
<td className="pd-r">{formatKilowatt(section.sectionTotalPower)}</td>
|
||||
<td colSpan={4} />
|
||||
</tr>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function PrintDocument({
|
||||
project,
|
||||
entries,
|
||||
namedSnapshots,
|
||||
logoSrc,
|
||||
printedAt,
|
||||
}: PrintDocumentProps) {
|
||||
const stand = dateFormat.format(printedAt);
|
||||
return (
|
||||
<div className="pd-pages">
|
||||
<CoverSheet
|
||||
entries={entries}
|
||||
logoSrc={logoSrc}
|
||||
namedSnapshots={namedSnapshots}
|
||||
project={project}
|
||||
stand={stand}
|
||||
/>
|
||||
<OverviewSheet entries={entries} logoSrc={logoSrc} project={project} stand={stand} />
|
||||
{entries.map((entry) => (
|
||||
<BoardPlanSheet
|
||||
entry={entry}
|
||||
key={entry.summary.board.id}
|
||||
logoSrc={logoSrc}
|
||||
project={project}
|
||||
stand={stand}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue