forked from jappel/leistungsbilanz-ts
cable-sizing: max length, socket minimum, manual-entry warning, SVG icon
- Cross-section rows now include maxLengthForVoltageDropM, the inverse of the voltage-drop formula: the longest single run that stays within the requested max voltage drop at the given load/cosPhi/phase. Shown as a KPI and in the ok-summary alert. - Added PRACTICAL_MINIMUM_CROSS_SECTION_MM2 (2.5 mm² for single_phase circuits) - a planning convention already named in this projects own docs/spec/06-future-sizing-and-calculations.md, not a thermal/ voltage-drop requirement. Only ever raises a calculated recommendation, never lowers one that already needs more. circuitCategory is looked up from the circuits section and passed through from the editor. - The manual cross-section field now warns (not blocks - manual override must stay possible per the same spec doc) when the typed value is not a standard cross-section, is smaller than the last calculation, or is below the practical minimum for single_phase circuits. - Modal title now includes the circuit displayName next to the equipment identifier. - Replaced the calculator emoji trigger icon with an inline SVG - the emoji did not render in at least one tested environment (missing font glyph), SVG has no such dependency.
This commit is contained in:
parent
68d2bad6e8
commit
287da1c0e7
6 changed files with 210 additions and 6 deletions
|
|
@ -3,6 +3,7 @@
|
|||
import { type FormEvent, useMemo, useState } from "react";
|
||||
import {
|
||||
CONDUCTOR_MATERIALS,
|
||||
CROSS_SECTIONS_MM2,
|
||||
GROUPING_LABELS,
|
||||
GROUPING_OPTIONS,
|
||||
HARMONIC_NEUTRAL_LOAD_LABELS,
|
||||
|
|
@ -12,11 +13,13 @@ import {
|
|||
LAYING_METHODS,
|
||||
LAYING_METHOD_GROUP,
|
||||
LAYING_METHOD_LABELS,
|
||||
PRACTICAL_MINIMUM_CROSS_SECTION_MM2,
|
||||
TEMPERATURE_FACTOR_AIR,
|
||||
TEMPERATURE_FACTOR_GROUND,
|
||||
type CableSizingAlert,
|
||||
type CableSizingInput,
|
||||
type CableSizingResult,
|
||||
type CircuitCategory,
|
||||
type ConductorMaterial,
|
||||
type HarmonicNeutralLoad,
|
||||
type InsulationMaterial,
|
||||
|
|
@ -28,6 +31,7 @@ import { FormModal } from "./form-modal";
|
|||
|
||||
interface CableSizingModalProps {
|
||||
circuit: CircuitTreeCircuitDto;
|
||||
circuitCategory?: CircuitCategory;
|
||||
isSaving: boolean;
|
||||
projectId: string;
|
||||
onClose: () => void;
|
||||
|
|
@ -44,8 +48,19 @@ function temperatureOptions(method: LayingMethod) {
|
|||
return Object.keys(table).map(Number).sort((a, b) => a - b);
|
||||
}
|
||||
|
||||
// Extracts the first decimal number from a free-text cross-section entry
|
||||
// like "2.5 mm²" or "2,5". Returns null if nothing parseable is found -
|
||||
// used only for the manual-entry sanity check below, never persisted.
|
||||
function parseCrossSectionMm2(text: string): number | null {
|
||||
const match = text.match(/([0-9]+(?:[.,][0-9]+)?)/);
|
||||
if (!match) return null;
|
||||
const value = Number(match[1].replace(",", "."));
|
||||
return Number.isFinite(value) ? value : null;
|
||||
}
|
||||
|
||||
export function CableSizingModal({
|
||||
circuit,
|
||||
circuitCategory,
|
||||
isSaving,
|
||||
projectId,
|
||||
onClose,
|
||||
|
|
@ -92,6 +107,7 @@ export function CableSizingModal({
|
|||
maxVoltageDropPercent,
|
||||
harmonicNeutralLoad,
|
||||
existingProtectionRatedCurrentA: circuit.protectionDevice?.ratedCurrentA,
|
||||
circuitCategory,
|
||||
}),
|
||||
[
|
||||
phase,
|
||||
|
|
@ -107,9 +123,34 @@ export function CableSizingModal({
|
|||
maxVoltageDropPercent,
|
||||
harmonicNeutralLoad,
|
||||
circuit.protectionDevice?.ratedCurrentA,
|
||||
circuitCategory,
|
||||
]
|
||||
);
|
||||
|
||||
const manualCrossSectionWarning = useMemo(() => {
|
||||
const trimmed = manualCrossSection.trim();
|
||||
if (!trimmed) return null;
|
||||
const parsed = parseCrossSectionMm2(trimmed);
|
||||
if (parsed == null) return null;
|
||||
if (!(CROSS_SECTIONS_MM2 as readonly number[]).includes(parsed)) {
|
||||
return `${parsed} mm² ist kein Standard-Querschnitt (${CROSS_SECTIONS_MM2.join(", ")}).`;
|
||||
}
|
||||
if (
|
||||
result?.dataVerified &&
|
||||
result.recommendedCrossSectionMm2 != null &&
|
||||
parsed < result.recommendedCrossSectionMm2
|
||||
) {
|
||||
return `${parsed} mm² ist kleiner als die zuletzt berechnete Empfehlung (${result.recommendedCrossSectionMm2} mm²) - passt nicht zu Last${circuit.protectionDevice ? "/Sicherung" : ""} und Verlegeart.`;
|
||||
}
|
||||
const practicalMinimum = circuitCategory
|
||||
? PRACTICAL_MINIMUM_CROSS_SECTION_MM2[circuitCategory]
|
||||
: undefined;
|
||||
if (practicalMinimum != null && parsed < practicalMinimum) {
|
||||
return `${parsed} mm² liegt unter dem Praxis-Mindestquerschnitt für 1-phasige Stromkreise (${practicalMinimum} mm²).`;
|
||||
}
|
||||
return null;
|
||||
}, [manualCrossSection, result, circuit.protectionDevice, circuitCategory]);
|
||||
|
||||
function handleLayingMethodChange(method: LayingMethod) {
|
||||
setLayingMethod(method);
|
||||
const options = temperatureOptions(method);
|
||||
|
|
@ -162,7 +203,9 @@ export function CableSizingModal({
|
|||
onSubmit={handleSubmit}
|
||||
submitDisabled={!manualCrossSection.trim()}
|
||||
submitLabel="Übernehmen"
|
||||
title={`Kabel dimensionieren - ${circuit.equipmentIdentifier}`}
|
||||
title={`Kabel dimensionieren - ${circuit.equipmentIdentifier}${
|
||||
circuit.displayName ? ` (${circuit.displayName})` : ""
|
||||
}`}
|
||||
>
|
||||
<div className="row g-3">
|
||||
<div className="col-12 col-md-4">
|
||||
|
|
@ -401,6 +444,16 @@ export function CableSizingModal({
|
|||
<div className="text-muted small">Korrekturfaktor</div>
|
||||
<div>{result.combinedDerationFactor.toFixed(2)}</div>
|
||||
</div>
|
||||
<div className="col-6 col-md-3">
|
||||
<div className="text-muted small">Max. Länge (ΔU-Grenze)</div>
|
||||
<div>
|
||||
{result.rows.find((row) => row.recommended)?.maxLengthForVoltageDropM != null
|
||||
? `${result.rows
|
||||
.find((row) => row.recommended)!
|
||||
.maxLengthForVoltageDropM!.toFixed(0)} m`
|
||||
: "-"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{alerts.map((alert, index) => (
|
||||
<div
|
||||
|
|
@ -459,6 +512,11 @@ export function CableSizingModal({
|
|||
value={manualCrossSection}
|
||||
/>
|
||||
</div>
|
||||
{manualCrossSectionWarning && (
|
||||
<div className="col-12">
|
||||
<div className="alert alert-warning mb-0">{manualCrossSectionWarning}</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</FormModal>
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue