Standardize phase type labels
This commit is contained in:
@@ -37,6 +37,9 @@ The pure grid modules have no React state and are covered by focused unit tests.
|
|||||||
- `cosPhi`
|
- `cosPhi`
|
||||||
- room snapshots
|
- room snapshots
|
||||||
- category/cost group and related row attributes
|
- 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`
|
- `ProjectDevice`
|
||||||
- Reusable device template entity at project level.
|
- Reusable device template entity at project level.
|
||||||
- Can be linked to `CircuitDeviceRow` entries, with copied display values on insert.
|
- Can be linked to `CircuitDeviceRow` entries, with copied display values on insert.
|
||||||
|
|||||||
+2
-1
@@ -689,7 +689,8 @@ body {
|
|||||||
border-color: #c2410c;
|
border-color: #c2410c;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-grid input {
|
.tree-grid input,
|
||||||
|
.tree-grid select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 5rem;
|
min-width: 5rem;
|
||||||
border: 1px solid #9fb6e0;
|
border: 1px solid #9fb6e0;
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ export default function ProjectsPage() {
|
|||||||
|
|
||||||
async function handleQuickUpdateGlobalDevice(
|
async function handleQuickUpdateGlobalDevice(
|
||||||
device: GlobalDeviceDto,
|
device: GlobalDeviceDto,
|
||||||
key: "name" | "displayName" | "category",
|
key: "name" | "displayName" | "category" | "phaseCount",
|
||||||
value: string
|
value: string
|
||||||
) {
|
) {
|
||||||
const payload: CreateGlobalDeviceInput = {
|
const payload: CreateGlobalDeviceInput = {
|
||||||
@@ -203,7 +203,12 @@ export default function ProjectsPage() {
|
|||||||
quantity: device.quantity,
|
quantity: device.quantity,
|
||||||
installedPowerPerUnitKw: device.installedPowerPerUnitKw,
|
installedPowerPerUnitKw: device.installedPowerPerUnitKw,
|
||||||
demandFactor: device.demandFactor,
|
demandFactor: device.demandFactor,
|
||||||
phaseCount: device.phaseCount ?? undefined,
|
phaseCount:
|
||||||
|
key === "phaseCount"
|
||||||
|
? value === "3"
|
||||||
|
? 3
|
||||||
|
: 1
|
||||||
|
: device.phaseCount ?? 1,
|
||||||
powerFactor: device.powerFactor ?? undefined,
|
powerFactor: device.powerFactor ?? undefined,
|
||||||
note: device.note ?? undefined,
|
note: device.note ?? undefined,
|
||||||
};
|
};
|
||||||
@@ -416,8 +421,8 @@ export default function ProjectsPage() {
|
|||||||
setGlobalDeviceForm((current) => ({ ...current, phaseCount: event.target.value }))
|
setGlobalDeviceForm((current) => ({ ...current, phaseCount: event.target.value }))
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<option value="1">1-ph</option>
|
<option value="1">1-phasig</option>
|
||||||
<option value="3">3-ph</option>
|
<option value="3">3-phasig</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div className="col-6 col-md-2">
|
<div className="col-6 col-md-2">
|
||||||
@@ -437,6 +442,7 @@ export default function ProjectsPage() {
|
|||||||
<th>Anzahl</th>
|
<th>Anzahl</th>
|
||||||
<th>Leistung je Stück [kW]</th>
|
<th>Leistung je Stück [kW]</th>
|
||||||
<th>GZF</th>
|
<th>GZF</th>
|
||||||
|
<th>Phasenart</th>
|
||||||
<th>Aktion</th>
|
<th>Aktion</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -479,6 +485,23 @@ export default function ProjectsPage() {
|
|||||||
<td>{device.quantity}</td>
|
<td>{device.quantity}</td>
|
||||||
<td>{device.installedPowerPerUnitKw}</td>
|
<td>{device.installedPowerPerUnitKw}</td>
|
||||||
<td>{device.demandFactor}</td>
|
<td>{device.demandFactor}</td>
|
||||||
|
<td>
|
||||||
|
<select
|
||||||
|
className="form-select form-select-sm"
|
||||||
|
aria-label={`Phasenart für ${device.displayName}`}
|
||||||
|
value={String(device.phaseCount ?? 1)}
|
||||||
|
onChange={(event) =>
|
||||||
|
void handleQuickUpdateGlobalDevice(
|
||||||
|
device,
|
||||||
|
"phaseCount",
|
||||||
|
event.target.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<option value="1">1-phasig</option>
|
||||||
|
<option value="3">3-phasig</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm btn-outline-danger"
|
className="btn btn-sm btn-outline-danger"
|
||||||
@@ -492,7 +515,7 @@ export default function ProjectsPage() {
|
|||||||
))}
|
))}
|
||||||
{!globalDevices.length ? (
|
{!globalDevices.length ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan={7} className="text-center text-secondary py-4">
|
<td colSpan={8} className="text-center text-secondary py-4">
|
||||||
Noch keine globalen Geräte vorhanden.
|
Noch keine globalen Geräte vorhanden.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
UPDATE `project_devices`
|
||||||
|
SET `phase_type` = CASE
|
||||||
|
WHEN LOWER(REPLACE(REPLACE(TRIM(`phase_type`), '-', ''), '_', ''))
|
||||||
|
IN ('1ph', '1phase', '1phasig', 'einphasig', 'singlephase')
|
||||||
|
THEN 'single_phase'
|
||||||
|
WHEN LOWER(REPLACE(REPLACE(TRIM(`phase_type`), '-', ''), '_', ''))
|
||||||
|
IN ('3ph', '3phase', '3phasig', 'dreiphasig', 'threephase')
|
||||||
|
THEN 'three_phase'
|
||||||
|
ELSE `phase_type`
|
||||||
|
END
|
||||||
|
WHERE LOWER(REPLACE(REPLACE(TRIM(`phase_type`), '-', ''), '_', ''))
|
||||||
|
IN (
|
||||||
|
'1ph',
|
||||||
|
'1phase',
|
||||||
|
'1phasig',
|
||||||
|
'einphasig',
|
||||||
|
'singlephase',
|
||||||
|
'3ph',
|
||||||
|
'3phase',
|
||||||
|
'3phasig',
|
||||||
|
'dreiphasig',
|
||||||
|
'threephase'
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
UPDATE `circuit_device_rows`
|
||||||
|
SET `phase_type` = CASE
|
||||||
|
WHEN LOWER(REPLACE(REPLACE(TRIM(`phase_type`), '-', ''), '_', ''))
|
||||||
|
IN ('1ph', '1phase', '1phasig', 'einphasig', 'singlephase')
|
||||||
|
THEN 'single_phase'
|
||||||
|
WHEN LOWER(REPLACE(REPLACE(TRIM(`phase_type`), '-', ''), '_', ''))
|
||||||
|
IN ('3ph', '3phase', '3phasig', 'dreiphasig', 'threephase')
|
||||||
|
THEN 'three_phase'
|
||||||
|
ELSE `phase_type`
|
||||||
|
END
|
||||||
|
WHERE LOWER(REPLACE(REPLACE(TRIM(`phase_type`), '-', ''), '_', ''))
|
||||||
|
IN (
|
||||||
|
'1ph',
|
||||||
|
'1phase',
|
||||||
|
'1phasig',
|
||||||
|
'einphasig',
|
||||||
|
'singlephase',
|
||||||
|
'3ph',
|
||||||
|
'3phase',
|
||||||
|
'3phasig',
|
||||||
|
'dreiphasig',
|
||||||
|
'threephase'
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
WITH `derived_circuit_voltages` AS (
|
||||||
|
SELECT
|
||||||
|
`c`.`id` AS `circuit_id`,
|
||||||
|
CASE
|
||||||
|
WHEN `cs`.`key` = 'three_phase' THEN `p`.`three_phase_voltage_v`
|
||||||
|
WHEN `cs`.`key` IN ('lighting', 'single_phase') THEN `p`.`single_phase_voltage_v`
|
||||||
|
WHEN EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM `circuit_device_rows` AS `three_phase_row`
|
||||||
|
WHERE `three_phase_row`.`circuit_id` = `c`.`id`
|
||||||
|
AND `three_phase_row`.`phase_type` = 'three_phase'
|
||||||
|
) AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM `circuit_device_rows` AS `single_phase_row`
|
||||||
|
WHERE `single_phase_row`.`circuit_id` = `c`.`id`
|
||||||
|
AND `single_phase_row`.`phase_type` = 'single_phase'
|
||||||
|
) THEN `p`.`three_phase_voltage_v`
|
||||||
|
ELSE `p`.`single_phase_voltage_v`
|
||||||
|
END AS `derived_voltage`
|
||||||
|
FROM `circuits` AS `c`
|
||||||
|
INNER JOIN `circuit_lists` AS `cl`
|
||||||
|
ON `cl`.`id` = `c`.`circuit_list_id`
|
||||||
|
INNER JOIN `projects` AS `p`
|
||||||
|
ON `p`.`id` = `cl`.`project_id`
|
||||||
|
INNER JOIN `circuit_sections` AS `cs`
|
||||||
|
ON `cs`.`id` = `c`.`section_id`
|
||||||
|
)
|
||||||
|
UPDATE `circuits`
|
||||||
|
SET `voltage` = (
|
||||||
|
SELECT `derived_voltage`
|
||||||
|
FROM `derived_circuit_voltages`
|
||||||
|
WHERE `circuit_id` = `circuits`.`id`
|
||||||
|
)
|
||||||
|
WHERE `id` IN (
|
||||||
|
SELECT `circuit_id`
|
||||||
|
FROM `derived_circuit_voltages`
|
||||||
|
);
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
UPDATE `circuit_device_rows`
|
||||||
|
SET `phase_type` = COALESCE(
|
||||||
|
(
|
||||||
|
SELECT `pd`.`phase_type`
|
||||||
|
FROM `project_devices` AS `pd`
|
||||||
|
WHERE `pd`.`id` = `circuit_device_rows`.`linked_project_device_id`
|
||||||
|
AND `pd`.`phase_type` IN ('single_phase', 'three_phase')
|
||||||
|
),
|
||||||
|
(
|
||||||
|
SELECT CASE
|
||||||
|
WHEN `cs`.`key` = 'three_phase' THEN 'three_phase'
|
||||||
|
WHEN `cs`.`key` IN ('lighting', 'single_phase') THEN 'single_phase'
|
||||||
|
ELSE NULL
|
||||||
|
END
|
||||||
|
FROM `circuits` AS `c`
|
||||||
|
INNER JOIN `circuit_sections` AS `cs`
|
||||||
|
ON `cs`.`id` = `c`.`section_id`
|
||||||
|
WHERE `c`.`id` = `circuit_device_rows`.`circuit_id`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
WHERE `phase_type` IS NULL
|
||||||
|
AND (
|
||||||
|
EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM `project_devices` AS `pd`
|
||||||
|
WHERE `pd`.`id` = `circuit_device_rows`.`linked_project_device_id`
|
||||||
|
AND `pd`.`phase_type` IN ('single_phase', 'three_phase')
|
||||||
|
)
|
||||||
|
OR EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM `circuits` AS `c`
|
||||||
|
INNER JOIN `circuit_sections` AS `cs`
|
||||||
|
ON `cs`.`id` = `c`.`section_id`
|
||||||
|
WHERE `c`.`id` = `circuit_device_rows`.`circuit_id`
|
||||||
|
AND `cs`.`key` IN ('lighting', 'single_phase', 'three_phase')
|
||||||
|
)
|
||||||
|
);
|
||||||
|
--> statement-breakpoint
|
||||||
|
WITH `derived_circuit_voltages` AS (
|
||||||
|
SELECT
|
||||||
|
`c`.`id` AS `circuit_id`,
|
||||||
|
CASE
|
||||||
|
WHEN `cs`.`key` = 'three_phase' THEN `p`.`three_phase_voltage_v`
|
||||||
|
WHEN `cs`.`key` IN ('lighting', 'single_phase') THEN `p`.`single_phase_voltage_v`
|
||||||
|
WHEN EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM `circuit_device_rows` AS `three_phase_row`
|
||||||
|
WHERE `three_phase_row`.`circuit_id` = `c`.`id`
|
||||||
|
AND `three_phase_row`.`phase_type` = 'three_phase'
|
||||||
|
) AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM `circuit_device_rows` AS `single_phase_row`
|
||||||
|
WHERE `single_phase_row`.`circuit_id` = `c`.`id`
|
||||||
|
AND `single_phase_row`.`phase_type` = 'single_phase'
|
||||||
|
) THEN `p`.`three_phase_voltage_v`
|
||||||
|
ELSE `p`.`single_phase_voltage_v`
|
||||||
|
END AS `derived_voltage`
|
||||||
|
FROM `circuits` AS `c`
|
||||||
|
INNER JOIN `circuit_lists` AS `cl`
|
||||||
|
ON `cl`.`id` = `c`.`circuit_list_id`
|
||||||
|
INNER JOIN `projects` AS `p`
|
||||||
|
ON `p`.`id` = `cl`.`project_id`
|
||||||
|
INNER JOIN `circuit_sections` AS `cs`
|
||||||
|
ON `cs`.`id` = `c`.`section_id`
|
||||||
|
)
|
||||||
|
UPDATE `circuits`
|
||||||
|
SET `voltage` = (
|
||||||
|
SELECT `derived_voltage`
|
||||||
|
FROM `derived_circuit_voltages`
|
||||||
|
WHERE `circuit_id` = `circuits`.`id`
|
||||||
|
)
|
||||||
|
WHERE `id` IN (
|
||||||
|
SELECT `circuit_id`
|
||||||
|
FROM `derived_circuit_voltages`
|
||||||
|
);
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
UPDATE `circuit_device_rows`
|
||||||
|
SET `phase_type` = 'single_phase'
|
||||||
|
WHERE `phase_type` IS NULL;
|
||||||
|
--> statement-breakpoint
|
||||||
|
UPDATE `global_devices`
|
||||||
|
SET `phase_count` = 1
|
||||||
|
WHERE `phase_count` IS NULL OR `phase_count` NOT IN (1, 3);
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -141,6 +141,27 @@
|
|||||||
"when": 1785310907349,
|
"when": 1785310907349,
|
||||||
"tag": "0019_normalize_project_voltages",
|
"tag": "0019_normalize_project_voltages",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 20,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1785314248007,
|
||||||
|
"tag": "0020_normalize_phase_types",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 21,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1785314558555,
|
||||||
|
"tag": "0021_fill_missing_phase_types",
|
||||||
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 22,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1785314626476,
|
||||||
|
"tag": "0022_default_remaining_phase_types",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -11,6 +11,7 @@ import type {
|
|||||||
ExecuteCircuitDeviceRowUpdateCommandInput,
|
ExecuteCircuitDeviceRowUpdateCommandInput,
|
||||||
} from "../../domain/ports/circuit-device-row-project-command.store.js";
|
} from "../../domain/ports/circuit-device-row-project-command.store.js";
|
||||||
import { deriveOverriddenFieldsForLocalEdit } from "../../domain/services/project-device-overrides.js";
|
import { deriveOverriddenFieldsForLocalEdit } from "../../domain/services/project-device-overrides.js";
|
||||||
|
import { isElectricalPhaseType } from "../../domain/services/project-voltage.service.js";
|
||||||
import type { AppDatabase } from "../database-context.js";
|
import type { AppDatabase } from "../database-context.js";
|
||||||
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
||||||
import { circuitLists } from "../schema/circuit-lists.js";
|
import { circuitLists } from "../schema/circuit-lists.js";
|
||||||
@@ -81,6 +82,13 @@ export class CircuitDeviceRowProjectCommandRepository
|
|||||||
change.value,
|
change.value,
|
||||||
])
|
])
|
||||||
) as CircuitDeviceRowPatchInput;
|
) as CircuitDeviceRowPatchInput;
|
||||||
|
if (
|
||||||
|
input.source === "user" &&
|
||||||
|
patch.phaseType !== undefined &&
|
||||||
|
!isElectricalPhaseType(patch.phaseType)
|
||||||
|
) {
|
||||||
|
throw new Error("Device-row phase type is invalid.");
|
||||||
|
}
|
||||||
this.assertLinkedProjectDevice(tx, input.projectId, patch);
|
this.assertLinkedProjectDevice(tx, input.projectId, patch);
|
||||||
this.assertRoom(tx, input.projectId, patch);
|
this.assertRoom(tx, input.projectId, patch);
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import type {
|
|||||||
ExecuteCircuitDeviceRowStructureCommandInput,
|
ExecuteCircuitDeviceRowStructureCommandInput,
|
||||||
} from "../../domain/ports/circuit-device-row-structure-project-command.store.js";
|
} from "../../domain/ports/circuit-device-row-structure-project-command.store.js";
|
||||||
import type { AppDatabase } from "../database-context.js";
|
import type { AppDatabase } from "../database-context.js";
|
||||||
|
import { isElectricalPhaseType } from "../../domain/services/project-voltage.service.js";
|
||||||
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
||||||
import { circuitLists } from "../schema/circuit-lists.js";
|
import { circuitLists } from "../schema/circuit-lists.js";
|
||||||
import { circuits } from "../schema/circuits.js";
|
import { circuits } from "../schema/circuits.js";
|
||||||
@@ -81,6 +82,9 @@ export class CircuitDeviceRowStructureProjectCommandRepository
|
|||||||
"User commands cannot assign legacy consumer ids."
|
"User commands cannot assign legacy consumer ids."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (source === "user" && !isElectricalPhaseType(row.phaseType)) {
|
||||||
|
throw new Error("Device-row phase type is invalid.");
|
||||||
|
}
|
||||||
this.assertCircuitInProject(database, projectId, row.circuitId);
|
this.assertCircuitInProject(database, projectId, row.circuitId);
|
||||||
assertCircuitDeviceRowReferencesInProject(database, projectId, row);
|
assertCircuitDeviceRowReferencesInProject(database, projectId, row);
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import type {
|
|||||||
CircuitStructureProjectCommandStore,
|
CircuitStructureProjectCommandStore,
|
||||||
ExecuteCircuitStructureCommandInput,
|
ExecuteCircuitStructureCommandInput,
|
||||||
} from "../../domain/ports/circuit-structure-project-command.store.js";
|
} from "../../domain/ports/circuit-structure-project-command.store.js";
|
||||||
|
import { isElectricalPhaseType } from "../../domain/services/project-voltage.service.js";
|
||||||
import type { AppDatabase } from "../database-context.js";
|
import type { AppDatabase } from "../database-context.js";
|
||||||
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
import { circuitDeviceRows } from "../schema/circuit-device-rows.js";
|
||||||
import { circuitLists } from "../schema/circuit-lists.js";
|
import { circuitLists } from "../schema/circuit-lists.js";
|
||||||
@@ -136,6 +137,9 @@ export class CircuitStructureProjectCommandRepository
|
|||||||
"User commands cannot assign legacy consumer ids."
|
"User commands cannot assign legacy consumer ids."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (source === "user" && !isElectricalPhaseType(row.phaseType)) {
|
||||||
|
throw new Error("Device-row phase type is invalid.");
|
||||||
|
}
|
||||||
assertCircuitDeviceRowReferencesInProject(
|
assertCircuitDeviceRowReferencesInProject(
|
||||||
database,
|
database,
|
||||||
projectId,
|
projectId,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export class GlobalDeviceRepository {
|
|||||||
installedPowerPerUnitKw: input.installedPowerPerUnitKw,
|
installedPowerPerUnitKw: input.installedPowerPerUnitKw,
|
||||||
demandFactor: input.demandFactor,
|
demandFactor: input.demandFactor,
|
||||||
voltageV: null,
|
voltageV: null,
|
||||||
phaseCount: input.phaseCount ?? null,
|
phaseCount: input.phaseCount,
|
||||||
powerFactor: input.powerFactor ?? null,
|
powerFactor: input.powerFactor ?? null,
|
||||||
note: input.note ?? null,
|
note: input.note ?? null,
|
||||||
});
|
});
|
||||||
@@ -46,7 +46,7 @@ export class GlobalDeviceRepository {
|
|||||||
installedPowerPerUnitKw: input.installedPowerPerUnitKw,
|
installedPowerPerUnitKw: input.installedPowerPerUnitKw,
|
||||||
demandFactor: input.demandFactor,
|
demandFactor: input.demandFactor,
|
||||||
voltageV: null,
|
voltageV: null,
|
||||||
phaseCount: input.phaseCount ?? null,
|
phaseCount: input.phaseCount,
|
||||||
powerFactor: input.powerFactor ?? null,
|
powerFactor: input.powerFactor ?? null,
|
||||||
note: input.note ?? null,
|
note: input.note ?? null,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
resolveCircuitPhaseType,
|
resolveCircuitPhaseType,
|
||||||
resolveProjectVoltage,
|
resolveProjectVoltage,
|
||||||
|
normalizeKnownElectricalPhaseType,
|
||||||
} from "../services/project-voltage.service.js";
|
} from "../services/project-voltage.service.js";
|
||||||
|
|
||||||
export const legacyProjectStateSnapshotSchemaVersion = 1 as const;
|
export const legacyProjectStateSnapshotSchemaVersion = 1 as const;
|
||||||
@@ -225,7 +226,7 @@ export function parseProjectStateSnapshot(
|
|||||||
value: unknown
|
value: unknown
|
||||||
): ProjectStateSnapshot {
|
): ProjectStateSnapshot {
|
||||||
const version = isPlainObject(value) ? value.schemaVersion : undefined;
|
const version = isPlainObject(value) ? value.schemaVersion : undefined;
|
||||||
const snapshot =
|
const parsedSnapshot =
|
||||||
version === legacyProjectStateSnapshotSchemaVersion
|
version === legacyProjectStateSnapshotSchemaVersion
|
||||||
? upgradeSupplyTypesProjectStateSnapshot(
|
? upgradeSupplyTypesProjectStateSnapshot(
|
||||||
upgradeDistributionBoardProjectStateSnapshot(
|
upgradeDistributionBoardProjectStateSnapshot(
|
||||||
@@ -255,10 +256,57 @@ export function parseProjectStateSnapshot(
|
|||||||
supplyTypesProjectStateSnapshotSchema.parse(value)
|
supplyTypesProjectStateSnapshotSchema.parse(value)
|
||||||
)
|
)
|
||||||
: projectStateSnapshotSchema.parse(value);
|
: projectStateSnapshotSchema.parse(value);
|
||||||
|
const snapshot = normalizeSnapshotPhaseTypes(parsedSnapshot);
|
||||||
assertProjectStateSnapshotRelations(snapshot);
|
assertProjectStateSnapshotRelations(snapshot);
|
||||||
return snapshot;
|
return snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeSnapshotPhaseTypes(
|
||||||
|
snapshot: ProjectStateSnapshot
|
||||||
|
): ProjectStateSnapshot {
|
||||||
|
const sectionById = new Map(
|
||||||
|
snapshot.circuitSections.map((section) => [section.id, section])
|
||||||
|
);
|
||||||
|
const projectDeviceById = new Map(
|
||||||
|
snapshot.projectDevices.map((device) => [device.id, device])
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
...snapshot,
|
||||||
|
circuits: snapshot.circuits.map((circuit) => {
|
||||||
|
const section = sectionById.get(circuit.sectionId);
|
||||||
|
const deviceRows = circuit.deviceRows.map((row) => {
|
||||||
|
const normalizedPhaseType =
|
||||||
|
normalizeKnownElectricalPhaseType(row.phaseType);
|
||||||
|
const linkedPhaseType = row.linkedProjectDeviceId
|
||||||
|
? projectDeviceById.get(row.linkedProjectDeviceId)?.phaseType
|
||||||
|
: undefined;
|
||||||
|
return {
|
||||||
|
...row,
|
||||||
|
phaseType:
|
||||||
|
normalizedPhaseType ??
|
||||||
|
linkedPhaseType ??
|
||||||
|
(section?.key === "three_phase"
|
||||||
|
? "three_phase"
|
||||||
|
: "single_phase"),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
...circuit,
|
||||||
|
voltage: section
|
||||||
|
? resolveProjectVoltage(
|
||||||
|
resolveCircuitPhaseType(
|
||||||
|
section.key,
|
||||||
|
deviceRows.map((row) => row.phaseType)
|
||||||
|
),
|
||||||
|
snapshot.project
|
||||||
|
)
|
||||||
|
: circuit.voltage,
|
||||||
|
deviceRows,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function upgradeLegacyProjectStateSnapshot(
|
function upgradeLegacyProjectStateSnapshot(
|
||||||
snapshot: z.infer<typeof legacyProjectStateSnapshotSchema>
|
snapshot: z.infer<typeof legacyProjectStateSnapshotSchema>
|
||||||
): z.infer<typeof previousProjectStateSnapshotSchema> {
|
): z.infer<typeof previousProjectStateSnapshotSchema> {
|
||||||
|
|||||||
@@ -1,5 +1,39 @@
|
|||||||
export type ElectricalPhaseType = "single_phase" | "three_phase";
|
export type ElectricalPhaseType = "single_phase" | "three_phase";
|
||||||
|
|
||||||
|
export function isElectricalPhaseType(
|
||||||
|
value: unknown
|
||||||
|
): value is ElectricalPhaseType {
|
||||||
|
return value === "single_phase" || value === "three_phase";
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeKnownElectricalPhaseType(
|
||||||
|
value: string | null
|
||||||
|
): ElectricalPhaseType | null {
|
||||||
|
if (value === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const normalized = value
|
||||||
|
.trim()
|
||||||
|
.toLocaleLowerCase("de-DE")
|
||||||
|
.replaceAll("-", "")
|
||||||
|
.replaceAll("_", "");
|
||||||
|
if (
|
||||||
|
["1ph", "1phase", "1phasig", "einphasig", "singlephase"].includes(
|
||||||
|
normalized
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return "single_phase";
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
["3ph", "3phase", "3phasig", "dreiphasig", "threephase"].includes(
|
||||||
|
normalized
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return "three_phase";
|
||||||
|
}
|
||||||
|
throw new Error(`Unknown electrical phase type: ${value}`);
|
||||||
|
}
|
||||||
|
|
||||||
export interface ProjectVoltageSettings {
|
export interface ProjectVoltageSettings {
|
||||||
singlePhaseVoltageV: number;
|
singlePhaseVoltageV: number;
|
||||||
threePhaseVoltageV: number;
|
threePhaseVoltageV: number;
|
||||||
@@ -25,8 +59,7 @@ export function resolveCircuitPhaseType(
|
|||||||
return "single_phase";
|
return "single_phase";
|
||||||
}
|
}
|
||||||
const assignedPhaseTypes = devicePhaseTypes.filter(
|
const assignedPhaseTypes = devicePhaseTypes.filter(
|
||||||
(phaseType): phaseType is ElectricalPhaseType =>
|
isElectricalPhaseType
|
||||||
phaseType === "single_phase" || phaseType === "three_phase"
|
|
||||||
);
|
);
|
||||||
return assignedPhaseTypes.length > 0 &&
|
return assignedPhaseTypes.length > 0 &&
|
||||||
assignedPhaseTypes.every((phaseType) => phaseType === "three_phase")
|
assignedPhaseTypes.every((phaseType) => phaseType === "three_phase")
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
buildDeviceRowEditPatch,
|
buildDeviceRowEditPatch,
|
||||||
defaultVisibleColumnKeys,
|
defaultVisibleColumnKeys,
|
||||||
deviceFieldKeys,
|
deviceFieldKeys,
|
||||||
|
formatPhaseTypeLabel,
|
||||||
formatValue,
|
formatValue,
|
||||||
} from "../utils/circuit-grid-model";
|
} from "../utils/circuit-grid-model";
|
||||||
import {
|
import {
|
||||||
@@ -153,16 +154,6 @@ function normalizeUiError(err: unknown): string {
|
|||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatPhaseTypeLabel(value: string): string {
|
|
||||||
if (value === "three_phase") {
|
|
||||||
return "Dreiphasig";
|
|
||||||
}
|
|
||||||
if (value === "single_phase") {
|
|
||||||
return "Einphasig";
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPrintableKey(event: KeyboardEvent<HTMLElement>) {
|
function isPrintableKey(event: KeyboardEvent<HTMLElement>) {
|
||||||
return event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey;
|
return event.key.length === 1 && !event.metaKey && !event.ctrlKey && !event.altKey;
|
||||||
}
|
}
|
||||||
@@ -235,7 +226,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
const pendingSelectedDeviceRowIdsAfterReload = useRef<string[] | null>(null);
|
const pendingSelectedDeviceRowIdsAfterReload = useRef<string[] | null>(null);
|
||||||
const pendingSelectedCircuitIdsAfterReload = useRef<string[] | null>(null);
|
const pendingSelectedCircuitIdsAfterReload = useRef<string[] | null>(null);
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
const inputRef = useRef<HTMLInputElement | HTMLSelectElement | null>(null);
|
||||||
const focusTokenRef = useRef(1);
|
const focusTokenRef = useRef(1);
|
||||||
const projectRevisionRef = useRef<number | null>(null);
|
const projectRevisionRef = useRef<number | null>(null);
|
||||||
|
|
||||||
@@ -449,7 +440,14 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
{activeColumnFilters.map(({ column, values }) => {
|
{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;
|
const valueSummary = values.length > 2 ? `${shownValues} +${values.length - 2}` : shownValues;
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
@@ -1213,9 +1211,12 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
inputRef.current.focus();
|
inputRef.current.focus();
|
||||||
if (editingCell.mode === "selectExisting") {
|
if (
|
||||||
|
inputRef.current instanceof HTMLInputElement &&
|
||||||
|
editingCell.mode === "selectExisting"
|
||||||
|
) {
|
||||||
inputRef.current.select();
|
inputRef.current.select();
|
||||||
} else {
|
} else if (inputRef.current instanceof HTMLInputElement) {
|
||||||
const len = inputRef.current.value.length;
|
const len = inputRef.current.value.length;
|
||||||
inputRef.current.setSelectionRange(len, len);
|
inputRef.current.setSelectionRange(len, len);
|
||||||
}
|
}
|
||||||
@@ -1245,7 +1246,10 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Spreadsheet behavior: typing on a selected cell starts overwrite mode.
|
// Spreadsheet behavior: typing on a selected cell starts overwrite mode.
|
||||||
const currentDisplay = mode === "replaceWithTypedChar" ? typedChar ?? "" : String(visibleCell.value ?? "");
|
const currentDisplay =
|
||||||
|
mode === "replaceWithTypedChar" && cell.cellKey !== "phaseType"
|
||||||
|
? typedChar ?? ""
|
||||||
|
: String(visibleCell.value ?? "");
|
||||||
focusTokenRef.current += 1;
|
focusTokenRef.current += 1;
|
||||||
setEditingCell({
|
setEditingCell({
|
||||||
...cell,
|
...cell,
|
||||||
@@ -1384,7 +1388,10 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Commits the active editor input through command history so edits remain undoable.
|
// Commits the active editor input through command history so edits remain undoable.
|
||||||
async function commitEdit(direction: SaveDirection = "stay") {
|
async function commitEdit(
|
||||||
|
direction: SaveDirection = "stay",
|
||||||
|
draftOverride?: string
|
||||||
|
) {
|
||||||
if (!editingCell) {
|
if (!editingCell) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1394,10 +1401,11 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
setEditingCell(null);
|
setEditingCell(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const draft = draftOverride ?? editingCell.draft;
|
||||||
if (
|
if (
|
||||||
editingCell.cellKey === "equipmentIdentifier" &&
|
editingCell.cellKey === "equipmentIdentifier" &&
|
||||||
row.circuit &&
|
row.circuit &&
|
||||||
hasEquipmentIdentifierConflict(allCircuits, row.circuit.id, editingCell.draft)
|
hasEquipmentIdentifierConflict(allCircuits, row.circuit.id, draft)
|
||||||
) {
|
) {
|
||||||
setError("Dieses Betriebsmittelkennzeichen ist bereits vorhanden. Die Änderung wurde nicht gespeichert.");
|
setError("Dieses Betriebsmittelkennzeichen ist bereits vorhanden. Die Änderung wurde nicht gespeichert.");
|
||||||
return;
|
return;
|
||||||
@@ -1424,7 +1432,11 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
const command: HistoryCommand = {
|
const command: HistoryCommand = {
|
||||||
label: "Aus freier Zeile erstellen",
|
label: "Aus freier Zeile erstellen",
|
||||||
redo: async () => {
|
redo: async () => {
|
||||||
const selection = await createFromPlaceholder(row.sectionId, editingCell.cellKey, editingCell.draft);
|
const selection = await createFromPlaceholder(
|
||||||
|
row.sectionId,
|
||||||
|
editingCell.cellKey,
|
||||||
|
draft
|
||||||
|
);
|
||||||
targetSelection = selection;
|
targetSelection = selection;
|
||||||
return nextSelectionIntent();
|
return nextSelectionIntent();
|
||||||
},
|
},
|
||||||
@@ -1435,7 +1447,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cell.kind === "circuitField" && row.circuit) {
|
if (cell.kind === "circuitField" && row.circuit) {
|
||||||
const next = editingCell.draft;
|
const next = draft;
|
||||||
const command: HistoryCommand = {
|
const command: HistoryCommand = {
|
||||||
label: "Stromkreisfeld bearbeiten",
|
label: "Stromkreisfeld bearbeiten",
|
||||||
redo: async () => {
|
redo: async () => {
|
||||||
@@ -1449,7 +1461,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cell.kind === "deviceField" && row.device) {
|
if (cell.kind === "deviceField" && row.device) {
|
||||||
const next = editingCell.draft;
|
const next = draft;
|
||||||
const command: HistoryCommand = {
|
const command: HistoryCommand = {
|
||||||
label: "Gerätefeld bearbeiten",
|
label: "Gerätefeld bearbeiten",
|
||||||
redo: async () => {
|
redo: async () => {
|
||||||
@@ -1463,7 +1475,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (cell.kind === "deviceField" && row.rowType === "reserveCircuit" && row.circuit) {
|
if (cell.kind === "deviceField" && row.rowType === "reserveCircuit" && row.circuit) {
|
||||||
const next = editingCell.draft;
|
const next = draft;
|
||||||
const command: HistoryCommand = {
|
const command: HistoryCommand = {
|
||||||
label: "Gerätezeile im Reservestromkreis erstellen",
|
label: "Gerätezeile im Reservestromkreis erstellen",
|
||||||
redo: async () => {
|
redo: async () => {
|
||||||
@@ -2816,7 +2828,18 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
</div>
|
</div>
|
||||||
<div className="header-filter-values">
|
<div className="header-filter-values">
|
||||||
{(distinctValuesByColumn[column.key] ?? [])
|
{(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) => {
|
.map((value) => {
|
||||||
const selected = filterDraftValues.includes(value);
|
const selected = filterDraftValues.includes(value);
|
||||||
return (
|
return (
|
||||||
@@ -2834,12 +2857,26 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
<span className="selection-marker" aria-hidden="true">{selected ? "✓" : ""}</span>
|
<span className="selection-marker" aria-hidden="true">{selected ? "✓" : ""}</span>
|
||||||
<span>{value}</span>
|
<span>
|
||||||
|
{column.key === "phaseType"
|
||||||
|
? formatPhaseTypeLabel(value)
|
||||||
|
: value}
|
||||||
|
</span>
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{(distinctValuesByColumn[column.key] ?? []).filter((value) =>
|
{(distinctValuesByColumn[column.key] ?? []).filter(
|
||||||
value.toLocaleLowerCase("de-DE").includes(filterValueSearch.trim().toLocaleLowerCase("de-DE"))
|
(value) =>
|
||||||
|
(column.key === "phaseType"
|
||||||
|
? formatPhaseTypeLabel(value)
|
||||||
|
: value
|
||||||
|
)
|
||||||
|
.toLocaleLowerCase("de-DE")
|
||||||
|
.includes(
|
||||||
|
filterValueSearch
|
||||||
|
.trim()
|
||||||
|
.toLocaleLowerCase("de-DE")
|
||||||
|
)
|
||||||
).length === 0 ? (
|
).length === 0 ? (
|
||||||
<div className="header-filter-empty">Keine passenden Werte gefunden.</div>
|
<div className="header-filter-empty">Keine passenden Werte gefunden.</div>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -3375,9 +3412,52 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isEditing ? (
|
{isEditing && column.key === "phaseType" ? (
|
||||||
|
<select
|
||||||
|
ref={(element) => {
|
||||||
|
inputRef.current = element;
|
||||||
|
}}
|
||||||
|
value={editingCell.draft}
|
||||||
|
aria-label="Phasenart auswählen"
|
||||||
|
onChange={(event) =>
|
||||||
|
void commitEdit("stay", event.target.value)
|
||||||
|
}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
event.preventDefault();
|
||||||
|
void commitEdit("stay");
|
||||||
|
} else if (event.key === "Escape") {
|
||||||
|
event.preventDefault();
|
||||||
|
cancelEdit();
|
||||||
|
} else if (event.key === "Tab") {
|
||||||
|
event.preventDefault();
|
||||||
|
void commitEdit(
|
||||||
|
event.shiftKey ? "prev" : "next"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onBlur={() => {
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const active =
|
||||||
|
document.activeElement as HTMLElement | null;
|
||||||
|
if (
|
||||||
|
!active ||
|
||||||
|
!containerRef.current?.contains(active)
|
||||||
|
) {
|
||||||
|
setEditingCell(null);
|
||||||
|
focusGridWithoutScroll();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="single_phase">1-phasig</option>
|
||||||
|
<option value="three_phase">3-phasig</option>
|
||||||
|
</select>
|
||||||
|
) : isEditing ? (
|
||||||
<input
|
<input
|
||||||
ref={inputRef}
|
ref={(element) => {
|
||||||
|
inputRef.current = element;
|
||||||
|
}}
|
||||||
value={editingCell.draft}
|
value={editingCell.draft}
|
||||||
aria-invalid={hasDraftIdentifierConflict}
|
aria-invalid={hasDraftIdentifierConflict}
|
||||||
title={hasDraftIdentifierConflict ? "Dieses Betriebsmittelkennzeichen ist bereits vorhanden." : undefined}
|
title={hasDraftIdentifierConflict ? "Dieses Betriebsmittelkennzeichen ist bereits vorhanden." : undefined}
|
||||||
|
|||||||
@@ -6,16 +6,6 @@ import { getCircuitTree } from "../utils/api";
|
|||||||
import type { CircuitTreeCircuitDto, CircuitTreeResponseDto } from "../types";
|
import type { CircuitTreeCircuitDto, CircuitTreeResponseDto } from "../types";
|
||||||
import { formatValue } from "../utils/circuit-grid-model";
|
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) {
|
function renderCircuitSummaryLabel(circuit: CircuitTreeCircuitDto) {
|
||||||
if (circuit.displayName?.trim()) {
|
if (circuit.displayName?.trim()) {
|
||||||
return circuit.displayName;
|
return circuit.displayName;
|
||||||
@@ -140,7 +130,7 @@ function SectionRows(props: { section: CircuitTreeResponseDto["sections"][number
|
|||||||
<tr key={circuit.id} className={circuit.isReserve ? "reserve-row" : ""}>
|
<tr key={circuit.id} className={circuit.isReserve ? "reserve-row" : ""}>
|
||||||
<td>{circuit.equipmentIdentifier}</td>
|
<td>{circuit.equipmentIdentifier}</td>
|
||||||
<td>{row.displayName || row.name}</td>
|
<td>{row.displayName || row.name}</td>
|
||||||
<td>{formatPhaseType(row.phaseType)}</td>
|
<td>{formatValue(row.phaseType ?? undefined, "phaseType")}</td>
|
||||||
<td>{row.connectionKind ?? "-"}</td>
|
<td>{row.connectionKind ?? "-"}</td>
|
||||||
<td>{row.costGroup ?? "-"}</td>
|
<td>{row.costGroup ?? "-"}</td>
|
||||||
<td>{row.category ?? "-"}</td>
|
<td>{row.category ?? "-"}</td>
|
||||||
@@ -190,7 +180,7 @@ function SectionRows(props: { section: CircuitTreeResponseDto["sections"][number
|
|||||||
<tr key={row.id} className="device-row">
|
<tr key={row.id} className="device-row">
|
||||||
<td className="text-muted"> </td>
|
<td className="text-muted"> </td>
|
||||||
<td className="indented-cell">{row.displayName || row.name}</td>
|
<td className="indented-cell">{row.displayName || row.name}</td>
|
||||||
<td>{formatPhaseType(row.phaseType)}</td>
|
<td>{formatValue(row.phaseType ?? undefined, "phaseType")}</td>
|
||||||
<td>{row.connectionKind ?? "-"}</td>
|
<td>{row.connectionKind ?? "-"}</td>
|
||||||
<td>{row.costGroup ?? "-"}</td>
|
<td>{row.costGroup ?? "-"}</td>
|
||||||
<td>{row.category ?? "-"}</td>
|
<td>{row.category ?? "-"}</td>
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ export interface CreateGlobalDeviceInput {
|
|||||||
quantity: number;
|
quantity: number;
|
||||||
installedPowerPerUnitKw: number;
|
installedPowerPerUnitKw: number;
|
||||||
demandFactor: number;
|
demandFactor: number;
|
||||||
phaseCount?: 1 | 3;
|
phaseCount: 1 | 3;
|
||||||
powerFactor?: number;
|
powerFactor?: number;
|
||||||
note?: string;
|
note?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -214,9 +214,22 @@ export function formatValue(value: GridValue, key?: CellKey): string {
|
|||||||
if (typeof value === "number") {
|
if (typeof value === "number") {
|
||||||
return formatNumber(value, getMaximumFractionDigits(key));
|
return formatNumber(value, getMaximumFractionDigits(key));
|
||||||
}
|
}
|
||||||
|
if (key === "phaseType") {
|
||||||
|
return formatPhaseTypeLabel(String(value));
|
||||||
|
}
|
||||||
return 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 {
|
export function parseNumeric(cellKey: CellKey, draft: string): number | undefined {
|
||||||
const trimmed = draft.trim();
|
const trimmed = draft.trim();
|
||||||
if (trimmed === "") {
|
if (trimmed === "") {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export const createGlobalDeviceSchema = z.object({
|
|||||||
quantity: z.number().min(0),
|
quantity: z.number().min(0),
|
||||||
installedPowerPerUnitKw: z.number().min(0),
|
installedPowerPerUnitKw: z.number().min(0),
|
||||||
demandFactor: z.number().min(0).max(1),
|
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(),
|
powerFactor: z.number().min(0).max(1).optional(),
|
||||||
note: z.string().optional(),
|
note: z.string().optional(),
|
||||||
}).strict();
|
}).strict();
|
||||||
|
|||||||
@@ -82,6 +82,8 @@ describe("circuit grid model", () => {
|
|||||||
assert.equal(formatValue(0.87654, "simultaneityFactor"), "0,88");
|
assert.equal(formatValue(0.87654, "simultaneityFactor"), "0,88");
|
||||||
assert.equal(formatValue(230.4, "voltage"), "230");
|
assert.equal(formatValue(230.4, "voltage"), "230");
|
||||||
assert.equal(formatValue(16, "protectionRatedCurrent"), "16");
|
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", () => {
|
it("uses circuit display values for block sorting and falls back to the first device", () => {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
resolveCircuitPhaseType,
|
resolveCircuitPhaseType,
|
||||||
resolveProjectVoltage,
|
resolveProjectVoltage,
|
||||||
|
normalizeKnownElectricalPhaseType,
|
||||||
} from "../src/domain/services/project-voltage.service.js";
|
} from "../src/domain/services/project-voltage.service.js";
|
||||||
|
|
||||||
describe("circuit power calculation", () => {
|
describe("circuit power calculation", () => {
|
||||||
@@ -43,5 +44,17 @@ describe("project voltage derivation", () => {
|
|||||||
assert.equal(resolveCircuitPhaseType("unassigned", []), "single_phase");
|
assert.equal(resolveCircuitPhaseType("unassigned", []), "single_phase");
|
||||||
assert.equal(resolveCircuitPhaseType("unassigned", [null]), "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/
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user