diff --git a/e2e/__screenshots__/work-orders/work-orders.visual.spec.ts/wo-empty.png b/e2e/__screenshots__/work-orders/work-orders.visual.spec.ts/wo-empty.png
index e64d4c12..b21a8ded 100644
Binary files a/e2e/__screenshots__/work-orders/work-orders.visual.spec.ts/wo-empty.png and b/e2e/__screenshots__/work-orders/work-orders.visual.spec.ts/wo-empty.png differ
diff --git a/src/app/(protected)/workorders/_components/list/work-orders-list-header.tsx b/src/app/(protected)/workorders/_components/list/work-orders-list-header.tsx
index 8208d7de..3f52ea0f 100644
--- a/src/app/(protected)/workorders/_components/list/work-orders-list-header.tsx
+++ b/src/app/(protected)/workorders/_components/list/work-orders-list-header.tsx
@@ -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 (
<>
-
+
+
+ {/* Keyword search is scoped to the active week; make that scope visible. */}
+ {!advancedFiltersActive && search.trim() !== "" && Boolean(weekLabel) && (
+
+ Searching within {weekLabel}
+
+ )}
+
{Boolean(onOpenAdvancedFilters) && (
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,
diff --git a/src/app/(protected)/workorders/index.tsx b/src/app/(protected)/workorders/index.tsx
index 571251cb..e12e040d 100644
--- a/src/app/(protected)/workorders/index.tsx
+++ b/src/app/(protected)/workorders/index.tsx
@@ -43,10 +43,12 @@ export default function WorkOrdersListPage() {
actions={
filters.setAdvOpen(true)}
onNewWorkOrder={() => page.setWizardOpen(true)}
activeFilterCount={filters.activeFilterCount}
+ advancedFiltersActive={filters.advApplied != null}
isFetching={tableData.isFetching && !tableData.isLoading}
/>
}
diff --git a/src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx b/src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx
index c3fb07b9..7e4df369 100644
--- a/src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx
+++ b/src/test/app/(protected)/workorders/work-orders-board-core-affordances.test.tsx
@@ -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(
- ,
+ ,
{ withAuth: false },
);
@@ -202,6 +207,7 @@ describe("WorkOrdersListHeader slice affordances", () => {
renderWithProviders(
({
+ searchBoard: vi.fn(),
+ getBoard: vi.fn(),
+ emptyList: [] as const,
+}));
+
+vi.mock("@/hooks/use-debounce", () => ({
+ useDebounce: (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 }) => (
+ {children}
+ );
+ return renderHook(() => useBoardWithFilters(), { wrapper });
+}
+
+describe("keyword search scope indicator", () => {
+ it("shows no scope badge while the search box is empty", () => {
+ renderWithProviders(
+ ,
+ { withAuth: false },
+ );
+
+ expect(screen.queryByText(/searching within/i)).not.toBeInTheDocument();
+ });
+
+ it("names the active week while a keyword is typed", () => {
+ renderWithProviders(
+ ,
+ { withAuth: false },
+ );
+
+ expect(screen.getByText("Searching within 5/4–5/8")).toBeInTheDocument();
+ });
+
+ it("hides the scope label while Advanced Filters are active", () => {
+ renderWithProviders(
+ ,
+ { 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;
+ expect(params.search).toBeUndefined();
+ expect(params).not.toHaveProperty("weekStart");
+ });
+});