From becc4913e331f33a18db9ab0309960566f85b514 Mon Sep 17 00:00:00 2001 From: Arthur Bassi Date: Wed, 22 Jul 2026 17:10:27 -0300 Subject: [PATCH] fix(work-orders): hide unfinished affordances and route View to detail --- .../list/work-orders-list-header.tsx | 49 +++--- src/app/(protected)/workorders/index.tsx | 10 +- ...ork-orders-board-core-affordances.test.tsx | 147 ++++++++++++++++++ 3 files changed, 174 insertions(+), 32 deletions(-) create mode 100644 src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx diff --git a/src/app/(protected)/workorders/_components/list/work-orders-list-header.tsx b/src/app/(protected)/workorders/_components/list/work-orders-list-header.tsx index 71dd3c7e..1fb9c732 100644 --- a/src/app/(protected)/workorders/_components/list/work-orders-list-header.tsx +++ b/src/app/(protected)/workorders/_components/list/work-orders-list-header.tsx @@ -1,36 +1,25 @@ import { CircularProgress } from "@mui/material"; - import { Plus, SlidersHorizontal } from "lucide-react"; - import { SearchInput } from "@/components/ui/search-input"; - import { ToolbarButton } from "@/components/ui/toolbar-button"; type WorkOrdersListHeaderProps = { search: string; - onSearchChange: (value: string) => void; - - onOpenAdvancedFilters: () => void; - - onNewWorkOrder: () => void; - + /** Omit until Advanced Search slice mounts the sheet. */ + onOpenAdvancedFilters?: () => void; + /** Omit until wizard shell slice mounts New WO. */ + onNewWorkOrder?: () => void; activeFilterCount: number; - isFetching?: boolean; }; export function WorkOrdersListHeader({ search, - onSearchChange, - onOpenAdvancedFilters, - onNewWorkOrder, - activeFilterCount, - isFetching = false, }: WorkOrdersListHeaderProps) { return ( @@ -42,20 +31,24 @@ export function WorkOrdersListHeader({ aria-label="Search work orders" /> - } - onClick={onOpenAdvancedFilters} - aria-label="Advanced Filters" - title="Advanced Filters" - badge={activeFilterCount} - > - Filters - + {onOpenAdvancedFilters && ( + } + onClick={onOpenAdvancedFilters} + aria-label="Advanced Filters" + title="Advanced Filters" + badge={activeFilterCount} + > + Filters + + )} - } onClick={onNewWorkOrder}> - New WO - + {onNewWorkOrder && ( + } onClick={onNewWorkOrder}> + New WO + + )} {isFetching && } diff --git a/src/app/(protected)/workorders/index.tsx b/src/app/(protected)/workorders/index.tsx index 5b746b2f..f3cc3d84 100644 --- a/src/app/(protected)/workorders/index.tsx +++ b/src/app/(protected)/workorders/index.tsx @@ -1,5 +1,6 @@ import { useMemo, useState } from "react"; import { Box } from "@mui/material"; +import { useNavigate } from "react-router"; import { PageHeader } from "@/components/ui/page-header"; import { MediaUploadModal } from "@/app/(protected)/workorders/_components/media/media-upload-modal"; import { WorkOrdersAdvancedFiltersBanner } from "@/app/(protected)/workorders/_components/list/work-orders-advanced-filters-banner"; @@ -22,6 +23,7 @@ import { useWorkOrderTableMutations } from "@/domain/work-orders/use-cases/use-w import { useAuthContext } from "@/providers/auth-context"; export default function WorkOrdersListPage() { + const navigate = useNavigate(); const { user } = useAuthContext(); const [mediaRow, setMediaRow] = useState(null); const [mediaFiles, setMediaFiles] = useState>({}); @@ -60,8 +62,6 @@ export default function WorkOrdersListPage() { filters.setAdvOpen(true)} - onNewWorkOrder={() => undefined} activeFilterCount={filters.activeFilterCount} isFetching={tableData.isFetching && !tableData.isLoading} /> @@ -98,11 +98,13 @@ export default function WorkOrdersListPage() { sites={locations} vendors={vendors} pmTypes={pmTypes} - onOpenSlideOver={(row) => tableData.setSelectedId(row.id)} + onOpenSlideOver={(row, edit) => { + tableData.setSelectedId(row.id); + navigate(edit ? `/workorders/edit/${row.id}` : `/workorders/${row.id}`); + }} onOpenCompDoc={() => undefined} onOpenMedia={setMediaRow} onClearSearch={() => filters.handleSearchChange("")} - onOpenAdvancedFilters={() => filters.setAdvOpen(true)} /> {activeMediaRow && ( diff --git a/src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx b/src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx new file mode 100644 index 00000000..0c0b295f --- /dev/null +++ b/src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx @@ -0,0 +1,147 @@ +import { fireEvent, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { WorkOrdersListHeader } from "@/app/(protected)/workorders/_components/list/work-orders-list-header"; +import WorkOrdersListPage from "@/app/(protected)/workorders/index"; +import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row"; +import { renderWithProviders } from "@/test/test-utils"; + +const navigate = vi.fn(); + +vi.mock("react-router", async () => { + const actual = await vi.importActual("react-router"); + return { + ...actual, + useNavigate: () => navigate, + }; +}); + +vi.mock("@/providers/auth-context", () => ({ + useAuthContext: () => ({ user: { id: "u1" } }), +})); + +vi.mock("@/app/(protected)/workorders/_hooks/use-work-orders-list-filters", () => ({ + useWorkOrdersListFilters: () => ({ + search: "", + handleSearchChange: vi.fn(), + setAdvOpen: vi.fn(), + activeFilterCount: 0, + advApplied: null, + clearAdvancedFilterChip: vi.fn(), + clearAdvancedFilters: vi.fn(), + weekMonday: "2026-07-20", + setWeekMonday: vi.fn(), + dispatcherIds: new Set(), + setDispatcherIds: vi.fn(), + typeFilter: "All", + setTypeFilter: vi.fn(), + }), +})); + +vi.mock("@/app/(protected)/workorders/_components/list/filter-bar/work-orders-filter-bar", () => ({ + WorkOrdersFilterBar: () => null, +})); + +vi.mock( + "@/app/(protected)/workorders/_components/list/work-orders-advanced-filters-banner", + () => ({ + WorkOrdersAdvancedFiltersBanner: () => null, + }), +); + +vi.mock("@/app/(protected)/workorders/_components/media/media-upload-modal", () => ({ + MediaUploadModal: () => null, +})); + +const canceledRow = { + id: 77, + status: "Canceled", + woNumber: "WO-77", +} as WorkOrderTableRow; + +vi.mock("@/app/(protected)/workorders/_hooks/use-work-orders-table-data", () => ({ + useWorkOrdersTableData: () => ({ + rows: [canceledRow], + totalCount: 1, + scopeTotalCount: 1, + isFetching: false, + isLoading: false, + setSelectedId: vi.fn(), + patchRow: vi.fn(), + }), +})); + +vi.mock("@/domain/work-orders/use-cases/use-work-order-table-mutations", () => ({ + useWorkOrderTableMutations: () => ({ patchField: vi.fn() }), +})); + +vi.mock("@/domain/work-orders/use-cases/use-work-orders-list", () => ({ + useWorkOrderUsers: () => ({ data: [] }), +})); + +vi.mock("@/domain/work-orders/use-cases/use-work-order-detail", () => ({ + useWorkOrderLocations: () => ({ data: [] }), + useWorkOrderVendors: () => ({ data: [] }), +})); + +vi.mock("@/domain/settings/dropdown-options/use-cases/use-dropdown-options-by-category", () => ({ + useDropdownOptionsByCategory: () => ({ data: [] }), +})); + +vi.mock("@/domain/work-orders/mocks/is-wo-table-mock-enabled", () => ({ + isWoTableMockEnabled: () => false, +})); + +vi.mock("@/app/(protected)/workorders/_components/list/table/wo-table", () => ({ + WoTable: ({ + onOpenSlideOver, + }: { + onOpenSlideOver: (row: WorkOrderTableRow, edit?: boolean) => void; + }) => ( + + ), +})); + +describe("WorkOrdersListHeader slice affordances", () => { + it("hides New WO and Advanced Filters when callbacks are omitted", () => { + renderWithProviders( + , + { withAuth: false }, + ); + + expect(screen.queryByRole("button", { name: /new wo/i })).not.toBeInTheDocument(); + expect(screen.queryByLabelText("Advanced Filters")).not.toBeInTheDocument(); + expect(screen.getByLabelText("Search work orders")).toBeInTheDocument(); + }); + + it("shows New WO and Advanced Filters when callbacks are provided", () => { + renderWithProviders( + , + { withAuth: false }, + ); + + expect(screen.getByRole("button", { name: /new wo/i })).toBeInTheDocument(); + expect(screen.getByLabelText("Advanced Filters")).toBeInTheDocument(); + }); +}); + +describe("WorkOrdersListPage board-core public interface", () => { + it("does not render New WO or Advanced Filters, and View navigates to detail", () => { + navigate.mockReset(); + + renderWithProviders(, { route: "/workorders", withAuth: false }); + + expect(screen.queryByRole("button", { name: /new wo/i })).not.toBeInTheDocument(); + expect(screen.queryByLabelText("Advanced Filters")).not.toBeInTheDocument(); + + fireEvent.click(screen.getByRole("button", { name: /view canceled row/i })); + expect(navigate).toHaveBeenCalledWith("/workorders/77"); + }); +});