From 460397dacb3b272c9729e0e7107ee2e6b79c5a7c Mon Sep 17 00:00:00 2001 From: Alexandre Brandizzi Date: Thu, 24 Sep 2026 20:44:03 -0300 Subject: [PATCH 1/3] fix(workorders): let dispatchers clear the completed date from the board cell (SH-390) The completed-date cell editor had no Clear control. It now offers Clear when a date is set and the row is editable, patching completedDate to an empty value, which the board PATCH persists as null. Completed and Canceled rows stay locked, matching set/change. --- .../list/table/wo-table-row-service-cells.tsx | 3 + .../wo-table-completed-date-clear.test.tsx | 128 ++++++++++++++++++ .../completed-date-board-column.test.ts | 5 + 3 files changed, 136 insertions(+) create mode 100644 src/test/app/(protected)/workorders/wo-table-completed-date-clear.test.tsx diff --git a/src/app/(protected)/workorders/_components/list/table/wo-table-row-service-cells.tsx b/src/app/(protected)/workorders/_components/list/table/wo-table-row-service-cells.tsx index ad995968..21731f67 100644 --- a/src/app/(protected)/workorders/_components/list/table/wo-table-row-service-cells.tsx +++ b/src/app/(protected)/workorders/_components/list/table/wo-table-row-service-cells.tsx @@ -114,6 +114,9 @@ export function WoTableRowServiceCells({ value={row.completedDate} q={search} onSave={(date) => onPatchRow({ completedDate: date })} + onClear={ + row.completedDate && !viewOnly ? () => onPatchRow({ completedDate: "" }) : undefined + } /> e.stopPropagation()}> diff --git a/src/test/app/(protected)/workorders/wo-table-completed-date-clear.test.tsx b/src/test/app/(protected)/workorders/wo-table-completed-date-clear.test.tsx new file mode 100644 index 00000000..6e2c5ccd --- /dev/null +++ b/src/test/app/(protected)/workorders/wo-table-completed-date-clear.test.tsx @@ -0,0 +1,128 @@ +import { fireEvent, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { WoTableRowServiceCells } from "@/app/(protected)/workorders/_components/list/table/wo-table-row-service-cells"; +import type { WoTableRowHandlers } from "@/app/(protected)/workorders/_components/list/table/wo-table-row"; +import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row"; +import { renderWithProviders } from "@/test/test-utils"; + +function makeRow(overrides: Partial = {}): WorkOrderTableRow { + return { + id: 9, + woNumber: "24818000001", + site: "BK5", + type: "PM", + severity: null, + dispatcherId: "u1", + dispatcherName: "Alex", + scheduledOn: "2026-07-15", + completedDate: "2026-07-20", + targetWeek: "2026-07-15", + dayGroup: "07/15", + dayLabel: "Tue", + location: "Lobby", + locationId: "loc-1", + pm: "HVAC", + serviceNotes: "", + extraServices: [], + vendorId: "7", + company: "Acme", + tech: "Sam", + techPhone: "", + apptStart: "", + apptEnd: "", + apptTime: "09:00 – 10:00", + status: "Incomplete", + docStatus: "Yes", + pocName: "", + pocPhone: "", + pocNotes: "", + dueDate: "2026-07-22", + rescheduleCount: 0, + carriedOver: 0, + originalWeek: "", + originalDate: "", + isPastDue: false, + scheduleWeekOnly: false, + isAddOn: false, + mediaCount: 1, + title: "", + priority: "", + primaryDispatchId: null, + rowVersion: "", + dispatchRowVersion: "", + flagColor: null, + additionalContacts: [], + upliftSummary: { hasUplift: false, pendingCount: 0 }, + ...overrides, + }; +} + +const handlers: WoTableRowHandlers = { + onRowClick: vi.fn(), + onView: vi.fn(), + onEdit: vi.fn(), + onOpenMedia: vi.fn(), + onPatch: vi.fn(), + onScheduleDate: vi.fn(), + onScheduleWeek: vi.fn(), + onScheduleRange: vi.fn(), + onOpenSlideOver: vi.fn(), + onOpenCompDoc: vi.fn(), + onOpenUplifts: vi.fn(), + onReorder: vi.fn(), + onDragStart: vi.fn(), + onDragOver: vi.fn(), + onDrop: vi.fn(), + onDragEnd: vi.fn(), +}; + +function renderCells(row: WorkOrderTableRow, viewOnly = false) { + const onPatchRow = vi.fn(); + renderWithProviders( + + + + + + +
, + ); + return onPatchRow; +} + +describe("WoTableRowServiceCells completed date Clear (SH-390)", () => { + it("shows Clear in the completed-date editor and patches completedDate to empty", async () => { + const onPatchRow = renderCells(makeRow()); + + fireEvent.click(screen.getByRole("button", { name: /07-20/ })); + + fireEvent.click(await screen.findByRole("button", { name: /clear/i })); + + expect(onPatchRow).toHaveBeenCalledWith({ completedDate: "" }); + }); + + it("offers no Clear when the completed date is already empty", async () => { + renderCells(makeRow({ completedDate: "" })); + + fireEvent.click(screen.getByRole("button", { name: "—" })); + + expect(await screen.findByRole("button", { name: "20" })).toBeInTheDocument(); + expect(screen.queryByRole("button", { name: /clear/i })).not.toBeInTheDocument(); + }); + + it("offers no Clear on a Completed (view-only) row", async () => { + renderCells(makeRow({ status: "Completed" }), true); + + fireEvent.click(screen.getByRole("button", { name: /07-20/ })); + + expect(await screen.findByRole("button", { name: "20" })).toBeInTheDocument(); + expect(screen.queryByRole("button", { name: /clear/i })).not.toBeInTheDocument(); + }); +}); diff --git a/src/test/domain/work-orders/mappers/completed-date-board-column.test.ts b/src/test/domain/work-orders/mappers/completed-date-board-column.test.ts index 1820df72..d8a3c17d 100644 --- a/src/test/domain/work-orders/mappers/completed-date-board-column.test.ts +++ b/src/test/domain/work-orders/mappers/completed-date-board-column.test.ts @@ -16,6 +16,11 @@ describe("COMPLETED DATE board column wiring", () => { expect(ops).toEqual([{ field: "completedDate", value: "2026-07-30" }]); }); + it("expands a clearing completedDate patch to an empty-string board field op (SH-390)", () => { + const ops = expandScalarBoardOps({ completedDate: "" }); + expect(ops).toEqual([{ field: "completedDate", value: "" }]); + }); + it("maps completedDate returned by a board GET row", () => { const row = mapWorkOrderTableRow({ id: 42, From 55f548d5076cdd5853a57dbacdc9d439b325e3ec Mon Sep 17 00:00:00 2001 From: Alexandre Brandizzi Date: Thu, 24 Sep 2026 21:04:37 -0300 Subject: [PATCH 2/3] test(workorders): cover completed-date Clear through the board row (SH-390) Clear on an open row reaches the board patch with completedDate empty; Completed and Canceled rows offer no Clear and send no patch. --- ...wo-table-row-completed-date-clear.test.tsx | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 src/test/app/(protected)/workorders/wo-table-row-completed-date-clear.test.tsx diff --git a/src/test/app/(protected)/workorders/wo-table-row-completed-date-clear.test.tsx b/src/test/app/(protected)/workorders/wo-table-row-completed-date-clear.test.tsx new file mode 100644 index 00000000..b3ae1582 --- /dev/null +++ b/src/test/app/(protected)/workorders/wo-table-row-completed-date-clear.test.tsx @@ -0,0 +1,128 @@ +import { fireEvent, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { WoTableRow } from "@/app/(protected)/workorders/_components/list/table/wo-table-row"; +import type { WoTableRowHandlers } from "@/app/(protected)/workorders/_components/list/table/wo-table-row"; +import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row"; +import { renderWithProviders } from "@/test/test-utils"; + +function makeRow(overrides: Partial = {}): WorkOrderTableRow { + return { + id: 9, + woNumber: "24818000001", + site: "BK5", + type: "PM", + severity: null, + dispatcherId: "u1", + dispatcherName: "Alex", + scheduledOn: "2026-07-15", + completedDate: "2026-07-20", + targetWeek: "2026-07-15", + dayGroup: "07/15", + dayLabel: "Tue", + location: "Lobby", + locationId: "loc-1", + pm: "HVAC", + serviceNotes: "", + extraServices: [], + vendorId: "7", + company: "Acme", + tech: "Sam", + techPhone: "", + apptStart: "", + apptEnd: "", + apptTime: "09:00 – 10:00", + status: "Incomplete", + docStatus: "Yes", + pocName: "", + pocPhone: "", + pocNotes: "", + dueDate: "2026-07-22", + rescheduleCount: 0, + carriedOver: 0, + originalWeek: "", + originalDate: "", + isPastDue: false, + scheduleWeekOnly: false, + isAddOn: false, + mediaCount: 1, + title: "", + priority: "", + primaryDispatchId: null, + rowVersion: "", + dispatchRowVersion: "", + flagColor: null, + additionalContacts: [], + upliftSummary: { hasUplift: false, pendingCount: 0 }, + ...overrides, + }; +} + +function makeHandlers(): WoTableRowHandlers { + return { + onRowClick: vi.fn(), + onView: vi.fn(), + onEdit: vi.fn(), + onOpenMedia: vi.fn(), + onPatch: vi.fn(), + onScheduleDate: vi.fn(), + onScheduleWeek: vi.fn(), + onScheduleRange: vi.fn(), + onOpenSlideOver: vi.fn(), + onOpenCompDoc: vi.fn(), + onOpenUplifts: vi.fn(), + onReorder: vi.fn(), + onDragStart: vi.fn(), + onDragOver: vi.fn(), + onDrop: vi.fn(), + onDragEnd: vi.fn(), + }; +} + +function renderRow(row: WorkOrderTableRow, handlers: WoTableRowHandlers) { + renderWithProviders( + + + + +
, + { withAuth: false }, + ); +} + +describe("WoTableRow completed date Clear (SH-390)", () => { + it("clears the completed date of an open row through the board patch", async () => { + const handlers = makeHandlers(); + renderRow(makeRow(), handlers); + + fireEvent.click(screen.getByRole("button", { name: /07-20/ })); + fireEvent.click(await screen.findByRole("button", { name: /clear/i })); + + expect(handlers.onPatch).toHaveBeenCalledTimes(1); + expect(vi.mocked(handlers.onPatch).mock.calls[0].slice(0, 2)).toEqual([ + 9, + { completedDate: "" }, + ]); + }); + + it.each(["Completed", "Canceled"])("offers no Clear on a %s row", async (status) => { + const handlers = makeHandlers(); + renderRow(makeRow({ status }), handlers); + + fireEvent.click(screen.getByRole("button", { name: /07-20/ })); + + expect(await screen.findByRole("button", { name: "20" })).toBeInTheDocument(); + expect(screen.queryByRole("button", { name: /clear/i })).not.toBeInTheDocument(); + expect(handlers.onPatch).not.toHaveBeenCalled(); + }); +}); From ab5e73ce82b8211577ee4fc553d79769274933c4 Mon Sep 17 00:00:00 2001 From: Alexandre Brandizzi Date: Thu, 24 Sep 2026 21:38:55 -0300 Subject: [PATCH 3/3] test(workorders): type the terminal statuses in the SH-390 row test --- .../workorders/wo-table-row-completed-date-clear.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/app/(protected)/workorders/wo-table-row-completed-date-clear.test.tsx b/src/test/app/(protected)/workorders/wo-table-row-completed-date-clear.test.tsx index b3ae1582..84275975 100644 --- a/src/test/app/(protected)/workorders/wo-table-row-completed-date-clear.test.tsx +++ b/src/test/app/(protected)/workorders/wo-table-row-completed-date-clear.test.tsx @@ -115,7 +115,7 @@ describe("WoTableRow completed date Clear (SH-390)", () => { ]); }); - it.each(["Completed", "Canceled"])("offers no Clear on a %s row", async (status) => { + it.each(["Completed", "Canceled"] as const)("offers no Clear on a %s row", async (status) => { const handlers = makeHandlers(); renderRow(makeRow({ status }), handlers);