Merge branch 'dev' into fix/ab/sh-320-inline-sh-prefix-wo-number

This commit is contained in:
Alexandre Brandizzi 2026-09-16 19:46:22 -03:00 • committed by GitHub
commit ace66f52e5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 165 additions and 9 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 99 KiB

View file

@ -5,31 +5,49 @@ import { ToolbarButton } from "@/components/ui/toolbar-button";
type WorkOrdersListHeaderProps = {
search: string;
/** Compact label of the active board week (e.g. "5/4–5/8"). */
weekLabel: string;
onSearchChange: (value: string) => void;
/** Omit until Advanced Search slice mounts the sheet. */
onOpenAdvancedFilters?: () => void;
/** Omit until wizard shell slice mounts New WO. */
onNewWorkOrder?: () => void;
activeFilterCount: number;
/** True while Advanced Filters are applied; results are global and the keyword box is inert. */
advancedFiltersActive?: boolean;
isFetching?: boolean;
};
export function WorkOrdersListHeader({
search,
weekLabel,
onSearchChange,
onOpenAdvancedFilters,
onNewWorkOrder,
activeFilterCount,
advancedFiltersActive = false,
isFetching = false,
}: WorkOrdersListHeaderProps) {
return (
<>
<SearchInput
value={search}
onChange={onSearchChange}
placeholder="Search this week…"
aria-label="Search work orders"
/>
<div className="relative">
<SearchInput
value={search}
onChange={onSearchChange}
placeholder="Search this week…"
aria-label="Search work orders"
/>
{/* Keyword search is scoped to the active week; make that scope visible. */}
{!advancedFiltersActive && search.trim() !== "" && Boolean(weekLabel) && (
<span
role="status"
className="absolute left-0 whitespace-nowrap text-(length:--text-xs) text-muted-foreground"
style={{ top: "calc(100% + 4px)" }}
>
Searching within {weekLabel}
</span>
)}
</div>
{Boolean(onOpenAdvancedFilters) && (
<ToolbarButton

View file

@ -33,7 +33,8 @@ export function useWorkOrdersBoardSource(filters: FiltersReturn): WorkOrdersBoar
() =>
buildResolvedBoardSearchParams({
advApplied: filters.advApplied,
search: filters.search,
// Advanced Filters are global by design; the week-scoped keyword box has no effect.
search: "",
page: filters.page,
pageSize: filters.pageSize,
sortBy: filters.sortBy,
@ -43,7 +44,6 @@ export function useWorkOrdersBoardSource(filters: FiltersReturn): WorkOrdersBoar
}),
[
filters.advApplied,
filters.search,
filters.page,
filters.pageSize,
filters.sortBy,

View file

@ -43,10 +43,12 @@ export default function WorkOrdersListPage() {
actions={
<WorkOrdersListHeader
search={filters.search}
weekLabel={filters.weekLabel}
onSearchChange={filters.handleSearchChange}
onOpenAdvancedFilters={() => filters.setAdvOpen(true)}
onNewWorkOrder={() => page.setWizardOpen(true)}
activeFilterCount={filters.activeFilterCount}
advancedFiltersActive={filters.advApplied != null}
isFetching={tableData.isFetching && !tableData.isLoading}
/>
}

View file

@ -189,7 +189,12 @@ vi.mock("@/app/(protected)/workorders/_components/list/table/wo-table", () => ({
describe("WorkOrdersListHeader slice affordances", () => {
it("hides New WO and Advanced Filters when callbacks are omitted", () => {
renderWithProviders(
<WorkOrdersListHeader search="" onSearchChange={vi.fn()} activeFilterCount={0} />,
<WorkOrdersListHeader
search=""
weekLabel="5/4–5/8"
onSearchChange={vi.fn()}
activeFilterCount={0}
/>,
{ withAuth: false },
);
@ -202,6 +207,7 @@ describe("WorkOrdersListHeader slice affordances", () => {
renderWithProviders(
<WorkOrdersListHeader
search=""
weekLabel="5/4–5/8"
onSearchChange={vi.fn()}
onNewWorkOrder={vi.fn()}
onOpenAdvancedFilters={vi.fn()}

View file

@ -0,0 +1,130 @@
import { act, renderHook, screen, waitFor } from "@testing-library/react";
import { QueryClientProvider } from "@tanstack/react-query";
import type { ReactNode } from "react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { EMPTY_ADVANCED_FILTERS } from "@/domain/work-orders/types/work-order-advanced-filters";
import { createTestQueryClient, renderWithProviders } from "@/test/test-utils";
const { searchBoard, getBoard, emptyList } = vi.hoisted(() => ({
searchBoard: vi.fn(),
getBoard: vi.fn(),
emptyList: [] as const,
}));
vi.mock("@/hooks/use-debounce", () => ({
useDebounce: <T,>(value: T) => value,
}));
vi.mock("@/domain/work-orders/api/work-orders-api", () => ({
workOrdersApi: {
searchBoard: (...args: unknown[]) => searchBoard(...args),
getBoard: (...args: unknown[]) => getBoard(...args),
},
}));
vi.mock("@/domain/work-orders/use-cases/use-work-order-detail", () => ({
useWorkOrderLocations: () => ({ data: emptyList }),
useWorkOrderVendors: () => ({ data: emptyList }),
}));
import { WorkOrdersListHeader } from "@/app/(protected)/workorders/_components/list/work-orders-list-header";
import { useWorkOrdersBoardSource } from "@/app/(protected)/workorders/_hooks/use-work-orders-board-source";
import { useWorkOrdersListFilters } from "@/app/(protected)/workorders/_hooks/use-work-orders-list-filters";
const EMPTY_RESULT = { items: [], totalCount: 0, page: 0, pageSize: 25 };
function useBoardWithFilters() {
const filters = useWorkOrdersListFilters();
useWorkOrdersBoardSource(filters);
return filters;
}
function renderBoardSource() {
const client = createTestQueryClient();
const wrapper = ({ children }: { children: ReactNode }) => (
<QueryClientProvider client={client}>{children}</QueryClientProvider>
);
return renderHook(() => useBoardWithFilters(), { wrapper });
}
describe("keyword search scope indicator", () => {
it("shows no scope badge while the search box is empty", () => {
renderWithProviders(
<WorkOrdersListHeader
search=""
weekLabel="5/4–5/8"
onSearchChange={vi.fn()}
activeFilterCount={0}
/>,
{ withAuth: false },
);
expect(screen.queryByText(/searching within/i)).not.toBeInTheDocument();
});
it("names the active week while a keyword is typed", () => {
renderWithProviders(
<WorkOrdersListHeader
search="hvac"
weekLabel="5/4–5/8"
onSearchChange={vi.fn()}
activeFilterCount={0}
/>,
{ withAuth: false },
);
expect(screen.getByText("Searching within 5/4–5/8")).toBeInTheDocument();
});
it("hides the scope label while Advanced Filters are active", () => {
renderWithProviders(
<WorkOrdersListHeader
search="hvac"
weekLabel="5/4–5/8"
onSearchChange={vi.fn()}
activeFilterCount={1}
advancedFiltersActive
/>,
{ withAuth: false },
);
expect(screen.queryByText(/searching within/i)).not.toBeInTheDocument();
});
});
describe("keyword search vs Advanced Filters scope", () => {
beforeEach(() => {
searchBoard.mockReset().mockResolvedValue(EMPTY_RESULT);
getBoard.mockReset().mockResolvedValue(EMPTY_RESULT);
});
it("scopes keyword search to the active week", async () => {
const { result } = renderBoardSource();
act(() => {
result.current.setWeekMonday("2026-05-04");
result.current.handleSearchChange("hvac");
});
await waitFor(() =>
expect(getBoard).toHaveBeenCalledWith(
expect.objectContaining({ weekStart: "2026-05-04", weekEnd: "2026-05-08", search: "hvac" }),
),
);
expect(searchBoard).not.toHaveBeenCalled();
});
it("keeps Advanced Filters global and ignores the week-scoped keyword", async () => {
const { result } = renderBoardSource();
act(() => {
result.current.handleSearchChange("hvac");
result.current.applyAdvancedFilters({ ...EMPTY_ADVANCED_FILTERS, rescheduled: true });
});
await waitFor(() => expect(searchBoard).toHaveBeenCalled());
const params = searchBoard.mock.lastCall?.[0] as Record<string, unknown>;
expect(params.search).toBeUndefined();
expect(params).not.toHaveProperty("weekStart");
});
});