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");
+ });
+});