"use client"; import type { ReactNode } from "react"; import type { CircuitTreeCircuitDto, CircuitTreeComponentDto, CircuitTreeDeviceRowDto, OfficeProfileDto, ProjectDto, ProjectSnapshotMetadataDto, } from "../types"; import { formatCircuitCable, formatCurrent, formatFactor, 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); // 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(); 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]
    PGeräte
    [kW]
    GZF
    Vert.
    Pgl
    [kW]
    IB
    [A]
    Σ inkl.
    unterlagert [kW]
    {entry.depth ? "└ " : ""} {board.boardNumber ?? "–"} {board.name} {board.location ?? "–"} {entry.summary.floorName ?? "–"} {board.supplyType ?? "–"} {board.fedFrom ?? "–"} {entry.summary.circuitCount} {formatKilowatt(entry.summary.installedPowerKw)} {formatKilowatt(entry.summary.devicePowerKw)} {formatFactor(entry.summary.simultaneityFactor)} {formatKilowatt(entry.summary.demandPowerKw)} {formatCurrent(entry.summary.designCurrentA)} {formatKilowatt(entry.cumulatedDemandPowerKw)}
    Summe aller Verteiler (ohne übergeordneten GZF) {formatKilowatt(sum((entry) => entry.summary.installedPowerKw))} {formatKilowatt(sum((entry) => entry.summary.devicePowerKw))} {formatKilowatt(sum((entry) => entry.summary.demandPowerKw))}

    Vorläufig: „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.

    Pinst = Anzahl × Leistung je Stück · PGeräte = mit Geräte-GZF · Pgl = mit Verteiler-GZF · IB = S / (√3 · {project.threePhaseVoltageV} V), S aus cos φ je Gerätezeile

    ); } 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 circuitCells = ( <> {formatProtectionDevice(circuit.protectionDevice) ?? } {formatCircuitCable(circuit) ?? (circuit.isReserve ? "–" : )} {circuit.cableLength ?? (circuit.isReserve ? "–" : )} {circuit.remark ?? ""} ); if (rows.length === 0) { return ( {circuit.equipmentIdentifier} {circuit.displayName ?? "Reserve"} – – {circuitCells} ); } if (rows.length === 1) { 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)} ))} ); } 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.devicePowerKw)}
    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) => ( ))}
    ); }