shoc-frontend-new/src/domain/work-orders/utils/board-query-params.ts
Arthur Bassi fc3e7591dc
Some checks are pending
Frontend checks / Build and test (push) Waiting to run
Frontend checks / governance (push) Waiting to run
Frontend checks / Visual regression (push) Waiting to run
Deploy dev content / Deploy shoc-frontend-new-dev through Terraform (push) Waiting to run
fix(work-orders): add Unscheduled option to Advanced Filters Date range (#181)
* fix(work-orders): add Unscheduled option to Advanced Filters Date range

* fix(work-orders): page unscheduled results server-side

---------

Co-authored-by: Codex Review Integration <codex-review@local.invalid>
Co-authored-by: Alexandre Brandizzi <alex_brandizzi@hotmail.com>
2026-09-15 19:11:35 -03:00

270 lines
8.7 KiB
TypeScript

import { ASSIGNEE_FILTER_UNASSIGNED } from "@/domain/work-orders/types/work-order-list";
import {
EMPTY_ADVANCED_FILTERS,
type WorkOrderAdvancedFilters,
} from "@/domain/work-orders/types/work-order-advanced-filters";
import type { WorkOrderBarFilters } from "@/domain/work-orders/types/work-order-list-filters";
import {
BOARD_DISPATCHER_UNASSIGNED,
DATE_RANGE_TO_PRESET,
DOC_STATUS_API,
LIFECYCLE_STATUS_API,
NON_TERMINAL_LIFECYCLE_STATUSES,
type WorkOrderBoardQueryParams,
type WorkOrderBoardSearchQueryParams,
} 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 type { CompletionDocFilter } from "@/domain/work-orders/types/work-order-advanced-filters";
import type { WizardWOStatus, WOType } from "@/domain/work-orders/types/work-order-wizard";
export const BOARD_SEARCH_MAX_PAGE_SIZE = 200;
/** Wide Custom window so applied advanced search is not pinned to ThisWeek. */
export const ADVANCED_SEARCH_ALL_WEEKS_FROM = "2000-01-01";
export const ADVANCED_SEARCH_ALL_WEEKS_TO = "2099-12-31";
/**
* Wide Custom window for SH-165. GET /board/search defaults omitted
* `datePreset` to ThisWeek (`WorkOrderSearchDateRangeResolver`), which would
* hide scheduled-unassigned WOs outside the current week.
*/
export const UNASSIGNED_QUEUE_DATE_FROM = "1970-01-01";
export const UNASSIGNED_QUEUE_DATE_TO = "2099-12-31";
function toBoardDispatcherId(id: string): string {
if (id === ASSIGNEE_FILTER_UNASSIGNED || id === BOARD_DISPATCHER_UNASSIGNED) {
return BOARD_DISPATCHER_UNASSIGNED;
}
return id;
}
function mapDispatchers(ids: Iterable<string>): string[] | undefined {
const list = [...ids].map(toBoardDispatcherId);
return list.length > 0 ? list : undefined;
}
/** Maps UI type filters to API `types` + separate `overdue` flag. Overdue is never sent as types=99. */
function mapWoTypeFilters(types: Iterable<WOType | string>): {
types?: number[];
overdue?: boolean;
} {
const list: number[] = [];
let overdue = false;
for (const type of types) {
if (type === "All") {
continue;
}
if (type === "Overdue") {
overdue = true;
continue;
}
const api = mapWizardTypeToApi(type as WOType);
if (api != null) {
list.push(api);
}
}
return {
types: list.length > 0 ? list : undefined,
overdue: overdue || undefined,
};
}
function mapStatuses(statuses: Iterable<WizardWOStatus | string>): number[] | undefined {
const list: number[] = [];
for (const status of statuses) {
const value = LIFECYCLE_STATUS_API[status as WizardWOStatus];
if (typeof value === "number") {
list.push(value);
} else if (status === "Canceled" || status === "Cancelled") {
list.push(LIFECYCLE_STATUS_API.Canceled);
}
}
return list.length > 0 ? list : undefined;
}
function mapDocStatuses(docs: Iterable<CompletionDocFilter>): number[] | undefined {
const list: number[] = [];
for (const doc of docs) {
if (doc === "Yes") list.push(DOC_STATUS_API.Yes);
else if (doc === "No") list.push(DOC_STATUS_API.No);
else if (doc === "NN") list.push(DOC_STATUS_API.NotNeeded);
}
return list.length > 0 ? list : undefined;
}
function resolveVendorIds(
vendorTechs: Iterable<string>,
vendorIdsByCompanyName?: Map<string, number>,
): number[] | undefined {
if (!vendorIdsByCompanyName) return undefined;
const vendorIds: number[] = [];
for (const name of vendorTechs) {
const id = vendorIdsByCompanyName.get(name);
if (typeof id === "number") {
vendorIds.push(id);
}
}
return vendorIds.length > 0 ? vendorIds : undefined;
}
function mapSearchFacetParams(
filters: WorkOrderAdvancedFilters,
): Pick<
WorkOrderBoardSearchQueryParams,
| "severities"
| "rescheduled"
| "carriedOver"
| "addOn"
| "avetaOnly"
| "flagColors"
| "internalOnly"
| "hasUplift"
| "upliftStatuses"
> {
return {
severities: filters.severities?.size ? [...filters.severities] : undefined,
rescheduled: filters.rescheduled || undefined,
carriedOver: filters.carriedOver || undefined,
addOn: filters.addOn || undefined,
avetaOnly: filters.avetaOnly || undefined,
flagColors: filters.flagColors?.size ? [...filters.flagColors] : undefined,
internalOnly: filters.internalOnly || undefined,
hasUplift: filters.hasUplift || undefined,
upliftStatuses:
filters.hasUplift && filters.upliftStatuses?.size ? [...filters.upliftStatuses] : undefined,
};
}
/** Build GET /board query from weekly filter bar. */
export function toBoardQueryParams(
bar: WorkOrderBarFilters,
search: string,
): WorkOrderBoardQueryParams {
const [, weekTo] = weekRange(bar.weekMonday);
const typeFilters = bar.types.size > 0 ? mapWoTypeFilters(bar.types) : {};
return {
weekStart: bar.weekMonday,
weekEnd: weekTo,
dispatchers: mapDispatchers(bar.dispatcherIds),
types: typeFilters.types,
overdue: typeFilters.overdue,
search: search.trim().length >= 2 ? search.trim() : undefined,
};
}
function resolveAdvancedSearchDate(filters: WorkOrderAdvancedFilters): {
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 {};
}
if (filters.dateRange === EMPTY_ADVANCED_FILTERS.dateRange) {
return {
datePreset: "Custom",
dateFrom: ADVANCED_SEARCH_ALL_WEEKS_FROM,
dateTo: ADVANCED_SEARCH_ALL_WEEKS_TO,
};
}
const datePreset = DATE_RANGE_TO_PRESET[filters.dateRange];
if (datePreset === "Custom") {
return {
datePreset,
dateFrom: filters.customFrom || undefined,
dateTo: filters.customTo || undefined,
};
}
return { datePreset };
}
/** Build GET /board/search query from advanced filters (+ optional bar/search). */
export function toBoardSearchQueryParams(input: {
filters: WorkOrderAdvancedFilters;
search?: string;
page?: number;
pageSize?: number;
sortBy?: string;
sortDir?: string;
/** Optional company-name → vendorId resolution. */
vendorIdsByCompanyName?: Map<string, number>;
}): WorkOrderBoardSearchQueryParams {
const { filters } = input;
const date = resolveAdvancedSearchDate(filters);
const pageSize = Math.min(
input.pageSize ?? BOARD_SEARCH_MAX_PAGE_SIZE,
BOARD_SEARCH_MAX_PAGE_SIZE,
);
const typeFilters = mapWoTypeFilters(filters.types);
const trimmedSearch = input.search?.trim();
const search = trimmedSearch && trimmedSearch.length >= 2 ? trimmedSearch : undefined;
return {
page: input.page ?? 0,
pageSize,
search,
datePreset: date.datePreset,
dateFrom: date.dateFrom,
dateTo: date.dateTo,
sites: filters.sites.size > 0 ? [...filters.sites] : undefined,
types: typeFilters.types,
overdue: typeFilters.overdue,
dispatchers: mapDispatchers(filters.dispatchers),
statuses: mapStatuses(filters.statuses),
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",
};
}
/** ky/URLSearchParams-friendly object; arrays become repeated keys. */
export function buildBoardSearchParamsRecord(
params: WorkOrderBoardQueryParams | WorkOrderBoardSearchQueryParams,
): Record<string, string | number | boolean | (string | number)[]> {
const out: Record<string, string | number | boolean | (string | number)[]> = {};
for (const [key, value] of Object.entries(params)) {
if (value === undefined || value === null || value === "") {
continue;
}
if (Array.isArray(value)) {
if (value.length === 0) continue;
out[key] = value;
continue;
}
out[key] = value as string | number | boolean;
}
return out;
}
export function woTypeFilterIsOverdue(type: string): boolean {
return type === "Overdue";
}
/** Week-independent GET /board/search params for the pinned Unassigned queue. */
export function toUnassignedQueueQueryParams(
page = 0,
pageSize = BOARD_SEARCH_MAX_PAGE_SIZE,
): WorkOrderBoardSearchQueryParams {
return {
page,
pageSize: Math.min(pageSize || BOARD_SEARCH_MAX_PAGE_SIZE, BOARD_SEARCH_MAX_PAGE_SIZE),
dispatchers: [BOARD_DISPATCHER_UNASSIGNED],
statuses: [...NON_TERMINAL_LIFECYCLE_STATUSES],
datePreset: "Custom",
dateFrom: UNASSIGNED_QUEUE_DATE_FROM,
dateTo: UNASSIGNED_QUEUE_DATE_TO,
};
}