mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-01 22:07:53 +00:00
fix(work-orders): keep search and reschedule on the selected day (SH-240)
Empty search and Schedule On must follow the day-tab board, not the whole week or Unscheduled.
This commit is contained in:
parent
8ab56d9782
commit
0c75bd42f4
12 changed files with 214 additions and 30 deletions
|
|
@ -5,7 +5,6 @@ import type {
|
|||
} from "@/domain/work-orders/types/work-order-table-row";
|
||||
import { applySchedulePatch } from "@/domain/work-orders/use-cases/use-work-order-table-mutations";
|
||||
import { shouldNotifyCrossWeek } from "@/domain/work-orders/utils/should-notify-cross-week";
|
||||
import { mondayOf } from "@/domain/work-orders/utils/wizard-date-utils";
|
||||
import type { ReturnTypeOfUseWorkOrdersTableData } from "./wo-table-types";
|
||||
import { rescheduleNotify, weekMoveNotify } from "./reschedule-notify";
|
||||
import type { WoTableRowHandlers } from "./wo-table-row";
|
||||
|
|
@ -13,7 +12,8 @@ import type { WoTableRowHandlers } from "./wo-table-row";
|
|||
type UseWoTableRowHandlersOptions = {
|
||||
tableData: ReturnTypeOfUseWorkOrdersTableData;
|
||||
weekMonday: string;
|
||||
onWeekChange: (mondayIso: string) => void;
|
||||
onNavigateToDay: (dateIso: string) => void;
|
||||
onOpenAdvancedFilters?: () => void;
|
||||
onOpenSlideOver: (row: WorkOrderTableRow, edit?: boolean) => void;
|
||||
onOpenCompDoc?: (row: WorkOrderTableRow) => void;
|
||||
onOpenUplifts?: (row: WorkOrderTableRow) => void;
|
||||
|
|
@ -26,7 +26,8 @@ type UseWoTableRowHandlersOptions = {
|
|||
export function useWoTableRowHandlers({
|
||||
tableData,
|
||||
weekMonday,
|
||||
onWeekChange,
|
||||
onNavigateToDay,
|
||||
onOpenAdvancedFilters,
|
||||
onOpenSlideOver,
|
||||
onOpenCompDoc,
|
||||
onOpenUplifts,
|
||||
|
|
@ -35,14 +36,7 @@ export function useWoTableRowHandlers({
|
|||
onScheduleHighlight,
|
||||
onDuplicateFound,
|
||||
}: UseWoTableRowHandlersOptions): WoTableRowHandlers {
|
||||
const {
|
||||
dragState: dragStateRef,
|
||||
setDragViz,
|
||||
reorderRows,
|
||||
endDrag,
|
||||
setUnschedOpen,
|
||||
triggerFlash,
|
||||
} = tableData;
|
||||
const { dragState: dragStateRef, setDragViz, reorderRows, endDrag, triggerFlash } = tableData;
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
|
|
@ -80,14 +74,16 @@ export function useWoTableRowHandlers({
|
|||
onScheduleDate: (id: string | number, date: string) => {
|
||||
onPatch(id, applySchedulePatch(date, false));
|
||||
if (shouldNotifyCrossWeek(date, weekMonday)) {
|
||||
const targetMonday = mondayOf(date);
|
||||
rescheduleNotify(date, {
|
||||
onNavigate: () => {
|
||||
onWeekChange(targetMonday);
|
||||
onNavigateToDay(date);
|
||||
setTimeout(() => triggerFlash(id), 180);
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
onNavigateToDay(date);
|
||||
triggerFlash(id);
|
||||
},
|
||||
onScheduleWeek: (id: string | number, week: string) => {
|
||||
onPatch(id, {
|
||||
|
|
@ -97,12 +93,10 @@ export function useWoTableRowHandlers({
|
|||
dayGroup: "",
|
||||
dayLabel: "",
|
||||
});
|
||||
setUnschedOpen(true);
|
||||
triggerFlash(id);
|
||||
weekMoveNotify(week, {
|
||||
onNavigate: () => {
|
||||
setUnschedOpen(true);
|
||||
setTimeout(() => triggerFlash(id), 180);
|
||||
onNavigateToDay(week);
|
||||
onOpenAdvancedFilters?.();
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
@ -138,10 +132,10 @@ export function useWoTableRowHandlers({
|
|||
setDragViz,
|
||||
reorderRows,
|
||||
endDrag,
|
||||
setUnschedOpen,
|
||||
triggerFlash,
|
||||
weekMonday,
|
||||
onWeekChange,
|
||||
onNavigateToDay,
|
||||
onOpenAdvancedFilters,
|
||||
onOpenSlideOver,
|
||||
onOpenCompDoc,
|
||||
onOpenUplifts,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ type WoTableProps = {
|
|||
vendors: VendorDropdownItem[];
|
||||
pmTypes: string[];
|
||||
weekMonday: string;
|
||||
onWeekChange: (mondayIso: string) => void;
|
||||
onNavigateToDay: (dateIso: string) => void;
|
||||
onOpenSlideOver: (row: WorkOrderTableRow, edit?: boolean) => void;
|
||||
/** Omit until completion-doc slice mounts CompDocDialog. */
|
||||
onOpenCompDoc?: (row: WorkOrderTableRow) => void;
|
||||
|
|
@ -75,7 +75,7 @@ export function WoTable({
|
|||
vendors,
|
||||
pmTypes,
|
||||
weekMonday,
|
||||
onWeekChange,
|
||||
onNavigateToDay,
|
||||
onOpenSlideOver,
|
||||
onOpenCompDoc,
|
||||
onOpenUplifts,
|
||||
|
|
@ -98,7 +98,8 @@ export function WoTable({
|
|||
const handlers = useWoTableRowHandlers({
|
||||
tableData,
|
||||
weekMonday,
|
||||
onWeekChange,
|
||||
onNavigateToDay,
|
||||
onOpenAdvancedFilters,
|
||||
onOpenSlideOver,
|
||||
onOpenCompDoc,
|
||||
onOpenUplifts,
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import { compactWeekLabel, currentWeekMonday } from "@/domain/work-orders/utils/
|
|||
import {
|
||||
firstWeekday,
|
||||
initialSelectedDay,
|
||||
scheduledDayTab,
|
||||
weekdaysMonFri,
|
||||
} from "@/domain/work-orders/utils/week-day-nav-utils";
|
||||
|
||||
|
|
@ -72,6 +73,13 @@ export function useWorkOrdersListFilters() {
|
|||
setSelectedDayIsoState(value);
|
||||
};
|
||||
|
||||
const navigateToDay = (dateIso: string) => {
|
||||
const tab = scheduledDayTab(dateIso);
|
||||
setWeekMondayState(tab.weekMonday);
|
||||
setSelectedDayIsoState(tab.selectedDayIso);
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
const setDispatcherIds = (value: Set<string>) => {
|
||||
setDispatcherIdsState(new Set(value));
|
||||
setPage(0);
|
||||
|
|
@ -156,6 +164,7 @@ export function useWorkOrdersListFilters() {
|
|||
setPageSize,
|
||||
setWeekMonday,
|
||||
setSelectedDayIso,
|
||||
navigateToDay,
|
||||
setDispatcherIds,
|
||||
setTypeFilter,
|
||||
setSortBy,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ import { useWorkOrdersTableLocalState } from "@/app/(protected)/workorders/_hook
|
|||
import { currentWeekMonday } from "@/domain/work-orders/utils/list-week-utils";
|
||||
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
||||
import { deriveDayFields } from "@/domain/work-orders/utils/table-day-utils";
|
||||
import { buildSelectedDayGroup } from "@/domain/work-orders/utils/week-day-nav-utils";
|
||||
import {
|
||||
buildSelectedDayGroup,
|
||||
isDayScopedSearchEmpty,
|
||||
} from "@/domain/work-orders/utils/week-day-nav-utils";
|
||||
import {
|
||||
composeBoardSections,
|
||||
filterByBarAndSearch,
|
||||
|
|
@ -83,12 +86,17 @@ export function useWorkOrdersTableData(filters: FiltersReturn) {
|
|||
);
|
||||
|
||||
const searchActive = filters.search.trim() !== "";
|
||||
const visibleRows = advActive ? advResults : filtered;
|
||||
const noSearchResults = visibleRows.length === 0 && searchActive;
|
||||
const isEmptyWeek =
|
||||
!advActive && scheduled.length === 0 && unassigned.length === 0 && !searchActive;
|
||||
const displayGroups = dayGroups;
|
||||
const dayVisibleCount = displayGroups[0]?.items.length ?? 0;
|
||||
const noSearchResults =
|
||||
!advActive &&
|
||||
isDayScopedSearchEmpty({
|
||||
searchActive,
|
||||
unassignedCount: unassigned.length,
|
||||
dayItemCount: dayVisibleCount,
|
||||
});
|
||||
const isEmptyWeek =
|
||||
!advActive && scheduled.length === 0 && unassigned.length === 0 && !searchActive;
|
||||
|
||||
const todayKey =
|
||||
filters.weekMonday === currentWeekMonday()
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ export default function WorkOrdersListPage() {
|
|||
vendors={page.vendors}
|
||||
pmTypes={page.pmTypes}
|
||||
weekMonday={filters.weekMonday}
|
||||
onWeekChange={filters.setWeekMonday}
|
||||
onNavigateToDay={filters.navigateToDay}
|
||||
onOpenSlideOver={page.handleOpenSlideOver}
|
||||
onOpenCompDoc={(row) => openCompletionDoc(row, page.setDocRow)}
|
||||
onOpenMedia={page.setMediaRow}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import type {
|
|||
} from "@/domain/work-orders/types/work-order-table-row";
|
||||
import { isLiveWeek } from "@/domain/work-orders/utils/list-week-utils";
|
||||
import { isDateInWeek } from "@/domain/work-orders/utils/table-day-utils";
|
||||
import { addDaysIso, todayIso } from "@/domain/work-orders/utils/wizard-date-utils";
|
||||
import { addDaysIso, mondayOf, todayIso } from "@/domain/work-orders/utils/wizard-date-utils";
|
||||
|
||||
const DAY_TAB_WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] as const;
|
||||
|
||||
|
|
@ -16,6 +16,25 @@ export function firstWeekday(weekMonday: string): string {
|
|||
return weekMonday;
|
||||
}
|
||||
|
||||
/** Maps a scheduled date onto the Mon–Fri tab that can show it (weekends fall back to Monday). */
|
||||
export function scheduledDayTab(dateIso: string): { weekMonday: string; selectedDayIso: string } {
|
||||
const day = dateIso.slice(0, 10);
|
||||
const weekMonday = mondayOf(day);
|
||||
const days = weekdaysMonFri(weekMonday);
|
||||
return {
|
||||
weekMonday,
|
||||
selectedDayIso: days.includes(day) ? day : firstWeekday(weekMonday),
|
||||
};
|
||||
}
|
||||
|
||||
export function isDayScopedSearchEmpty(args: {
|
||||
searchActive: boolean;
|
||||
unassignedCount: number;
|
||||
dayItemCount: number;
|
||||
}): boolean {
|
||||
return args.searchActive && args.unassignedCount === 0 && args.dayItemCount === 0;
|
||||
}
|
||||
|
||||
export function initialSelectedDay(weekMonday: string, today = todayIso()): string {
|
||||
if (isLiveWeek(weekMonday) && isDateInWeek(today, weekMonday)) {
|
||||
return today;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ describe("useWoTableRowHandlers onDuplicateFound", () => {
|
|||
useWoTableRowHandlers({
|
||||
tableData: buildTableData(),
|
||||
weekMonday: "2026-07-20",
|
||||
onWeekChange: vi.fn(),
|
||||
onNavigateToDay: vi.fn(),
|
||||
onOpenSlideOver,
|
||||
onOpenMedia: vi.fn(),
|
||||
onPatch: vi.fn(),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,116 @@
|
|||
import { renderHook } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { useWoTableRowHandlers } from "@/app/(protected)/workorders/_components/list/table/use-wo-table-row-handlers";
|
||||
import { applySchedulePatch } from "@/domain/work-orders/use-cases/use-work-order-table-mutations";
|
||||
|
||||
const { rescheduleNotify, weekMoveNotify } = vi.hoisted(() => ({
|
||||
rescheduleNotify: vi.fn(),
|
||||
weekMoveNotify: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("@/app/(protected)/workorders/_components/list/table/reschedule-notify", () => ({
|
||||
rescheduleNotify,
|
||||
weekMoveNotify,
|
||||
}));
|
||||
|
||||
function buildTableData(triggerFlash = vi.fn()) {
|
||||
return {
|
||||
dragState: { current: { dragId: null, overId: null } },
|
||||
setDragViz: vi.fn(),
|
||||
reorderRows: vi.fn(),
|
||||
endDrag: vi.fn(),
|
||||
triggerFlash,
|
||||
setSelectedId: vi.fn(),
|
||||
} as never;
|
||||
}
|
||||
|
||||
describe("useWoTableRowHandlers schedule navigation (SH-240)", () => {
|
||||
it("selects the scheduled weekday in the same week so the row stays visible", () => {
|
||||
const onNavigateToDay = vi.fn();
|
||||
const onPatch = vi.fn();
|
||||
const triggerFlash = vi.fn();
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useWoTableRowHandlers({
|
||||
tableData: buildTableData(triggerFlash),
|
||||
weekMonday: "2026-05-18",
|
||||
onNavigateToDay,
|
||||
onOpenSlideOver: vi.fn(),
|
||||
onOpenMedia: vi.fn(),
|
||||
onPatch,
|
||||
onScheduleHighlight: vi.fn(),
|
||||
}),
|
||||
);
|
||||
|
||||
result.current.onScheduleDate("wo-1", "2026-05-21");
|
||||
|
||||
expect(onPatch).toHaveBeenCalledWith("wo-1", applySchedulePatch("2026-05-21", false));
|
||||
expect(onNavigateToDay).toHaveBeenCalledWith("2026-05-21");
|
||||
expect(triggerFlash).toHaveBeenCalledWith("wo-1");
|
||||
expect(rescheduleNotify).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("sends cross-week view-it navigation to the target day, not Monday", () => {
|
||||
const onNavigateToDay = vi.fn();
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useWoTableRowHandlers({
|
||||
tableData: buildTableData(),
|
||||
weekMonday: "2026-05-18",
|
||||
onNavigateToDay,
|
||||
onOpenSlideOver: vi.fn(),
|
||||
onOpenMedia: vi.fn(),
|
||||
onPatch: vi.fn(),
|
||||
onScheduleHighlight: vi.fn(),
|
||||
}),
|
||||
);
|
||||
|
||||
result.current.onScheduleDate("wo-1", "2026-05-27");
|
||||
|
||||
expect(onNavigateToDay).not.toHaveBeenCalled();
|
||||
expect(rescheduleNotify).toHaveBeenCalledWith(
|
||||
"2026-05-27",
|
||||
expect.objectContaining({ onNavigate: expect.any(Function) }),
|
||||
);
|
||||
|
||||
const { onNavigate } = rescheduleNotify.mock.calls[0][1] as { onNavigate: () => void };
|
||||
onNavigate();
|
||||
expect(onNavigateToDay).toHaveBeenCalledWith("2026-05-27");
|
||||
});
|
||||
|
||||
it("opens Advanced Filters for week-only moves instead of the removed Unscheduled section", () => {
|
||||
const onNavigateToDay = vi.fn();
|
||||
const onOpenAdvancedFilters = vi.fn();
|
||||
const onPatch = vi.fn();
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useWoTableRowHandlers({
|
||||
tableData: buildTableData(),
|
||||
weekMonday: "2026-05-18",
|
||||
onNavigateToDay,
|
||||
onOpenAdvancedFilters,
|
||||
onOpenSlideOver: vi.fn(),
|
||||
onOpenMedia: vi.fn(),
|
||||
onPatch,
|
||||
onScheduleHighlight: vi.fn(),
|
||||
}),
|
||||
);
|
||||
|
||||
result.current.onScheduleWeek("wo-1", "2026-05-25");
|
||||
|
||||
expect(onPatch).toHaveBeenCalledWith(
|
||||
"wo-1",
|
||||
expect.objectContaining({
|
||||
scheduledOn: "",
|
||||
scheduleWeekOnly: true,
|
||||
targetWeek: "2026-05-25",
|
||||
}),
|
||||
);
|
||||
expect(weekMoveNotify).toHaveBeenCalled();
|
||||
|
||||
const { onNavigate } = weekMoveNotify.mock.calls[0][1] as { onNavigate: () => void };
|
||||
onNavigate();
|
||||
expect(onNavigateToDay).toHaveBeenCalledWith("2026-05-25");
|
||||
expect(onOpenAdvancedFilters).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
|
@ -99,4 +99,15 @@ describe("useWorkOrdersListFilters day state (SH-240)", () => {
|
|||
expect(result.current.weekMonday).toBe("2026-08-10");
|
||||
expect(result.current.selectedDayIso).toBe("2026-08-10");
|
||||
});
|
||||
|
||||
it("follows a scheduled date onto that weekday instead of resetting to Monday", () => {
|
||||
const { result } = renderHook(() => useWorkOrdersListFilters());
|
||||
|
||||
act(() => {
|
||||
result.current.navigateToDay("2026-05-20");
|
||||
});
|
||||
|
||||
expect(result.current.weekMonday).toBe("2026-05-18");
|
||||
expect(result.current.selectedDayIso).toBe("2026-05-20");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ vi.mock("@/app/(protected)/workorders/_hooks/use-work-orders-list-filters", () =
|
|||
selectedDayIso: "2026-07-20",
|
||||
setWeekMonday: vi.fn(),
|
||||
setSelectedDayIso: vi.fn(),
|
||||
navigateToDay: vi.fn(),
|
||||
dispatcherIds: new Set<string>(),
|
||||
setDispatcherIds: vi.fn(),
|
||||
typeFilter: "All",
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ vi.mock("@/app/(protected)/workorders/_hooks/use-work-orders-list-filters", () =
|
|||
selectedDayIso: "2026-07-20",
|
||||
setWeekMonday: vi.fn(),
|
||||
setSelectedDayIso: vi.fn(),
|
||||
navigateToDay: vi.fn(),
|
||||
dispatcherIds: new Set<string>(),
|
||||
setDispatcherIds: vi.fn(),
|
||||
typeFilter: "All",
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ import {
|
|||
firstWeekday,
|
||||
formatDayTabLabel,
|
||||
initialSelectedDay,
|
||||
isDayScopedSearchEmpty,
|
||||
rowMatchesSelectedDay,
|
||||
scheduledDayTab,
|
||||
weekdaysMonFri,
|
||||
} from "@/domain/work-orders/utils/week-day-nav-utils";
|
||||
import { addDaysIso } from "@/domain/work-orders/utils/wizard-date-utils";
|
||||
|
|
@ -119,4 +121,26 @@ describe("week-day-nav-utils (SH-240)", () => {
|
|||
expect(group.label).toBe("Mon · 05/18");
|
||||
expect(group.items.map((item) => item.id)).toEqual(["mon"]);
|
||||
});
|
||||
|
||||
it("maps a scheduled weekday onto its week and day tab", () => {
|
||||
expect(scheduledDayTab("2026-05-20")).toEqual({
|
||||
weekMonday: "2026-05-18",
|
||||
selectedDayIso: "2026-05-20",
|
||||
});
|
||||
});
|
||||
|
||||
it("treats search as empty only when the selected day and Unassigned have no hits", () => {
|
||||
expect(
|
||||
isDayScopedSearchEmpty({ searchActive: true, unassignedCount: 0, dayItemCount: 0 }),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isDayScopedSearchEmpty({ searchActive: true, unassignedCount: 0, dayItemCount: 1 }),
|
||||
).toBe(false);
|
||||
expect(
|
||||
isDayScopedSearchEmpty({ searchActive: true, unassignedCount: 2, dayItemCount: 0 }),
|
||||
).toBe(false);
|
||||
expect(
|
||||
isDayScopedSearchEmpty({ searchActive: false, unassignedCount: 0, dayItemCount: 0 }),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue