Standardize phase type labels

This commit is contained in:
2026-07-29 10:46:00 +02:00
parent 084103bf54
commit 096ba8aa1c
23 changed files with 6117 additions and 52 deletions
+107 -27
View File
@@ -25,6 +25,7 @@ import {
buildDeviceRowEditPatch,
defaultVisibleColumnKeys,
deviceFieldKeys,
formatPhaseTypeLabel,
formatValue,
} from "../utils/circuit-grid-model";
import {
@@ -153,16 +154,6 @@ function normalizeUiError(err: unknown): string {
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>) {
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 pendingSelectedCircuitIdsAfterReload = useRef<string[] | 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 projectRevisionRef = useRef<number | null>(null);
@@ -449,7 +440,14 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
</button>
) : 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 (
<button
@@ -1213,9 +1211,12 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
return;
}
inputRef.current.focus();
if (editingCell.mode === "selectExisting") {
if (
inputRef.current instanceof HTMLInputElement &&
editingCell.mode === "selectExisting"
) {
inputRef.current.select();
} else {
} else if (inputRef.current instanceof HTMLInputElement) {
const len = inputRef.current.value.length;
inputRef.current.setSelectionRange(len, len);
}
@@ -1245,7 +1246,10 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
return;
}
// 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;
setEditingCell({
...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.
async function commitEdit(direction: SaveDirection = "stay") {
async function commitEdit(
direction: SaveDirection = "stay",
draftOverride?: string
) {
if (!editingCell) {
return;
}
@@ -1394,10 +1401,11 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
setEditingCell(null);
return;
}
const draft = draftOverride ?? editingCell.draft;
if (
editingCell.cellKey === "equipmentIdentifier" &&
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.");
return;
@@ -1424,7 +1432,11 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
const command: HistoryCommand = {
label: "Aus freier Zeile erstellen",
redo: async () => {
const selection = await createFromPlaceholder(row.sectionId, editingCell.cellKey, editingCell.draft);
const selection = await createFromPlaceholder(
row.sectionId,
editingCell.cellKey,
draft
);
targetSelection = selection;
return nextSelectionIntent();
},
@@ -1435,7 +1447,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
}
if (cell.kind === "circuitField" && row.circuit) {
const next = editingCell.draft;
const next = draft;
const command: HistoryCommand = {
label: "Stromkreisfeld bearbeiten",
redo: async () => {
@@ -1449,7 +1461,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
}
if (cell.kind === "deviceField" && row.device) {
const next = editingCell.draft;
const next = draft;
const command: HistoryCommand = {
label: "Gerätefeld bearbeiten",
redo: async () => {
@@ -1463,7 +1475,7 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
}
if (cell.kind === "deviceField" && row.rowType === "reserveCircuit" && row.circuit) {
const next = editingCell.draft;
const next = draft;
const command: HistoryCommand = {
label: "Gerätezeile im Reservestromkreis erstellen",
redo: async () => {
@@ -2816,7 +2828,18 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
</div>
<div className="header-filter-values">
{(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
}
>
<span className="selection-marker" aria-hidden="true">{selected ? "✓" : ""}</span>
<span>{value}</span>
<span>
{column.key === "phaseType"
? formatPhaseTypeLabel(value)
: value}
</span>
</button>
);
})}
{(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 ? (
<div className="header-filter-empty">Keine passenden Werte gefunden.</div>
) : 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
ref={inputRef}
ref={(element) => {
inputRef.current = element;
}}
value={editingCell.draft}
aria-invalid={hasDraftIdentifierConflict}
title={hasDraftIdentifierConflict ? "Dieses Betriebsmittelkennzeichen ist bereits vorhanden." : undefined}