mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 06:53:12 +00:00
fix(workorders): applying a panel filter replaces the exact id selection
The server honours only the id set when one is sent, so a facet applied during an ids drill-down showed as an active chip while doing nothing. Applying any facet now drops the selection; applying the panel unchanged keeps it. The ids param is also capped at the server limit whatever built the set.
This commit is contained in:
parent
5f8218b09a
commit
74dd1292a4
4 changed files with 79 additions and 4 deletions
|
|
@ -6,9 +6,9 @@ import type { WOType } from "@/domain/work-orders/types/work-order-wizard";
|
|||
import type { WorkOrderBoardQueryParams } from "@/domain/work-orders/types/work-order-board";
|
||||
import {
|
||||
clearAdvancedFilterGroup,
|
||||
cloneAdvancedFilters,
|
||||
countAdvancedFilters,
|
||||
getInitialAdvancedFilters,
|
||||
resolveAppliedAdvancedFilters,
|
||||
} from "@/domain/work-orders/utils/advanced-filters-utils";
|
||||
import { toBoardQueryParams } from "@/domain/work-orders/utils/board-query-params";
|
||||
import { asDayPageSize, DEFAULT_DAY_PAGE_SIZE } from "@/domain/work-orders/utils/day-pagination";
|
||||
|
|
@ -90,7 +90,7 @@ export function useWorkOrdersListFilters() {
|
|||
};
|
||||
|
||||
const applyAdvancedFilters = (filters: WorkOrderAdvancedFilters) => {
|
||||
setAdvApplied(cloneAdvancedFilters(filters));
|
||||
setAdvApplied(resolveAppliedAdvancedFilters(filters));
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,18 @@ export function countAdvancedFilters(filters: WorkOrderAdvancedFilters): number
|
|||
return ADVANCED_FILTER_ACTIVE_SIGNALS(filters).filter(Boolean).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* The server honours only the id set when one is sent, so an id selection and panel facets never
|
||||
* combine: applying any facet replaces the selection with a normal filtered board.
|
||||
*/
|
||||
export function resolveAppliedAdvancedFilters(
|
||||
filters: WorkOrderAdvancedFilters,
|
||||
): WorkOrderAdvancedFilters {
|
||||
const next = cloneAdvancedFilters(filters);
|
||||
const withoutIds = { ...next, workOrderIds: new Set<number>() };
|
||||
return next.workOrderIds.size > 0 && countAdvancedFilters(withoutIds) > 0 ? withoutIds : next;
|
||||
}
|
||||
|
||||
function monthStart(y: number, m: number): string {
|
||||
return `${y}-${pad2(m + 1)}-01`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
} from "@/domain/work-orders/types/work-order-board";
|
||||
import { mapWizardTypeToApi } from "@/domain/work-orders/mappers/work-order-board-mapper";
|
||||
import { weekRange } from "@/domain/work-orders/utils/list-week-utils";
|
||||
import { normalizeWorkOrderIds } from "@/domain/work-orders/utils/work-order-ids-filter";
|
||||
import type { CompletionDocFilter } from "@/domain/work-orders/types/work-order-advanced-filters";
|
||||
import type { WizardWOStatus, WOType } from "@/domain/work-orders/types/work-order-wizard";
|
||||
|
||||
|
|
@ -135,7 +136,9 @@ function mapSearchFacetParams(
|
|||
|
||||
/** Sent as one comma-separated `ids` value; the server then ignores every other filter. */
|
||||
function mapWorkOrderIds(ids: Set<number> | undefined): string | undefined {
|
||||
return ids?.size ? [...ids].join(",") : undefined;
|
||||
// Normalized here too, so a caller that fills the set directly still stays within the server's limit.
|
||||
const normalized = ids?.size ? normalizeWorkOrderIds(ids) : [];
|
||||
return normalized.length > 0 ? normalized.join(",") : undefined;
|
||||
}
|
||||
|
||||
/** Build GET /board query from weekly filter bar. */
|
||||
|
|
|
|||
|
|
@ -6,8 +6,13 @@ import { WorkOrdersAdvancedFiltersBanner } from "@/app/(protected)/workorders/_c
|
|||
import { buildResolvedBoardSearchParams } from "@/app/(protected)/workorders/_hooks/build-resolved-board-search-params";
|
||||
import { useDashboardDrilldownFilters } from "@/app/(protected)/workorders/_hooks/use-dashboard-drilldown-filters";
|
||||
import { useWorkOrdersListFilters } from "@/app/(protected)/workorders/_hooks/use-work-orders-list-filters";
|
||||
import { EMPTY_ADVANCED_FILTERS } from "@/domain/work-orders/types/work-order-advanced-filters";
|
||||
import { cloneAdvancedFilters } from "@/domain/work-orders/utils/advanced-filters-utils";
|
||||
import { buildBoardSearchParamsRecord } from "@/domain/work-orders/utils/board-query-params";
|
||||
import { buildWorkOrderIdsHref } from "@/domain/work-orders/utils/work-order-ids-filter";
|
||||
import {
|
||||
buildWorkOrderIdsHref,
|
||||
WORK_ORDER_IDS_MAX,
|
||||
} from "@/domain/work-orders/utils/work-order-ids-filter";
|
||||
import { renderWithProviders } from "@/test/test-utils";
|
||||
|
||||
/** The Work Orders page's filter wiring, without the table: URL → applied filters → board/search params. */
|
||||
|
|
@ -32,6 +37,15 @@ function BoardFilterHarness() {
|
|||
{params == null ? "weekly board" : JSON.stringify(buildBoardSearchParamsRecord(params))}
|
||||
</output>
|
||||
<output aria-label="Current search">{location.search}</output>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => filters.applyAdvancedFilters({ ...filters.advInitial, rescheduled: true })}
|
||||
>
|
||||
Apply rescheduled only
|
||||
</button>
|
||||
<button type="button" onClick={() => filters.applyAdvancedFilters(filters.advInitial)}>
|
||||
Apply unchanged
|
||||
</button>
|
||||
{filters.advApplied != null && (
|
||||
<WorkOrdersAdvancedFiltersBanner
|
||||
applied={filters.advApplied}
|
||||
|
|
@ -85,6 +99,52 @@ describe("Work Orders exact id filter", () => {
|
|||
expect(screen.queryByText(/selected work order/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("replaces the selection when a panel filter is applied, so no chip shows a filter the server ignores", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<BoardFilterHarness />, {
|
||||
route: "/workorders?ids=4,9",
|
||||
withAuth: false,
|
||||
});
|
||||
|
||||
expect(await screen.findByText("Showing 2 selected work orders")).toBeInTheDocument();
|
||||
await user.click(screen.getByRole("button", { name: "Apply rescheduled only" }));
|
||||
|
||||
const params = searchParams() as Record<string, unknown>;
|
||||
expect(params.ids).toBeUndefined();
|
||||
expect(params.rescheduled).toBe(true);
|
||||
expect(screen.queryByText(/selected work order/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps the selection when the panel is applied without a filter", async () => {
|
||||
const user = userEvent.setup();
|
||||
renderWithProviders(<BoardFilterHarness />, {
|
||||
route: "/workorders?ids=4,9",
|
||||
withAuth: false,
|
||||
});
|
||||
|
||||
expect(await screen.findByText("Showing 2 selected work orders")).toBeInTheDocument();
|
||||
await user.click(screen.getByRole("button", { name: "Apply unchanged" }));
|
||||
|
||||
expect((searchParams() as Record<string, unknown>).ids).toBe("4,9");
|
||||
expect(screen.getByText("Showing 2 selected work orders")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("never sends more ids than the server accepts, however the selection was built", () => {
|
||||
const ids = new Set(Array.from({ length: WORK_ORDER_IDS_MAX + 30 }, (_, index) => index + 1));
|
||||
const params = buildResolvedBoardSearchParams({
|
||||
advApplied: { ...cloneAdvancedFilters(EMPTY_ADVANCED_FILTERS), workOrderIds: ids },
|
||||
search: "",
|
||||
page: 0,
|
||||
pageSize: 200,
|
||||
sortBy: "",
|
||||
sortDir: "",
|
||||
locations: [],
|
||||
vendors: [],
|
||||
});
|
||||
|
||||
expect(params?.ids?.split(",")).toHaveLength(WORK_ORDER_IDS_MAX);
|
||||
});
|
||||
|
||||
it("ignores other drill-down params sent alongside the id set", async () => {
|
||||
renderWithProviders(<BoardFilterHarness />, {
|
||||
route: "/workorders?ids=5&statuses=Scheduled&dispatchers=u1",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue