mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 11:33:12 +00:00
feat(work-orders): add SH-244 advanced filter fields and AAP indicator styles
This commit is contained in:
parent
19a54e38c6
commit
93ab14abf3
8 changed files with 411 additions and 41 deletions
135
e2e/work-orders/advanced-filters-sh244.spec.ts
Normal file
135
e2e/work-orders/advanced-filters-sh244.spec.ts
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
import { expect, test, type Page, type Route } from "@playwright/test";
|
||||
|
||||
const FROZEN_NOW = "2026-08-19T15:00:00.000Z";
|
||||
|
||||
const TEST_USER = {
|
||||
token: "wo-adv-filters-e2e-token",
|
||||
expiration: "2030-01-01T00:00:00.000Z",
|
||||
email: "admin@seahavenind.com",
|
||||
userRoles: "Admin",
|
||||
phoneNumber: "5551234567",
|
||||
fullname: "Work Order E2E Admin",
|
||||
id: "1",
|
||||
};
|
||||
|
||||
async function fulfillJson(route: Route, body: unknown, status = 200) {
|
||||
await route.fulfill({ status, contentType: "application/json", body: JSON.stringify(body) });
|
||||
}
|
||||
|
||||
function boardRow(id: number, woNumber: string, scheduledDate: string | null) {
|
||||
return {
|
||||
id,
|
||||
woNumber,
|
||||
workOrderType: 2,
|
||||
siteCode: woNumber,
|
||||
lifecycleStatusLabel: "Scheduled",
|
||||
scheduledDate,
|
||||
dispatcherId: "1",
|
||||
dispatcherName: "Pat Dispatcher",
|
||||
pm: "HVAC",
|
||||
};
|
||||
}
|
||||
|
||||
async function mockWorkOrderApis(page: Page, searchUrls: string[]) {
|
||||
await page.route("**/api/**", (route) => {
|
||||
const pathname = new URL(route.request().url()).pathname;
|
||||
if (!pathname.startsWith("/api/")) {
|
||||
return route.fallback();
|
||||
}
|
||||
if (pathname.includes("/workorders/board") || pathname.includes("DropdownOptions")) {
|
||||
return route.fallback();
|
||||
}
|
||||
return fulfillJson(route, { data: [] });
|
||||
});
|
||||
await page.route("**/api/**/DropdownOptions/ByCategory/**", (route) => {
|
||||
const pathname = new URL(route.request().url()).pathname;
|
||||
if (pathname.toLowerCase().includes("problem")) {
|
||||
return fulfillJson(route, { data: [{ id: 1, value: "HVAC", category: "Problem" }] });
|
||||
}
|
||||
return fulfillJson(route, { data: [] });
|
||||
});
|
||||
await page.route("**/api/workorders/board*", (route) => {
|
||||
if (route.request().url().includes("board/search")) {
|
||||
return route.fallback();
|
||||
}
|
||||
return fulfillJson(route, {
|
||||
weekStart: "2026-08-17",
|
||||
weekEnd: "2026-08-21",
|
||||
counts: { returned: 1, total: 1 },
|
||||
unscheduled: [],
|
||||
scheduled: [boardRow(1, "ADV-WO", "2026-08-19")],
|
||||
});
|
||||
});
|
||||
await page.route("**/api/workorders/board/search*", (route) => {
|
||||
searchUrls.push(route.request().url());
|
||||
return fulfillJson(route, { items: [], totalCount: 0, page: 0, pageSize: 50 });
|
||||
});
|
||||
}
|
||||
|
||||
async function signInAndOpenWorkOrders(page: Page) {
|
||||
await page.clock.install({ time: new Date(FROZEN_NOW) });
|
||||
await page.clock.resume();
|
||||
await page.addInitScript((user) => localStorage.setItem("auth", JSON.stringify(user)), TEST_USER);
|
||||
}
|
||||
|
||||
async function applyAdvancedFilters(page: Page) {
|
||||
await page.getByRole("button", { name: "Advanced Filters" }).click();
|
||||
const sheet = page.getByRole("dialog", { name: "Advanced Filters" });
|
||||
await expect(sheet).toBeVisible();
|
||||
return sheet;
|
||||
}
|
||||
|
||||
test.describe("Work Orders advanced filters (SH-244)", () => {
|
||||
test("sends carriedOver on board/search and shows empty copy", async ({ page }) => {
|
||||
const searchUrls: string[] = [];
|
||||
await signInAndOpenWorkOrders(page);
|
||||
await mockWorkOrderApis(page, searchUrls);
|
||||
|
||||
await page.goto("/workorders");
|
||||
await expect(page.getByRole("heading", { name: "Work Orders" })).toBeVisible({
|
||||
timeout: 15_000,
|
||||
});
|
||||
|
||||
const sheet = await applyAdvancedFilters(page);
|
||||
await sheet.getByRole("button", { name: /Indicators/i }).click();
|
||||
await sheet.getByRole("button", { name: /Carried Over ≥ 2/i }).click();
|
||||
await sheet.getByRole("button", { name: /apply filters/i }).click();
|
||||
|
||||
await expect.poll(() => searchUrls.some((url) => url.includes("carriedOver=true"))).toBe(true);
|
||||
await expect(page.getByText("No work orders match these filters")).toBeVisible();
|
||||
});
|
||||
|
||||
test("sends combined SH-244 facets on the same board/search request", async ({ page }) => {
|
||||
const searchUrls: string[] = [];
|
||||
await signInAndOpenWorkOrders(page);
|
||||
await mockWorkOrderApis(page, searchUrls);
|
||||
|
||||
await page.goto("/workorders");
|
||||
await expect(page.getByRole("heading", { name: "Work Orders" })).toBeVisible({
|
||||
timeout: 15_000,
|
||||
});
|
||||
|
||||
const sheet = await applyAdvancedFilters(page);
|
||||
await sheet.getByRole("button", { name: /Severity/i }).click();
|
||||
await sheet.getByRole("button", { name: /SEV 1/i }).click();
|
||||
await sheet.getByRole("button", { name: /Indicators/i }).click();
|
||||
await sheet.getByRole("button", { name: /^Add-On$/i }).click();
|
||||
await sheet.getByRole("button", { name: /^Service/i }).click();
|
||||
await sheet.getByText("HVAC").click();
|
||||
await sheet.getByRole("button", { name: /Completion doc/i }).click();
|
||||
await sheet.getByRole("button", { name: /^Pending$/i }).click();
|
||||
await sheet.getByRole("button", { name: /apply filters/i }).click();
|
||||
|
||||
await expect
|
||||
.poll(() => {
|
||||
const url = searchUrls[searchUrls.length - 1] ?? "";
|
||||
return (
|
||||
url.includes("severities=1") &&
|
||||
url.includes("addOn=true") &&
|
||||
url.includes("pmTypes=HVAC") &&
|
||||
(url.includes("docStatuses=2") || url.includes("DocStatuses=2"))
|
||||
);
|
||||
})
|
||||
.toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
@ -13,10 +13,11 @@ import {
|
|||
import { AdvDocPill, AdvStatusPill } from "./adv-filter-pill";
|
||||
import { AdvDateRangeSection } from "./adv-date-range-section";
|
||||
import { AdvMultiSelect } from "./adv-multi-select";
|
||||
import { AdvParityFilterSections } from "./adv-parity-filter-sections";
|
||||
import { AdvFlagInternalUpliftSections, AdvIndicatorsSection } from "./adv-parity-filter-sections";
|
||||
import { AdvSection } from "./adv-section";
|
||||
import { AdvSeverityList } from "./adv-severity-list";
|
||||
import { AdvTypeGrid } from "./adv-type-grid";
|
||||
import { PmTypeIcon } from "@/app/(protected)/workorders/_components/list/table/pm-type-icons";
|
||||
import { SelectAllAction } from "@/components/ui/select-all-action";
|
||||
import {
|
||||
ALL_WIZARD_STATUSES,
|
||||
|
|
@ -124,20 +125,18 @@ export function AdvancedSearchFilterSections({
|
|||
</div>
|
||||
</AdvSection>
|
||||
|
||||
<AdvParityFilterSections
|
||||
draft={draft}
|
||||
setDraft={setDraft}
|
||||
toggleIn={toggleIn}
|
||||
setMany={setMany}
|
||||
/>
|
||||
<AdvIndicatorsSection draft={draft} setDraft={setDraft} />
|
||||
|
||||
<AdvSection icon={Flame} label="PM type" count={draft.pmTypes.size}>
|
||||
<AdvSection icon={Flame} label="Service" count={draft.pmTypes.size}>
|
||||
<AdvMultiSelect
|
||||
options={pmTypes.map((pmType) => ({ value: pmType, label: pmType }))}
|
||||
selected={draft.pmTypes}
|
||||
onToggle={(value) => toggleIn("pmTypes", value)}
|
||||
onBulkToggle={(values, select) => setMany("pmTypes", values, select)}
|
||||
placeholder="Search pm type…"
|
||||
placeholder="Search service…"
|
||||
renderLeft={(pmType) => (
|
||||
<PmTypeIcon pmType={pmType} size={14} className="shrink-0 text-muted-foreground" />
|
||||
)}
|
||||
/>
|
||||
</AdvSection>
|
||||
|
||||
|
|
@ -164,6 +163,13 @@ export function AdvancedSearchFilterSections({
|
|||
))}
|
||||
</div>
|
||||
</AdvSection>
|
||||
|
||||
<AdvFlagInternalUpliftSections
|
||||
draft={draft}
|
||||
setDraft={setDraft}
|
||||
toggleIn={toggleIn}
|
||||
setMany={setMany}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { Dispatch, SetStateAction } from "react";
|
||||
import { Check, DollarSign, Flag, Hash, RefreshCw } from "lucide-react";
|
||||
import type { Dispatch, ReactNode, SetStateAction } from "react";
|
||||
import { ArrowRight, Check, DollarSign, Flag, Hash, Plus, RefreshCw } from "lucide-react";
|
||||
import { Checkbox } from "@mui/material";
|
||||
import { AdvDocPill, AdvFilterPill } from "./adv-filter-pill";
|
||||
import { AdvDocPill } from "./adv-filter-pill";
|
||||
import { AdvMultiSelect } from "./adv-multi-select";
|
||||
import { AdvSection } from "./adv-section";
|
||||
import { FLAG_COLORS } from "@/domain/work-orders/flag-colors";
|
||||
|
|
@ -15,9 +15,12 @@ import {
|
|||
type UpliftFilterStatus,
|
||||
} from "@/domain/work-orders/types/work-order-uplift-filter";
|
||||
|
||||
type AdvParityFilterSectionsProps = {
|
||||
type DraftProps = {
|
||||
draft: WorkOrderAdvancedFilters;
|
||||
setDraft: Dispatch<SetStateAction<WorkOrderAdvancedFilters>>;
|
||||
};
|
||||
|
||||
type AdvFlagInternalUpliftSectionsProps = DraftProps & {
|
||||
toggleIn: (key: AdvancedFilterSetKey, value: string) => void;
|
||||
setMany: (key: AdvancedFilterSetKey, values: string[], select: boolean) => void;
|
||||
};
|
||||
|
|
@ -35,12 +38,88 @@ function FlagColorDot({ value }: { value: string }) {
|
|||
);
|
||||
}
|
||||
|
||||
export function AdvParityFilterSections({
|
||||
type IndicatorKind = "rescheduled" | "carriedOver" | "addOn";
|
||||
|
||||
const INDICATOR_SELECTED: Record<IndicatorKind, { background: string; color: string }> = {
|
||||
rescheduled: { background: "#f5f3ff", color: "#7c3aed" },
|
||||
carriedOver: { background: "var(--color-bg-muted)", color: "var(--color-text-primary)" },
|
||||
addOn: { background: "var(--type-addon-bg)", color: "var(--type-addon-text)" },
|
||||
};
|
||||
|
||||
function IndicatorPill({
|
||||
selected,
|
||||
onClick,
|
||||
kind,
|
||||
children,
|
||||
}: {
|
||||
selected: boolean;
|
||||
onClick: () => void;
|
||||
kind: IndicatorKind;
|
||||
children: ReactNode;
|
||||
}) {
|
||||
const selectedLook = INDICATOR_SELECTED[kind];
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClick}
|
||||
data-indicator={kind}
|
||||
data-selected={selected ? "true" : "false"}
|
||||
className="inline-flex items-center gap-1.5"
|
||||
style={{
|
||||
height: 26,
|
||||
padding: "0 9px",
|
||||
borderRadius: 9999,
|
||||
border: `1px solid ${selected ? "var(--color-primary)" : "var(--color-border)"}`,
|
||||
background: selected ? selectedLook.background : "var(--color-bg-surface)",
|
||||
color: selected ? selectedLook.color : "var(--color-text-secondary)",
|
||||
fontSize: 12,
|
||||
fontWeight: 500,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export function AdvIndicatorsSection({ draft, setDraft }: DraftProps) {
|
||||
return (
|
||||
<AdvSection icon={RefreshCw} label="Indicators" count={indicatorCount(draft)}>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
<IndicatorPill
|
||||
kind="rescheduled"
|
||||
selected={draft.rescheduled}
|
||||
onClick={() => setDraft((prev) => ({ ...prev, rescheduled: !prev.rescheduled }))}
|
||||
>
|
||||
<RefreshCw size={12} className="shrink-0" style={{ color: "#7c3aed" }} />
|
||||
Rescheduled ≥ 2
|
||||
</IndicatorPill>
|
||||
<IndicatorPill
|
||||
kind="carriedOver"
|
||||
selected={draft.carriedOver}
|
||||
onClick={() => setDraft((prev) => ({ ...prev, carriedOver: !prev.carriedOver }))}
|
||||
>
|
||||
<ArrowRight size={12} className="shrink-0" style={{ color: "var(--color-text-muted)" }} />
|
||||
Carried Over ≥ 2
|
||||
</IndicatorPill>
|
||||
<IndicatorPill
|
||||
kind="addOn"
|
||||
selected={draft.addOn}
|
||||
onClick={() => setDraft((prev) => ({ ...prev, addOn: !prev.addOn }))}
|
||||
>
|
||||
<Plus size={12} className="shrink-0" style={{ color: "var(--type-addon-text)" }} />
|
||||
Add-On
|
||||
</IndicatorPill>
|
||||
</div>
|
||||
</AdvSection>
|
||||
);
|
||||
}
|
||||
|
||||
export function AdvFlagInternalUpliftSections({
|
||||
draft,
|
||||
setDraft,
|
||||
toggleIn,
|
||||
setMany,
|
||||
}: AdvParityFilterSectionsProps) {
|
||||
}: AdvFlagInternalUpliftSectionsProps) {
|
||||
const toggleUpliftStatus = (status: UpliftFilterStatus) => {
|
||||
setDraft((prev) => {
|
||||
const next = new Set(prev.upliftStatuses);
|
||||
|
|
@ -55,29 +134,6 @@ export function AdvParityFilterSections({
|
|||
|
||||
return (
|
||||
<>
|
||||
<AdvSection icon={RefreshCw} label="Indicators" count={indicatorCount(draft)}>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
<AdvFilterPill
|
||||
label="Rescheduled ≥ 2"
|
||||
selected={draft.rescheduled}
|
||||
onClick={() => setDraft((prev) => ({ ...prev, rescheduled: !prev.rescheduled }))}
|
||||
className="inline-flex items-center gap-1.5"
|
||||
/>
|
||||
<AdvFilterPill
|
||||
label="Carried Over ≥ 2"
|
||||
selected={draft.carriedOver}
|
||||
onClick={() => setDraft((prev) => ({ ...prev, carriedOver: !prev.carriedOver }))}
|
||||
className="inline-flex items-center gap-1.5"
|
||||
/>
|
||||
<AdvFilterPill
|
||||
label="Add-On"
|
||||
selected={draft.addOn}
|
||||
onClick={() => setDraft((prev) => ({ ...prev, addOn: !prev.addOn }))}
|
||||
className="inline-flex items-center gap-1.5"
|
||||
/>
|
||||
</div>
|
||||
</AdvSection>
|
||||
|
||||
<AdvSection icon={Flag} label="Flag" count={draft.flagColors.size}>
|
||||
<AdvMultiSelect
|
||||
options={FLAG_COLORS.map((color) => ({ value: color.value, label: color.name }))}
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ export function appendStatusPmVendorDocChips(
|
|||
pushSetChip({
|
||||
chips,
|
||||
key: "pm",
|
||||
group: "PM type",
|
||||
group: "Service",
|
||||
set: filters.pmTypes,
|
||||
nameOf: (v) => v,
|
||||
clear: (current) => ({ ...current, pmTypes: new Set() }),
|
||||
|
|
|
|||
|
|
@ -120,4 +120,33 @@ describe("WoTable search empty vs Unassigned queue (SH-165/SH-241)", () => {
|
|||
expect(queryByText("No work orders match your search")).not.toBeInTheDocument();
|
||||
expect(getByText("queue unavailable")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows empty advanced-filter copy with Adjust filters", () => {
|
||||
const onOpenAdvancedFilters = vi.fn();
|
||||
const { getByText, getByRole } = renderWithProviders(
|
||||
<WoTable
|
||||
tableData={tableData({
|
||||
advActive: true,
|
||||
advResults: [],
|
||||
searchActive: false,
|
||||
noSearchResults: false,
|
||||
})}
|
||||
users={[]}
|
||||
sites={[]}
|
||||
vendors={[]}
|
||||
pmTypes={[]}
|
||||
weekMonday="2026-05-18"
|
||||
onNavigateToDay={vi.fn()}
|
||||
onOpenSlideOver={vi.fn()}
|
||||
onOpenMedia={vi.fn()}
|
||||
onOpenAdvancedFilters={onOpenAdvancedFilters}
|
||||
onPatch={vi.fn()}
|
||||
/>,
|
||||
{ withAuth: false },
|
||||
);
|
||||
|
||||
expect(getByText("No work orders match these filters")).toBeInTheDocument();
|
||||
getByRole("button", { name: "Adjust filters" }).click();
|
||||
expect(onOpenAdvancedFilters).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ describe("WorkOrders Advanced Search page wiring (SH-121)", () => {
|
|||
expect(within(sheet).getByRole("button", { name: /apply filters/i })).toBeInTheDocument();
|
||||
|
||||
// Expand filter sections that start collapsed and assert option inputs are wired.
|
||||
for (const label of ["Site", "Assigned to", "PM type", "Vendor / tech"]) {
|
||||
for (const label of ["Site", "Assigned to", "Service", "Vendor / tech"]) {
|
||||
fireEvent.click(within(sheet).getByRole("button", { name: new RegExp(label, "i") }));
|
||||
}
|
||||
expect(within(sheet).getByText("Seattle")).toBeInTheDocument();
|
||||
|
|
@ -200,6 +200,40 @@ describe("WorkOrders Advanced Search page wiring (SH-121)", () => {
|
|||
expect(params.dateTo).toBe("2099-12-31");
|
||||
});
|
||||
|
||||
it("applies Service and Completion doc through the public Advanced Filters sheet", () => {
|
||||
const view = renderWithProviders(<WorkOrdersListPage />, {
|
||||
route: "/workorders",
|
||||
withAuth: false,
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: "Advanced Filters" }));
|
||||
advOpen = true;
|
||||
view.rerender(<WorkOrdersListPage />);
|
||||
|
||||
const sheet = screen.getByRole("dialog", { name: "Advanced Filters" });
|
||||
fireEvent.click(within(sheet).getByRole("button", { name: /^Service/i }));
|
||||
fireEvent.click(within(sheet).getByText("HVAC"));
|
||||
|
||||
fireEvent.click(within(sheet).getByRole("button", { name: /Completion doc/i }));
|
||||
fireEvent.click(within(sheet).getByRole("button", { name: /^Pending$/i }));
|
||||
fireEvent.click(within(sheet).getByRole("button", { name: /^Uploaded$/i }));
|
||||
fireEvent.click(within(sheet).getByRole("button", { name: /^N\/N$/i }));
|
||||
|
||||
fireEvent.click(within(sheet).getByRole("button", { name: /apply filters/i }));
|
||||
|
||||
expect(applyAdvancedFilters).toHaveBeenCalled();
|
||||
const appliedCalls = applyAdvancedFilters.mock.calls;
|
||||
const applied = appliedCalls[appliedCalls.length - 1]?.[0] as typeof EMPTY_ADVANCED_FILTERS;
|
||||
expect(applied.pmTypes.has("HVAC")).toBe(true);
|
||||
expect(applied.docs.has("No")).toBe(true);
|
||||
expect(applied.docs.has("Yes")).toBe(true);
|
||||
expect(applied.docs.has("NN")).toBe(true);
|
||||
|
||||
const params = toBoardSearchQueryParams({ filters: applied });
|
||||
expect(params.pmTypes).toEqual(["HVAC"]);
|
||||
expect(params.docStatuses).toEqual([2, 1, 3]);
|
||||
});
|
||||
|
||||
it("applies Indicators, Flag, and Internal WO# through the public Advanced Filters sheet", () => {
|
||||
const view = renderWithProviders(<WorkOrdersListPage />, {
|
||||
route: "/workorders",
|
||||
|
|
@ -216,6 +250,18 @@ describe("WorkOrders Advanced Search page wiring (SH-121)", () => {
|
|||
fireEvent.click(within(sheet).getByRole("button", { name: /Carried Over ≥ 2/i }));
|
||||
fireEvent.click(within(sheet).getByRole("button", { name: /^Add-On$/i }));
|
||||
|
||||
const rescheduled = within(sheet).getByRole("button", { name: /Rescheduled ≥ 2/i });
|
||||
const carriedOver = within(sheet).getByRole("button", { name: /Carried Over ≥ 2/i });
|
||||
const addOn = within(sheet).getByRole("button", { name: /^Add-On$/i });
|
||||
expect(rescheduled).toHaveAttribute("data-selected", "true");
|
||||
expect(carriedOver).toHaveAttribute("data-selected", "true");
|
||||
expect(addOn).toHaveAttribute("data-selected", "true");
|
||||
expect(rescheduled.style.background).toBe("rgb(245, 243, 255)");
|
||||
expect(rescheduled.style.color).toBe("rgb(124, 58, 237)");
|
||||
expect(carriedOver.style.background).toBe("var(--color-bg-muted)");
|
||||
expect(addOn.style.background).toBe("var(--type-addon-bg)");
|
||||
expect(addOn.style.color).toBe("var(--type-addon-text)");
|
||||
|
||||
fireEvent.click(within(sheet).getByRole("button", { name: /^Flag$/i }));
|
||||
fireEvent.click(within(sheet).getByRole("button", { name: /^Red$/i }));
|
||||
|
||||
|
|
|
|||
|
|
@ -120,6 +120,24 @@ describe("clearAdvancedFilterGroup", () => {
|
|||
expect(chips.some((c) => c.key === "uplift" && c.value === "Pending")).toBe(true);
|
||||
});
|
||||
|
||||
it("builds chips for service, carried over, add-on, and completion doc", () => {
|
||||
const filters = {
|
||||
...cloneAdvancedFilters(EMPTY_ADVANCED_FILTERS),
|
||||
pmTypes: new Set(["HVAC"]),
|
||||
carriedOver: true,
|
||||
addOn: true,
|
||||
docs: new Set(["No" as const, "Yes" as const]),
|
||||
};
|
||||
|
||||
const chips = buildAdvancedFilterChips(filters);
|
||||
expect(chips.some((c) => c.key === "pm" && c.group === "Service" && c.value === "HVAC")).toBe(
|
||||
true,
|
||||
);
|
||||
expect(chips.some((c) => c.key === "carriedover" && c.value === "Carried Over")).toBe(true);
|
||||
expect(chips.some((c) => c.key === "addon" && c.value === "Add-On")).toBe(true);
|
||||
expect(chips.some((c) => c.key === "doc" && c.group === "Comp doc")).toBe(true);
|
||||
});
|
||||
|
||||
it("clears uplift chip and drops hasUplift with statuses", () => {
|
||||
const filters = {
|
||||
...cloneAdvancedFilters(EMPTY_ADVANCED_FILTERS),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { ASSIGNEE_FILTER_UNASSIGNED } from "@/domain/work-orders/types/work-order-list";
|
||||
import { EMPTY_ADVANCED_FILTERS } from "@/domain/work-orders/types/work-order-advanced-filters";
|
||||
import { WORK_ORDER_TYPE_API } from "@/domain/work-orders/types/work-order-board";
|
||||
import {
|
||||
DOC_STATUS_API,
|
||||
LIFECYCLE_STATUS_API,
|
||||
WORK_ORDER_TYPE_API,
|
||||
} from "@/domain/work-orders/types/work-order-board";
|
||||
import {
|
||||
BOARD_SEARCH_MAX_PAGE_SIZE,
|
||||
buildBoardSearchParamsRecord,
|
||||
|
|
@ -163,6 +167,82 @@ describe("toBoardSearchQueryParams", () => {
|
|||
expect(params.upliftStatuses).toBeUndefined();
|
||||
});
|
||||
|
||||
it("maps service types and completion docs onto board search params", () => {
|
||||
const params = toBoardSearchQueryParams({
|
||||
filters: {
|
||||
...EMPTY_ADVANCED_FILTERS,
|
||||
pmTypes: new Set(["HVAC", "Electrical"]),
|
||||
docs: new Set(["No", "Yes", "NN"]),
|
||||
},
|
||||
});
|
||||
expect(params.pmTypes).toEqual(["HVAC", "Electrical"]);
|
||||
expect(params.docStatuses).toEqual([
|
||||
DOC_STATUS_API.No,
|
||||
DOC_STATUS_API.Yes,
|
||||
DOC_STATUS_API.NotNeeded,
|
||||
]);
|
||||
});
|
||||
|
||||
it("maps each SH-244 facet alone onto board search params (AND-ready query)", () => {
|
||||
const severity = toBoardSearchQueryParams({
|
||||
filters: { ...EMPTY_ADVANCED_FILTERS, severities: new Set([4]) },
|
||||
});
|
||||
expect(severity.severities).toEqual([4]);
|
||||
expect(severity.carriedOver).toBeUndefined();
|
||||
expect(severity.addOn).toBeUndefined();
|
||||
expect(severity.pmTypes).toBeUndefined();
|
||||
expect(severity.docStatuses).toBeUndefined();
|
||||
|
||||
const carried = toBoardSearchQueryParams({
|
||||
filters: { ...EMPTY_ADVANCED_FILTERS, carriedOver: true },
|
||||
});
|
||||
expect(carried.carriedOver).toBe(true);
|
||||
expect(carried.severities).toBeUndefined();
|
||||
|
||||
const addOn = toBoardSearchQueryParams({
|
||||
filters: { ...EMPTY_ADVANCED_FILTERS, addOn: true },
|
||||
});
|
||||
expect(addOn.addOn).toBe(true);
|
||||
expect(addOn.carriedOver).toBeUndefined();
|
||||
|
||||
const service = toBoardSearchQueryParams({
|
||||
filters: { ...EMPTY_ADVANCED_FILTERS, pmTypes: new Set(["HVAC"]) },
|
||||
});
|
||||
expect(service.pmTypes).toEqual(["HVAC"]);
|
||||
|
||||
const docs = toBoardSearchQueryParams({
|
||||
filters: { ...EMPTY_ADVANCED_FILTERS, docs: new Set(["NN"]) },
|
||||
});
|
||||
expect(docs.docStatuses).toEqual([DOC_STATUS_API.NotNeeded]);
|
||||
});
|
||||
|
||||
it("combines SH-244 facets with a SH-121 status facet on the same request", () => {
|
||||
const params = toBoardSearchQueryParams({
|
||||
filters: {
|
||||
...EMPTY_ADVANCED_FILTERS,
|
||||
severities: new Set([1]),
|
||||
carriedOver: true,
|
||||
addOn: true,
|
||||
pmTypes: new Set(["HVAC"]),
|
||||
docs: new Set(["Yes"]),
|
||||
statuses: new Set(["Scheduled"]),
|
||||
},
|
||||
});
|
||||
expect(params.severities).toEqual([1]);
|
||||
expect(params.carriedOver).toBe(true);
|
||||
expect(params.addOn).toBe(true);
|
||||
expect(params.pmTypes).toEqual(["HVAC"]);
|
||||
expect(params.docStatuses).toEqual([DOC_STATUS_API.Yes]);
|
||||
expect(params.statuses).toEqual([LIFECYCLE_STATUS_API.Scheduled]);
|
||||
const record = buildBoardSearchParamsRecord(params);
|
||||
expect(record.severities).toEqual([1]);
|
||||
expect(record.carriedOver).toBe(true);
|
||||
expect(record.addOn).toBe(true);
|
||||
expect(record.pmTypes).toEqual(["HVAC"]);
|
||||
expect(record.docStatuses).toEqual([DOC_STATUS_API.Yes]);
|
||||
expect(record.statuses).toEqual([LIFECYCLE_STATUS_API.Scheduled]);
|
||||
});
|
||||
|
||||
it("maps severity and uplift facets onto board search params", () => {
|
||||
const params = toBoardSearchQueryParams({
|
||||
filters: {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue