2026-09-16 19:37:15 -03:00
|
|
|
import { describe, expect, it } from "vitest";
|
2026-09-25 17:10:51 -03:00
|
|
|
import { buildResolvedBoardSearchParams } from "@/app/(protected)/workorders/_hooks/build-resolved-board-search-params";
|
|
|
|
|
import {
|
|
|
|
|
buildSiteOpenWorkOrdersHref,
|
|
|
|
|
parseDashboardDrilldownParams,
|
|
|
|
|
} from "@/domain/work-orders/utils/dashboard-drilldown-params";
|
2026-09-24 21:09:04 -03:00
|
|
|
import {
|
|
|
|
|
avetaPendingDrilldownSearch,
|
|
|
|
|
unassignedDrilldownSearch,
|
|
|
|
|
} from "@/domain/dashboard/utils/work-order-drilldown-links";
|
|
|
|
|
import { NON_TERMINAL_LIFECYCLE_STATUSES } from "@/domain/work-orders/types/work-order-board";
|
2026-09-24 22:45:10 -03:00
|
|
|
import { ASSIGNEE_FILTER_UNASSIGNED } from "@/domain/work-orders/types/work-order-list";
|
|
|
|
|
import { buildAdvancedFilterChips } from "@/domain/work-orders/utils/advanced-filters-utils";
|
2026-09-24 21:09:04 -03:00
|
|
|
import { toBoardSearchQueryParams } from "@/domain/work-orders/utils/board-query-params";
|
2026-09-16 19:37:15 -03:00
|
|
|
|
|
|
|
|
describe("parseDashboardDrilldownParams", () => {
|
|
|
|
|
it("returns null when no recognized param is present", () => {
|
|
|
|
|
expect(parseDashboardDrilldownParams(new URLSearchParams())).toBeNull();
|
|
|
|
|
expect(
|
|
|
|
|
parseDashboardDrilldownParams(new URLSearchParams({ dateFrom: "2026-09-16" })),
|
|
|
|
|
).toBeNull();
|
|
|
|
|
expect(parseDashboardDrilldownParams(new URLSearchParams({ aveta: "0" }))).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("maps dates to a custom advanced-filter range", () => {
|
|
|
|
|
const filters = parseDashboardDrilldownParams(
|
|
|
|
|
new URLSearchParams({ dateFrom: "2026-09-14", dateTo: "2026-09-18" }),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(filters).not.toBeNull();
|
|
|
|
|
expect(filters?.dateRange).toBe("custom");
|
|
|
|
|
expect(filters?.customFrom).toBe("2026-09-14");
|
|
|
|
|
expect(filters?.customTo).toBe("2026-09-18");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("parses the Aveta Pending drilldown and drops unknown statuses", () => {
|
|
|
|
|
const params = avetaPendingDrilldownSearch("2026-09-16");
|
|
|
|
|
params.append("statuses", "NotAStatus");
|
|
|
|
|
const filters = parseDashboardDrilldownParams(params);
|
|
|
|
|
|
|
|
|
|
expect(filters).not.toBeNull();
|
|
|
|
|
expect(filters?.avetaOnly).toBe(true);
|
2026-09-17 00:36:55 -03:00
|
|
|
// The Aveta drilldown no longer sends `docs=No` (completion-doc status), so
|
|
|
|
|
// no completion-document filter is applied.
|
|
|
|
|
expect(filters?.docs.size).toBe(0);
|
2026-09-16 19:37:15 -03:00
|
|
|
expect(filters?.statuses).not.toBeNull();
|
|
|
|
|
expect([...(filters?.statuses ?? [])]).not.toContain("Completed");
|
|
|
|
|
expect([...(filters?.statuses ?? [])]).not.toContain("NotAStatus");
|
|
|
|
|
expect(filters?.statuses.has("Scheduled")).toBe(true);
|
|
|
|
|
expect(filters?.statuses.has("Pending Quote")).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("parses dispatcher and uplift params", () => {
|
|
|
|
|
const filters = parseDashboardDrilldownParams(
|
|
|
|
|
new URLSearchParams({
|
|
|
|
|
dispatchers: " 12 , 13",
|
|
|
|
|
upliftStatuses: "pending,bogus",
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(filters?.dispatchers).toEqual(new Set(["12", "13"]));
|
|
|
|
|
expect(filters?.hasUplift).toBe(true);
|
|
|
|
|
expect(filters?.upliftStatuses).toEqual(new Set(["pending"]));
|
|
|
|
|
});
|
|
|
|
|
|
feat(dashboard): dispatcher scope picker, status distribution, region drilldown, perf counts
- SH-336: add the dispatcher-scope picker (All / My WOs / individual) for
Scheduler/Admin via a new viewAllDispatchersOnDashboard role gate, a static
'My WOs' label for Dispatcher, and hide the dispatcher tables (and skip their
queries) when the viewer cannot see all dispatchers. Scope drives dispatcherId
on stats, workload, performance, and regions (SH-347 contract).
- SH-352: add the Status Distribution module with its empty state and status
drill-down.
- SH-347: show Assigned/Completed counts and colour the completion rate
(green >=90, amber 70-89, red <70) on Dispatcher Performance.
- SH-348: drill a region bar into the board region filter; hold the
unconfirmed 'Unmapped/Other' bucket off the chart.
- SH-349: label Vendor Insights as all-time, company-wide.
- Make the drilldown-filters effect fire only on URL change; add a deterministic
clock to the status-drilldown test; add the dashboard visual regression spec
and baselines.
2026-09-17 02:56:31 -03:00
|
|
|
it("parses the region drilldown and drops unknown region names", () => {
|
|
|
|
|
const filters = parseDashboardDrilldownParams(
|
|
|
|
|
new URLSearchParams({ regions: "California, Atlantis" }),
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect(filters?.regions).toEqual(new Set(["California"]));
|
|
|
|
|
});
|
|
|
|
|
|
2026-09-24 21:09:04 -03:00
|
|
|
it("turns the Unassigned tile link into the board search the Dashboard counts (SH-392)", () => {
|
|
|
|
|
const allTime = parseDashboardDrilldownParams(unassignedDrilldownSearch("all-time"));
|
|
|
|
|
expect(allTime).not.toBeNull();
|
|
|
|
|
expect(toBoardSearchQueryParams({ filters: allTime! })).toMatchObject({
|
|
|
|
|
dispatchers: ["__unassigned__"],
|
|
|
|
|
statuses: [...NON_TERMINAL_LIFECYCLE_STATUSES],
|
|
|
|
|
datePreset: "Custom",
|
|
|
|
|
dateFrom: "2000-01-01",
|
|
|
|
|
dateTo: "2099-12-31",
|
|
|
|
|
includeDateless: true,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const week = parseDashboardDrilldownParams(unassignedDrilldownSearch("this-week"));
|
|
|
|
|
const weekParams = toBoardSearchQueryParams({ filters: week! });
|
|
|
|
|
expect(week?.dateRange).toBe("custom");
|
|
|
|
|
expect(weekParams).toMatchObject({
|
|
|
|
|
dispatchers: ["__unassigned__"],
|
|
|
|
|
statuses: [...NON_TERMINAL_LIFECYCLE_STATUSES],
|
|
|
|
|
datePreset: "Custom",
|
|
|
|
|
});
|
|
|
|
|
expect(weekParams.includeDateless).toBeUndefined();
|
|
|
|
|
});
|
|
|
|
|
|
2026-09-24 22:45:10 -03:00
|
|
|
it("opens the Unassigned tile link as the Unassigned option of Assigned to", () => {
|
|
|
|
|
const filters = parseDashboardDrilldownParams(unassignedDrilldownSearch("this-week"));
|
|
|
|
|
expect(filters?.dispatchers).toEqual(new Set([ASSIGNEE_FILTER_UNASSIGNED]));
|
|
|
|
|
|
|
|
|
|
const chips = buildAdvancedFilterChips(filters!, { dispatcherNameOf: (id) => id });
|
|
|
|
|
const assignedTo = chips.filter((chip) => chip.group === "Assigned to");
|
|
|
|
|
expect(assignedTo.map((chip) => chip.value)).toEqual(["Unassigned"]);
|
|
|
|
|
|
|
|
|
|
const cleared = assignedTo[0]!.clear(filters!);
|
|
|
|
|
expect(cleared.dispatchers.size).toBe(0);
|
|
|
|
|
});
|
|
|
|
|
|
2026-09-25 17:10:51 -03:00
|
|
|
it("turns the site open work-order link into that site's open, all-weeks board search", () => {
|
|
|
|
|
const href = buildSiteOpenWorkOrdersHref(12);
|
|
|
|
|
const filters = parseDashboardDrilldownParams(new URL(href, "https://app.test").searchParams);
|
|
|
|
|
|
|
|
|
|
expect(filters?.sites).toEqual(new Set(["12"]));
|
|
|
|
|
expect(filters?.workOrderIds.size).toBe(0);
|
|
|
|
|
const params = buildResolvedBoardSearchParams({
|
|
|
|
|
advApplied: filters,
|
|
|
|
|
search: "",
|
|
|
|
|
page: 0,
|
|
|
|
|
pageSize: 200,
|
|
|
|
|
sortBy: "",
|
|
|
|
|
sortDir: "",
|
|
|
|
|
locations: [{ id: 12, name: "DAL1", siteCode: "DAL1" }],
|
|
|
|
|
vendors: [],
|
|
|
|
|
});
|
|
|
|
|
expect(params).toMatchObject({
|
|
|
|
|
sites: ["DAL1"],
|
|
|
|
|
statuses: [...NON_TERMINAL_LIFECYCLE_STATUSES],
|
|
|
|
|
datePreset: "Custom",
|
|
|
|
|
dateFrom: "2000-01-01",
|
|
|
|
|
dateTo: "2099-12-31",
|
|
|
|
|
includeDateless: true,
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("keeps an exact id set ahead of a site filter", () => {
|
|
|
|
|
const filters = parseDashboardDrilldownParams(new URLSearchParams({ ids: "4,9", sites: "12" }));
|
|
|
|
|
|
|
|
|
|
expect(filters?.workOrderIds).toEqual(new Set([4, 9]));
|
|
|
|
|
expect(filters?.sites.size).toBe(0);
|
|
|
|
|
});
|
|
|
|
|
|
2026-09-16 19:37:15 -03:00
|
|
|
it("rejects partial or malformed date ranges", () => {
|
|
|
|
|
expect(
|
|
|
|
|
parseDashboardDrilldownParams(
|
|
|
|
|
new URLSearchParams({ dateFrom: "2026-9-16", dateTo: "2026-09-18" }),
|
|
|
|
|
),
|
|
|
|
|
).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
});
|