mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 13:53:12 +00:00
fix(work-orders): hide unfinished affordances and route View to detail
This commit is contained in:
parent
0709bd1172
commit
becc4913e3
3 changed files with 174 additions and 32 deletions
|
|
@ -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"
|
||||
/>
|
||||
|
||||
<ToolbarButton
|
||||
variant="outline"
|
||||
icon={<SlidersHorizontal size={13} />}
|
||||
onClick={onOpenAdvancedFilters}
|
||||
aria-label="Advanced Filters"
|
||||
title="Advanced Filters"
|
||||
badge={activeFilterCount}
|
||||
>
|
||||
Filters
|
||||
</ToolbarButton>
|
||||
{onOpenAdvancedFilters && (
|
||||
<ToolbarButton
|
||||
variant="outline"
|
||||
icon={<SlidersHorizontal size={13} />}
|
||||
onClick={onOpenAdvancedFilters}
|
||||
aria-label="Advanced Filters"
|
||||
title="Advanced Filters"
|
||||
badge={activeFilterCount}
|
||||
>
|
||||
Filters
|
||||
</ToolbarButton>
|
||||
)}
|
||||
|
||||
<ToolbarButton variant="primary" icon={<Plus size={13} />} onClick={onNewWorkOrder}>
|
||||
New WO
|
||||
</ToolbarButton>
|
||||
{onNewWorkOrder && (
|
||||
<ToolbarButton variant="primary" icon={<Plus size={13} />} onClick={onNewWorkOrder}>
|
||||
New WO
|
||||
</ToolbarButton>
|
||||
)}
|
||||
|
||||
{isFetching && <CircularProgress size={14} />}
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -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<WorkOrderTableRow | null>(null);
|
||||
const [mediaFiles, setMediaFiles] = useState<Record<string, MediaFile[]>>({});
|
||||
|
|
@ -60,8 +62,6 @@ export default function WorkOrdersListPage() {
|
|||
<WorkOrdersListHeader
|
||||
search={filters.search}
|
||||
onSearchChange={filters.handleSearchChange}
|
||||
onOpenAdvancedFilters={() => 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 && (
|
||||
|
|
|
|||
|
|
@ -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<typeof import("react-router")>("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<string>(),
|
||||
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;
|
||||
}) => (
|
||||
<button type="button" onClick={() => onOpenSlideOver(canceledRow, false)}>
|
||||
View canceled row
|
||||
</button>
|
||||
),
|
||||
}));
|
||||
|
||||
describe("WorkOrdersListHeader slice affordances", () => {
|
||||
it("hides New WO and Advanced Filters when callbacks are omitted", () => {
|
||||
renderWithProviders(
|
||||
<WorkOrdersListHeader search="" onSearchChange={vi.fn()} activeFilterCount={0} />,
|
||||
{ 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(
|
||||
<WorkOrdersListHeader
|
||||
search=""
|
||||
onSearchChange={vi.fn()}
|
||||
onNewWorkOrder={vi.fn()}
|
||||
onOpenAdvancedFilters={vi.fn()}
|
||||
activeFilterCount={2}
|
||||
/>,
|
||||
{ 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(<WorkOrdersListPage />, { 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");
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue