cable-sizing: size for the protective device, not just the load; manual entry; icon position

- calculateCableSizing now selects cross-section against
  designCurrentA = max(operatingCurrentA, existingProtectionRatedCurrentA)
  instead of the load current alone. A breaker only trips at its own rated
  current (In), so a cable sized for the actual load could overheat under
  sustained current below that threshold - the standard In <= Iz rule.
  Voltage-drop calculation still uses the real operating current, only the
  capacity check changed. Alerts and the ok-summary now name the breaker as
  the limiting factor when it is one.
- Added a manual cable type/cross-section entry in the modal itself:
  running a calculation pre-fills these fields as a suggestion, but they
  are the single field that actually gets applied, and stay editable -
  this restores the direct manual-entry capability the modal replaced when
  it took over the cableSummary/cableCrossSection cell click.
- Fixed calculator icon position from a trailing ::after (inconsistent
  position depending on cell text length, easy to miss on empty cells) to
  a fixed-position ::before, so it is always in the same place regardless
  of whether cable data is already filled in.
- No new display for the existing protection device - it already has its
  own Schutz column in this app.
This commit is contained in:
Grovy311 2026-08-07 18:02:28 +02:00
parent 05964fc15c
commit 68d2bad6e8
5 changed files with 145 additions and 29 deletions

View file

@ -31,7 +31,11 @@ interface CableSizingModalProps {
isSaving: boolean;
projectId: string;
onClose: () => void;
onApply: (patch: { cableCrossSection: string; cableLength?: number }) => Promise<void>;
onApply: (patch: {
cableCrossSection: string;
cableType?: string;
cableLength?: number;
}) => Promise<void>;
}
function temperatureOptions(method: LayingMethod) {
@ -58,6 +62,12 @@ export function CableSizingModal({
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<string | null>(null);
const [result, setResult] = useState<CableSizingResult | null>(null);
@ -121,6 +131,12 @@ export function CableSizingModal({
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 {
@ -130,21 +146,22 @@ export function CableSizingModal({
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
if (!result?.recommendedCrossSectionMm2) return;
if (!manualCrossSection.trim()) return;
await onApply({
cableCrossSection: `${result.recommendedCrossSectionMm2} mm²`,
cableCrossSection: manualCrossSection.trim(),
cableType: manualCableType.trim() || undefined,
cableLength: lengthM,
});
}
return (
<FormModal
description="Empfehlung auf Basis von Verlegeart, Isolierstoff, Temperatur und Häufung nach DIN VDE 0298-4. Ersetzt keine vollständige Norm-Prüfung; nicht verifizierte Verlegearten liefern bewusst kein Ergebnis. Du kannst das Ergebnis vor der Übernahme frei prüfen."
description="Empfehlung auf Basis von Verlegeart, Isolierstoff, Temperatur und Häufung nach DIN VDE 0298-4, oder Kabeltyp/Querschnitt direkt manuell eintragen. Ersetzt keine vollständige Norm-Prüfung; nicht verifizierte Verlegearten liefern bewusst kein Ergebnis."
isSaving={isSaving}
onClose={onClose}
onSubmit={handleSubmit}
submitDisabled={!result?.dataVerified || !result.recommendedCrossSectionMm2}
submitLabel="Empfehlung übernehmen"
submitDisabled={!manualCrossSection.trim()}
submitLabel="Übernehmen"
title={`Kabel dimensionieren - ${circuit.equipmentIdentifier}`}
>
<div className="row g-3">
@ -183,7 +200,6 @@ export function CableSizingModal({
value={`${circuit.circuitTotalPower.toFixed(2)} kW`}
/>
</div>
<div className="col-12 col-md-6">
<label className="form-label" htmlFor="cable-sizing-method">
Verlegeart (DIN VDE 0298-4)
@ -355,6 +371,14 @@ export function CableSizingModal({
<div className="text-muted small">Betriebsstrom Ib</div>
<div>{result.operatingCurrentA.toFixed(1)} A</div>
</div>
<div className="col-6 col-md-3">
<div className="text-muted small">
Bemessungsstrom (für Querschnittswahl){circuit.protectionDevice ? " *" : ""}
</div>
<div>
<strong>{result.designCurrentA.toFixed(1)} A</strong>
</div>
</div>
<div className="col-6 col-md-3">
<div className="text-muted small">Empfohlener Querschnitt</div>
<div>
@ -394,14 +418,48 @@ export function CableSizingModal({
{alert.text}
</div>
))}
{circuit.protectionDevice && (
<p className="text-muted small mt-2 mb-0">
* 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.
</p>
)}
</div>
)}
{calculationId ? null : (
<p className="text-muted small mt-2">
Zuerst berechnen, dann prüfen und ggf. übernehmen - nichts wird automatisch
geschrieben.
</p>
)}
<hr className="my-3" />
<p className="text-muted small mb-2">
Querschnitt manuell eingeben oder eine Berechnung oben übernehmen - beides schreibt in
dasselbe Feld, du kannst es vor dem Übernehmen frei anpassen.
</p>
<div className="row g-3">
<div className="col-12 col-md-6">
<label className="form-label" htmlFor="cable-sizing-manual-type">
Kabeltyp
</label>
<input
className="form-control"
id="cable-sizing-manual-type"
onChange={(event) => setManualCableType(event.target.value)}
placeholder="z.B. NYM-J 3x1.5"
value={manualCableType}
/>
</div>
<div className="col-12 col-md-6">
<label className="form-label" htmlFor="cable-sizing-manual-cross-section">
Querschnitt
</label>
<input
className="form-control"
id="cable-sizing-manual-cross-section"
onChange={(event) => setManualCrossSection(event.target.value)}
placeholder="z.B. 4 mm²"
value={manualCrossSection}
/>
</div>
</div>
</FormModal>
);
}

View file

@ -1403,6 +1403,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
async function handleApplyCableSizing(patch: {
cableCrossSection: string;
cableType?: string;
cableLength?: number;
}) {
const circuit = cableSizingEditorCircuit;