diff --git a/src/app/(protected)/workorders/_components/list/table/wo-table-row-cells.tsx b/src/app/(protected)/workorders/_components/list/table/wo-table-row-cells.tsx
index aac347e1..c17deae3 100644
--- a/src/app/(protected)/workorders/_components/list/table/wo-table-row-cells.tsx
+++ b/src/app/(protected)/workorders/_components/list/table/wo-table-row-cells.tsx
@@ -1,6 +1,7 @@
import type { CSSProperties } from "react";
import type { LocationOption, WorkOrderUser } from "@/domain/work-orders/types/work-order";
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
+import { applyAssignedToTableSave } from "@/domain/work-orders/utils/work-order-feedback-toasts";
import { AssignedToCell } from "./cells/assigned-to-cell";
import { DateCell } from "./cells/date-cell";
import { DueDateCell } from "./cells/due-date-cell";
@@ -111,9 +112,7 @@ export function WoTableRowCells({
dispatcherId={row.dispatcherId}
users={users}
highlight={!row.dispatcherId}
- onSave={(id, name) =>
- handlers.onPatch(row.id, { dispatcherId: id, dispatcherName: name })
- }
+ onSave={(id, name) => applyAssignedToTableSave(handlers.onPatch, row.id, id, name)}
/>
({
+ SiteCell: () => null,
+}));
+vi.mock(
+ "@/app/(protected)/workorders/_components/list/table/cells/editable-wo-number-cell",
+ () => ({
+ EditableWONumberCell: () => null,
+ }),
+);
+vi.mock("@/app/(protected)/workorders/_components/list/table/cells/type-cell", () => ({
+ TypeCell: () => null,
+}));
+vi.mock("@/app/(protected)/workorders/_components/list/table/cells/date-cell", () => ({
+ DateCell: () => null,
+}));
+vi.mock("@/app/(protected)/workorders/_components/list/table/cells/due-date-cell", () => ({
+ DueDateCell: () => null,
+}));
+vi.mock("@/app/(protected)/workorders/_components/list/table/cells/assigned-to-cell", () => ({
+ AssignedToCell: ({ onSave }: { onSave: (id: string, name: string) => void }) => (
+
+ ),
+}));
+
+const row = { id: 7, dispatcherId: "u1" } as WorkOrderTableRow;
+
+describe("WoTableRowCells assigned-to persist", () => {
+ it("forwards assigned-to save through patch onSuccess instead of onPatchRow", () => {
+ const onPatch = vi.fn();
+ const onPatchRow = vi.fn();
+ const handlers = { onPatch } as unknown as WoTableRowHandlers;
+
+ renderWithProviders(
+ ,
+ { withAuth: false },
+ );
+
+ fireEvent.click(screen.getByRole("button", { name: "persist-assigned" }));
+
+ expect(onPatchRow).not.toHaveBeenCalled();
+ expect(onPatch).toHaveBeenCalledWith(
+ 7,
+ { dispatcherId: "u2", dispatcherName: "Admin test" },
+ { onSuccess: notifyWorkOrderSaved },
+ );
+ });
+});
diff --git a/src/test/domain/work-orders/use-cases/use-work-order-table-mutations-assigned-toast.test.tsx b/src/test/domain/work-orders/use-cases/use-work-order-table-mutations-assigned-toast.test.tsx
new file mode 100644
index 00000000..0bfe1699
--- /dev/null
+++ b/src/test/domain/work-orders/use-cases/use-work-order-table-mutations-assigned-toast.test.tsx
@@ -0,0 +1,97 @@
+import type { ReactNode } from "react";
+import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
+import { act, renderHook, waitFor } from "@testing-library/react";
+import { beforeEach, describe, expect, it, vi } from "vitest";
+import { toast } from "react-toastify";
+import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
+import { useWorkOrderTableMutations } from "@/domain/work-orders/use-cases/use-work-order-table-mutations";
+import {
+ applyAssignedToTableSave,
+ workOrderSavedMessage,
+} from "@/domain/work-orders/utils/work-order-feedback-toasts";
+
+const patchBoardField = vi.fn();
+
+vi.mock("@/domain/work-orders/api/work-orders-api", () => ({
+ workOrdersApi: {
+ patchBoardField: (...args: unknown[]) => patchBoardField(...args),
+ },
+}));
+
+vi.mock("react-toastify", () => ({
+ toast: { error: vi.fn(), success: vi.fn(), warning: vi.fn() },
+}));
+
+const ROW = {
+ id: 7,
+ dispatcherId: "u1",
+ dispatcherName: "Admin",
+ rowVersion: "v1",
+} as WorkOrderTableRow;
+
+function makeWrapper() {
+ const client = new QueryClient({
+ defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
+ });
+ return function Wrapper({ children }: { children: ReactNode }) {
+ return {children};
+ };
+}
+
+describe("useWorkOrderTableMutations assigned-to toast", () => {
+ beforeEach(() => {
+ patchBoardField.mockReset();
+ vi.mocked(toast.success).mockClear();
+ vi.mocked(toast.error).mockClear();
+ });
+
+ it("toasts assigned-to save only after the board PATCH succeeds", async () => {
+ let release: () => void = () => undefined;
+ const gate = new Promise((resolve) => {
+ release = resolve;
+ });
+ patchBoardField.mockImplementation(async () => {
+ await gate;
+ return { ...ROW, dispatcherId: "u2", dispatcherName: "Admin test", rowVersion: "v2" };
+ });
+
+ const { result } = renderHook(
+ () =>
+ useWorkOrderTableMutations({
+ onPatch: () => undefined,
+ clearPatch: () => undefined,
+ getRow: () => ROW,
+ }),
+ { wrapper: makeWrapper() },
+ );
+
+ act(() => {
+ applyAssignedToTableSave(result.current.patchField, 7, "u2", "Admin test");
+ });
+
+ expect(toast.success).not.toHaveBeenCalled();
+ release();
+ await waitFor(() => expect(toast.success).toHaveBeenCalledWith(workOrderSavedMessage()));
+ });
+
+ it("does not toast assigned-to save when the board PATCH fails", async () => {
+ patchBoardField.mockRejectedValue(new Error("Failed to update work order"));
+
+ const { result } = renderHook(
+ () =>
+ useWorkOrderTableMutations({
+ onPatch: () => undefined,
+ clearPatch: () => undefined,
+ getRow: () => ROW,
+ }),
+ { wrapper: makeWrapper() },
+ );
+
+ act(() => {
+ applyAssignedToTableSave(result.current.patchField, 7, "u2", "Admin test");
+ });
+
+ await waitFor(() => expect(toast.error).toHaveBeenCalled());
+ expect(toast.success).not.toHaveBeenCalled();
+ });
+});
diff --git a/src/test/domain/work-orders/utils/work-order-feedback-toasts.test.ts b/src/test/domain/work-orders/utils/work-order-feedback-toasts.test.ts
index 643a02dc..e9a5483f 100644
--- a/src/test/domain/work-orders/utils/work-order-feedback-toasts.test.ts
+++ b/src/test/domain/work-orders/utils/work-order-feedback-toasts.test.ts
@@ -2,10 +2,12 @@ import { beforeEach, describe, expect, it, vi } from "vitest";
import { toast } from "react-toastify";
import { toVendorTablePatch } from "@/domain/work-orders/utils/vendor-assignment-patch";
import {
+ applyAssignedToTableSave,
applyVendorTableSave,
isVendorAssignmentPatch,
notifySlideOverSaveSuccess,
notifyVendorAssigned,
+ notifyWorkOrderSaved,
patchWorkOrderAsCanceled,
patchWorkOrderAsCompleted,
vendorAssignedMessage,
@@ -137,6 +139,45 @@ describe("work-order-feedback-toasts", () => {
expect(toast.success).not.toHaveBeenCalledWith(vendorAssignedMessage());
});
+ it("toasts generic save after assigned-to persist onSuccess", () => {
+ const onPatch = vi.fn();
+ applyAssignedToTableSave(onPatch, 7, "u2", "Admin test");
+
+ expect(onPatch).toHaveBeenCalledWith(
+ 7,
+ { dispatcherId: "u2", dispatcherName: "Admin test" },
+ { onSuccess: notifyWorkOrderSaved },
+ );
+ expect(toast.success).not.toHaveBeenCalled();
+
+ const { onSuccess } = onPatch.mock.calls[0][2] as { onSuccess: () => void };
+ onSuccess();
+ expect(toast.success).toHaveBeenCalledWith(workOrderSavedMessage());
+ expect(toast.warning).not.toHaveBeenCalled();
+ });
+
+ it("toasts the same save copy after unassign persist onSuccess", () => {
+ const onPatch = vi.fn();
+ applyAssignedToTableSave(onPatch, 7, "", "");
+
+ expect(onPatch).toHaveBeenCalledWith(
+ 7,
+ { dispatcherId: "", dispatcherName: "" },
+ { onSuccess: notifyWorkOrderSaved },
+ );
+ expect(toast.success).not.toHaveBeenCalled();
+
+ const { onSuccess } = onPatch.mock.calls[0][2] as { onSuccess: () => void };
+ onSuccess();
+ expect(toast.success).toHaveBeenCalledWith(workOrderSavedMessage());
+ });
+
+ it("does not toast assigned-to save when persist onSuccess is skipped", () => {
+ const onPatch = vi.fn();
+ applyAssignedToTableSave(onPatch, 7, "u2", "Admin test");
+ expect(toast.success).not.toHaveBeenCalled();
+ });
+
it("completes a work order without a success toast", () => {
const patchField = vi.fn();
patchWorkOrderAsCompleted(patchField, 42);
|