Merge pull request #258 from Sea-Haven-Industries/fix/ab/sh-390-clear-completed-date

fix(workorders): clear the completed date from the board cell (SH-390)
This commit is contained in:
Alexandre Brandizzi 2026-09-25 00:50:40 +00:00 • committed by GitHub
commit cd888d933d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 264 additions and 0 deletions

View file

@ -114,6 +114,9 @@ export function WoTableRowServiceCells({
value={row.completedDate}
q={search}
onSave={(date) => onPatchRow({ completedDate: date })}
onClear={
row.completedDate && !viewOnly ? () => onPatchRow({ completedDate: "" }) : undefined
}
/>
</td>
<td style={{ padding: "0 14px" }} onClick={(e) => e.stopPropagation()}>

View file

@ -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> = {}): 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(
<table>
<tbody>
<tr>
<WoTableRowServiceCells
row={row}
vendors={[]}
pmTypes={[]}
viewOnly={viewOnly}
handlers={handlers}
onPatchRow={onPatchRow}
/>
</tr>
</tbody>
</table>,
);
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();
});
});

View file

@ -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> = {}): 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(
<table>
<tbody>
<WoTableRow
row={row}
allRows={[row]}
users={[]}
sites={[]}
vendors={[]}
pmTypes={["HVAC"]}
isSelected={false}
isFlashing={false}
dragViz={{ dragId: null, overId: null }}
handlers={handlers}
/>
</tbody>
</table>,
{ 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();
});
});

View file

@ -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,