fix(work-orders): search all weeks when advanced filters are applied

This commit is contained in:
arthur.bassi 2026-08-18 17:40:47 -03:00
parent 7902476e2a
commit bea2858c81
8 changed files with 92 additions and 27 deletions

View file

@ -14,6 +14,7 @@ type DispatcherFilterProps = {
users: WorkOrderUser[];
usersLoading?: boolean;
currentUserId?: string | number;
disabled?: boolean;
};
function userInitials(name: string): string {
@ -40,6 +41,7 @@ export function DispatcherFilter({
users,
usersLoading = false,
currentUserId,
disabled = false,
}: DispatcherFilterProps) {
const { anchorEl, open, setAnchor, close } = useMenuAnchor();
@ -86,6 +88,7 @@ export function DispatcherFilter({
<MenuAnchorButton
onAnchor={setAnchor}
open={open}
disabled={disabled}
className="flex w-auto shrink-0 items-center gap-2 border whitespace-nowrap transition-colors"
style={
!allSelected

View file

@ -16,6 +16,8 @@ type WeekNavProps = {
weekMonday: string;
onChange: (mondayIso: string) => void;
disabled?: boolean;
};
const VISIBLE_COUNT = 3;
@ -51,7 +53,7 @@ function WeekLiveBadge() {
);
}
export function WeekNav({ weekMonday, onChange }: WeekNavProps) {
export function WeekNav({ weekMonday, onChange, disabled = false }: WeekNavProps) {
const weeks = useMemo(
() => buildWeekWindow(weekMonday, WINDOW_BEFORE, WINDOW_AFTER),
@ -91,7 +93,7 @@ export function WeekNav({ weekMonday, onChange }: WeekNavProps) {
return (
<div className="flex shrink-0 items-center gap-1.5">
<NavArrowButton
disabled={!canGoBack}
disabled={disabled || !canGoBack}
onClick={() => setOffset((current) => current - 1)}
aria-label="Previous weeks"
>
@ -109,7 +111,7 @@ export function WeekNav({ weekMonday, onChange }: WeekNavProps) {
<NavPillButton
key={week.mondayIso}
active={isActive}
disabled={isDisabled}
disabled={disabled || isDisabled}
onClick={() => onChange(week.mondayIso)}
>
{week.label}
@ -120,7 +122,7 @@ export function WeekNav({ weekMonday, onChange }: WeekNavProps) {
})}
<NavArrowButton
disabled={!canGoForward}
disabled={disabled || !canGoForward}
onClick={() => setOffset((current) => current + 1)}
aria-label="Next weeks"
>

View file

@ -8,15 +8,22 @@ type WoTypeSegmentProps = {
value: WorkOrderTypeFilter;
onChange: (value: WorkOrderTypeFilter) => void;
disabled?: boolean;
};
const OPTIONS: WorkOrderTypeFilter[] = ["All", ...WO_TYPES];
export function WoTypeSegment({ value, onChange }: WoTypeSegmentProps) {
export function WoTypeSegment({ value, onChange, disabled = false }: WoTypeSegmentProps) {
return (
<SegmentGroup>
{OPTIONS.map((option) => (
<SegmentButton key={option} active={value === option} onClick={() => onChange(option)}>
<SegmentButton
key={option}
active={value === option}
disabled={disabled}
onClick={() => onChange(option)}
>
{option}
</SegmentButton>
))}

View file

@ -16,6 +16,7 @@ type WorkOrdersFilterBarProps = {
currentUserId?: string | number;
filteredCount: number;
scopeTotalCount: number;
disabled?: boolean;
};
export function WorkOrdersFilterBar({
@ -30,20 +31,25 @@ export function WorkOrdersFilterBar({
currentUserId,
filteredCount,
scopeTotalCount,
disabled = false,
}: WorkOrdersFilterBarProps) {
const resultLabel = `${filteredCount} of ${scopeTotalCount}`;
return (
<div className="-mx-2.5 flex h-14 shrink-0 items-center gap-3 border-b border-(--color-border) bg-(--color-bg-surface) px-4">
<div
className="-mx-2.5 flex h-14 shrink-0 items-center gap-3 border-b border-(--color-border) bg-(--color-bg-surface) px-4"
aria-disabled={disabled}
>
<DispatcherFilter
selectedIds={dispatcherIds}
onChange={onDispatcherChange}
users={users}
usersLoading={usersLoading}
currentUserId={currentUserId}
disabled={disabled}
/>
<WeekNav weekMonday={weekMonday} onChange={onWeekChange} />
<WoTypeSegment value={typeFilter} onChange={onTypeChange} />
<WeekNav weekMonday={weekMonday} onChange={onWeekChange} disabled={disabled} />
<WoTypeSegment value={typeFilter} onChange={onTypeChange} disabled={disabled} />
<div className="flex-1" />
<span className="shrink-0 text-xs font-medium text-muted-foreground">{resultLabel}</span>
</div>

View file

@ -49,6 +49,7 @@ export default function WorkOrdersListPage() {
currentUserId={user?.id}
filteredCount={tableData.totalCount}
scopeTotalCount={tableData.scopeTotalCount}
disabled={filters.advApplied != null}
/>
{filters.advApplied != null && (

View file

@ -1,5 +1,8 @@
import { ASSIGNEE_FILTER_UNASSIGNED } from "@/domain/work-orders/types/work-order-list";
import type { WorkOrderAdvancedFilters } from "@/domain/work-orders/types/work-order-advanced-filters";
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,
@ -16,6 +19,10 @@ import type { WizardWOStatus, WOType } from "@/domain/work-orders/types/work-ord
export const BOARD_SEARCH_MAX_PAGE_SIZE = 100;
/** 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";
function toBoardDispatcherId(id: string): string {
if (id === ASSIGNEE_FILTER_UNASSIGNED || id === BOARD_DISPATCHER_UNASSIGNED) {
return BOARD_DISPATCHER_UNASSIGNED;
@ -136,6 +143,31 @@ export function toBoardQueryParams(
};
}
function resolveAdvancedSearchDate(filters: WorkOrderAdvancedFilters): {
datePreset: WorkOrderBoardSearchQueryParams["datePreset"];
dateFrom?: string;
dateTo?: string;
} {
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;
@ -148,7 +180,7 @@ export function toBoardSearchQueryParams(input: {
vendorIdsByCompanyName?: Map<string, number>;
}): WorkOrderBoardSearchQueryParams {
const { filters } = input;
const datePreset = DATE_RANGE_TO_PRESET[filters.dateRange];
const date = resolveAdvancedSearchDate(filters);
const pageSize = Math.min(
input.pageSize ?? BOARD_SEARCH_MAX_PAGE_SIZE,
BOARD_SEARCH_MAX_PAGE_SIZE,
@ -158,11 +190,13 @@ export function toBoardSearchQueryParams(input: {
const trimmedSearch = input.search?.trim();
const search = trimmedSearch && trimmedSearch.length >= 2 ? trimmedSearch : undefined;
const params: WorkOrderBoardSearchQueryParams = {
return {
page: input.page ?? 0,
pageSize,
search,
datePreset,
datePreset: date.datePreset,
dateFrom: date.dateFrom,
dateTo: date.dateTo,
sites: filters.sites.size > 0 ? [...filters.sites] : undefined,
types: typeFilters.types,
overdue: typeFilters.overdue,
@ -175,13 +209,6 @@ export function toBoardSearchQueryParams(input: {
sortBy: input.sortBy || "scheduledDate",
sortDir: input.sortDir === "desc" ? "desc" : "asc",
};
if (datePreset === "Custom") {
params.dateFrom = filters.customFrom || undefined;
params.dateTo = filters.customTo || undefined;
}
return params;
}
/** ky/URLSearchParams-friendly object; arrays become repeated keys. */

View file

@ -106,10 +106,6 @@ vi.mock("@/app/(protected)/workorders/_components/list/table/wo-table", () => ({
WoTable: () => null,
}));
vi.mock("@/app/(protected)/workorders/_components/list/filter-bar/work-orders-filter-bar", () => ({
WorkOrdersFilterBar: () => null,
}));
vi.mock("@/app/(protected)/workorders/_components/media/media-upload-modal", () => ({
MediaUploadModal: () => null,
}));
@ -196,6 +192,9 @@ describe("WorkOrders Advanced Search page wiring (SH-121)", () => {
expect(params.severities).toEqual([1]);
expect(params.hasUplift).toBe(true);
expect(params.upliftStatuses).toEqual(["pending"]);
expect(params.datePreset).toBe("Custom");
expect(params.dateFrom).toBe("2000-01-01");
expect(params.dateTo).toBe("2099-12-31");
});
it("applies Indicators, Flag, and Internal WO# through the public Advanced Filters sheet", () => {
@ -239,5 +238,22 @@ describe("WorkOrders Advanced Search page wiring (SH-121)", () => {
expect(params.addOn).toBe(true);
expect(params.flagColors).toEqual(["#EF4444"]);
expect(params.internalOnly).toBe(true);
expect(params.datePreset).toBe("Custom");
expect(params.dateFrom).toBe("2000-01-01");
expect(params.dateTo).toBe("2099-12-31");
});
it("disables dispatcher, week, and type bar while advanced filters are applied", () => {
advApplied = { ...EMPTY_ADVANCED_FILTERS, rescheduled: true };
renderWithProviders(<WorkOrdersListPage />, {
route: "/workorders",
withAuth: false,
});
expect(screen.getByRole("button", { name: "All dispatchers" })).toBeDisabled();
expect(screen.getByRole("button", { name: "Previous weeks" })).toBeDisabled();
expect(screen.getByRole("button", { name: "Next weeks" })).toBeDisabled();
expect(screen.getByRole("button", { name: "All" })).toBeDisabled();
expect(screen.getByRole("button", { name: "PM" })).toBeDisabled();
});
});

View file

@ -65,7 +65,9 @@ describe("toBoardSearchQueryParams", () => {
});
expect(params.page).toBe(0);
expect(params.pageSize).toBe(BOARD_SEARCH_MAX_PAGE_SIZE);
expect(params.datePreset).toBe("ThisWeek");
expect(params.datePreset).toBe("Custom");
expect(params.dateFrom).toBe("2000-01-01");
expect(params.dateTo).toBe("2099-12-31");
});
it("maps date presets to PascalCase API values", () => {
@ -96,8 +98,9 @@ describe("toBoardSearchQueryParams", () => {
customTo: "2026-06-30",
},
});
expect(thisWeek.dateFrom).toBeUndefined();
expect(thisWeek.dateTo).toBeUndefined();
expect(thisWeek.datePreset).toBe("Custom");
expect(thisWeek.dateFrom).toBe("2000-01-01");
expect(thisWeek.dateTo).toBe("2099-12-31");
});
it("maps Overdue-only advanced filter to overdue=true without types", () => {