"use client"; import type { ReactNode } from "react"; import type { CircuitTreeCircuitDto, CircuitTreeComponentDto, CircuitTreeDeviceRowDto, OfficeProfileDto, ProjectDto, ProjectSnapshotMetadataDto, } from "../types"; import { formatCircuitCable, formatCurrent, formatFactor, formatFeedingReference, formatKilowatt, formatProtectionDevice, formatUnitPower, type PrintBoardHierarchyEntry, } from "./print-document-model"; 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 ; } return multiline ? {value} : <>{value}; } const dateFormat = new Intl.DateTimeFormat("de-DE", { day: "2-digit", month: "2-digit", year: "numeric", }); function Placeholder({ label = "ergänzen" }: { label?: string }) { return [{label}]; } function Open() { return offen; } function Logo({ src, className }: { src: string | null; className: string }) { return src ? ( // eslint-disable-next-line @next/next/no-img-element Logo ) : ( Logo ); } function SheetFooter({ project, stand }: { project: ProjectDto; stand: string }) { return ( ); } 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 (
{kicker}
{number ? {number} : null} {name}
{rows.map(([label, value], index) => (
{label}
{value}
))}
Projekt
{project.name}
Projekt-Nr.
{project.internalProjectNumber ?? "–"}
Stand
{stand}
Datenstand
Rev. {project.currentRevision}
); } function boardLabel(entry: PrintBoardHierarchyEntry) { const { board } = entry.summary; return board.boardNumber ? `${board.boardNumber} – ${board.name}` : board.name; } function CoverSheet({ project, office, entries, namedSnapshots, logoSrc, stand, }: Omit & { stand: string }) { return (
Projektdokumentation Elektrotechnik

Leistungsbilanz

Verteilerübersicht und Verteilerpläne

Projekt
Bauvorhaben{project.name}
Anschrift
Bauherr
Projektnummer intern
Projektnummer extern
Leistungsphase
GewerkKG 440 – Starkstromanlagen
Beschreibung{project.description ?? "–"}
Planverfasser
Büro
Anschrift
Projektleitung
Bearbeitung
Dokument
DokumentartLeistungsbilanz
Stand{stand}
DatenstandRevision {project.currentRevision}
Verteiler{entries.length}
{namedSnapshots.length ? ( namedSnapshots.map((snapshot, index) => ( {/* Snapshots do not record an editor yet; the project's current editor is shown for every entry. */} )) ) : ( )}
Planstände
IndexDatumÄnderungBearbeitung
{String.fromCharCode(97 + (index % 26))} {dateFormat.format(new Date(snapshot.createdAtIso))} {snapshot.name}
–
{entries.map((entry, index) => ( ))}
Inhalt
1Verteilerübersicht und Leistungssummen
2.{index + 1} Verteilerplan {boardLabel(entry)}
); } function StructureNode({ entry, entries, }: { entry: PrintBoardHierarchyEntry; entries: PrintBoardHierarchyEntry[]; }) { const children = entries.filter( (candidate) => candidate.parentBoardId === entry.summary.board.id ); const { board } = entry.summary; return (
  • {board.boardNumber ?? board.name} {board.boardNumber ? {board.name} : null} {formatKilowatt(entry.summary.demandPowerKw)} kW
    {children.length ? (
      {children.map((child) => ( ))}
    ) : null}
  • ); } function OverviewSheet({ project, entries, logoSrc, stand, }: { project: ProjectDto; entries: PrintBoardHierarchyEntry[]; logoSrc: string | null; stand: string; }) { const roots = entries.filter((entry) => entry.parentBoardId === null); // Root boards may still carry an external feed description such as // "HAK / Zähleranlage"; logical board links determine the actual tree. const feedGroups = new Map(); 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 (

    Versorgungsstruktur

      {[...feedGroups].map(([feed, feedRoots]) => { const nodes = feedRoots.map((root) => ( )); return feed ? (
    • Einspeisung {feed}
        {nodes}
    • ) : ( nodes ); })}
    {entries.map((entry) => { const { board } = entry.summary; return ( ); })}
    Nr.BezeichnungÖrtlichkeitEtageNetzGespeist von SK Pinst
    [kW]
    P vor Vert.-GZF
    [kW]
    GZF
    Vert.
    Pgl inkl. UV
    [kW]
    IB
    [A]
    {entry.depth ? "└ " : ""} {board.boardNumber ?? "–"} {board.name} {board.location ?? "–"} {entry.summary.floorName ?? "–"} {board.supplyType ?? "–"} {formatFeedingReference(entry.summary)} {entry.summary.circuitCount} {formatKilowatt(entry.summary.installedPowerKw)} {formatKilowatt(entry.summary.powerBeforeBoardSimultaneityKw)} {formatFactor(entry.summary.simultaneityFactor)} {formatKilowatt(entry.summary.demandPowerKw)} {formatCurrent(entry.summary.designCurrentA)}
    Summe der Einspeisungen {formatKilowatt(sum((entry) => entry.summary.installedPowerKw))} {formatKilowatt(sum((entry) => entry.parentBoardId === null ? entry.summary.powerBeforeBoardSimultaneityKw : 0 ))} {formatKilowatt(sum((entry) => entry.parentBoardId === null ? entry.summary.demandPowerKw : 0 ))}

    Die Struktur folgt den logischen Verknüpfungen. Eine Unterverteilung wird im gewählten Abgang mit ihrer Leistung nach eigenem Verteiler-GZF berücksichtigt. Ohne gewählten Abgang erscheint sie nur in der Hierarchie.

    Pinst = lokale Geräte ohne GZF · P vor Vert.-GZF = Geräte-GZF einschließlich gespeister Unterverteilungen · Pgl = zusätzlich mit Verteiler-GZF · IB aus S und der Phasenart des speisenden Abgangs

    ); } const planColumnCount = 11; function roomLabel(row: CircuitTreeDeviceRowDto) { return row.roomNumberSnapshot ? `${row.roomNumberSnapshot} ${row.roomNameSnapshot ?? ""}`.trim() : "–"; } function ComponentRow({ component }: { component: CircuitTreeComponentDto }) { return ( {component.equipmentIdentifier} {component.name} {formatProtectionDevice(component.protectionDevice) ?? "–"} ); } function CircuitRows({ circuit }: { circuit: CircuitTreeCircuitDto }) { const rows = circuit.deviceRows; const downstreamBoards = circuit.downstreamBoardLoads ?? []; const circuitCells = ( <> {formatProtectionDevice(circuit.protectionDevice) ?? } {formatCircuitCable(circuit) ?? (circuit.isReserve ? "–" : )} {circuit.cableLength ?? (circuit.isReserve ? "–" : )} {circuit.remark ?? ""} ); if (rows.length === 0 && downstreamBoards.length === 0) { return ( {circuit.equipmentIdentifier} {circuit.displayName ?? "Reserve"} – – {circuitCells} ); } if (rows.length === 1 && downstreamBoards.length === 0) { const row = rows[0]; const title = circuit.displayName ?? row.displayName; return ( {circuit.equipmentIdentifier} {title} {title !== row.displayName ? ( · {row.displayName} ) : null} {roomLabel(row)} {row.quantity} {formatUnitPower(row.powerPerUnit)} {formatFactor(row.simultaneityFactor)} {formatKilowatt(circuit.circuitTotalPower)} {circuitCells} ); } return ( <> {circuit.equipmentIdentifier} {circuit.displayName ?? ""} {formatKilowatt(circuit.circuitTotalPower)} {circuitCells} {rows.map((row) => ( {row.displayName} {roomLabel(row)} {row.quantity} {formatUnitPower(row.powerPerUnit)} {formatFactor(row.simultaneityFactor)} {formatKilowatt(row.rowTotalPower)} ))} {downstreamBoards.map((load) => ( Unterverteilung {load.boardNumber ?? load.name} – {formatKilowatt(load.activePowerKw)} Automatisch aus der verknüpften Verteilung ))} ); } function BoardPlanSheet({ entry, project, logoSrc, stand, }: { entry: PrintBoardHierarchyEntry; project: ProjectDto; logoSrc: string | null; stand: string; }) { const { summary } = entry; const { board, tree } = summary; return (
    {/* The title block lives in so it repeats on every printed page. */} {tree.headerComponents.map((component) => ( ))} {tree.sections.map((section) => ( ))} {tree.footerComponents.map((component) => ( ))}
    Pgl, <> {formatKilowatt(summary.demandPowerKw)} kW · IB{" "} {formatCurrent(summary.designCurrentA)} A , ], ]} stand={stand} />
    SK-Nr.BezeichnungRaum Anz. P/Stk.
    [kW]
    GZF P
    [kW]
    SchutzKabel Länge
    [m]
    Bemerkung
    Summe Verteiler (Pinst {formatKilowatt(summary.installedPowerKw)} kW) {formatKilowatt(summary.powerBeforeBoardSimultaneityKw)}
    Gleichzeitigkeitsfaktor Verteiler {formatFactor(summary.simultaneityFactor)}
    Gleichzeitige Leistung Pgl {formatKilowatt(summary.demandPowerKw)} IB ≈ {formatCurrent(summary.designCurrentA)} A
    ); } function SectionRows({ section, }: { section: PrintBoardHierarchyEntry["summary"]["tree"]["sections"][number]; }) { return ( <> {section.prefix} {section.displayName} {section.components.map((component) => ( ))} {section.circuits.map((circuit) => ( ))} Summe {section.displayName} {formatKilowatt(section.sectionTotalPower)} ); } export function PrintDocument({ project, office, entries, namedSnapshots, logoSrc, printedAt, }: PrintDocumentProps) { const stand = dateFormat.format(printedAt); return (
    {entries.map((entry) => ( ))}
    ); }