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/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..84275975 --- /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"] as const)("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(); + }); +}); 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,