mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-03 03:23:20 +00:00
fix(work-orders): wait for Unassigned queue before empty search (SH-240)
Keep SH-165 loading and error visible instead of a premature empty-search shell.
This commit is contained in:
parent
1a54cbd2af
commit
be975c792c
5 changed files with 175 additions and 6 deletions
|
|
@ -9,6 +9,7 @@ import type {
|
|||
WorkOrderTablePatch,
|
||||
WorkOrderTableRow,
|
||||
} from "@/domain/work-orders/types/work-order-table-row";
|
||||
import { isUnassignedQueueReady } from "@/domain/work-orders/utils/week-day-nav-utils";
|
||||
import { DuplicateWoNumberDialog } from "@/app/(protected)/workorders/_components/list/duplicate-wo-number-dialog";
|
||||
import { WoTableSkeleton } from "@/app/(protected)/workorders/_components/loading/wo-table-skeleton";
|
||||
import type { ReturnTypeOfUseWorkOrdersTableData } from "./wo-table-types";
|
||||
|
|
@ -170,7 +171,12 @@ export function WoTable({
|
|||
);
|
||||
}
|
||||
|
||||
if (!tableData.advActive && tableData.noSearchResults) {
|
||||
const showSearchEmpty =
|
||||
!tableData.advActive &&
|
||||
tableData.noSearchResults &&
|
||||
isUnassignedQueueReady(tableData.unassignedLoading, tableData.unassignedError);
|
||||
|
||||
if (showSearchEmpty) {
|
||||
return (
|
||||
<WoTableEmptyShell
|
||||
message="No work orders match your search"
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import { deriveDayFields } from "@/domain/work-orders/utils/table-day-utils";
|
|||
import {
|
||||
buildSelectedDayGroup,
|
||||
isDayScopedSearchEmpty,
|
||||
isUnassignedQueueReady,
|
||||
} from "@/domain/work-orders/utils/week-day-nav-utils";
|
||||
import {
|
||||
composeBoardSections,
|
||||
|
|
@ -94,6 +95,7 @@ export function useWorkOrdersTableData(filters: FiltersReturn) {
|
|||
searchActive,
|
||||
unassignedCount: unassigned.length,
|
||||
dayItemCount: dayVisibleCount,
|
||||
unassignedQueueReady: isUnassignedQueueReady(unassignedLoading, unassignedError),
|
||||
});
|
||||
const isEmptyWeek =
|
||||
!advActive && scheduled.length === 0 && unassigned.length === 0 && !searchActive;
|
||||
|
|
|
|||
|
|
@ -27,12 +27,25 @@ export function scheduledDayTab(dateIso: string): { weekMonday: string; selected
|
|||
};
|
||||
}
|
||||
|
||||
export function isUnassignedQueueReady(
|
||||
unassignedLoading: boolean,
|
||||
unassignedError: Error | null,
|
||||
): boolean {
|
||||
return !unassignedLoading && unassignedError == null;
|
||||
}
|
||||
|
||||
export function isDayScopedSearchEmpty(args: {
|
||||
searchActive: boolean;
|
||||
unassignedCount: number;
|
||||
dayItemCount: number;
|
||||
unassignedQueueReady: boolean;
|
||||
}): boolean {
|
||||
return args.searchActive && args.unassignedCount === 0 && args.dayItemCount === 0;
|
||||
return (
|
||||
args.searchActive &&
|
||||
args.unassignedQueueReady &&
|
||||
args.unassignedCount === 0 &&
|
||||
args.dayItemCount === 0
|
||||
);
|
||||
}
|
||||
|
||||
export function initialSelectedDay(weekMonday: string, today = todayIso()): string {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
import { describe, expect, it, vi } from "vitest";
|
||||
import { WoTable } from "@/app/(protected)/workorders/_components/list/table/wo-table";
|
||||
import type { ReturnTypeOfUseWorkOrdersTableData } from "@/app/(protected)/workorders/_components/list/table/wo-table-types";
|
||||
import { renderWithProviders } from "@/test/test-utils";
|
||||
|
||||
vi.mock("@/domain/locations/use-cases/use-location-sites", () => ({
|
||||
useLocationSites: () => ({ data: [] }),
|
||||
}));
|
||||
|
||||
vi.mock("@/app/(protected)/workorders/_components/list/table/use-wo-table-inline-create", () => ({
|
||||
useWoTableInlineCreate: () => ({ handleInlineSave: vi.fn(), saving: false }),
|
||||
}));
|
||||
|
||||
function tableData(
|
||||
overrides: Partial<ReturnTypeOfUseWorkOrdersTableData> = {},
|
||||
): ReturnTypeOfUseWorkOrdersTableData {
|
||||
return {
|
||||
rows: [],
|
||||
isLoading: false,
|
||||
isFetching: false,
|
||||
error: null,
|
||||
refetch: vi.fn(),
|
||||
advActive: false,
|
||||
advResults: [],
|
||||
unassigned: [],
|
||||
unassignedError: null,
|
||||
unassignedLoading: false,
|
||||
unscheduled: [],
|
||||
displayGroups: [{ key: "05/18", label: "Mon · 05/18", date: "2026-05-18", items: [] }],
|
||||
searchActive: true,
|
||||
noSearchResults: true,
|
||||
isEmptyWeek: false,
|
||||
todayKey: null,
|
||||
draftDayKey: null,
|
||||
draft: { locationId: "", site: "" },
|
||||
setDraft: vi.fn(),
|
||||
unassignedOpen: true,
|
||||
setUnassignedOpen: vi.fn(),
|
||||
unschedOpen: true,
|
||||
setUnschedOpen: vi.fn(),
|
||||
selectedId: null,
|
||||
setSelectedId: vi.fn(),
|
||||
selectedRow: null,
|
||||
flashId: null,
|
||||
triggerFlash: vi.fn(),
|
||||
dragViz: null,
|
||||
setDragViz: vi.fn(),
|
||||
dragState: { current: { dragId: null, overId: null } },
|
||||
patchRow: vi.fn(),
|
||||
clearRowPatch: vi.fn(),
|
||||
reorderRows: vi.fn(),
|
||||
endDrag: vi.fn(),
|
||||
startDayDraft: vi.fn(),
|
||||
cancelDraft: vi.fn(),
|
||||
addRow: vi.fn(),
|
||||
scopeTotalCount: 0,
|
||||
totalCount: 0,
|
||||
search: "no-match",
|
||||
...overrides,
|
||||
} as ReturnTypeOfUseWorkOrdersTableData;
|
||||
}
|
||||
|
||||
function renderTable(data: ReturnTypeOfUseWorkOrdersTableData) {
|
||||
return renderWithProviders(
|
||||
<WoTable
|
||||
tableData={data}
|
||||
users={[]}
|
||||
sites={[]}
|
||||
vendors={[]}
|
||||
pmTypes={[]}
|
||||
weekMonday="2026-05-18"
|
||||
onNavigateToDay={vi.fn()}
|
||||
onOpenSlideOver={vi.fn()}
|
||||
onOpenMedia={vi.fn()}
|
||||
onClearSearch={vi.fn()}
|
||||
onPatch={vi.fn()}
|
||||
/>,
|
||||
{ withAuth: false },
|
||||
);
|
||||
}
|
||||
|
||||
describe("WoTable search empty vs Unassigned queue (SH-165/SH-240)", () => {
|
||||
it("shows empty search only after the Unassigned queue has settled empty", () => {
|
||||
const { getByText, queryByText } = renderTable(tableData());
|
||||
|
||||
expect(getByText("No work orders match your search")).toBeInTheDocument();
|
||||
expect(queryByText(/Loading unassigned work orders/)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps Unassigned loading visible instead of a premature empty search", () => {
|
||||
const { getByText, queryByText } = renderTable(
|
||||
tableData({
|
||||
noSearchResults: true,
|
||||
unassignedLoading: true,
|
||||
}),
|
||||
);
|
||||
|
||||
expect(queryByText("No work orders match your search")).not.toBeInTheDocument();
|
||||
expect(getByText(/Loading unassigned work orders/)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps Unassigned error visible instead of hiding it behind empty search", () => {
|
||||
const { getByText, queryByText } = renderTable(
|
||||
tableData({
|
||||
noSearchResults: true,
|
||||
unassignedError: new Error("queue unavailable"),
|
||||
}),
|
||||
);
|
||||
|
||||
expect(queryByText("No work orders match your search")).not.toBeInTheDocument();
|
||||
expect(getByText("queue unavailable")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -7,6 +7,7 @@ import {
|
|||
formatDayTabLabel,
|
||||
initialSelectedDay,
|
||||
isDayScopedSearchEmpty,
|
||||
isUnassignedQueueReady,
|
||||
rowMatchesSelectedDay,
|
||||
scheduledDayTab,
|
||||
weekdaysMonFri,
|
||||
|
|
@ -131,16 +132,50 @@ describe("week-day-nav-utils (SH-240)", () => {
|
|||
|
||||
it("treats search as empty only when the selected day and Unassigned have no hits", () => {
|
||||
expect(
|
||||
isDayScopedSearchEmpty({ searchActive: true, unassignedCount: 0, dayItemCount: 0 }),
|
||||
isDayScopedSearchEmpty({
|
||||
searchActive: true,
|
||||
unassignedCount: 0,
|
||||
dayItemCount: 0,
|
||||
unassignedQueueReady: true,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isDayScopedSearchEmpty({ searchActive: true, unassignedCount: 0, dayItemCount: 1 }),
|
||||
isDayScopedSearchEmpty({
|
||||
searchActive: true,
|
||||
unassignedCount: 0,
|
||||
dayItemCount: 1,
|
||||
unassignedQueueReady: true,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
isDayScopedSearchEmpty({ searchActive: true, unassignedCount: 2, dayItemCount: 0 }),
|
||||
isDayScopedSearchEmpty({
|
||||
searchActive: true,
|
||||
unassignedCount: 2,
|
||||
dayItemCount: 0,
|
||||
unassignedQueueReady: true,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
isDayScopedSearchEmpty({ searchActive: false, unassignedCount: 0, dayItemCount: 0 }),
|
||||
isDayScopedSearchEmpty({
|
||||
searchActive: false,
|
||||
unassignedCount: 0,
|
||||
dayItemCount: 0,
|
||||
unassignedQueueReady: true,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("does not treat search as empty while the Unassigned queue is loading or failed", () => {
|
||||
expect(isUnassignedQueueReady(true, null)).toBe(false);
|
||||
expect(isUnassignedQueueReady(false, new Error("queue unavailable"))).toBe(false);
|
||||
expect(isUnassignedQueueReady(false, null)).toBe(true);
|
||||
expect(
|
||||
isDayScopedSearchEmpty({
|
||||
searchActive: true,
|
||||
unassignedCount: 0,
|
||||
dayItemCount: 0,
|
||||
unassignedQueueReady: false,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue