From 75435475fce173d456a250aa77dd6a1225f8a7b1 Mon Sep 17 00:00:00 2001 From: Julian Appel Date: Mon, 4 May 2026 23:27:13 +0200 Subject: [PATCH] Drag and drop working --- src/app/globals.css | 64 ++++ src/domain/services/circuit-write.service.ts | 45 +++ .../components/circuit-tree-editor.tsx | 290 +++++++++++++++++- src/frontend/utils/api.ts | 7 + .../controllers/circuit-section.controller.ts | 19 ++ src/server/routes/circuit-section.routes.ts | 4 +- src/shared/validation/circuit.schemas.ts | 5 + tests/circuit-write.rules.test.ts | 30 ++ 8 files changed, 448 insertions(+), 16 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index 560cc7c..a9021c9 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -217,6 +217,14 @@ body { opacity: 0.55; } +.tree-grid .circuit-drag-handle { + cursor: grab; +} + +.tree-grid .circuit-dragging-block td { + opacity: 0.55; +} + .tree-grid .cell-selected { outline: 2px solid #4c7dd9; outline-offset: -2px; @@ -248,6 +256,62 @@ body { background: #eef4ff !important; } +.tree-grid .drop-target-invalid { + box-shadow: inset 0 0 0 2px #d97706; + background: #fff7ed !important; +} + +.tree-grid tr.circuit-insert-before td, +.tree-grid tr.circuit-insert-after td { + position: relative; +} + +.tree-grid tr.circuit-insert-before td::before { + content: ""; + position: absolute; + left: -1px; + right: -1px; + top: -2px; + border-top: 4px solid #2563eb; + pointer-events: none; +} + +.tree-grid tr.circuit-insert-before td:first-child::after { + content: ""; + position: absolute; + left: -7px; + top: -7px; + width: 0; + height: 0; + border-top: 7px solid transparent; + border-bottom: 7px solid transparent; + border-left: 10px solid #2563eb; + pointer-events: none; +} + +.tree-grid tr.circuit-insert-after td::after { + content: ""; + position: absolute; + left: -1px; + right: -1px; + bottom: -2px; + border-bottom: 4px solid #2563eb; + pointer-events: none; +} + +.tree-grid tr.circuit-insert-after td:first-child::before { + content: ""; + position: absolute; + left: -7px; + bottom: -7px; + width: 0; + height: 0; + border-top: 7px solid transparent; + border-bottom: 7px solid transparent; + border-left: 10px solid #2563eb; + pointer-events: none; +} + .drop-hint { font-size: 0.75rem; color: #1f4ea3; diff --git a/src/domain/services/circuit-write.service.ts b/src/domain/services/circuit-write.service.ts index 5b31a59..b253547 100644 --- a/src/domain/services/circuit-write.service.ts +++ b/src/domain/services/circuit-write.service.ts @@ -7,6 +7,7 @@ import type { CreateCircuitDeviceRowInput, CreateCircuitInput, MoveCircuitDeviceRowInput, + ReorderSectionCircuitsInput, UpdateCircuitDeviceRowInput, UpdateCircuitInput, } from "../../shared/validation/circuit.schemas.js"; @@ -408,4 +409,48 @@ export class CircuitWriteService { return this.circuitRepository.listBySection(sectionId); } + + async reorderCircuitsInSection(sectionId: string, input: ReorderSectionCircuitsInput) { + const section = await this.circuitSectionRepository.findById(sectionId); + if (!section) { + throw new Error("Invalid section id."); + } + const sectionCircuits = await this.circuitRepository.listBySection(sectionId); + const sectionIds = new Set(sectionCircuits.map((circuit) => circuit.id)); + if (sectionCircuits.length !== input.orderedCircuitIds.length) { + throw new Error("orderedCircuitIds must include all circuits of the section."); + } + for (const circuitId of input.orderedCircuitIds) { + if (!sectionIds.has(circuitId)) { + throw new Error("Circuit id does not belong to section."); + } + } + + for (let index = 0; index < input.orderedCircuitIds.length; index += 1) { + const circuitId = input.orderedCircuitIds[index]; + const circuit = sectionCircuits.find((entry) => entry.id === circuitId); + if (!circuit) { + throw new Error("Invalid circuit id."); + } + await this.circuitRepository.update(circuit.id, { + sectionId: circuit.sectionId, + equipmentIdentifier: circuit.equipmentIdentifier, + displayName: circuit.displayName ?? undefined, + sortOrder: (index + 1) * 10, + protectionType: circuit.protectionType ?? undefined, + protectionRatedCurrent: circuit.protectionRatedCurrent ?? undefined, + protectionCharacteristic: circuit.protectionCharacteristic ?? undefined, + cableType: circuit.cableType ?? undefined, + cableCrossSection: circuit.cableCrossSection ?? undefined, + cableLength: circuit.cableLength ?? undefined, + rcdAssignment: circuit.rcdAssignment ?? undefined, + terminalDesignation: circuit.terminalDesignation ?? undefined, + voltage: circuit.voltage ?? undefined, + status: circuit.status ?? undefined, + isReserve: Boolean(circuit.isReserve), + remark: circuit.remark ?? undefined, + }); + } + return this.circuitRepository.listBySection(sectionId); + } } diff --git a/src/frontend/components/circuit-tree-editor.tsx b/src/frontend/components/circuit-tree-editor.tsx index 154ae2d..8119f15 100644 --- a/src/frontend/components/circuit-tree-editor.tsx +++ b/src/frontend/components/circuit-tree-editor.tsx @@ -10,6 +10,7 @@ import { getNextCircuitIdentifier, listProjectDevices, moveCircuitDeviceRowById, + reorderSectionCircuits, renumberCircuitSection, updateCircuitById, updateCircuitDeviceRowById, @@ -83,6 +84,11 @@ type DeviceRowMoveDropIntent = | { kind: "move-to-circuit"; circuitId: string; sectionId: string } | { kind: "move-to-new-circuit"; sectionId: string }; +type CircuitReorderDropIntent = + | { kind: "before-circuit"; sectionId: string; targetCircuitId: string; valid: boolean } + | { kind: "after-circuit"; sectionId: string; targetCircuitId: string; valid: boolean } + | { kind: "section-end"; sectionId: string; valid: boolean }; + const columns: Array<{ key: CellKey; label: string; numeric?: boolean }> = [ { key: "equipmentIdentifier", label: "Equipment identifier" }, { key: "displayName", label: "Display name" }, @@ -274,6 +280,8 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str const [dropIntent, setDropIntent] = useState(null); const [draggingDeviceRowId, setDraggingDeviceRowId] = useState(null); const [deviceMoveIntent, setDeviceMoveIntent] = useState(null); + const [draggingCircuitId, setDraggingCircuitId] = useState(null); + const [circuitReorderIntent, setCircuitReorderIntent] = useState(null); const [pendingFocus, setPendingFocus] = useState(null); const pendingSelectionAfterReload = useRef(null); const containerRef = useRef(null); @@ -947,6 +955,41 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str return null; } + function findCircuitSectionId(circuitId: string) { + for (const section of data?.sections ?? []) { + if (section.circuits.some((circuit) => circuit.id === circuitId)) { + return section.id; + } + } + return null; + } + + async function applyCircuitReorder(intent: CircuitReorderDropIntent, sourceCircuitId: string) { + const section = data?.sections.find((entry) => entry.id === intent.sectionId); + if (!section) { + throw new Error("Invalid section id."); + } + const ids = section.circuits.map((circuit) => circuit.id); + const fromIndex = ids.indexOf(sourceCircuitId); + if (fromIndex < 0) { + throw new Error("Invalid source circuit."); + } + const nextIds = [...ids]; + nextIds.splice(fromIndex, 1); + + if (intent.kind === "section-end") { + nextIds.push(sourceCircuitId); + } else { + const targetIndex = nextIds.indexOf(intent.targetCircuitId); + if (targetIndex < 0) { + throw new Error("Invalid target circuit."); + } + const insertIndex = intent.kind === "after-circuit" ? targetIndex + 1 : targetIndex; + nextIds.splice(insertIndex, 0, sourceCircuitId); + } + await reorderSectionCircuits(section.id, nextIds); + } + async function handleDropWithIntent(event: DragEvent, intent: ProjectDeviceDropIntent) { event.preventDefault(); event.stopPropagation(); @@ -1017,6 +1060,39 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str } } + async function handleCircuitReorderDrop(event: DragEvent, intent: CircuitReorderDropIntent) { + event.preventDefault(); + event.stopPropagation(); + const sourceCircuitId = event.dataTransfer.getData("application/x-circuit-id") || draggingCircuitId; + setCircuitReorderIntent(null); + setDraggingCircuitId(null); + if (!sourceCircuitId) { + setError("Missing dragged circuit."); + return; + } + if (!intent.valid) { + setError("Cross-section circuit move is not allowed in this phase."); + return; + } + try { + setError(null); + setIsSaving(true); + await applyCircuitReorder(intent, sourceCircuitId); + pendingSelectionAfterReload.current = { + rowKey: `circuitSummary:${sourceCircuitId}`, + cellKey: "equipmentIdentifier", + rowType: "circuitSummary", + sectionId: intent.sectionId, + circuitId: sourceCircuitId, + }; + await loadTree({ showLoading: false }); + } catch (err) { + setError(normalizeUiError(err)); + } finally { + setIsSaving(false); + } + } + async function handleDeleteDevice(rowId: string) { if (!confirm("Delete this device row?")) { return; @@ -1174,7 +1250,9 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str setSelectedProjectDeviceId(device.id); setDraggingProjectDeviceId(device.id); setDraggingDeviceRowId(null); + setDraggingCircuitId(null); setDeviceMoveIntent(null); + setCircuitReorderIntent(null); event.dataTransfer.effectAllowed = "copy"; event.dataTransfer.setData("application/x-project-device-id", device.id); }} @@ -1261,6 +1339,16 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str dropIntent?.kind === "new-circuit" && dropIntent.sectionId === section.id ? "drop-target-active" : "" }`} onDragOver={(event) => { + if (draggingCircuitId) { + event.preventDefault(); + event.dataTransfer.dropEffect = "none"; + setCircuitReorderIntent({ + kind: "section-end", + sectionId: section.id, + valid: false, + }); + return; + } if (!draggingProjectDeviceId) { return; } @@ -1272,8 +1360,23 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str if (dropIntent?.kind === "new-circuit" && dropIntent.sectionId === section.id) { setDropIntent(null); } + if (circuitReorderIntent?.kind === "section-end" && circuitReorderIntent.sectionId === section.id) { + setCircuitReorderIntent(null); + } + }} + onDrop={(event) => { + if (draggingProjectDeviceId) { + void handleDropWithIntent(event, { kind: "new-circuit", sectionId: section.id }); + return; + } + if (draggingCircuitId) { + void handleCircuitReorderDrop(event, { + kind: "section-end", + sectionId: section.id, + valid: false, + }); + } }} - onDrop={(event) => void handleDropWithIntent(event, { kind: "new-circuit", sectionId: section.id })} >
@@ -1311,12 +1414,81 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str } ${ row.rowType === "placeholder" && ((dropIntent?.kind === "new-circuit" && dropIntent.sectionId === row.sectionId) || - (deviceMoveIntent?.kind === "move-to-new-circuit" && deviceMoveIntent.sectionId === row.sectionId)) + (deviceMoveIntent?.kind === "move-to-new-circuit" && deviceMoveIntent.sectionId === row.sectionId) || + (circuitReorderIntent?.kind === "section-end" && circuitReorderIntent.sectionId === row.sectionId)) ? "drop-target-active" : "" + } ${ + row.rowType === "placeholder" && + circuitReorderIntent?.kind === "section-end" && + circuitReorderIntent.sectionId === row.sectionId && + !circuitReorderIntent.valid + ? "drop-target-invalid" + : "" + } ${ + row.circuit && + circuitReorderIntent && + (circuitReorderIntent.kind === "before-circuit" || circuitReorderIntent.kind === "after-circuit") && + circuitReorderIntent.targetCircuitId === row.circuit.id && + circuitReorderIntent.valid + ? "drop-target-active" + : "" + } ${ + row.circuit && + circuitReorderIntent?.kind === "before-circuit" && + circuitReorderIntent.targetCircuitId === row.circuit.id && + circuitReorderIntent.valid + ? "circuit-insert-before" + : "" + } ${ + row.circuit && + circuitReorderIntent?.kind === "after-circuit" && + circuitReorderIntent.targetCircuitId === row.circuit.id && + circuitReorderIntent.valid + ? "circuit-insert-after" + : "" + } ${ + row.circuit && + circuitReorderIntent && + (circuitReorderIntent.kind === "before-circuit" || circuitReorderIntent.kind === "after-circuit") && + circuitReorderIntent.targetCircuitId === row.circuit.id && + !circuitReorderIntent.valid + ? "drop-target-invalid" + : "" + } ${ + row.circuit?.id && draggingCircuitId && row.circuit.id === draggingCircuitId ? "circuit-dragging-block" : "" }`} onClick={() => setActiveSectionId(row.sectionId)} onDragOver={(event) => { + if (draggingCircuitId) { + if (row.rowType === "placeholder") { + const sourceSectionId = findCircuitSectionId(draggingCircuitId); + const valid = sourceSectionId === row.sectionId; + event.preventDefault(); + event.dataTransfer.dropEffect = valid ? "move" : "none"; + setCircuitReorderIntent({ kind: "section-end", sectionId: row.sectionId, valid }); + return; + } + if (row.circuit && row.rowType !== "deviceRow") { + const sourceSectionId = findCircuitSectionId(draggingCircuitId); + if (row.circuit.id === draggingCircuitId) { + setCircuitReorderIntent(null); + return; + } + const valid = sourceSectionId === row.sectionId; + const rect = (event.currentTarget as HTMLTableRowElement).getBoundingClientRect(); + const isAfter = event.clientY > rect.top + rect.height / 2; + event.preventDefault(); + event.dataTransfer.dropEffect = valid ? "move" : "none"; + setCircuitReorderIntent({ + kind: isAfter ? "after-circuit" : "before-circuit", + sectionId: row.sectionId, + targetCircuitId: row.circuit.id, + valid, + }); + } + return; + } if (draggingProjectDeviceId) { if (row.rowType === "placeholder") { event.preventDefault(); @@ -1373,8 +1545,49 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str } return current; }); + setCircuitReorderIntent((current) => { + if (!current) { + return null; + } + if (current.kind === "section-end" && row.rowType === "placeholder" && current.sectionId === row.sectionId) { + return null; + } + if ( + (current.kind === "before-circuit" || current.kind === "after-circuit") && + current.targetCircuitId === row.circuit?.id + ) { + return null; + } + return current; + }); }} onDrop={(event) => { + if (draggingCircuitId) { + if (row.rowType === "placeholder") { + const sourceSectionId = findCircuitSectionId(draggingCircuitId); + void handleCircuitReorderDrop(event, { + kind: "section-end", + sectionId: row.sectionId, + valid: sourceSectionId === row.sectionId, + }); + return; + } + if (row.circuit && row.rowType !== "deviceRow") { + if (row.circuit.id === draggingCircuitId) { + return; + } + const sourceSectionId = findCircuitSectionId(draggingCircuitId); + const rect = (event.currentTarget as HTMLTableRowElement).getBoundingClientRect(); + const isAfter = event.clientY > rect.top + rect.height / 2; + void handleCircuitReorderDrop(event, { + kind: isAfter ? "after-circuit" : "before-circuit", + sectionId: row.sectionId, + targetCircuitId: row.circuit.id, + valid: sourceSectionId === row.sectionId, + }); + } + return; + } if (draggingProjectDeviceId) { if (row.rowType === "placeholder") { void handleDropWithIntent(event, { kind: "new-circuit", sectionId: row.sectionId }); @@ -1417,33 +1630,64 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str Boolean(row.device) && column.key === "displayName" && (row.rowType === "deviceRow" || row.rowType === "circuitCompact") ? "device-drag-handle" : "" + } ${ + column.key === "equipmentIdentifier" && + Boolean(row.circuit) && + (row.rowType === "circuitCompact" || row.rowType === "circuitSummary" || row.rowType === "reserveCircuit") + ? "circuit-drag-handle" + : "" } ${ draggingDeviceRowId && row.device?.id === draggingDeviceRowId ? "device-dragging" : "" }`} draggable={ !isEditing && - Boolean(row.device) && - column.key === "displayName" && - (row.rowType === "deviceRow" || row.rowType === "circuitCompact") + ((Boolean(row.device) && + column.key === "displayName" && + (row.rowType === "deviceRow" || row.rowType === "circuitCompact")) || + (Boolean(row.circuit) && + column.key === "equipmentIdentifier" && + (row.rowType === "circuitCompact" || + row.rowType === "circuitSummary" || + row.rowType === "reserveCircuit"))) } onDragStart={(event) => { if ( - !row.device || - column.key !== "displayName" || - (row.rowType !== "deviceRow" && row.rowType !== "circuitCompact") + row.circuit && + column.key === "equipmentIdentifier" && + (row.rowType === "circuitCompact" || + row.rowType === "circuitSummary" || + row.rowType === "reserveCircuit") ) { + setDraggingProjectDeviceId(null); + setDropIntent(null); + setDraggingDeviceRowId(null); + setDeviceMoveIntent(null); + setDraggingCircuitId(row.circuit.id); + setCircuitReorderIntent(null); + event.dataTransfer.effectAllowed = "move"; + event.dataTransfer.setData("application/x-circuit-id", row.circuit.id); return; } - setDraggingProjectDeviceId(null); - setDropIntent(null); - setDraggingDeviceRowId(row.device.id); - setDeviceMoveIntent(null); - event.dataTransfer.effectAllowed = "move"; - event.dataTransfer.setData("application/x-circuit-device-row-id", row.device.id); + if ( + row.device && + column.key === "displayName" && + (row.rowType === "deviceRow" || row.rowType === "circuitCompact") + ) { + setDraggingProjectDeviceId(null); + setDropIntent(null); + setDraggingCircuitId(null); + setCircuitReorderIntent(null); + setDraggingDeviceRowId(row.device.id); + setDeviceMoveIntent(null); + event.dataTransfer.effectAllowed = "move"; + event.dataTransfer.setData("application/x-circuit-device-row-id", row.device.id); + } }} onDragEnd={() => { setDraggingDeviceRowId(null); setDeviceMoveIntent(null); + setDraggingCircuitId(null); + setCircuitReorderIntent(null); }} onClick={() => { if (cell.editable) { @@ -1539,6 +1783,24 @@ export function CircuitTreeEditor(props: { projectId: string; circuitListId: str {deviceMoveIntent?.kind === "move-to-circuit" && deviceMoveIntent.circuitId === row.circuit?.id ? ( move device to this circuit ) : null} + {circuitReorderIntent?.kind === "section-end" && + row.rowType === "placeholder" && + circuitReorderIntent.sectionId === row.sectionId ? ( + + {circuitReorderIntent.valid ? "move circuit to section end" : "cross-section move not allowed"} + + ) : null} + {circuitReorderIntent && + (circuitReorderIntent.kind === "before-circuit" || circuitReorderIntent.kind === "after-circuit") && + circuitReorderIntent.targetCircuitId === row.circuit?.id ? ( + + {circuitReorderIntent.valid + ? circuitReorderIntent.kind === "before-circuit" + ? "move circuit before this circuit" + : "move circuit after this circuit" + : "cross-section move not allowed"} + + ) : null} ); diff --git a/src/frontend/utils/api.ts b/src/frontend/utils/api.ts index bba3fb3..72c0aab 100644 --- a/src/frontend/utils/api.ts +++ b/src/frontend/utils/api.ts @@ -148,6 +148,13 @@ export function renumberCircuitSection(sectionId: string) { }); } +export function reorderSectionCircuits(sectionId: string, orderedCircuitIds: string[]) { + return request(`/api/circuit-sections/${sectionId}/circuits/reorder`, { + method: "PATCH", + body: JSON.stringify({ orderedCircuitIds }), + }); +} + export function listFloors(projectId: string) { return request(`/api/projects/${projectId}/floors`); } diff --git a/src/server/controllers/circuit-section.controller.ts b/src/server/controllers/circuit-section.controller.ts index ee393be..0677f61 100644 --- a/src/server/controllers/circuit-section.controller.ts +++ b/src/server/controllers/circuit-section.controller.ts @@ -1,5 +1,6 @@ import type { Request, Response } from "express"; import { CircuitWriteService } from "../../domain/services/circuit-write.service.js"; +import { reorderSectionCircuitsSchema } from "../../shared/validation/circuit.schemas.js"; const circuitWriteService = new CircuitWriteService(); @@ -17,3 +18,21 @@ export async function renumberCircuitSection(req: Request, res: Response) { } } +export async function reorderSectionCircuits(req: Request, res: Response) { + const { sectionId } = req.params; + if (typeof sectionId !== "string") { + return res.status(400).json({ error: "Invalid sectionId" }); + } + + const parsed = reorderSectionCircuitsSchema.safeParse(req.body); + if (!parsed.success) { + return res.status(400).json({ error: parsed.error.flatten() }); + } + + try { + const updatedCircuits = await circuitWriteService.reorderCircuitsInSection(sectionId, parsed.data); + return res.json({ sectionId, circuits: updatedCircuits }); + } catch (error) { + return res.status(400).json({ error: error instanceof Error ? error.message : "Failed to reorder circuits." }); + } +} diff --git a/src/server/routes/circuit-section.routes.ts b/src/server/routes/circuit-section.routes.ts index d358a0c..3b48631 100644 --- a/src/server/routes/circuit-section.routes.ts +++ b/src/server/routes/circuit-section.routes.ts @@ -1,7 +1,7 @@ import { Router } from "express"; -import { renumberCircuitSection } from "../controllers/circuit-section.controller.js"; +import { renumberCircuitSection, reorderSectionCircuits } from "../controllers/circuit-section.controller.js"; export const circuitSectionRouter = Router(); circuitSectionRouter.post("/circuit-sections/:sectionId/renumber", renumberCircuitSection); - +circuitSectionRouter.patch("/circuit-sections/:sectionId/circuits/reorder", reorderSectionCircuits); diff --git a/src/shared/validation/circuit.schemas.ts b/src/shared/validation/circuit.schemas.ts index 7a84113..2ab62d0 100644 --- a/src/shared/validation/circuit.schemas.ts +++ b/src/shared/validation/circuit.schemas.ts @@ -62,8 +62,13 @@ export const moveCircuitDeviceRowSchema = z { message: "Either targetCircuitId or targetSectionId+createNewCircuit=true is required." } ); +export const reorderSectionCircuitsSchema = z.object({ + orderedCircuitIds: z.array(z.string().min(1)).min(1), +}); + export type CreateCircuitInput = z.infer; export type UpdateCircuitInput = z.infer; export type CreateCircuitDeviceRowInput = z.infer; export type UpdateCircuitDeviceRowInput = z.infer; export type MoveCircuitDeviceRowInput = z.infer; +export type ReorderSectionCircuitsInput = z.infer; diff --git a/tests/circuit-write.rules.test.ts b/tests/circuit-write.rules.test.ts index e17cc30..d40a99a 100644 --- a/tests/circuit-write.rules.test.ts +++ b/tests/circuit-write.rules.test.ts @@ -300,4 +300,34 @@ describe("circuit write service rules", () => { assert.equal(createdCircuitPayload?.equipmentIdentifier, "-2F8"); assert.equal(createdCircuitPayload?.isReserve, false); }); + + it("reorders circuits inside one section without renumbering identifiers", async () => { + const updates: Array<{ id: string; sortOrder: number; equipmentIdentifier: string }> = []; + const service = new CircuitWriteService({ + circuitSectionRepository: { + async findById() { + return { id: "s1", circuitListId: "l1" } as never; + }, + } as never, + circuitRepository: { + async listBySection() { + return [ + { id: "c1", sectionId: "s1", equipmentIdentifier: "-2F7", sortOrder: 10, isReserve: 0 }, + { id: "c2", sectionId: "s1", equipmentIdentifier: "-2F9", sortOrder: 20, isReserve: 0 }, + { id: "c3", sectionId: "s1", equipmentIdentifier: "-2F5", sortOrder: 30, isReserve: 1 }, + ] as never[]; + }, + async update(id: string, payload: { sortOrder: number; equipmentIdentifier: string }) { + updates.push({ id, sortOrder: payload.sortOrder, equipmentIdentifier: payload.equipmentIdentifier }); + }, + } as never, + }); + + await service.reorderCircuitsInSection("s1", { orderedCircuitIds: ["c3", "c1", "c2"] }); + assert.deepEqual(updates, [ + { id: "c3", sortOrder: 10, equipmentIdentifier: "-2F5" }, + { id: "c1", sortOrder: 20, equipmentIdentifier: "-2F7" }, + { id: "c2", sortOrder: 30, equipmentIdentifier: "-2F9" }, + ]); + }); });