"use client"; import { type FormEvent, useMemo, useState } from "react"; import { CONDUCTOR_MATERIALS, GROUPING_LABELS, GROUPING_OPTIONS, HARMONIC_NEUTRAL_LOAD_LABELS, HARMONIC_NEUTRAL_LOAD_OPTIONS, INSULATION_MATERIALS, INSULATION_MATERIAL_LABELS, LAYING_METHODS, LAYING_METHOD_GROUP, LAYING_METHOD_LABELS, TEMPERATURE_FACTOR_AIR, TEMPERATURE_FACTOR_GROUND, type CableSizingAlert, type CableSizingInput, type CableSizingResult, type ConductorMaterial, type HarmonicNeutralLoad, type InsulationMaterial, type LayingMethod, } from "../../cable-sizing/domain/cable-sizing-calculation"; import { calculateCableSizing } from "./cable-sizing-api"; import type { CircuitTreeCircuitDto } from "../types"; import { FormModal } from "./form-modal"; interface CableSizingModalProps { circuit: CircuitTreeCircuitDto; isSaving: boolean; projectId: string; onClose: () => void; onApply: (patch: { cableCrossSection: string; cableType?: string; cableLength?: number; }) => Promise; } function temperatureOptions(method: LayingMethod) { const table = LAYING_METHOD_GROUP[method] === "air" ? TEMPERATURE_FACTOR_AIR : TEMPERATURE_FACTOR_GROUND; return Object.keys(table).map(Number).sort((a, b) => a - b); } export function CableSizingModal({ circuit, isSaving, projectId, onClose, onApply, }: CableSizingModalProps) { const [layingMethod, setLayingMethod] = useState("C"); const [insulation, setInsulation] = useState("pvc"); const [conductorMaterial, setConductorMaterial] = useState("copper"); const [ambientTemperatureC, setAmbientTemperatureC] = useState(30); const [groupingCircuits, setGroupingCircuits] = useState(1); const [cosPhi, setCosPhi] = useState(1); const [maxVoltageDropPercent, setMaxVoltageDropPercent] = useState(3); const [harmonicNeutralLoad, setHarmonicNeutralLoad] = useState("none"); const [lengthM, setLengthM] = useState(circuit.cableLength ?? 0); const [phase, setPhase] = useState<1 | 3>(circuit.voltage === 400 ? 3 : 1); // Manual override: applying this never requires a calculation. Prefilled // from the circuit's current values so opening the modal on an already // specified cable doesn't lose that data. const [manualCableType, setManualCableType] = useState(circuit.cableType ?? ""); const [manualCrossSection, setManualCrossSection] = useState(circuit.cableCrossSection ?? ""); const [calculating, setCalculating] = useState(false); const [calculationId, setCalculationId] = useState(null); const [result, setResult] = useState(null); const [alerts, setAlerts] = useState([]); const [error, setError] = useState(null); const voltage = circuit.voltage ?? (phase === 1 ? 230 : 400); const input: CableSizingInput = useMemo( () => ({ phase, mode: "power", powerKw: circuit.circuitTotalPower, cosPhi, voltage, lengthM, layingMethod, conductorMaterial, insulation, ambientTemperatureC, groupingCircuits, maxVoltageDropPercent, harmonicNeutralLoad, existingProtectionRatedCurrentA: circuit.protectionDevice?.ratedCurrentA, }), [ phase, circuit.circuitTotalPower, cosPhi, voltage, lengthM, layingMethod, conductorMaterial, insulation, ambientTemperatureC, groupingCircuits, maxVoltageDropPercent, harmonicNeutralLoad, circuit.protectionDevice?.ratedCurrentA, ] ); function handleLayingMethodChange(method: LayingMethod) { setLayingMethod(method); const options = temperatureOptions(method); const reference = LAYING_METHOD_GROUP[method] === "air" ? 30 : 20; setAmbientTemperatureC(options.includes(reference) ? reference : options[0]); setResult(null); setCalculationId(null); } async function handleCalculate() { setCalculating(true); setError(null); try { const response = await calculateCableSizing(input, { projectId, circuitId: circuit.id, equipmentIdentifier: circuit.equipmentIdentifier, }); setResult(response.result); setAlerts(response.alerts); setCalculationId(response.calculationId); // Calculating fills the manual field as a suggestion, but that field // stays the single source of truth for what gets applied - the user // can still edit it by hand before submitting. if (response.result.dataVerified && response.result.recommendedCrossSectionMm2) { setManualCrossSection(`${response.result.recommendedCrossSectionMm2} mm²`); } } catch (err) { setError(err instanceof Error ? err.message : "Berechnung fehlgeschlagen"); } finally { setCalculating(false); } } async function handleSubmit(event: FormEvent) { event.preventDefault(); if (!manualCrossSection.trim()) return; await onApply({ cableCrossSection: manualCrossSection.trim(), cableType: manualCableType.trim() || undefined, cableLength: lengthM, }); } return (
setLengthM(Number(event.target.value))} type="number" value={lengthM} />
{phase === 3 && (
)}
setCosPhi(Number(event.target.value))} step={0.05} type="range" value={cosPhi} /> {cosPhi.toFixed(2)}
setMaxVoltageDropPercent(Number(event.target.value))} step={0.5} type="range" value={maxVoltageDropPercent} /> {maxVoltageDropPercent.toFixed(1)} %
{error &&

{error}

} {result && !result.dataVerified && (
Für Verlegeart {layingMethod} mit Isolierstoff {insulation.toUpperCase()} liegen keine geprüften Strombelastbarkeits-Tabellenwerte vor - keine Dimensionierung möglich. Bitte eine andere Verlegeart/Isolierstoff-Kombination wählen.
)} {result && result.dataVerified && (
Betriebsstrom Ib
{result.operatingCurrentA.toFixed(1)} A
Bemessungsstrom (für Querschnittswahl){circuit.protectionDevice ? " *" : ""}
{result.designCurrentA.toFixed(1)} A
Empfohlener Querschnitt
{result.recommendedCrossSectionMm2 ? `${result.recommendedCrossSectionMm2} mm²` : "-"}
ΔU bei Empfehlung
{result.voltageDropAtRecommendedPercent != null ? `${result.voltageDropAtRecommendedPercent.toFixed(2)} %` : "-"}
Korrekturfaktor
{result.combinedDerationFactor.toFixed(2)}
{alerts.map((alert, index) => (
{alert.text}
))} {circuit.protectionDevice && (

* Bemessungsstrom = Maximum aus Betriebsstrom und vorhandener Sicherung ({circuit.protectionDevice.ratedCurrentA} A) - die Sicherung löst erst bei ihrem eigenen Bemessungsstrom aus, das Kabel muss also dafür ausgelegt sein, nicht nur für die tatsächliche Last.

)}
)}

Querschnitt manuell eingeben oder eine Berechnung oben übernehmen - beides schreibt in dasselbe Feld, du kannst es vor dem Übernehmen frei anpassen.

setManualCableType(event.target.value)} placeholder="z.B. NYM-J 3x1.5" value={manualCableType} />
setManualCrossSection(event.target.value)} placeholder="z.B. 4 mm²" value={manualCrossSection} />
); }