diff --git a/docs/circuit-list-editor-architecture.md b/docs/circuit-list-editor-architecture.md index b7754df..a81c73a 100644 --- a/docs/circuit-list-editor-architecture.md +++ b/docs/circuit-list-editor-architecture.md @@ -37,6 +37,9 @@ The pure grid modules have no React state and are covered by focused unit tests. - `cosPhi` - room snapshots - category/cost group and related row attributes + - canonical `phaseType` values `single_phase` or `three_phase`; the + frontend always presents these as `1-phasig` or `3-phasig` and edits + them through a selection control - `ProjectDevice` - Reusable device template entity at project level. - Can be linked to `CircuitDeviceRow` entries, with copied display values on insert. diff --git a/src/app/globals.css b/src/app/globals.css index c460a0d..25a05de 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -689,7 +689,8 @@ body { border-color: #c2410c; } -.tree-grid input { +.tree-grid input, +.tree-grid select { width: 100%; min-width: 5rem; border: 1px solid #9fb6e0; diff --git a/src/app/projects/page.tsx b/src/app/projects/page.tsx index 39f7aa9..14a459d 100644 --- a/src/app/projects/page.tsx +++ b/src/app/projects/page.tsx @@ -193,7 +193,7 @@ export default function ProjectsPage() { async function handleQuickUpdateGlobalDevice( device: GlobalDeviceDto, - key: "name" | "displayName" | "category", + key: "name" | "displayName" | "category" | "phaseCount", value: string ) { const payload: CreateGlobalDeviceInput = { @@ -203,7 +203,12 @@ export default function ProjectsPage() { quantity: device.quantity, installedPowerPerUnitKw: device.installedPowerPerUnitKw, demandFactor: device.demandFactor, - phaseCount: device.phaseCount ?? undefined, + phaseCount: + key === "phaseCount" + ? value === "3" + ? 3 + : 1 + : device.phaseCount ?? 1, powerFactor: device.powerFactor ?? undefined, note: device.note ?? undefined, }; @@ -416,8 +421,8 @@ export default function ProjectsPage() { setGlobalDeviceForm((current) => ({ ...current, phaseCount: event.target.value })) } > - - + +
@@ -437,6 +442,7 @@ export default function ProjectsPage() { Anzahl Leistung je Stück [kW] GZF + Phasenart Aktion @@ -479,6 +485,23 @@ export default function ProjectsPage() { {device.quantity} {device.installedPowerPerUnitKw} {device.demandFactor} + + + ) : null} {activeColumnFilters.map(({ column, values }) => { - const shownValues = values.slice(0, 2).join(", "); + const shownValues = values + .slice(0, 2) + .map((value) => + column.key === "phaseType" + ? formatPhaseTypeLabel(value) + : value + ) + .join(", "); const valueSummary = values.length > 2 ? `${shownValues} +${values.length - 2}` : shownValues; return (
{(distinctValuesByColumn[column.key] ?? []) - .filter((value) => value.toLocaleLowerCase("de-DE").includes(filterValueSearch.trim().toLocaleLowerCase("de-DE"))) + .filter((value) => + (column.key === "phaseType" + ? formatPhaseTypeLabel(value) + : value + ) + .toLocaleLowerCase("de-DE") + .includes( + filterValueSearch + .trim() + .toLocaleLowerCase("de-DE") + ) + ) .map((value) => { const selected = filterDraftValues.includes(value); return ( @@ -2834,12 +2857,26 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str } > - {value} + + {column.key === "phaseType" + ? formatPhaseTypeLabel(value) + : value} + ); })} - {(distinctValuesByColumn[column.key] ?? []).filter((value) => - value.toLocaleLowerCase("de-DE").includes(filterValueSearch.trim().toLocaleLowerCase("de-DE")) + {(distinctValuesByColumn[column.key] ?? []).filter( + (value) => + (column.key === "phaseType" + ? formatPhaseTypeLabel(value) + : value + ) + .toLocaleLowerCase("de-DE") + .includes( + filterValueSearch + .trim() + .toLocaleLowerCase("de-DE") + ) ).length === 0 ? (
Keine passenden Werte gefunden.
) : null} @@ -3375,9 +3412,52 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str } }} > - {isEditing ? ( + {isEditing && column.key === "phaseType" ? ( + + ) : isEditing ? ( { + inputRef.current = element; + }} value={editingCell.draft} aria-invalid={hasDraftIdentifierConflict} title={hasDraftIdentifierConflict ? "Dieses Betriebsmittelkennzeichen ist bereits vorhanden." : undefined} diff --git a/src/frontend/components/circuit-tree-preview.tsx b/src/frontend/components/circuit-tree-preview.tsx index fc5e9ed..24af57e 100644 --- a/src/frontend/components/circuit-tree-preview.tsx +++ b/src/frontend/components/circuit-tree-preview.tsx @@ -6,16 +6,6 @@ import { getCircuitTree } from "../utils/api"; import type { CircuitTreeCircuitDto, CircuitTreeResponseDto } from "../types"; import { formatValue } from "../utils/circuit-grid-model"; -function formatPhaseType(value: string | undefined) { - if (value === "three_phase") { - return "Dreiphasig"; - } - if (value === "single_phase") { - return "Einphasig"; - } - return value ?? "-"; -} - function renderCircuitSummaryLabel(circuit: CircuitTreeCircuitDto) { if (circuit.displayName?.trim()) { return circuit.displayName; @@ -140,7 +130,7 @@ function SectionRows(props: { section: CircuitTreeResponseDto["sections"][number {circuit.equipmentIdentifier} {row.displayName || row.name} - {formatPhaseType(row.phaseType)} + {formatValue(row.phaseType ?? undefined, "phaseType")} {row.connectionKind ?? "-"} {row.costGroup ?? "-"} {row.category ?? "-"} @@ -190,7 +180,7 @@ function SectionRows(props: { section: CircuitTreeResponseDto["sections"][number {row.displayName || row.name} - {formatPhaseType(row.phaseType)} + {formatValue(row.phaseType ?? undefined, "phaseType")} {row.connectionKind ?? "-"} {row.costGroup ?? "-"} {row.category ?? "-"} diff --git a/src/frontend/types.ts b/src/frontend/types.ts index d285df7..085b517 100644 --- a/src/frontend/types.ts +++ b/src/frontend/types.ts @@ -187,7 +187,7 @@ export interface CreateGlobalDeviceInput { quantity: number; installedPowerPerUnitKw: number; demandFactor: number; - phaseCount?: 1 | 3; + phaseCount: 1 | 3; powerFactor?: number; note?: string; } diff --git a/src/frontend/utils/circuit-grid-model.ts b/src/frontend/utils/circuit-grid-model.ts index f5701fd..d138ed2 100644 --- a/src/frontend/utils/circuit-grid-model.ts +++ b/src/frontend/utils/circuit-grid-model.ts @@ -214,9 +214,22 @@ export function formatValue(value: GridValue, key?: CellKey): string { if (typeof value === "number") { return formatNumber(value, getMaximumFractionDigits(key)); } + if (key === "phaseType") { + return formatPhaseTypeLabel(String(value)); + } return String(value); } +export function formatPhaseTypeLabel(value: string): string { + if (value === "single_phase") { + return "1-phasig"; + } + if (value === "three_phase") { + return "3-phasig"; + } + return value; +} + export function parseNumeric(cellKey: CellKey, draft: string): number | undefined { const trimmed = draft.trim(); if (trimmed === "") { diff --git a/src/shared/validation/global-device.schemas.ts b/src/shared/validation/global-device.schemas.ts index 2213f17..5eb43bb 100644 --- a/src/shared/validation/global-device.schemas.ts +++ b/src/shared/validation/global-device.schemas.ts @@ -7,7 +7,7 @@ export const createGlobalDeviceSchema = z.object({ quantity: z.number().min(0), installedPowerPerUnitKw: z.number().min(0), demandFactor: z.number().min(0).max(1), - phaseCount: z.union([z.literal(1), z.literal(3)]).optional(), + phaseCount: z.union([z.literal(1), z.literal(3)]), powerFactor: z.number().min(0).max(1).optional(), note: z.string().optional(), }).strict(); diff --git a/tests/circuit-grid-model.test.ts b/tests/circuit-grid-model.test.ts index 7126e24..8bff53a 100644 --- a/tests/circuit-grid-model.test.ts +++ b/tests/circuit-grid-model.test.ts @@ -82,6 +82,8 @@ describe("circuit grid model", () => { assert.equal(formatValue(0.87654, "simultaneityFactor"), "0,88"); assert.equal(formatValue(230.4, "voltage"), "230"); assert.equal(formatValue(16, "protectionRatedCurrent"), "16"); + assert.equal(formatValue("single_phase", "phaseType"), "1-phasig"); + assert.equal(formatValue("three_phase", "phaseType"), "3-phasig"); }); it("uses circuit display values for block sorting and falls back to the first device", () => { diff --git a/tests/circuit-power-calculation.test.ts b/tests/circuit-power-calculation.test.ts index 77eb688..8f6ae51 100644 --- a/tests/circuit-power-calculation.test.ts +++ b/tests/circuit-power-calculation.test.ts @@ -7,6 +7,7 @@ import { import { resolveCircuitPhaseType, resolveProjectVoltage, + normalizeKnownElectricalPhaseType, } from "../src/domain/services/project-voltage.service.js"; describe("circuit power calculation", () => { @@ -43,5 +44,17 @@ describe("project voltage derivation", () => { assert.equal(resolveCircuitPhaseType("unassigned", []), "single_phase"); assert.equal(resolveCircuitPhaseType("unassigned", [null]), "single_phase"); }); + + it("normalizes known legacy phase labels without guessing unknown values", () => { + assert.equal(normalizeKnownElectricalPhaseType("1phasig"), "single_phase"); + assert.equal(normalizeKnownElectricalPhaseType("1-phasig"), "single_phase"); + assert.equal(normalizeKnownElectricalPhaseType("3phase"), "three_phase"); + assert.equal(normalizeKnownElectricalPhaseType("3-phasig"), "three_phase"); + assert.equal(normalizeKnownElectricalPhaseType(null), null); + assert.throws( + () => normalizeKnownElectricalPhaseType("zweiphasig"), + /Unknown electrical phase type/ + ); + }); });