fix(sites): open the site's board when open work orders exceed the id cap

The delete dialog states the server's full open work-order count, but the
server returns at most 200 ids. "View open work orders" navigated with
that capped list, so a site with 240 open work orders showed 200, and an
empty list opened the unfiltered board.

The exact-id link is now used only when the ids cover the whole count.
Otherwise the link opens Work Orders filtered to the site and every open
status across all weeks, the ticket's "board filtered to that site". The
board drilldown now reads a `sites` param for this; `ids` still wins.
This commit is contained in:
Alexandre Brandizzi 2026-09-25 17:10:51 -03:00
parent b53d91b1da
commit 2281c7c82d
4 changed files with 96 additions and 5 deletions

View file

@ -11,6 +11,7 @@ import {
import { DIALOG_BLUR_BACKDROP_SX } from "@/components/ui/dialog-blur-backdrop";
import { useDeleteLocation } from "@/domain/locations/use-cases/use-delete-location";
import { useSiteOpenWorkOrders } from "@/domain/locations/use-cases/use-site-open-work-orders";
import { buildSiteOpenWorkOrdersHref } from "@/domain/work-orders/utils/dashboard-drilldown-params";
import { buildWorkOrderIdsHref } from "@/domain/work-orders/utils/work-order-ids-filter";
interface SiteDeleteDialogProps {
@ -52,6 +53,12 @@ export function SiteDeleteDialog({
const openCount = openWorkOrders?.count ?? 0;
// Without the count the warning cannot be shown, so Delete waits for a successful check.
const countUnknown = isLoading || isError;
// The server caps the id list; when it doesn't cover the stated count, open the site's whole board.
const openIds = openWorkOrders?.workOrderIds ?? [];
const openWorkOrdersHref =
openIds.length > 0 && openIds.length >= openCount
? buildWorkOrderIdsHref(openIds)
: buildSiteOpenWorkOrdersHref(siteId);
return (
<Dialog
@ -82,10 +89,7 @@ export function SiteDeleteDialog({
</Button>
)}
{openCount > 0 && (
<Button
variant="outlined"
onClick={() => navigate(buildWorkOrderIdsHref(openWorkOrders?.workOrderIds ?? []))}
>
<Button variant="outlined" onClick={() => navigate(openWorkOrdersHref)}>
View open work orders
</Button>
)}

View file

@ -61,6 +61,18 @@ function parseUpliftStatuses(raw: string | null): UpliftFilterStatus[] {
);
}
/** Open = every lifecycle status except Completed (Canceled is not a board status). */
const OPEN_STATUSES = ALL_WIZARD_STATUSES.filter((status) => status !== "Completed");
/** Work Orders filtered to one site's open work orders; `sites` takes the location id. */
export function buildSiteOpenWorkOrdersHref(siteId: string | number): string {
const params = new URLSearchParams({
sites: String(siteId),
statuses: OPEN_STATUSES.join(","),
});
return `/workorders?${params.toString()}`;
}
export function parseDashboardDrilldownParams(
params: URLSearchParams,
): WorkOrderAdvancedFilters | null {
@ -79,6 +91,7 @@ export function parseDashboardDrilldownParams(
const types = parseTypes(params.get("types"));
const dispatchers = parseList(params.get("dispatchers"));
const regions = parseRegions(params.get("regions"));
const sites = [...new Set(parseList(params.get("sites")))];
const upliftStatuses = parseUpliftStatuses(params.get("upliftStatuses"));
const hasUplift = params.get("hasUplift") === "1";
@ -90,6 +103,7 @@ export function parseDashboardDrilldownParams(
types.length,
dispatchers.length,
regions.length,
sites.length,
upliftStatuses.length,
hasUplift,
].some(Boolean);
@ -122,6 +136,9 @@ export function parseDashboardDrilldownParams(
if (regions.length > 0) {
filters.regions = new Set(regions);
}
if (sites.length > 0) {
filters.sites = new Set(sites);
}
if (upliftStatuses.length > 0) {
filters.upliftStatuses = new Set(upliftStatuses);
}

View file

@ -147,6 +147,39 @@ describe("Delete a site from the detail panel", () => {
expect(deleteSite).not.toHaveBeenCalled();
});
it.each([
["more open work orders than the capped id list", { count: 240, workOrderIds: [101, 205] }],
["an open count with no ids", { count: 4, workOrderIds: [] }],
])("links to the site's open work-order board when there are %s", async (_label, result) => {
getOpenWorkOrders.mockResolvedValue(result);
renderAs("Admin");
const panel = await openPanel();
fireEvent.click(within(panel).getByRole("button", { name: "Delete" }));
const dialog = await screen.findByRole("dialog", { name: "Delete this site?" });
await within(dialog).findByText(new RegExp(`It still has ${result.count} open work orders`));
fireEvent.click(within(dialog).getByRole("button", { name: "View open work orders" }));
const url = new URL(
(await screen.findByTestId("work-orders-url")).textContent ?? "",
"https://app.test",
);
expect(url.pathname).toBe("/workorders");
expect(url.searchParams.get("ids")).toBeNull();
expect(url.searchParams.get("sites")).toBe("1");
expect(url.searchParams.get("statuses")?.split(",")).toEqual([
"Incomplete",
"Pending",
"Scheduled",
"En Route",
"On Site",
"In Progress",
"Rescheduled",
"Pending Quote",
]);
});
it("confirms without a warning when nothing is open, deletes and closes the panel", async () => {
getOpenWorkOrders.mockResolvedValue({ count: 0, workOrderIds: [] });
deleteSite.mockResolvedValue(undefined);

View file

@ -1,5 +1,9 @@
import { describe, expect, it } from "vitest";
import { parseDashboardDrilldownParams } from "@/domain/work-orders/utils/dashboard-drilldown-params";
import { buildResolvedBoardSearchParams } from "@/app/(protected)/workorders/_hooks/build-resolved-board-search-params";
import {
buildSiteOpenWorkOrdersHref,
parseDashboardDrilldownParams,
} from "@/domain/work-orders/utils/dashboard-drilldown-params";
import {
avetaPendingDrilldownSearch,
unassignedDrilldownSearch,
@ -102,6 +106,39 @@ describe("parseDashboardDrilldownParams", () => {
expect(cleared.dispatchers.size).toBe(0);
});
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);
});
it("rejects partial or malformed date ranges", () => {
expect(
parseDashboardDrilldownParams(