Configure circuit protection
This commit is contained in:
@@ -17,6 +17,7 @@ import type {
|
||||
import type { AppDatabase } from "../database-context.js";
|
||||
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
||||
import { circuitLists } from "../schema/circuit-lists.js";
|
||||
import { circuitProtectionDevices } from "../schema/circuit-protection-devices.js";
|
||||
import { circuitSections } from "../schema/circuit-sections.js";
|
||||
import { circuits } from "../schema/circuits.js";
|
||||
import { executeProjectCommandTransaction } from "./project-command-transaction.persistence.js";
|
||||
@@ -396,6 +397,12 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
||||
remark: snapshot.remark,
|
||||
})
|
||||
.run();
|
||||
if (snapshot.protectionDevice) {
|
||||
database
|
||||
.insert(circuitProtectionDevices)
|
||||
.values(snapshot.protectionDevice)
|
||||
.run();
|
||||
}
|
||||
}
|
||||
|
||||
private assertTargetCircuitUnchanged(
|
||||
@@ -438,6 +445,27 @@ export class CircuitDeviceRowMoveProjectCommandRepository
|
||||
"Created target circuit changed before command execution."
|
||||
);
|
||||
}
|
||||
const protectionDevice = database
|
||||
.select()
|
||||
.from(circuitProtectionDevices)
|
||||
.where(eq(circuitProtectionDevices.circuitId, snapshot.id))
|
||||
.get();
|
||||
if (
|
||||
snapshot.protectionDevice === undefined
|
||||
? protectionDevice !== undefined
|
||||
: snapshot.protectionDevice === null
|
||||
? protectionDevice !== undefined
|
||||
: !protectionDevice ||
|
||||
Object.entries(snapshot.protectionDevice).some(
|
||||
([key, value]) =>
|
||||
(protectionDevice as Record<string, unknown>)[key] !==
|
||||
value
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
"Created target circuit protection changed before command execution."
|
||||
);
|
||||
}
|
||||
|
||||
const currentRowIds = database
|
||||
.select({ id: circuitDeviceRows.id })
|
||||
|
||||
@@ -17,6 +17,7 @@ import { isElectricalPhaseType } from "../../domain/services/project-voltage.ser
|
||||
import type { AppDatabase } from "../database-context.js";
|
||||
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
||||
import { circuitLists } from "../schema/circuit-lists.js";
|
||||
import { circuitProtectionDevices } from "../schema/circuit-protection-devices.js";
|
||||
import { circuitSections } from "../schema/circuit-sections.js";
|
||||
import { circuits } from "../schema/circuits.js";
|
||||
import {
|
||||
@@ -175,6 +176,12 @@ export class CircuitStructureProjectCommandRepository
|
||||
if (snapshot.deviceRows.length > 0) {
|
||||
database.insert(circuitDeviceRows).values(snapshot.deviceRows).run();
|
||||
}
|
||||
if (snapshot.protectionDevice) {
|
||||
database
|
||||
.insert(circuitProtectionDevices)
|
||||
.values(snapshot.protectionDevice)
|
||||
.run();
|
||||
}
|
||||
|
||||
return createCircuitDeleteProjectCommand(
|
||||
snapshot.id,
|
||||
@@ -219,6 +226,11 @@ export class CircuitStructureProjectCommandRepository
|
||||
asc(circuitDeviceRows.id)
|
||||
)
|
||||
.all();
|
||||
const protectionDevice = database
|
||||
.select()
|
||||
.from(circuitProtectionDevices)
|
||||
.where(eq(circuitProtectionDevices.circuitId, circuit.id))
|
||||
.get();
|
||||
const inverse = createCircuitInsertProjectCommand({
|
||||
id: circuit.id,
|
||||
circuitListId: circuit.circuitListId,
|
||||
@@ -240,6 +252,7 @@ export class CircuitStructureProjectCommandRepository
|
||||
isReserve: Boolean(circuit.isReserve),
|
||||
remark: circuit.remark,
|
||||
deviceRows: rows.map(toCircuitDeviceRowSnapshot),
|
||||
protectionDevice: protectionDevice ?? null,
|
||||
});
|
||||
|
||||
const result = database
|
||||
|
||||
@@ -66,17 +66,20 @@ export function assertCircuitProtectionUpdateProjectCommand(
|
||||
throw new Error("Circuit protection update must change state.");
|
||||
}
|
||||
if (expected !== null) {
|
||||
assertSnapshot(expected, circuitId);
|
||||
assertCircuitProtectionSnapshot(expected, circuitId);
|
||||
}
|
||||
if (target !== null) {
|
||||
assertSnapshot(target, circuitId);
|
||||
assertCircuitProtectionSnapshot(target, circuitId);
|
||||
}
|
||||
if (JSON.stringify(expected) === JSON.stringify(target)) {
|
||||
throw new Error("Circuit protection update must change state.");
|
||||
}
|
||||
}
|
||||
|
||||
function assertSnapshot(value: unknown, circuitId: string) {
|
||||
export function assertCircuitProtectionSnapshot(
|
||||
value: unknown,
|
||||
circuitId: string
|
||||
) {
|
||||
if (
|
||||
!isPlainObject(value) ||
|
||||
Object.keys(value).length !== 7 ||
|
||||
|
||||
@@ -5,6 +5,10 @@ import {
|
||||
type CircuitDeviceRowSnapshot,
|
||||
} from "./circuit-device-row-structure-project-command.model.js";
|
||||
import type { SerializedProjectCommand } from "./project-command.model.js";
|
||||
import {
|
||||
assertCircuitProtectionSnapshot,
|
||||
type CircuitProtectionSnapshot,
|
||||
} from "./circuit-protection-project-command.model.js";
|
||||
|
||||
export const circuitInsertCommandType = "circuit.insert" as const;
|
||||
export const circuitDeleteCommandType = "circuit.delete" as const;
|
||||
@@ -31,6 +35,7 @@ export interface CircuitSnapshot {
|
||||
isReserve: boolean;
|
||||
remark: string | null;
|
||||
deviceRows: CircuitDeviceRowSnapshot[];
|
||||
protectionDevice?: CircuitProtectionSnapshot | null;
|
||||
}
|
||||
|
||||
export interface CircuitInsertCommandPayload {
|
||||
@@ -142,6 +147,15 @@ export function assertCircuitInsertProjectCommand(
|
||||
if (!Array.isArray(circuit.deviceRows)) {
|
||||
throw new Error("circuit.deviceRows must be an array.");
|
||||
}
|
||||
if (
|
||||
circuit.protectionDevice !== undefined &&
|
||||
circuit.protectionDevice !== null
|
||||
) {
|
||||
assertCircuitProtectionSnapshot(
|
||||
circuit.protectionDevice,
|
||||
circuit.id as string
|
||||
);
|
||||
}
|
||||
if (circuit.isReserve !== (circuit.deviceRows.length === 0)) {
|
||||
throw new Error(
|
||||
"Circuit reserve state must match whether device rows exist."
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
"use client";
|
||||
|
||||
import { type FormEvent, useState } from "react";
|
||||
import {
|
||||
allowedRatedCurrentsAByProtectionDeviceType,
|
||||
breakerTripCharacteristics,
|
||||
fuseProtectionDeviceTypes,
|
||||
fuseUtilizationCategories,
|
||||
protectionDeviceTypeLabels,
|
||||
protectionDeviceTypes,
|
||||
ratedResidualCurrentsMa,
|
||||
rcdTypes,
|
||||
type ProtectionDeviceType,
|
||||
} from "../../shared/constants/protection-device";
|
||||
import type { CircuitTreeProtectionDeviceDto } from "../types";
|
||||
import { FormModal } from "./form-modal";
|
||||
|
||||
interface CircuitProtectionModalProps {
|
||||
equipmentIdentifier: string;
|
||||
initialProtection: CircuitTreeProtectionDeviceDto;
|
||||
isSaving: boolean;
|
||||
onClose: () => void;
|
||||
onSave: (protection: CircuitTreeProtectionDeviceDto) => Promise<void>;
|
||||
}
|
||||
|
||||
export function CircuitProtectionModal({
|
||||
equipmentIdentifier,
|
||||
initialProtection,
|
||||
isSaving,
|
||||
onClose,
|
||||
onSave,
|
||||
}: CircuitProtectionModalProps) {
|
||||
const [type, setType] = useState(initialProtection.type);
|
||||
const [ratedCurrentA, setRatedCurrentA] = useState(
|
||||
initialProtection.ratedCurrentA
|
||||
);
|
||||
const [fuseUtilizationCategory, setFuseUtilizationCategory] = useState(
|
||||
initialProtection.fuseUtilizationCategory ?? "gG"
|
||||
);
|
||||
const [tripCharacteristic, setTripCharacteristic] = useState(
|
||||
initialProtection.tripCharacteristic ?? "B"
|
||||
);
|
||||
const [rcdType, setRcdType] = useState(
|
||||
initialProtection.rcdType ?? "A"
|
||||
);
|
||||
const [ratedResidualCurrentMa, setRatedResidualCurrentMa] = useState(
|
||||
initialProtection.ratedResidualCurrentMa ?? 30
|
||||
);
|
||||
const usesFuseCategory = (
|
||||
fuseProtectionDeviceTypes as readonly string[]
|
||||
).includes(type);
|
||||
const usesTripCharacteristic =
|
||||
type === "LS" || type === "FI_LS" || type === "AFDD";
|
||||
const usesResidualCurrent = type === "FI" || type === "FI_LS";
|
||||
const allowedRatedCurrents =
|
||||
allowedRatedCurrentsAByProtectionDeviceType[type];
|
||||
|
||||
function handleTypeChange(nextType: ProtectionDeviceType) {
|
||||
setType(nextType);
|
||||
setRatedCurrentA(
|
||||
allowedRatedCurrentsAByProtectionDeviceType[nextType][0]
|
||||
);
|
||||
setFuseUtilizationCategory("gG");
|
||||
setTripCharacteristic("B");
|
||||
setRcdType("A");
|
||||
setRatedResidualCurrentMa(30);
|
||||
}
|
||||
|
||||
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
|
||||
event.preventDefault();
|
||||
await onSave({
|
||||
type,
|
||||
ratedCurrentA,
|
||||
...(usesFuseCategory ? { fuseUtilizationCategory } : {}),
|
||||
...(usesTripCharacteristic ? { tripCharacteristic } : {}),
|
||||
...(usesResidualCurrent
|
||||
? { rcdType, ratedResidualCurrentMa }
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<FormModal
|
||||
description="Die Auswahl ist planerisch gesetzt. Eine spätere Dimensionierung wird nur Empfehlungen und Warnungen ergänzen."
|
||||
isSaving={isSaving}
|
||||
onClose={onClose}
|
||||
onSubmit={handleSubmit}
|
||||
submitDisabled={
|
||||
!(allowedRatedCurrents as readonly number[]).includes(
|
||||
ratedCurrentA
|
||||
)
|
||||
}
|
||||
submitLabel="Schutzgerät speichern"
|
||||
title={`Schutzgerät bearbeiten – ${equipmentIdentifier}`}
|
||||
>
|
||||
<div className="row g-3">
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label" htmlFor="circuit-protection-type">
|
||||
Schutzgerät
|
||||
</label>
|
||||
<select
|
||||
autoFocus
|
||||
className="form-select"
|
||||
id="circuit-protection-type"
|
||||
onChange={(event) =>
|
||||
handleTypeChange(
|
||||
event.target.value as ProtectionDeviceType
|
||||
)
|
||||
}
|
||||
value={type}
|
||||
>
|
||||
{protectionDeviceTypes.map((entry) => (
|
||||
<option key={entry} value={entry}>
|
||||
{protectionDeviceTypeLabels[entry]}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label" htmlFor="circuit-protection-current">
|
||||
Bemessungsstrom
|
||||
</label>
|
||||
<select
|
||||
className="form-select"
|
||||
id="circuit-protection-current"
|
||||
onChange={(event) =>
|
||||
setRatedCurrentA(Number(event.target.value))
|
||||
}
|
||||
value={ratedCurrentA}
|
||||
>
|
||||
{allowedRatedCurrents.map((current) => (
|
||||
<option key={current} value={current}>
|
||||
{current} A
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
{usesFuseCategory ? (
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label" htmlFor="circuit-fuse-category">
|
||||
Sicherungscharakteristik
|
||||
</label>
|
||||
<select
|
||||
className="form-select"
|
||||
id="circuit-fuse-category"
|
||||
onChange={(event) =>
|
||||
setFuseUtilizationCategory(
|
||||
event.target
|
||||
.value as (typeof fuseUtilizationCategories)[number]
|
||||
)
|
||||
}
|
||||
value={fuseUtilizationCategory}
|
||||
>
|
||||
{fuseUtilizationCategories.map((entry) => (
|
||||
<option key={entry} value={entry}>
|
||||
{entry}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
) : null}
|
||||
{usesTripCharacteristic ? (
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label" htmlFor="circuit-trip-characteristic">
|
||||
Auslösecharakteristik
|
||||
</label>
|
||||
<select
|
||||
className="form-select"
|
||||
id="circuit-trip-characteristic"
|
||||
onChange={(event) =>
|
||||
setTripCharacteristic(
|
||||
event.target
|
||||
.value as (typeof breakerTripCharacteristics)[number]
|
||||
)
|
||||
}
|
||||
value={tripCharacteristic}
|
||||
>
|
||||
{breakerTripCharacteristics.map((entry) => (
|
||||
<option key={entry} value={entry}>
|
||||
{entry}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
) : null}
|
||||
{usesResidualCurrent ? (
|
||||
<>
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label" htmlFor="circuit-rcd-type">
|
||||
FI-Typ
|
||||
</label>
|
||||
<select
|
||||
className="form-select"
|
||||
id="circuit-rcd-type"
|
||||
onChange={(event) =>
|
||||
setRcdType(
|
||||
event.target.value as (typeof rcdTypes)[number]
|
||||
)
|
||||
}
|
||||
value={rcdType}
|
||||
>
|
||||
{rcdTypes.map((entry) => (
|
||||
<option key={entry} value={entry}>
|
||||
Typ {entry}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
<div className="col-12 col-md-6">
|
||||
<label className="form-label" htmlFor="circuit-residual-current">
|
||||
Bemessungsdifferenzstrom
|
||||
</label>
|
||||
<select
|
||||
className="form-select"
|
||||
id="circuit-residual-current"
|
||||
onChange={(event) =>
|
||||
setRatedResidualCurrentMa(Number(event.target.value))
|
||||
}
|
||||
value={ratedResidualCurrentMa}
|
||||
>
|
||||
{ratedResidualCurrentsMa.map((entry) => (
|
||||
<option key={entry} value={entry}>
|
||||
{entry} mA
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
</div>
|
||||
</FormModal>
|
||||
);
|
||||
}
|
||||
@@ -38,6 +38,10 @@ import {
|
||||
buildCircuitDeviceRowInsertSnapshot,
|
||||
buildCircuitInsertSnapshot,
|
||||
} from "../utils/circuit-structure-command";
|
||||
import {
|
||||
getCircuitProtectionEditorInitialValue,
|
||||
toCircuitProtectionSnapshot,
|
||||
} from "../utils/circuit-protection-editing";
|
||||
import {
|
||||
buildCircuitDeviceRowMoveAssignments,
|
||||
} from "../utils/circuit-device-row-move-command";
|
||||
@@ -95,6 +99,7 @@ import {
|
||||
redoProjectCommand,
|
||||
updateCircuitById,
|
||||
updateCircuitDeviceRowById,
|
||||
updateCircuitProtectionCommand,
|
||||
updateCircuitGroupCommand,
|
||||
updateDistributionBoardComponentCommand,
|
||||
undoProjectCommand,
|
||||
@@ -102,6 +107,7 @@ import {
|
||||
import type {
|
||||
CircuitTreeCircuitDto,
|
||||
CircuitTreeComponentDto,
|
||||
CircuitTreeProtectionDeviceDto,
|
||||
CircuitTreeResponseDto,
|
||||
CreateCircuitDeviceRowInputDto,
|
||||
CreateCircuitInputDto,
|
||||
@@ -117,6 +123,7 @@ import type {
|
||||
} from "../../domain/models/circuit-structure-project-command.model";
|
||||
import { DistributionBoardComponentModal } from "./distribution-board-component-modal";
|
||||
import { CircuitGroupModal } from "./circuit-group-modal";
|
||||
import { CircuitProtectionModal } from "./circuit-protection-modal";
|
||||
import type { CircuitGroupCategory } from "../../shared/constants/circuit-group";
|
||||
|
||||
type SaveDirection = "stay" | "next" | "prev";
|
||||
@@ -246,6 +253,8 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
useState<StructureComponentEditorIntent | null>(null);
|
||||
const [circuitGroupEditorIntent, setCircuitGroupEditorIntent] =
|
||||
useState<CircuitGroupEditorIntent | null>(null);
|
||||
const [protectionEditorCircuit, setProtectionEditorCircuit] =
|
||||
useState<CircuitTreeCircuitDto | null>(null);
|
||||
const [projectDevices, setProjectDevices] = useState<ProjectDeviceDto[]>([]);
|
||||
const [isProjectDeviceDrawerOpen, setIsProjectDeviceDrawerOpen] =
|
||||
useState(false);
|
||||
@@ -962,6 +971,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
circuitListId,
|
||||
values: { ...values, voltage },
|
||||
deviceRows,
|
||||
category: section.category,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1225,6 +1235,35 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
});
|
||||
}
|
||||
|
||||
async function handleSaveCircuitProtection(
|
||||
protection: CircuitTreeProtectionDeviceDto
|
||||
) {
|
||||
const circuit = protectionEditorCircuit;
|
||||
if (!circuit) {
|
||||
return;
|
||||
}
|
||||
await runCommand({
|
||||
label: "Stromkreisschutz bearbeiten",
|
||||
redo: async () => {
|
||||
const result = await updateCircuitProtectionCommand(
|
||||
projectId,
|
||||
getExpectedProjectRevision(),
|
||||
circuit.id,
|
||||
circuit.protectionDevice
|
||||
? toCircuitProtectionSnapshot(
|
||||
circuit.id,
|
||||
circuit.protectionDevice
|
||||
)
|
||||
: null,
|
||||
toCircuitProtectionSnapshot(circuit.id, protection)
|
||||
);
|
||||
applyProjectCommandResult(result);
|
||||
setProtectionEditorCircuit(null);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function handleRedo() {
|
||||
if (historyBusy || isSaving || !historyState || historyState.redoDepth === 0) {
|
||||
return;
|
||||
@@ -2834,6 +2873,23 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
onSave={handleSaveCircuitGroup}
|
||||
/>
|
||||
) : null}
|
||||
{protectionEditorCircuit ? (
|
||||
<CircuitProtectionModal
|
||||
equipmentIdentifier={
|
||||
protectionEditorCircuit.equipmentIdentifier
|
||||
}
|
||||
initialProtection={getCircuitProtectionEditorInitialValue(
|
||||
data.sections.find(
|
||||
(section) =>
|
||||
section.id === protectionEditorCircuit.sectionId
|
||||
)?.category,
|
||||
protectionEditorCircuit.protectionDevice
|
||||
)}
|
||||
isSaving={isSaving}
|
||||
onClose={() => setProtectionEditorCircuit(null)}
|
||||
onSave={handleSaveCircuitProtection}
|
||||
/>
|
||||
) : null}
|
||||
<div className="editor-toolbar">
|
||||
<button
|
||||
type="button"
|
||||
@@ -4033,6 +4089,15 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
>
|
||||
{row.circuit && row.rowType !== "deviceRow" ? (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
onClick={() =>
|
||||
setProtectionEditorCircuit(row.circuit!)
|
||||
}
|
||||
>
|
||||
Schutzgerät
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
|
||||
@@ -58,6 +58,9 @@ import type {
|
||||
import type {
|
||||
CircuitGroupSnapshot,
|
||||
} from "../../domain/models/circuit-group-structure-project-command.model";
|
||||
import type {
|
||||
CircuitProtectionSnapshot,
|
||||
} from "../../domain/models/circuit-protection-project-command.model";
|
||||
|
||||
async function request<T>(url: string, init?: RequestInit): Promise<T> {
|
||||
const response = await fetch(url, {
|
||||
@@ -430,6 +433,25 @@ export function deleteCircuitGroupCommand(
|
||||
);
|
||||
}
|
||||
|
||||
export function updateCircuitProtectionCommand(
|
||||
projectId: string,
|
||||
expectedRevision: number,
|
||||
circuitId: string,
|
||||
expected: CircuitProtectionSnapshot | null,
|
||||
target: CircuitProtectionSnapshot
|
||||
) {
|
||||
return executeProjectCommand(
|
||||
projectId,
|
||||
expectedRevision,
|
||||
{
|
||||
schemaVersion: 1,
|
||||
type: "circuit-protection.update",
|
||||
payload: { circuitId, expected, target },
|
||||
},
|
||||
"Stromkreisschutz bearbeiten"
|
||||
);
|
||||
}
|
||||
|
||||
export function updateCircuitById(
|
||||
projectId: string,
|
||||
expectedRevision: number,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { CircuitTreeCircuitDto, CircuitTreeDeviceRowDto } from "../types.js";
|
||||
import type { CircuitUpdatePatch } from "../../domain/models/circuit-project-command.model.js";
|
||||
import type { CircuitDeviceRowUpdatePatch } from "../../domain/models/circuit-device-row-project-command.model.js";
|
||||
import { protectionDeviceTypeLabels } from "../../shared/constants/protection-device";
|
||||
|
||||
export type CellKey =
|
||||
| "equipmentIdentifier"
|
||||
@@ -472,18 +473,54 @@ export function getDeviceValue(device: CircuitTreeDeviceRowDto, key: CellKey): G
|
||||
}
|
||||
|
||||
export function getCircuitValue(circuit: CircuitTreeCircuitDto, key: CellKey): GridValue {
|
||||
const protection = circuit.protectionDevice;
|
||||
const protectionCharacteristic = protection
|
||||
? [
|
||||
protection.fuseUtilizationCategory,
|
||||
protection.tripCharacteristic,
|
||||
protection.rcdType ? `Typ ${protection.rcdType}` : undefined,
|
||||
protection.ratedResidualCurrentMa !== undefined
|
||||
? `${protection.ratedResidualCurrentMa} mA`
|
||||
: undefined,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(" · ")
|
||||
: undefined;
|
||||
switch (key) {
|
||||
case "equipmentIdentifier": return circuit.equipmentIdentifier;
|
||||
case "displayName": return circuit.displayName;
|
||||
case "circuitTotalPower": return circuit.circuitTotalPower;
|
||||
case "protectionType": return circuit.protectionType;
|
||||
case "protectionRatedCurrent": return circuit.protectionRatedCurrent;
|
||||
case "protectionCharacteristic": return circuit.protectionCharacteristic;
|
||||
case "protectionType":
|
||||
return protection
|
||||
? protectionDeviceTypeLabels[protection.type]
|
||||
: circuit.protectionType;
|
||||
case "protectionRatedCurrent":
|
||||
return protection?.ratedCurrentA ?? circuit.protectionRatedCurrent;
|
||||
case "protectionCharacteristic":
|
||||
return protectionCharacteristic || circuit.protectionCharacteristic;
|
||||
case "protectionSummary": {
|
||||
const current = circuit.protectionRatedCurrent !== undefined && circuit.protectionRatedCurrent !== null
|
||||
? `${circuit.protectionRatedCurrent}A`
|
||||
if (!protection) {
|
||||
const legacyCurrent =
|
||||
circuit.protectionRatedCurrent !== undefined &&
|
||||
circuit.protectionRatedCurrent !== null
|
||||
? `${circuit.protectionRatedCurrent}A`
|
||||
: "";
|
||||
return [
|
||||
circuit.protectionType,
|
||||
legacyCurrent,
|
||||
circuit.protectionCharacteristic,
|
||||
].filter(Boolean).join(" ").trim() || undefined;
|
||||
}
|
||||
const currentValue =
|
||||
protection.ratedCurrentA;
|
||||
const current = currentValue !== undefined && currentValue !== null
|
||||
? `${currentValue} A`
|
||||
: "";
|
||||
return [circuit.protectionType, current, circuit.protectionCharacteristic].filter(Boolean).join(" ").trim() || undefined;
|
||||
return [
|
||||
protectionDeviceTypeLabels[protection.type],
|
||||
current,
|
||||
protectionCharacteristic,
|
||||
].filter(Boolean).join(" · ").trim() || undefined;
|
||||
}
|
||||
case "cableSummary": {
|
||||
const length = circuit.cableLength !== undefined && circuit.cableLength !== null ? `${circuit.cableLength} m` : "";
|
||||
@@ -539,7 +576,14 @@ export function compareSortValues(left: GridValue, right: GridValue): number {
|
||||
}
|
||||
|
||||
export function getCellKind(rowType: RowType, key: CellKey): CellKind {
|
||||
if (key === "rowTotalPower" || key === "circuitTotalPower") return "computed";
|
||||
if (
|
||||
key === "rowTotalPower" ||
|
||||
key === "circuitTotalPower" ||
|
||||
key === "protectionType" ||
|
||||
key === "protectionRatedCurrent" ||
|
||||
key === "protectionCharacteristic" ||
|
||||
key === "protectionSummary"
|
||||
) return "computed";
|
||||
if (key === "voltage") return "readonly";
|
||||
if (rowType === "section") return "readonly";
|
||||
if (rowType === "placeholder") {
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import type {
|
||||
CircuitProtectionSnapshot,
|
||||
} from "../../domain/models/circuit-protection-project-command.model";
|
||||
import { createDefaultCircuitProtection } from "../../domain/services/protection-device-defaults";
|
||||
import type { CircuitGroupCategory } from "../../shared/constants/circuit-group";
|
||||
import type {
|
||||
CircuitTreeProtectionDeviceDto,
|
||||
} from "../types";
|
||||
|
||||
export function toCircuitProtectionSnapshot(
|
||||
circuitId: string,
|
||||
protection: CircuitTreeProtectionDeviceDto
|
||||
): CircuitProtectionSnapshot {
|
||||
return {
|
||||
circuitId,
|
||||
type: protection.type,
|
||||
ratedCurrentA: protection.ratedCurrentA,
|
||||
fuseUtilizationCategory:
|
||||
protection.fuseUtilizationCategory ?? null,
|
||||
tripCharacteristic: protection.tripCharacteristic ?? null,
|
||||
rcdType: protection.rcdType ?? null,
|
||||
ratedResidualCurrentMa:
|
||||
protection.ratedResidualCurrentMa ?? null,
|
||||
};
|
||||
}
|
||||
|
||||
export function getCircuitProtectionEditorInitialValue(
|
||||
category: CircuitGroupCategory | undefined,
|
||||
protection: CircuitTreeProtectionDeviceDto | undefined
|
||||
): CircuitTreeProtectionDeviceDto {
|
||||
if (protection) {
|
||||
return protection;
|
||||
}
|
||||
const fallback = createDefaultCircuitProtection(category ?? "lighting");
|
||||
return { ...fallback };
|
||||
}
|
||||
@@ -8,6 +8,8 @@ import type {
|
||||
CreateCircuitDeviceRowInputDto,
|
||||
CreateCircuitInputDto,
|
||||
} from "../types.js";
|
||||
import type { CircuitGroupCategory } from "../../shared/constants/circuit-group";
|
||||
import { createDefaultCircuitProtection } from "../../domain/services/protection-device-defaults";
|
||||
|
||||
export function buildCircuitDeviceRowInsertSnapshot(input: {
|
||||
id: string;
|
||||
@@ -46,8 +48,12 @@ export function buildCircuitInsertSnapshot(input: {
|
||||
circuitListId: string;
|
||||
values: CreateCircuitInputDto;
|
||||
deviceRows: CircuitDeviceRowSnapshot[];
|
||||
category?: CircuitGroupCategory;
|
||||
}): CircuitSnapshot {
|
||||
const { id, circuitListId, values, deviceRows } = input;
|
||||
const { id, circuitListId, values, deviceRows, category } = input;
|
||||
const defaultProtection = category
|
||||
? createDefaultCircuitProtection(category)
|
||||
: null;
|
||||
return {
|
||||
id,
|
||||
circuitListId,
|
||||
@@ -69,5 +75,30 @@ export function buildCircuitInsertSnapshot(input: {
|
||||
isReserve: deviceRows.length === 0,
|
||||
remark: values.remark ?? null,
|
||||
deviceRows,
|
||||
...(defaultProtection
|
||||
? {
|
||||
protectionDevice: {
|
||||
circuitId: id,
|
||||
type: defaultProtection.type,
|
||||
ratedCurrentA: defaultProtection.ratedCurrentA,
|
||||
fuseUtilizationCategory:
|
||||
"fuseUtilizationCategory" in defaultProtection
|
||||
? defaultProtection.fuseUtilizationCategory
|
||||
: null,
|
||||
tripCharacteristic:
|
||||
"tripCharacteristic" in defaultProtection
|
||||
? defaultProtection.tripCharacteristic
|
||||
: null,
|
||||
rcdType:
|
||||
"rcdType" in defaultProtection
|
||||
? defaultProtection.rcdType
|
||||
: null,
|
||||
ratedResidualCurrentMa:
|
||||
"ratedResidualCurrentMa" in defaultProtection
|
||||
? defaultProtection.ratedResidualCurrentMa
|
||||
: null,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user