Manage distribution board components
This commit is contained in:
@@ -47,6 +47,15 @@ import {
|
||||
import {
|
||||
buildCircuitSectionRenumberAssignments,
|
||||
} from "../utils/circuit-section-renumber-command";
|
||||
import {
|
||||
buildDistributionBoardComponentSnapshot,
|
||||
getNextComponentSortOrder,
|
||||
getSuggestedGroupComponentIdentifier,
|
||||
toDistributionBoardComponentSnapshot,
|
||||
updateDistributionBoardComponentSnapshot,
|
||||
type DistributionBoardComponentEditorValues,
|
||||
type MutableDistributionBoardComponentRole,
|
||||
} from "../utils/distribution-board-component-editing";
|
||||
import { loadCircuitEditorSnapshot } from "../utils/circuit-editor-history";
|
||||
import type {
|
||||
CellKey,
|
||||
@@ -62,11 +71,13 @@ import type { VisibleGridRow } from "../utils/circuit-grid-projection";
|
||||
import {
|
||||
deleteCircuitCommand,
|
||||
deleteCircuitDeviceRowCommand,
|
||||
deleteDistributionBoardComponentCommand,
|
||||
getCircuitTree,
|
||||
getNextCircuitIdentifier,
|
||||
getProjectHistory,
|
||||
insertCircuitCommand,
|
||||
insertCircuitDeviceRowCommand,
|
||||
insertDistributionBoardComponentCommand,
|
||||
listProjectDevices,
|
||||
moveCircuitDeviceRowsCommand,
|
||||
moveCircuitDeviceRowsToNewCircuitCommand,
|
||||
@@ -76,10 +87,12 @@ import {
|
||||
redoProjectCommand,
|
||||
updateCircuitById,
|
||||
updateCircuitDeviceRowById,
|
||||
updateDistributionBoardComponentCommand,
|
||||
undoProjectCommand,
|
||||
} from "../utils/api";
|
||||
import type {
|
||||
CircuitTreeCircuitDto,
|
||||
CircuitTreeComponentDto,
|
||||
CircuitTreeResponseDto,
|
||||
CreateCircuitDeviceRowInputDto,
|
||||
CreateCircuitInputDto,
|
||||
@@ -93,6 +106,7 @@ import type {
|
||||
import type {
|
||||
CircuitSnapshot,
|
||||
} from "../../domain/models/circuit-structure-project-command.model";
|
||||
import { DistributionBoardComponentModal } from "./distribution-board-component-modal";
|
||||
|
||||
type SaveDirection = "stay" | "next" | "prev";
|
||||
type StartEditMode = "selectExisting" | "replaceWithTypedChar";
|
||||
@@ -161,6 +175,18 @@ function normalizeUiError(err: unknown): string {
|
||||
return message;
|
||||
}
|
||||
|
||||
type StructureComponentEditorIntent =
|
||||
| {
|
||||
kind: "create";
|
||||
role: MutableDistributionBoardComponentRole;
|
||||
sectionId?: string;
|
||||
initialEquipmentIdentifier?: string;
|
||||
}
|
||||
| {
|
||||
kind: "edit";
|
||||
component: CircuitTreeComponentDto;
|
||||
};
|
||||
|
||||
function getFullColumnLabel(column: ColumnDef): string {
|
||||
return column.fullLabel ?? column.label;
|
||||
}
|
||||
@@ -201,6 +227,8 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
const [editingCell, setEditingCell] = useState<EditingCell | null>(null);
|
||||
const [activeSectionId, setActiveSectionId] = useState<string | null>(null);
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [componentEditorIntent, setComponentEditorIntent] =
|
||||
useState<StructureComponentEditorIntent | null>(null);
|
||||
const [projectDevices, setProjectDevices] = useState<ProjectDeviceDto[]>([]);
|
||||
const [isProjectDeviceDrawerOpen, setIsProjectDeviceDrawerOpen] =
|
||||
useState(false);
|
||||
@@ -1031,6 +1059,85 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
await applyHistory("undo");
|
||||
}
|
||||
|
||||
async function handleSaveDistributionBoardComponent(
|
||||
values: DistributionBoardComponentEditorValues
|
||||
) {
|
||||
const intent = componentEditorIntent;
|
||||
if (!intent || !data) {
|
||||
return;
|
||||
}
|
||||
await runCommand({
|
||||
label:
|
||||
intent.kind === "edit"
|
||||
? "Verteilerkomponente bearbeiten"
|
||||
: "Verteilerkomponente hinzufügen",
|
||||
redo: async () => {
|
||||
const result =
|
||||
intent.kind === "edit"
|
||||
? await (() => {
|
||||
const expected = toDistributionBoardComponentSnapshot(
|
||||
intent.component
|
||||
);
|
||||
return updateDistributionBoardComponentCommand(
|
||||
projectId,
|
||||
getExpectedProjectRevision(),
|
||||
expected,
|
||||
updateDistributionBoardComponentSnapshot(expected, values)
|
||||
);
|
||||
})()
|
||||
: await (() => {
|
||||
const components =
|
||||
intent.role === "auxiliary"
|
||||
? data.footerComponents
|
||||
: data.sections.find(
|
||||
(section) => section.id === intent.sectionId
|
||||
)?.components ?? [];
|
||||
const snapshot = buildDistributionBoardComponentSnapshot({
|
||||
id: crypto.randomUUID(),
|
||||
circuitListId,
|
||||
role: intent.role,
|
||||
sectionId: intent.sectionId,
|
||||
sortOrder: getNextComponentSortOrder(components),
|
||||
values,
|
||||
});
|
||||
return insertDistributionBoardComponentCommand(
|
||||
projectId,
|
||||
getExpectedProjectRevision(),
|
||||
snapshot
|
||||
);
|
||||
})();
|
||||
applyProjectCommandResult(result);
|
||||
setComponentEditorIntent(null);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function handleDeleteDistributionBoardComponent(
|
||||
component: CircuitTreeComponentDto
|
||||
) {
|
||||
if (
|
||||
!confirm(
|
||||
`Verteilerkomponente „${component.equipmentIdentifier} ${component.name}“ entfernen?`
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
await runCommand({
|
||||
label: "Verteilerkomponente entfernen",
|
||||
redo: async () => {
|
||||
const result = await deleteDistributionBoardComponentCommand(
|
||||
projectId,
|
||||
getExpectedProjectRevision(),
|
||||
toDistributionBoardComponentSnapshot(component)
|
||||
);
|
||||
applyProjectCommandResult(result);
|
||||
setComponentEditorIntent(null);
|
||||
return null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function handleRedo() {
|
||||
if (historyBusy || isSaving || !historyState || historyState.redoDepth === 0) {
|
||||
return;
|
||||
@@ -2605,6 +2712,29 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
|
||||
return (
|
||||
<div className="tree-editor-shell">
|
||||
{componentEditorIntent ? (
|
||||
<DistributionBoardComponentModal
|
||||
initialComponent={
|
||||
componentEditorIntent.kind === "edit"
|
||||
? componentEditorIntent.component
|
||||
: undefined
|
||||
}
|
||||
initialEquipmentIdentifier={
|
||||
componentEditorIntent.kind === "create"
|
||||
? componentEditorIntent.initialEquipmentIdentifier
|
||||
: undefined
|
||||
}
|
||||
isSaving={isSaving}
|
||||
onClose={() => setComponentEditorIntent(null)}
|
||||
onSave={handleSaveDistributionBoardComponent}
|
||||
role={
|
||||
componentEditorIntent.kind === "edit"
|
||||
? (componentEditorIntent.component
|
||||
.role as MutableDistributionBoardComponentRole)
|
||||
: componentEditorIntent.role
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
<div className="editor-toolbar">
|
||||
<button
|
||||
type="button"
|
||||
@@ -2656,6 +2786,18 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
? "Projektgeräte schließen"
|
||||
: `Projektgeräte öffnen (${projectDevices.length})`}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
disabled={isSaving || historyBusy}
|
||||
onClick={() =>
|
||||
setComponentEditorIntent({
|
||||
kind: "create",
|
||||
role: "auxiliary",
|
||||
})
|
||||
}
|
||||
>
|
||||
Verteilergerät hinzufügen
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={clearSortAndFilters}
|
||||
@@ -2996,6 +3138,10 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
row.rowType === "footerComponent"
|
||||
) {
|
||||
const component = row.component!;
|
||||
const isMutableComponent =
|
||||
component.role === "group_upstream_protection" ||
|
||||
component.role === "group_residual_current_protection" ||
|
||||
component.role === "auxiliary";
|
||||
const protection = component.protectionDevice;
|
||||
const protectionSummary = protection
|
||||
? [
|
||||
@@ -3033,6 +3179,39 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
{protectionSummary}
|
||||
</span>
|
||||
) : null}
|
||||
<span className="structure-component-actions">
|
||||
{isMutableComponent ? (
|
||||
<>
|
||||
<button
|
||||
type="button"
|
||||
disabled={isSaving}
|
||||
onClick={() =>
|
||||
setComponentEditorIntent({
|
||||
kind: "edit",
|
||||
component,
|
||||
})
|
||||
}
|
||||
>
|
||||
Bearbeiten
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
disabled={isSaving}
|
||||
onClick={() =>
|
||||
void handleDeleteDistributionBoardComponent(
|
||||
component
|
||||
)
|
||||
}
|
||||
>
|
||||
Entfernen
|
||||
</button>
|
||||
</>
|
||||
) : (
|
||||
<span className="structure-component-fixed">
|
||||
Feste Verteilerkomponente
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -3040,6 +3219,14 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
}
|
||||
if (row.rowType === "section") {
|
||||
const section = data.sections.find((entry) => entry.id === row.sectionId)!;
|
||||
const hasUpstreamProtection = section.components.some(
|
||||
(component) =>
|
||||
component.role === "group_upstream_protection"
|
||||
);
|
||||
const hasGroupRcd = section.components.some(
|
||||
(component) =>
|
||||
component.role === "group_residual_current_protection"
|
||||
);
|
||||
return (
|
||||
<tr
|
||||
key={row.rowKey}
|
||||
@@ -3115,6 +3302,52 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str
|
||||
</span>
|
||||
</div>
|
||||
<div className="section-actions">
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
disabled={
|
||||
hasUpstreamProtection ||
|
||||
!section.category ||
|
||||
!section.groupNumber
|
||||
}
|
||||
onClick={() =>
|
||||
setComponentEditorIntent({
|
||||
kind: "create",
|
||||
role: "group_upstream_protection",
|
||||
sectionId: section.id,
|
||||
initialEquipmentIdentifier:
|
||||
getSuggestedGroupComponentIdentifier(
|
||||
section,
|
||||
"group_upstream_protection"
|
||||
),
|
||||
})
|
||||
}
|
||||
>
|
||||
Vorsicherung hinzufügen
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
tabIndex={-1}
|
||||
disabled={
|
||||
hasGroupRcd ||
|
||||
!section.category ||
|
||||
!section.groupNumber
|
||||
}
|
||||
onClick={() =>
|
||||
setComponentEditorIntent({
|
||||
kind: "create",
|
||||
role: "group_residual_current_protection",
|
||||
sectionId: section.id,
|
||||
initialEquipmentIdentifier:
|
||||
getSuggestedGroupComponentIdentifier(
|
||||
section,
|
||||
"group_residual_current_protection"
|
||||
),
|
||||
})
|
||||
}
|
||||
>
|
||||
Gruppen-FI hinzufügen
|
||||
</button>
|
||||
<button type="button" tabIndex={-1} onClick={() => void handleAddReserveCircuit(section.id)}>
|
||||
Stromkreis hinzufügen
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user