forked from jappel/leistungsbilanz-ts
Add proposed cable-sizing module
Isolated module (mirrors src/external-model/ dependency direction) that adds an on-demand, DIN VDE 0298-4 referenced cable cross-section calculator: a click-on-a-circuit input mask for laying method, insulation, ambient temperature, grouping and voltage-drop limit that suggests a cross-section for the circuit cableCrossSection/cableLength fields. Applying a suggestion goes through the existing circuit.update command (expectedRevision, undo/redo) unchanged - nothing here writes to circuits directly or touches the revision/command system. See docs/cable-sizing-module.md for the full rationale, API contract, verification status and maintenance plan. Proposal, not yet reviewed by the project owner - see the docs file. 423 existing + 11 new tests pass, build:api/build:web/typecheck:scripts clean.
This commit is contained in:
parent
2e3e85a8e5
commit
8b8526b8df
17 changed files with 4179 additions and 2 deletions
71
src/frontend/components/cable-sizing-api.ts
Normal file
71
src/frontend/components/cable-sizing-api.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
// Thin, self-contained fetch client for the cable-sizing module. Deliberately
|
||||
// not merged into ../utils/api.ts to keep this module's frontend footprint a
|
||||
// single new file rather than growing the existing shared API file - see
|
||||
// docs/cable-sizing-module.md.
|
||||
import type {
|
||||
CableSizingAlert,
|
||||
CableSizingInput,
|
||||
CableSizingResult,
|
||||
InsulationMaterial,
|
||||
LayingMethod,
|
||||
} from "../../cable-sizing/domain/cable-sizing-calculation";
|
||||
|
||||
async function request<T>(url: string, init?: RequestInit): Promise<T> {
|
||||
const response = await fetch(url, {
|
||||
...init,
|
||||
headers: { "Content-Type": "application/json", ...init?.headers },
|
||||
cache: "no-store",
|
||||
});
|
||||
if (!response.ok) {
|
||||
let details = await response.text();
|
||||
try {
|
||||
const parsed = JSON.parse(details) as { error?: unknown };
|
||||
if (typeof parsed.error === "string") details = parsed.error;
|
||||
} catch {
|
||||
// keep raw text
|
||||
}
|
||||
throw new Error(details || `Anfrage fehlgeschlagen (Status ${response.status})`);
|
||||
}
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
export interface CalculateCableSizingResponse {
|
||||
calculationId: string;
|
||||
result: CableSizingResult;
|
||||
alerts: CableSizingAlert[];
|
||||
}
|
||||
|
||||
export function calculateCableSizing(
|
||||
input: CableSizingInput,
|
||||
context?: { projectId?: string; circuitId?: string; equipmentIdentifier?: string }
|
||||
): Promise<CalculateCableSizingResponse> {
|
||||
return request("/api/cable-sizing/calculate", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ ...input, context }),
|
||||
});
|
||||
}
|
||||
|
||||
export function markCableSizingCalculationApplied(calculationId: string): Promise<unknown> {
|
||||
return request(`/api/cable-sizing/calculations/${calculationId}/applied`, {
|
||||
method: "POST",
|
||||
});
|
||||
}
|
||||
|
||||
export interface LayingMethodOption {
|
||||
method: LayingMethod;
|
||||
label: string;
|
||||
dataVerified: boolean;
|
||||
}
|
||||
|
||||
export function listLayingMethods(): Promise<LayingMethodOption[]> {
|
||||
return request("/api/cable-sizing/laying-methods");
|
||||
}
|
||||
|
||||
export interface InsulationMaterialOption {
|
||||
insulation: InsulationMaterial;
|
||||
label: string;
|
||||
}
|
||||
|
||||
export function listInsulationMaterials(): Promise<InsulationMaterialOption[]> {
|
||||
return request("/api/cable-sizing/insulation-materials");
|
||||
}
|
||||
407
src/frontend/components/cable-sizing-modal.tsx
Normal file
407
src/frontend/components/cable-sizing-modal.tsx
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
"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; cableLength?: number }) => Promise<void>;
|
||||
}
|
||||
|
||||
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<LayingMethod>("C");
|
||||
const [insulation, setInsulation] = useState<InsulationMaterial>("pvc");
|
||||
const [conductorMaterial, setConductorMaterial] = useState<ConductorMaterial>("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<HarmonicNeutralLoad>("none");
|
||||
const [lengthM, setLengthM] = useState(circuit.cableLength ?? 0);
|
||||
const [phase, setPhase] = useState<1 | 3>(circuit.voltage === 400 ? 3 : 1);
|
||||
|
||||
const [calculating, setCalculating] = useState(false);
|
||||
const [calculationId, setCalculationId] = useState<string | null>(null);
|
||||
const [result, setResult] = useState<CableSizingResult | null>(null);
|
||||
const [alerts, setAlerts] = useState<CableSizingAlert[]>([]);
|
||||
const [error, setError] = useState<string | null>(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);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : "Berechnung fehlgeschlagen");
|
||||
} finally {
|
||||
setCalculating(false);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
if (!result?.recommendedCrossSectionMm2) return;
|
||||
await onApply({
|
||||
cableCrossSection: `${result.recommendedCrossSectionMm2} mm²`,
|
||||
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."
|
||||
isSaving={isSaving}
|
||||
onClose={onClose}
|
||||
onSubmit={handleSubmit}
|
||||
submitDisabled={!result?.dataVerified || !result.recommendedCrossSectionMm2}
|
||||
submitLabel="Empfehlung übernehmen"
|
||||
title={`Kabel dimensionieren - ${circuit.equipmentIdentifier}`}
|
||||
>
|
||||
<div className="row g-3">
|
||||
<div className="col-12 col-md-4">
|
||||
<label className="form-label" htmlFor="cable-sizing-length">
|
||||
Leitungslänge (m)
|
||||
</label>
|
||||
<input
|
||||
className="form-control"
|
||||
id="cable-sizing-length"
|
||||
min={0}
|
||||
onChange={(event) => setLengthM(Number(event.target.value))}
|
||||
type="number"
|
||||
value={lengthM}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12 col-md-4">
|
||||
<label className="form-label" htmlFor="cable-sizing-phase">
|
||||
Phasen
|
||||
</label>
|
||||
<select
|
||||
className="form-select"
|
||||
id="cable-sizing-phase"
|
||||
onChange={(event) => setPhase(Number(event.target.value) as 1 | 3)}
|
||||
value={phase}
|
||||
>
|
||||
<option value={1}>1~ ({circuit.voltage ?? 230} V)</option>
|
||||
<option value={3}>3~ ({circuit.voltage ?? 400} V)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-12 col-md-4">
|
||||
<label className="form-label">Leistung (aus Stromkreis)</label>
|
||||
<input
|
||||
className="form-control"
|
||||
disabled
|
||||
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)
|
||||
</label>
|
||||
<select
|
||||
autoFocus
|
||||
className="form-select"
|
||||
id="cable-sizing-method"
|
||||
onChange={(event) => handleLayingMethodChange(event.target.value as LayingMethod)}
|
||||
value={layingMethod}
|
||||
>
|
||||
{LAYING_METHODS.map((method) => (
|
||||
<option key={method} value={method}>
|
||||
{LAYING_METHOD_LABELS[method]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label" htmlFor="cable-sizing-insulation">
|
||||
Isolierstoff
|
||||
</label>
|
||||
<select
|
||||
className="form-select"
|
||||
id="cable-sizing-insulation"
|
||||
onChange={(event) => {
|
||||
setInsulation(event.target.value as InsulationMaterial);
|
||||
setResult(null);
|
||||
}}
|
||||
value={insulation}
|
||||
>
|
||||
{INSULATION_MATERIALS.map((material) => (
|
||||
<option key={material} value={material}>
|
||||
{INSULATION_MATERIAL_LABELS[material]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label" htmlFor="cable-sizing-material">
|
||||
Leitermaterial
|
||||
</label>
|
||||
<select
|
||||
className="form-select"
|
||||
id="cable-sizing-material"
|
||||
onChange={(event) => setConductorMaterial(event.target.value as ConductorMaterial)}
|
||||
value={conductorMaterial}
|
||||
>
|
||||
{CONDUCTOR_MATERIALS.map((material) => (
|
||||
<option key={material} value={material}>
|
||||
{material === "copper" ? "Kupfer" : "Aluminium"}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label" htmlFor="cable-sizing-temperature">
|
||||
{LAYING_METHOD_GROUP[layingMethod] === "air"
|
||||
? "Umgebungstemperatur"
|
||||
: "Bodentemperatur"}
|
||||
</label>
|
||||
<select
|
||||
className="form-select"
|
||||
id="cable-sizing-temperature"
|
||||
onChange={(event) => setAmbientTemperatureC(Number(event.target.value))}
|
||||
value={ambientTemperatureC}
|
||||
>
|
||||
{temperatureOptions(layingMethod).map((temp) => (
|
||||
<option key={temp} value={temp}>
|
||||
{temp}°C
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label" htmlFor="cable-sizing-grouping">
|
||||
Häufung (Stromkreise nebeneinander)
|
||||
</label>
|
||||
<select
|
||||
className="form-select"
|
||||
id="cable-sizing-grouping"
|
||||
onChange={(event) => setGroupingCircuits(Number(event.target.value))}
|
||||
value={groupingCircuits}
|
||||
>
|
||||
{GROUPING_OPTIONS.map((grouping) => (
|
||||
<option key={grouping} value={grouping}>
|
||||
{GROUPING_LABELS[grouping]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{phase === 3 && (
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label" htmlFor="cable-sizing-harmonics">
|
||||
Oberschwingungsanteil Neutralleiter
|
||||
</label>
|
||||
<select
|
||||
className="form-select"
|
||||
id="cable-sizing-harmonics"
|
||||
onChange={(event) =>
|
||||
setHarmonicNeutralLoad(event.target.value as HarmonicNeutralLoad)
|
||||
}
|
||||
value={harmonicNeutralLoad}
|
||||
>
|
||||
{HARMONIC_NEUTRAL_LOAD_OPTIONS.map((option) => (
|
||||
<option key={option} value={option}>
|
||||
{HARMONIC_NEUTRAL_LOAD_LABELS[option]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
)}
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label" htmlFor="cable-sizing-cosphi">
|
||||
cos φ
|
||||
</label>
|
||||
<input
|
||||
className="form-range"
|
||||
id="cable-sizing-cosphi"
|
||||
max={1}
|
||||
min={0.6}
|
||||
onChange={(event) => setCosPhi(Number(event.target.value))}
|
||||
step={0.05}
|
||||
type="range"
|
||||
value={cosPhi}
|
||||
/>
|
||||
<span>{cosPhi.toFixed(2)}</span>
|
||||
</div>
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label" htmlFor="cable-sizing-voltage-drop">
|
||||
Max. zulässiger Spannungsfall
|
||||
</label>
|
||||
<input
|
||||
className="form-range"
|
||||
id="cable-sizing-voltage-drop"
|
||||
max={5}
|
||||
min={1}
|
||||
onChange={(event) => setMaxVoltageDropPercent(Number(event.target.value))}
|
||||
step={0.5}
|
||||
type="range"
|
||||
value={maxVoltageDropPercent}
|
||||
/>
|
||||
<span>{maxVoltageDropPercent.toFixed(1)} %</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
className="btn btn-secondary mt-3"
|
||||
disabled={calculating}
|
||||
onClick={handleCalculate}
|
||||
type="button"
|
||||
>
|
||||
{calculating ? "Berechnet…" : "Berechnen"}
|
||||
</button>
|
||||
{error && <p className="text-danger mt-2">{error}</p>}
|
||||
|
||||
{result && !result.dataVerified && (
|
||||
<div className="alert alert-danger mt-3">
|
||||
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.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{result && result.dataVerified && (
|
||||
<div className="mt-3">
|
||||
<div className="row g-2">
|
||||
<div className="col-6 col-md-3">
|
||||
<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">Empfohlener Querschnitt</div>
|
||||
<div>
|
||||
<strong>
|
||||
{result.recommendedCrossSectionMm2
|
||||
? `${result.recommendedCrossSectionMm2} mm²`
|
||||
: "-"}
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-6 col-md-3">
|
||||
<div className="text-muted small">ΔU bei Empfehlung</div>
|
||||
<div>
|
||||
{result.voltageDropAtRecommendedPercent != null
|
||||
? `${result.voltageDropAtRecommendedPercent.toFixed(2)} %`
|
||||
: "-"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-6 col-md-3">
|
||||
<div className="text-muted small">Korrekturfaktor</div>
|
||||
<div>{result.combinedDerationFactor.toFixed(2)}</div>
|
||||
</div>
|
||||
</div>
|
||||
{alerts.map((alert, index) => (
|
||||
<div
|
||||
className={`alert mt-2 alert-${
|
||||
alert.kind === "critical"
|
||||
? "danger"
|
||||
: alert.kind === "warn"
|
||||
? "warning"
|
||||
: alert.kind === "ok"
|
||||
? "success"
|
||||
: "info"
|
||||
}`}
|
||||
key={index}
|
||||
>
|
||||
{alert.text}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{calculationId ? null : (
|
||||
<p className="text-muted small mt-2">
|
||||
Zuerst berechnen, dann prüfen und ggf. übernehmen - nichts wird automatisch
|
||||
geschrieben.
|
||||
</p>
|
||||
)}
|
||||
</FormModal>
|
||||
);
|
||||
}
|
||||
|
|
@ -137,6 +137,7 @@ import type {
|
|||
import { DistributionBoardComponentModal } from "./distribution-board-component-modal";
|
||||
import { CircuitGroupModal } from "./circuit-group-modal";
|
||||
import { CircuitProtectionModal } from "./circuit-protection-modal";
|
||||
import { CableSizingModal } from "./cable-sizing-modal";
|
||||
import {
|
||||
circuitGroupCategoryLabels,
|
||||
type CircuitGroupCategory,
|
||||
|
|
@ -278,6 +279,8 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||
useState<CircuitGroupEditorIntent | null>(null);
|
||||
const [protectionEditorCircuit, setProtectionEditorCircuit] =
|
||||
useState<CircuitTreeCircuitDto | null>(null);
|
||||
const [cableSizingEditorCircuit, setCableSizingEditorCircuit] =
|
||||
useState<CircuitTreeCircuitDto | null>(null);
|
||||
const [projectDevices, setProjectDevices] = useState<ProjectDeviceDto[]>([]);
|
||||
const [isProjectDeviceDrawerOpen, setIsProjectDeviceDrawerOpen] =
|
||||
useState(false);
|
||||
|
|
@ -1398,6 +1401,30 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||
});
|
||||
}
|
||||
|
||||
async function handleApplyCableSizing(patch: {
|
||||
cableCrossSection: string;
|
||||
cableLength?: number;
|
||||
}) {
|
||||
const circuit = cableSizingEditorCircuit;
|
||||
if (!circuit) {
|
||||
return;
|
||||
}
|
||||
await runCommand({
|
||||
label: "Kabel dimensionieren",
|
||||
redo: async () => {
|
||||
const result = await updateCircuitById(
|
||||
projectId,
|
||||
getExpectedProjectRevision(),
|
||||
circuit.id,
|
||||
patch
|
||||
);
|
||||
applyProjectCommandResult(result);
|
||||
setCableSizingEditorCircuit(null);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function handleRedo() {
|
||||
if (historyBusy || isSaving || !historyState || historyState.redoDepth === 0) {
|
||||
return;
|
||||
|
|
@ -3139,6 +3166,15 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||
onSave={handleSaveCircuitProtection}
|
||||
/>
|
||||
) : null}
|
||||
{cableSizingEditorCircuit ? (
|
||||
<CableSizingModal
|
||||
circuit={cableSizingEditorCircuit}
|
||||
isSaving={isSaving}
|
||||
projectId={projectId}
|
||||
onClose={() => setCableSizingEditorCircuit(null)}
|
||||
onApply={handleApplyCableSizing}
|
||||
/>
|
||||
) : null}
|
||||
<div className="editor-toolbar">
|
||||
<button
|
||||
type="button"
|
||||
|
|
@ -4240,10 +4276,15 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||
row.circuit &&
|
||||
row.rowType !== "deviceRow"
|
||||
);
|
||||
const isCableSizingTrigger = Boolean(
|
||||
column.key === "cableCrossSection" &&
|
||||
row.circuit &&
|
||||
row.rowType !== "deviceRow"
|
||||
);
|
||||
return (
|
||||
<td
|
||||
key={column.key}
|
||||
className={`${column.numeric ? "num" : ""} ${cell.editable ? "cell-editable" : ""} ${isProtectionTrigger ? "cell-protection-trigger" : ""} ${isSelected ? "cell-selected" : ""} ${hasIdentifierConflict ? "cell-invalid" : ""} ${
|
||||
className={`${column.numeric ? "num" : ""} ${cell.editable ? "cell-editable" : ""} ${isProtectionTrigger ? "cell-protection-trigger" : ""} ${isCableSizingTrigger ? "cell-cable-sizing-trigger" : ""} ${isSelected ? "cell-selected" : ""} ${hasIdentifierConflict ? "cell-invalid" : ""} ${
|
||||
Boolean(row.device) && column.key === "displayName" && (row.rowType === "deviceRow" || row.rowType === "circuitCompact")
|
||||
? "device-drag-handle"
|
||||
: ""
|
||||
|
|
@ -4263,7 +4304,9 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||
title={
|
||||
isProtectionTrigger
|
||||
? "Schutzgerät bearbeiten"
|
||||
: column.key === "equipmentIdentifier" &&
|
||||
: isCableSizingTrigger
|
||||
? "Kabel dimensionieren"
|
||||
: column.key === "equipmentIdentifier" &&
|
||||
row.circuit &&
|
||||
(row.rowType === "circuitCompact" ||
|
||||
row.rowType === "circuitSummary" ||
|
||||
|
|
@ -4342,6 +4385,10 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||
setProtectionEditorCircuit(row.circuit!);
|
||||
return;
|
||||
}
|
||||
if (isCableSizingTrigger) {
|
||||
setCableSizingEditorCircuit(row.circuit!);
|
||||
return;
|
||||
}
|
||||
if (cell.editable) {
|
||||
handleRowSelectionClick(row, column.key, {
|
||||
ctrlKey: event.ctrlKey,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue