mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 16:19:01 +00:00
fix(work-orders): page unscheduled results server-side
This commit is contained in:
parent
eeba7e37d8
commit
36d3048caa
9 changed files with 360 additions and 67 deletions
|
|
@ -7,11 +7,7 @@ import { useWorkOrdersTableDayView } from "@/app/(protected)/workorders/_hooks/u
|
|||
import { useWorkOrdersTableLocalState } from "@/app/(protected)/workorders/_hooks/use-work-orders-table-local-state";
|
||||
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
||||
import { pickBoardPager, type DayPageSize } from "@/domain/work-orders/utils/day-pagination";
|
||||
import {
|
||||
filterByBarAndSearch,
|
||||
resolveAdvSearchDisplayRows,
|
||||
resolveAdvSearchScopeCount,
|
||||
} from "@/domain/work-orders/utils/table-filter-utils";
|
||||
import { filterByBarAndSearch } from "@/domain/work-orders/utils/table-filter-utils";
|
||||
|
||||
export type { DragVizState } from "@/app/(protected)/workorders/_hooks/use-work-orders-table-local-state";
|
||||
|
||||
|
|
@ -36,10 +32,10 @@ export function useWorkOrdersTableData(filters: FiltersReturn) {
|
|||
});
|
||||
const { displayRows, ...localUi } = local;
|
||||
|
||||
const unscheduledOnly = Boolean(filters.advApplied?.unscheduledOnly);
|
||||
// SH-338: the server owns rows/totals for advanced search (incl. Unscheduled).
|
||||
const advResults = useMemo(
|
||||
() => resolveAdvSearchDisplayRows(displayRows, advActive, unscheduledOnly),
|
||||
[advActive, displayRows, unscheduledOnly],
|
||||
() => (advActive ? displayRows : EMPTY_WEEKLY_ITEMS),
|
||||
[advActive, displayRows],
|
||||
);
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
|
|
@ -66,7 +62,7 @@ export function useWorkOrdersTableData(filters: FiltersReturn) {
|
|||
);
|
||||
|
||||
const scopeTotalCount = advActive
|
||||
? resolveAdvSearchScopeCount(unscheduledOnly, data?.totalCount, advResults.length)
|
||||
? (data?.totalCount ?? advResults.length)
|
||||
: dayView.dayTotalCount;
|
||||
useSyncOutOfRangeSearchPage({
|
||||
enabled: advActive,
|
||||
|
|
|
|||
|
|
@ -299,6 +299,12 @@ export interface WorkOrderBoardSearchQueryParams {
|
|||
avetaOnly?: boolean;
|
||||
flagColors?: string[];
|
||||
internalOnly?: boolean;
|
||||
/**
|
||||
* SH-338: server-side Unscheduled facet. `true` makes the server ignore the
|
||||
* date range only; every other facet still applies. Absent/false keeps the
|
||||
* legacy wire behavior.
|
||||
*/
|
||||
unscheduledOnly?: boolean;
|
||||
hasUplift?: boolean;
|
||||
upliftStatuses?: string[];
|
||||
myWorkOrders?: boolean;
|
||||
|
|
|
|||
|
|
@ -155,16 +155,14 @@ export function toBoardQueryParams(
|
|||
}
|
||||
|
||||
function resolveAdvancedSearchDate(filters: WorkOrderAdvancedFilters): {
|
||||
datePreset: WorkOrderBoardSearchQueryParams["datePreset"];
|
||||
datePreset?: WorkOrderBoardSearchQueryParams["datePreset"];
|
||||
dateFrom?: string;
|
||||
dateTo?: string;
|
||||
} {
|
||||
// SH-338: server-side unscheduledOnly owns the date-range exemption; the
|
||||
// client must not fake a Custom window and sends no date params.
|
||||
if (filters.unscheduledOnly) {
|
||||
return {
|
||||
datePreset: "Custom",
|
||||
dateFrom: ADVANCED_SEARCH_ALL_WEEKS_FROM,
|
||||
dateTo: ADVANCED_SEARCH_ALL_WEEKS_TO,
|
||||
};
|
||||
return {};
|
||||
}
|
||||
if (filters.dateRange === EMPTY_ADVANCED_FILTERS.dateRange) {
|
||||
return {
|
||||
|
|
@ -223,6 +221,7 @@ export function toBoardSearchQueryParams(input: {
|
|||
pmTypes: filters.pmTypes.size > 0 ? [...filters.pmTypes] : undefined,
|
||||
vendorIds: resolveVendorIds(filters.vendorTechs, input.vendorIdsByCompanyName),
|
||||
docStatuses: mapDocStatuses(filters.docs),
|
||||
unscheduledOnly: filters.unscheduledOnly || undefined,
|
||||
...mapSearchFacetParams(filters),
|
||||
sortBy: input.sortBy || "scheduledDate",
|
||||
sortDir: input.sortDir === "desc" ? "desc" : "asc",
|
||||
|
|
|
|||
|
|
@ -37,25 +37,6 @@ export function matchesAdvancedFilters(
|
|||
return ADVANCED_FILTER_PREDICATES.every((predicate) => predicate(row, filters));
|
||||
}
|
||||
|
||||
export function resolveAdvSearchDisplayRows(
|
||||
rows: WorkOrderTableRow[],
|
||||
advActive: boolean,
|
||||
unscheduledOnly: boolean,
|
||||
): WorkOrderTableRow[] {
|
||||
if (!advActive) return [];
|
||||
if (!unscheduledOnly) return rows;
|
||||
return rows.filter((row) => !row.scheduledOn);
|
||||
}
|
||||
|
||||
export function resolveAdvSearchScopeCount(
|
||||
unscheduledOnly: boolean,
|
||||
backendTotal: number | undefined,
|
||||
filteredLength: number,
|
||||
): number {
|
||||
if (unscheduledOnly) return filteredLength;
|
||||
return backendTotal ?? filteredLength;
|
||||
}
|
||||
|
||||
function rowMatchesSearch(row: WorkOrderTableRow, query: string): boolean {
|
||||
const q = query.trim().toLowerCase();
|
||||
if (!q) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,233 @@
|
|||
import { fireEvent, screen, waitFor } from "@testing-library/react";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { WorkOrderBoardSearchQueryParams } from "@/domain/work-orders/types/work-order-board";
|
||||
import { EMPTY_ADVANCED_FILTERS } from "@/domain/work-orders/types/work-order-advanced-filters";
|
||||
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
||||
import { renderWithProviders } from "@/test/test-utils";
|
||||
|
||||
const { searchBoard, getBoard, emptyList } = vi.hoisted(() => ({
|
||||
searchBoard: vi.fn(),
|
||||
getBoard: vi.fn(),
|
||||
emptyList: [] as const,
|
||||
}));
|
||||
|
||||
vi.mock("@/domain/work-orders/api/work-orders-api", () => ({
|
||||
workOrdersApi: {
|
||||
searchBoard: (...args: unknown[]) => searchBoard(...args),
|
||||
getBoard: (...args: unknown[]) => getBoard(...args),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("@/domain/work-orders/use-cases/use-work-order-detail", () => ({
|
||||
useWorkOrderLocations: () => ({ data: emptyList }),
|
||||
useWorkOrderVendors: () => ({ data: emptyList }),
|
||||
}));
|
||||
|
||||
vi.mock("@/domain/locations/use-cases/use-location-sites", () => ({
|
||||
useLocationSites: () => ({ data: emptyList }),
|
||||
}));
|
||||
|
||||
vi.mock("@/app/(protected)/workorders/_components/list/table/use-wo-table-inline-create", () => ({
|
||||
useWoTableInlineCreate: () => ({ handleInlineSave: vi.fn(), saving: false }),
|
||||
}));
|
||||
|
||||
vi.mock("react-toastify", () => ({ toast: { error: vi.fn(), success: vi.fn() } }));
|
||||
|
||||
import { WoTable } from "@/app/(protected)/workorders/_components/list/table/wo-table";
|
||||
import { useWorkOrdersListFilters } from "@/app/(protected)/workorders/_hooks/use-work-orders-list-filters";
|
||||
import { useWorkOrdersTableData } from "@/app/(protected)/workorders/_hooks/use-work-orders-table-data";
|
||||
|
||||
const PAGE_SIZE = 25;
|
||||
|
||||
function makeRow(overrides: Partial<WorkOrderTableRow> = {}): WorkOrderTableRow {
|
||||
return {
|
||||
id: "1",
|
||||
woNumber: "WO-1",
|
||||
site: "CLV2",
|
||||
type: "PM",
|
||||
severity: null,
|
||||
dispatcherId: "",
|
||||
dispatcherName: "",
|
||||
scheduledOn: "",
|
||||
completedDate: "",
|
||||
targetWeek: "",
|
||||
dayGroup: "",
|
||||
dayLabel: "",
|
||||
location: "Cleveland, OH",
|
||||
locationId: "",
|
||||
pm: "Backflow",
|
||||
serviceNotes: "",
|
||||
extraServices: [],
|
||||
company: "",
|
||||
tech: "",
|
||||
techPhone: "",
|
||||
apptStart: "",
|
||||
apptEnd: "",
|
||||
apptTime: "—",
|
||||
status: "Incomplete",
|
||||
docStatus: "No",
|
||||
pocName: "",
|
||||
pocPhone: "",
|
||||
pocNotes: "",
|
||||
dueDate: "",
|
||||
rescheduleCount: 0,
|
||||
carriedOver: 0,
|
||||
isAddOn: false,
|
||||
originalWeek: "",
|
||||
originalDate: "",
|
||||
isPastDue: false,
|
||||
scheduleWeekOnly: false,
|
||||
mediaCount: 0,
|
||||
title: "",
|
||||
priority: "",
|
||||
vendorId: "",
|
||||
primaryDispatchId: null,
|
||||
rowVersion: "v1",
|
||||
dispatchRowVersion: "",
|
||||
flagColor: null,
|
||||
additionalContacts: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
function emptyBoardResult() {
|
||||
return {
|
||||
items: [] as WorkOrderTableRow[],
|
||||
scheduled: [] as WorkOrderTableRow[],
|
||||
unscheduled: [] as WorkOrderTableRow[],
|
||||
totalCount: 0,
|
||||
returnedCount: 0,
|
||||
page: 0,
|
||||
pageSize: PAGE_SIZE,
|
||||
};
|
||||
}
|
||||
|
||||
function pagedResult(rows: WorkOrderTableRow[], page: number, pageSize: number) {
|
||||
const items = rows.slice(page * pageSize, page * pageSize + pageSize);
|
||||
return {
|
||||
items,
|
||||
scheduled: items,
|
||||
unscheduled: [] as WorkOrderTableRow[],
|
||||
totalCount: rows.length,
|
||||
returnedCount: items.length,
|
||||
page,
|
||||
pageSize,
|
||||
};
|
||||
}
|
||||
|
||||
function lastSearchParams(): WorkOrderBoardSearchQueryParams {
|
||||
const calls = searchBoard.mock.calls;
|
||||
return calls[calls.length - 1]?.[0] as WorkOrderBoardSearchQueryParams;
|
||||
}
|
||||
|
||||
function UnscheduledServerOwnedHarness() {
|
||||
const filters = useWorkOrdersListFilters();
|
||||
const tableData = useWorkOrdersTableData(filters);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="harness-state">
|
||||
{`adv=${String(tableData.advActive)} rows=${tableData.advResults.length} total=${tableData.scopeTotalCount}`}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => {
|
||||
filters.setPageSize(PAGE_SIZE);
|
||||
filters.applyAdvancedFilters({ ...EMPTY_ADVANCED_FILTERS, unscheduledOnly: true });
|
||||
}}
|
||||
>
|
||||
Apply unscheduled
|
||||
</button>
|
||||
<WoTable
|
||||
tableData={tableData}
|
||||
users={[]}
|
||||
sites={[]}
|
||||
vendors={[]}
|
||||
pmTypes={[]}
|
||||
weekMonday={filters.weekMonday}
|
||||
onNavigateToDay={filters.navigateToDay}
|
||||
onOpenSlideOver={() => undefined}
|
||||
onOpenMedia={() => undefined}
|
||||
onPatch={() => undefined}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
describe("unscheduled advanced search is server-owned (SH-338)", () => {
|
||||
beforeEach(() => {
|
||||
searchBoard.mockReset();
|
||||
getBoard.mockReset();
|
||||
getBoard.mockResolvedValue(emptyBoardResult());
|
||||
});
|
||||
|
||||
it("sends unscheduledOnly=true with no date window and pages server rows/totals", async () => {
|
||||
// Server owns totals: page 0 returns 3 rows while totalCount spans 2 pages.
|
||||
const pageZero = [
|
||||
makeRow({ id: "wo-0", woNumber: "WO-0", scheduledOn: "" }),
|
||||
// Server owns row filtering: a dated row on the page must still render.
|
||||
makeRow({ id: "wo-1", woNumber: "WO-1", scheduledOn: "2026-08-17" }),
|
||||
makeRow({ id: "wo-2", woNumber: "WO-2", scheduledOn: "" }),
|
||||
];
|
||||
const pageOne = [
|
||||
makeRow({ id: "wo-25", woNumber: "WO-25", scheduledOn: "" }),
|
||||
makeRow({ id: "wo-26", woNumber: "WO-26", scheduledOn: "" }),
|
||||
];
|
||||
const serverTotal = PAGE_SIZE + 2;
|
||||
searchBoard.mockImplementation(async (params: WorkOrderBoardSearchQueryParams) => ({
|
||||
items: (params.page ?? 0) === 0 ? pageZero : pageOne,
|
||||
scheduled: (params.page ?? 0) === 0 ? pageZero : pageOne,
|
||||
unscheduled: [] as WorkOrderTableRow[],
|
||||
totalCount: serverTotal,
|
||||
returnedCount: ((params.page ?? 0) === 0 ? pageZero : pageOne).length,
|
||||
page: params.page ?? 0,
|
||||
pageSize: params.pageSize ?? PAGE_SIZE,
|
||||
}));
|
||||
|
||||
renderWithProviders(<UnscheduledServerOwnedHarness />, { withAuth: false });
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Apply unscheduled" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId("harness-state")).toHaveTextContent("adv=true rows=3");
|
||||
});
|
||||
const params = lastSearchParams();
|
||||
expect(params.unscheduledOnly).toBe(true);
|
||||
expect(params.datePreset).toBeUndefined();
|
||||
expect(params.dateFrom).toBeUndefined();
|
||||
expect(params.dateTo).toBeUndefined();
|
||||
expect(params.page).toBe(0);
|
||||
expect(params.pageSize).toBe(PAGE_SIZE);
|
||||
|
||||
expect(screen.getByText("WO-0")).toBeInTheDocument();
|
||||
expect(screen.getByText("WO-1")).toBeInTheDocument();
|
||||
expect(screen.getByText("WO-2")).toBeInTheDocument();
|
||||
expect(screen.queryByText("WO-25")).not.toBeInTheDocument();
|
||||
expect(screen.getByText(`RESULTS · ${serverTotal} items`)).toBeInTheDocument();
|
||||
expect(screen.getByText("Page 1 of 2")).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Next page" }));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("WO-25")).toBeInTheDocument();
|
||||
expect(screen.getByText("WO-26")).toBeInTheDocument();
|
||||
expect(screen.getByText("Page 2 of 2")).toBeInTheDocument();
|
||||
});
|
||||
expect(lastSearchParams().page).toBe(1);
|
||||
expect(screen.getByTestId("harness-state")).toHaveTextContent("rows=2");
|
||||
expect(screen.getByText(`RESULTS · ${serverTotal} items`)).toBeInTheDocument();
|
||||
}, 15000);
|
||||
|
||||
it("shows the adv empty state when the server returns zero unscheduled results", async () => {
|
||||
searchBoard.mockResolvedValue(pagedResult([], 0, PAGE_SIZE));
|
||||
|
||||
renderWithProviders(<UnscheduledServerOwnedHarness />, { withAuth: false });
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Apply unscheduled" }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("No work orders match these filters")).toBeInTheDocument();
|
||||
});
|
||||
expect(screen.getByTestId("harness-state")).toHaveTextContent("adv=true rows=0 total=0");
|
||||
expect(screen.queryByText(/Page \d+ of \d+/)).not.toBeInTheDocument();
|
||||
}, 15000);
|
||||
});
|
||||
|
|
@ -341,7 +341,7 @@ describe("WorkOrders Advanced Search page wiring (SH-121)", () => {
|
|||
expect(screen.getByRole("button", { name: /work order types, all types/i })).toBeEnabled();
|
||||
});
|
||||
|
||||
it("applies Unscheduled from Date range without opening From/To or sending Unscheduled", () => {
|
||||
it("applies Unscheduled from Date range and sends unscheduledOnly without a faked window", () => {
|
||||
const view = renderWithProviders(<WorkOrdersListPage />, {
|
||||
route: "/workorders",
|
||||
withAuth: false,
|
||||
|
|
@ -382,10 +382,10 @@ describe("WorkOrders Advanced Search page wiring (SH-121)", () => {
|
|||
expect(applied.dateRange).toBe("custom");
|
||||
|
||||
const params = toBoardSearchQueryParams({ filters: applied });
|
||||
expect(params.datePreset).toBe("Custom");
|
||||
expect(params.dateFrom).toBe("2000-01-01");
|
||||
expect(params.dateTo).toBe("2099-12-31");
|
||||
expect(String(params.datePreset)).not.toBe("Unscheduled");
|
||||
expect(params.unscheduledOnly).toBe(true);
|
||||
expect(params.datePreset).toBeUndefined();
|
||||
expect(params.dateFrom).toBeUndefined();
|
||||
expect(params.dateTo).toBeUndefined();
|
||||
});
|
||||
|
||||
it("applies Aveta required only through Advanced Filters", () => {
|
||||
|
|
|
|||
|
|
@ -363,6 +363,42 @@ describe("workOrdersApi.searchBoard", () => {
|
|||
expect(result.items[0]?.pm).toBe("PM Filter");
|
||||
expect(result.items[0]?.scheduledOn).toBe("2026-07-15");
|
||||
});
|
||||
|
||||
it("serializes unscheduledOnly=true with no date window on the exact wire shape (SH-338)", async () => {
|
||||
await workOrdersApi.searchBoard({
|
||||
page: 0,
|
||||
pageSize: 50,
|
||||
unscheduledOnly: true,
|
||||
statuses: [3],
|
||||
sortBy: "scheduledDate",
|
||||
sortDir: "asc",
|
||||
});
|
||||
|
||||
const searchParams = apiGet.mock.calls[0]?.[1]?.searchParams as URLSearchParams;
|
||||
expect(String(searchParams)).toBe(
|
||||
"page=0&pageSize=50&unscheduledOnly=true&statuses=3&sortBy=scheduledDate&sortDir=asc",
|
||||
);
|
||||
expect(searchParams.get("unscheduledOnly")).toBe("true");
|
||||
expect(searchParams.has("datePreset")).toBe(false);
|
||||
expect(searchParams.has("dateFrom")).toBe(false);
|
||||
expect(searchParams.has("dateTo")).toBe(false);
|
||||
});
|
||||
|
||||
it("keeps the legacy wire shape (datePreset, no unscheduledOnly) when the toggle is off", async () => {
|
||||
await workOrdersApi.searchBoard({
|
||||
datePreset: "Custom",
|
||||
dateFrom: "2000-01-01",
|
||||
dateTo: "2099-12-31",
|
||||
page: 0,
|
||||
pageSize: 50,
|
||||
});
|
||||
|
||||
const searchParams = apiGet.mock.calls[0]?.[1]?.searchParams as URLSearchParams;
|
||||
expect(searchParams.has("unscheduledOnly")).toBe(false);
|
||||
expect(searchParams.get("datePreset")).toBe("Custom");
|
||||
expect(searchParams.get("dateFrom")).toBe("2000-01-01");
|
||||
expect(searchParams.get("dateTo")).toBe("2099-12-31");
|
||||
});
|
||||
});
|
||||
|
||||
describe("workOrdersApi.findByWoNumber", () => {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
WORK_ORDER_TYPE_API,
|
||||
} from "@/domain/work-orders/types/work-order-board";
|
||||
import type { WOType } from "@/domain/work-orders/types/work-order-wizard";
|
||||
import { queryKeys } from "@/infra/query-key/query-key";
|
||||
import {
|
||||
BOARD_SEARCH_MAX_PAGE_SIZE,
|
||||
buildBoardSearchParamsRecord,
|
||||
|
|
@ -88,6 +89,7 @@ describe("toBoardSearchQueryParams", () => {
|
|||
expect(params.datePreset).toBe("Custom");
|
||||
expect(params.dateFrom).toBe("2000-01-01");
|
||||
expect(params.dateTo).toBe("2099-12-31");
|
||||
expect(params.unscheduledOnly).toBeUndefined();
|
||||
});
|
||||
|
||||
it("forwards chrome pageSize 50 instead of the search max", () => {
|
||||
|
|
@ -133,14 +135,14 @@ describe("toBoardSearchQueryParams", () => {
|
|||
expect(thisWeek.dateTo).toBe("2099-12-31");
|
||||
});
|
||||
|
||||
it("uses wide Custom dates when Unscheduled is on and never sends Unscheduled", () => {
|
||||
it("sends unscheduledOnly=true with no faked Custom date window (SH-338)", () => {
|
||||
const withDefaultRange = toBoardSearchQueryParams({
|
||||
filters: { ...EMPTY_ADVANCED_FILTERS, unscheduledOnly: true },
|
||||
});
|
||||
expect(withDefaultRange.datePreset).toBe("Custom");
|
||||
expect(withDefaultRange.dateFrom).toBe("2000-01-01");
|
||||
expect(withDefaultRange.dateTo).toBe("2099-12-31");
|
||||
expect(String(withDefaultRange.datePreset)).not.toBe("Unscheduled");
|
||||
expect(withDefaultRange.unscheduledOnly).toBe(true);
|
||||
expect(withDefaultRange.datePreset).toBeUndefined();
|
||||
expect(withDefaultRange.dateFrom).toBeUndefined();
|
||||
expect(withDefaultRange.dateTo).toBeUndefined();
|
||||
|
||||
const overridingLastWeek = toBoardSearchQueryParams({
|
||||
filters: {
|
||||
|
|
@ -149,10 +151,41 @@ describe("toBoardSearchQueryParams", () => {
|
|||
unscheduledOnly: true,
|
||||
},
|
||||
});
|
||||
expect(overridingLastWeek.datePreset).toBe("Custom");
|
||||
expect(overridingLastWeek.dateFrom).toBe("2000-01-01");
|
||||
expect(overridingLastWeek.dateTo).toBe("2099-12-31");
|
||||
expect(String(overridingLastWeek.datePreset)).not.toBe("Unscheduled");
|
||||
expect(overridingLastWeek.unscheduledOnly).toBe(true);
|
||||
expect(overridingLastWeek.datePreset).toBeUndefined();
|
||||
expect(overridingLastWeek.dateFrom).toBeUndefined();
|
||||
expect(overridingLastWeek.dateTo).toBeUndefined();
|
||||
|
||||
const record = buildBoardSearchParamsRecord(overridingLastWeek);
|
||||
expect(record.unscheduledOnly).toBe(true);
|
||||
expect(record).not.toHaveProperty("datePreset");
|
||||
expect(record).not.toHaveProperty("dateFrom");
|
||||
expect(record).not.toHaveProperty("dateTo");
|
||||
});
|
||||
|
||||
it("keeps applying every other facet alongside unscheduledOnly", () => {
|
||||
const params = toBoardSearchQueryParams({
|
||||
filters: {
|
||||
...EMPTY_ADVANCED_FILTERS,
|
||||
unscheduledOnly: true,
|
||||
statuses: new Set(["Scheduled"]),
|
||||
severities: new Set([4]),
|
||||
pmTypes: new Set(["HVAC"]),
|
||||
},
|
||||
});
|
||||
expect(params.unscheduledOnly).toBe(true);
|
||||
expect(params.statuses).toEqual([LIFECYCLE_STATUS_API.Scheduled]);
|
||||
expect(params.severities).toEqual([4]);
|
||||
expect(params.pmTypes).toEqual(["HVAC"]);
|
||||
});
|
||||
|
||||
it("omits unscheduledOnly and keeps the wide Custom default when the toggle is off", () => {
|
||||
const params = toBoardSearchQueryParams({
|
||||
filters: { ...EMPTY_ADVANCED_FILTERS, dateRange: "last-week" },
|
||||
});
|
||||
expect(params.unscheduledOnly).toBeUndefined();
|
||||
expect(params.datePreset).toBe("LastWeek");
|
||||
expect(buildBoardSearchParamsRecord(params)).not.toHaveProperty("unscheduledOnly");
|
||||
});
|
||||
|
||||
it("maps Overdue-only advanced filter to overdue=true without types", () => {
|
||||
|
|
@ -210,6 +243,7 @@ describe("toBoardSearchQueryParams", () => {
|
|||
expect(params.internalOnly).toBeUndefined();
|
||||
expect(params.hasUplift).toBeUndefined();
|
||||
expect(params.upliftStatuses).toBeUndefined();
|
||||
expect(params.unscheduledOnly).toBeUndefined();
|
||||
});
|
||||
|
||||
it("maps service types and completion docs onto board search params", () => {
|
||||
|
|
@ -372,3 +406,30 @@ describe("toUnassignedQueueQueryParams", () => {
|
|||
expect(record).not.toHaveProperty("weekEnd");
|
||||
});
|
||||
});
|
||||
|
||||
describe("boardSearch query-key isolation", () => {
|
||||
it("separates unscheduledOnly=true from the same facets without it", () => {
|
||||
const without = toBoardSearchQueryParams({
|
||||
filters: { ...EMPTY_ADVANCED_FILTERS, rescheduled: true },
|
||||
});
|
||||
const withUnscheduled = toBoardSearchQueryParams({
|
||||
filters: { ...EMPTY_ADVANCED_FILTERS, rescheduled: true, unscheduledOnly: true },
|
||||
});
|
||||
|
||||
expect(queryKeys.workOrders.boardSearch(without)).not.toEqual(
|
||||
queryKeys.workOrders.boardSearch(withUnscheduled),
|
||||
);
|
||||
});
|
||||
|
||||
it("separates pages under the same unscheduledOnly filter", () => {
|
||||
const base = toBoardSearchQueryParams({
|
||||
filters: { ...EMPTY_ADVANCED_FILTERS, unscheduledOnly: true },
|
||||
page: 0,
|
||||
});
|
||||
const pageOne = { ...base, page: 1 };
|
||||
|
||||
expect(queryKeys.workOrders.boardSearch(base)).not.toEqual(
|
||||
queryKeys.workOrders.boardSearch(pageOne),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ import {
|
|||
matchesAdvancedFilters,
|
||||
mergeWeeklyAndUnassignedQueue,
|
||||
partitionBoardRows,
|
||||
resolveAdvSearchDisplayRows,
|
||||
resolveAdvSearchScopeCount,
|
||||
splitScheduledUnscheduled,
|
||||
} from "@/domain/work-orders/utils/table-filter-utils";
|
||||
import { deriveDayFields } from "@/domain/work-orders/utils/table-day-utils";
|
||||
|
|
@ -458,23 +456,6 @@ describe("matchesAdvancedFilters", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("resolveAdvSearchDisplayRows", () => {
|
||||
it("filters advActive rows to !scheduledOn when the Unscheduled toggle is on", () => {
|
||||
const dated = makeRow({ id: "dated", scheduledOn: "2026-06-23" });
|
||||
const undated = makeRow({ id: "undated", scheduledOn: "", targetWeek: "2026-07-06" });
|
||||
|
||||
expect(resolveAdvSearchDisplayRows([dated, undated], false, true)).toEqual([]);
|
||||
expect(resolveAdvSearchDisplayRows([dated, undated], true, false)).toEqual([dated, undated]);
|
||||
expect(resolveAdvSearchDisplayRows([dated, undated], true, true)).toEqual([undated]);
|
||||
});
|
||||
|
||||
it("uses the filtered list length as banner count when Unscheduled is on", () => {
|
||||
expect(resolveAdvSearchScopeCount(true, 150, 3)).toBe(3);
|
||||
expect(resolveAdvSearchScopeCount(false, 150, 3)).toBe(150);
|
||||
expect(resolveAdvSearchScopeCount(false, undefined, 3)).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe("computeIsPastDue", () => {
|
||||
it("is true when scheduledOn is before today even if dueDate is future", () => {
|
||||
expect(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue