diff --git a/src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab-edit-view.tsx b/src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab-edit-view.tsx
index fe420159..ce70990a 100644
--- a/src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab-edit-view.tsx
+++ b/src/app/(protected)/workorders/_components/detail/tabs/slide-over-info-tab-edit-view.tsx
@@ -50,6 +50,9 @@ export function SlideOverInfoTabEditView({
rescheduleCount={draft.rescheduleCount}
carriedOver={draft.carriedOver}
originalDate={draft.originalDate}
+ originalWeek={draft.originalWeek}
+ scheduledOn={draft.scheduledOn}
+ status={draft.status}
currentId={draft.id}
allOrders={allOrders}
onSave={(woNumber) => onDraftChange({ woNumber })}
diff --git a/src/app/(protected)/workorders/_components/list/table/cells/editable-wo-number-cell.tsx b/src/app/(protected)/workorders/_components/list/table/cells/editable-wo-number-cell.tsx
index b71f9c4f..369c1101 100644
--- a/src/app/(protected)/workorders/_components/list/table/cells/editable-wo-number-cell.tsx
+++ b/src/app/(protected)/workorders/_components/list/table/cells/editable-wo-number-cell.tsx
@@ -13,6 +13,9 @@ type EditableWONumberCellProps = {
carriedOver?: number;
isAddOn?: boolean;
originalDate?: string;
+ originalWeek?: string;
+ scheduledOn?: string;
+ status?: string;
q?: string;
currentId: string | number;
allOrders: WorkOrderTableRow[];
@@ -37,6 +40,9 @@ export function EditableWONumberCell({
carriedOver = 0,
isAddOn = false,
originalDate,
+ originalWeek,
+ scheduledOn,
+ status,
q,
currentId,
allOrders,
@@ -118,7 +124,13 @@ export function EditableWONumberCell({
-
+
);
}
@@ -155,7 +167,13 @@ export function EditableWONumberCell({
)}
-
+
);
}
diff --git a/src/app/(protected)/workorders/_components/list/table/cells/wo-counter-pills.tsx b/src/app/(protected)/workorders/_components/list/table/cells/wo-counter-pills.tsx
index a98552ae..32c13d49 100644
--- a/src/app/(protected)/workorders/_components/list/table/cells/wo-counter-pills.tsx
+++ b/src/app/(protected)/workorders/_components/list/table/cells/wo-counter-pills.tsx
@@ -1,5 +1,10 @@
import { RefreshCw } from "lucide-react";
import { Tooltip } from "@mui/material";
+import {
+ formatCarriedOverHover,
+ resolveCarryOverSourceWeek,
+ shouldShowCarriedOverPill,
+} from "@/domain/work-orders/utils/carry-over-display";
import { formatRescheduleHover } from "@/domain/work-orders/utils/reschedule-indicator";
const RESCHEDULE_PILL_STYLE = {
@@ -46,17 +51,30 @@ export function RescheduleCounterPill({
);
}
-export function CarriedOverCounterPill({ count }: { count: number }) {
- if (count < 2) {
+export function CarriedOverCounterPill({
+ count,
+ originalWeek,
+ originalDate,
+ scheduledOn,
+ status,
+}: {
+ count: number;
+ originalWeek?: string;
+ originalDate?: string;
+ scheduledOn?: string;
+ status?: string;
+}) {
+ if (!shouldShowCarriedOverPill(count, status)) {
return null;
}
- const label = `Carried over ${count} times`;
+ const sourceWeek = resolveCarryOverSourceWeek({ originalWeek, originalDate, scheduledOn });
+ const hover = formatCarriedOverHover(count, sourceWeek);
return (
-
+
diff --git a/src/app/(protected)/workorders/_components/list/table/wo-table-row-cells.tsx b/src/app/(protected)/workorders/_components/list/table/wo-table-row-cells.tsx
index c17deae3..c6fbde16 100644
--- a/src/app/(protected)/workorders/_components/list/table/wo-table-row-cells.tsx
+++ b/src/app/(protected)/workorders/_components/list/table/wo-table-row-cells.tsx
@@ -83,6 +83,9 @@ export function WoTableRowCells({
carriedOver={row.carriedOver}
isAddOn={row.isAddOn}
originalDate={row.originalDate}
+ originalWeek={row.originalWeek}
+ scheduledOn={row.scheduledOn}
+ status={row.status}
q={search}
currentId={row.id}
allOrders={allRows}
diff --git a/src/app/(protected)/workorders/_hooks/use-work-orders-table-local-state.ts b/src/app/(protected)/workorders/_hooks/use-work-orders-table-local-state.ts
index 86110721..9b3b853a 100644
--- a/src/app/(protected)/workorders/_hooks/use-work-orders-table-local-state.ts
+++ b/src/app/(protected)/workorders/_hooks/use-work-orders-table-local-state.ts
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { toast } from "react-toastify";
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
import { applyBoardRowOrderOnScheduleChange } from "@/domain/work-orders/utils/apply-board-row-order-on-schedule-change";
+import { applyCarryOverDisplayDayFields } from "@/domain/work-orders/utils/carry-over-display";
import { deriveDayFields } from "@/domain/work-orders/utils/table-day-utils";
import { enrichTableRow } from "@/domain/work-orders/utils/table-filter-utils";
import { isWorkOrderFullyLocked } from "@/domain/work-orders/utils/work-order-lock";
@@ -78,8 +79,11 @@ export function useWorkOrdersTableLocalState({
const displayRows = useMemo(() => {
const apiIds = new Set(apiRows.map((r) => String(r.id)));
const extra = localRows.filter((r) => !apiIds.has(String(r.id)));
- return applyRowOrderByDay([...apiRows, ...extra.map(enrichTableRow)], rowOrderByDay);
- }, [apiRows, localRows, rowOrderByDay]);
+ const merged = [...apiRows, ...extra.map(enrichTableRow)].map((row) =>
+ applyCarryOverDisplayDayFields(row, weekMonday),
+ );
+ return applyRowOrderByDay(merged, rowOrderByDay);
+ }, [apiRows, localRows, rowOrderByDay, weekMonday]);
const clearRowPatch = useCallback((id: string | number) => {
const key = String(id);
diff --git a/src/domain/work-orders/utils/carry-over-display.ts b/src/domain/work-orders/utils/carry-over-display.ts
new file mode 100644
index 00000000..dd92b2d3
--- /dev/null
+++ b/src/domain/work-orders/utils/carry-over-display.ts
@@ -0,0 +1,109 @@
+import { compactWeekLabel } from "@/domain/work-orders/utils/list-week-utils";
+import { formatRescheduleCountLabel } from "@/domain/work-orders/utils/reschedule-indicator";
+import { deriveDayFields } from "@/domain/work-orders/utils/table-day-utils";
+import { addDaysIso, mondayOf } from "@/domain/work-orders/utils/wizard-date-utils";
+
+const ISO_DATE = /^\d{4}-\d{2}-\d{2}$/;
+const WEEKDAY_COUNT = 7;
+const FRIDAY_OFFSET = 4;
+
+export type CarryOverRow = {
+ scheduledOn: string;
+ status?: string;
+ carriedOver?: number;
+};
+
+export function isTerminalCarryOverStatus(status?: string): boolean {
+ return status === "Completed" || status === "Canceled";
+}
+
+export function shouldShowCarriedOverPill(count: number, status?: string): boolean {
+ return count >= 2 && !isTerminalCarryOverStatus(status);
+}
+
+export function toIsoDate(value?: string): string {
+ if (!value) {
+ return "";
+ }
+ const iso = value.slice(0, 10);
+ return ISO_DATE.test(iso) ? iso : "";
+}
+
+export function resolveCarryOverSourceWeek(input: {
+ originalWeek?: string;
+ originalDate?: string;
+ scheduledOn?: string;
+}): string {
+ const originalWeek = toIsoDate(input.originalWeek);
+ if (originalWeek) {
+ return mondayOf(originalWeek);
+ }
+ const originalDate = toIsoDate(input.originalDate);
+ if (originalDate) {
+ return mondayOf(originalDate);
+ }
+ const scheduledOn = toIsoDate(input.scheduledOn);
+ return scheduledOn ? mondayOf(scheduledOn) : "";
+}
+
+export function formatCarriedOverHover(count: number, sourceWeekIso?: string): string {
+ const times = formatRescheduleCountLabel(count);
+ const base = `Carried over ${times} — this WO was not completed and moved to the next period`;
+ const sourceLabel = sourceWeekIso ? compactWeekLabel(sourceWeekIso) : "";
+ if (!sourceLabel) {
+ return base;
+ }
+ return `${base}. Source week: ${sourceLabel}.`;
+}
+
+/** Non-terminal WOs scheduled before the viewed week are display carry-overs. */
+export function isCarryOverDisplayRow(row: CarryOverRow, weekMonday: string): boolean {
+ if (isTerminalCarryOverStatus(row.status)) {
+ return false;
+ }
+ const scheduled = toIsoDate(row.scheduledOn);
+ return Boolean(scheduled) && scheduled < weekMonday;
+}
+
+export function mapCarryOverDisplayDay(scheduledOn: string, weekMonday: string): string {
+ const scheduled = toIsoDate(scheduledOn);
+ if (!scheduled || !ISO_DATE.test(weekMonday)) {
+ return "";
+ }
+ const sourceMonday = mondayOf(scheduled);
+ const offsetMs =
+ new Date(`${scheduled}T12:00:00`).getTime() - new Date(`${sourceMonday}T12:00:00`).getTime();
+ const offset = Math.round(offsetMs / 86_400_000);
+ if (offset < 0 || offset >= WEEKDAY_COUNT) {
+ return weekMonday;
+ }
+ if (offset > FRIDAY_OFFSET) {
+ return weekMonday;
+ }
+ return addDaysIso(weekMonday, offset);
+}
+
+export function rowMatchesCarryOverDay(
+ row: CarryOverRow,
+ selectedDayIso: string,
+ weekMonday: string,
+): boolean {
+ if (!isCarryOverDisplayRow(row, weekMonday)) {
+ return false;
+ }
+ return mapCarryOverDisplayDay(row.scheduledOn, weekMonday) === selectedDayIso;
+}
+
+/** Aligns UI day keys with the mapped weekday so reorder stays on the displayed tab. */
+export function applyCarryOverDisplayDayFields<
+ T extends CarryOverRow & { dayGroup: string; dayLabel: string },
+>(row: T, weekMonday: string): T {
+ if (!isCarryOverDisplayRow(row, weekMonday)) {
+ return row;
+ }
+ const displayDay = mapCarryOverDisplayDay(row.scheduledOn, weekMonday);
+ if (!displayDay) {
+ return row;
+ }
+ return { ...row, ...deriveDayFields(displayDay) };
+}
diff --git a/src/domain/work-orders/utils/table-filter-utils.ts b/src/domain/work-orders/utils/table-filter-utils.ts
index dcda43c5..4f2a045b 100644
--- a/src/domain/work-orders/utils/table-filter-utils.ts
+++ b/src/domain/work-orders/utils/table-filter-utils.ts
@@ -7,6 +7,7 @@ import type {
WorkOrderTableRow,
} from "@/domain/work-orders/types/work-order-table-row";
import { ADVANCED_FILTER_PREDICATES } from "@/domain/work-orders/utils/advanced-filter-predicates";
+import { isCarryOverDisplayRow } from "@/domain/work-orders/utils/carry-over-display";
import { isDateInWeek } from "@/domain/work-orders/utils/table-day-utils";
import { weekRange } from "@/domain/work-orders/utils/list-week-utils";
@@ -96,7 +97,7 @@ export function filterByBarAndSearch(
if (row.scheduledOn) {
if (row.scheduledOn < weekFrom || row.scheduledOn > weekTo) {
- return false;
+ return isCarryOverDisplayRow(row, bar.weekMonday);
}
} else if (row.targetWeek) {
if (!isDateInWeek(row.targetWeek, bar.weekMonday)) {
diff --git a/src/domain/work-orders/utils/week-day-nav-utils.ts b/src/domain/work-orders/utils/week-day-nav-utils.ts
index 073cf231..f2b10778 100644
--- a/src/domain/work-orders/utils/week-day-nav-utils.ts
+++ b/src/domain/work-orders/utils/week-day-nav-utils.ts
@@ -2,6 +2,7 @@ import type {
WorkOrderDayGroup,
WorkOrderTableRow,
} from "@/domain/work-orders/types/work-order-table-row";
+import { rowMatchesCarryOverDay } from "@/domain/work-orders/utils/carry-over-display";
import { isLiveWeek } from "@/domain/work-orders/utils/list-week-utils";
import { isDateInWeek } from "@/domain/work-orders/utils/table-day-utils";
import { addDaysIso, mondayOf, todayIso } from "@/domain/work-orders/utils/wizard-date-utils";
@@ -65,10 +66,17 @@ export function formatDayTabLabel(iso: string): string {
}
export function rowMatchesSelectedDay(
- row: Pick,
+ row: Pick,
selectedDayIso: string,
+ weekMonday?: string,
): boolean {
- return Boolean(row.scheduledOn) && row.scheduledOn.slice(0, 10) === selectedDayIso;
+ if (Boolean(row.scheduledOn) && row.scheduledOn.slice(0, 10) === selectedDayIso) {
+ return true;
+ }
+ if (!weekMonday) {
+ return false;
+ }
+ return rowMatchesCarryOverDay(row, selectedDayIso, weekMonday);
}
export function buildSelectedDayGroup(
@@ -83,6 +91,6 @@ export function buildSelectedDayGroup(
key: `${month}/${day}`,
label: formatDayTabLabel(date),
date,
- items: scheduled.filter((row) => rowMatchesSelectedDay(row, date)),
+ items: scheduled.filter((row) => rowMatchesSelectedDay(row, date, weekMonday)),
};
}
diff --git a/src/test/app/(protected)/workorders/carried-over-counter-pill.test.tsx b/src/test/app/(protected)/workorders/carried-over-counter-pill.test.tsx
index 277f4a73..d0059873 100644
--- a/src/test/app/(protected)/workorders/carried-over-counter-pill.test.tsx
+++ b/src/test/app/(protected)/workorders/carried-over-counter-pill.test.tsx
@@ -18,6 +18,20 @@ describe("CarriedOverCounterPill", () => {
expect(pill).toHaveTextContent("3");
});
+ it("includes the source week in the accessible name", () => {
+ renderWithProviders(
+ ,
+ );
+ expect(screen.getByLabelText(/Source week: 5\/4–5\/8/i)).toBeInTheDocument();
+ });
+
+ it("hides after resolution even when the counter remains", () => {
+ const { container } = renderWithProviders(
+ ,
+ );
+ expect(container).toBeEmptyDOMElement();
+ });
+
it("is distinguishable from the reschedule pill beside it", () => {
renderWithProviders(
<>
diff --git a/src/test/app/(protected)/workorders/use-work-orders-list-filters-week.test.tsx b/src/test/app/(protected)/workorders/use-work-orders-list-filters-week.test.tsx
new file mode 100644
index 00000000..223a9a96
--- /dev/null
+++ b/src/test/app/(protected)/workorders/use-work-orders-list-filters-week.test.tsx
@@ -0,0 +1,30 @@
+import { act, renderHook } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+import { useWorkOrdersListFilters } from "@/app/(protected)/workorders/_hooks/use-work-orders-list-filters";
+import { workOrdersApi } from "@/domain/work-orders/api/work-orders-api";
+
+vi.mock("@/hooks/use-debounce", () => ({
+ useDebounce: (value: T) => value,
+}));
+
+vi.mock("@/domain/work-orders/api/work-orders-api", () => ({
+ workOrdersApi: {
+ patchBoardField: vi.fn(),
+ getBoard: vi.fn(),
+ },
+}));
+
+describe("useWorkOrdersListFilters week navigation", () => {
+ it("changes the viewed week without patching schedule fields", () => {
+ const { result } = renderHook(() => useWorkOrdersListFilters());
+
+ act(() => {
+ result.current.setWeekMonday("2026-05-18");
+ });
+
+ expect(result.current.weekMonday).toBe("2026-05-18");
+ expect(result.current.selectedDayIso).toBe("2026-05-18");
+ expect(result.current.boardParams.weekStart).toBe("2026-05-18");
+ expect(workOrdersApi.patchBoardField).not.toHaveBeenCalled();
+ });
+});
diff --git a/src/test/domain/work-orders/mappers/work-order-table-row-mapper.test.ts b/src/test/domain/work-orders/mappers/work-order-table-row-mapper.test.ts
index b0208e84..81b8e3e8 100644
--- a/src/test/domain/work-orders/mappers/work-order-table-row-mapper.test.ts
+++ b/src/test/domain/work-orders/mappers/work-order-table-row-mapper.test.ts
@@ -2,6 +2,7 @@ import { describe, expect, it } from "vitest";
import { mapWorkOrderTableRow } from "@/domain/work-orders/mappers/work-order-table-row-mapper";
import { WORK_ORDER_TYPE_API } from "@/domain/work-orders/types/work-order-board";
+import { buildSelectedDayGroup } from "@/domain/work-orders/utils/week-day-nav-utils";
describe("mapWorkOrderTableRow isAddOn", () => {
it("reads isAddOn from board DTO when present", () => {
@@ -137,3 +138,32 @@ describe("mapWorkOrderTableRow primaryDispatchStatus", () => {
expect(row.primaryDispatchId).toBe(11);
});
});
+
+describe("mapWorkOrderTableRow carry-over fields", () => {
+ it("maps carriedOver and originalWeek without rewriting scheduledOn", () => {
+ const row = mapWorkOrderTableRow({
+ id: 41,
+ workOrderType: WORK_ORDER_TYPE_API.PM,
+ scheduledDate: "2026-05-13",
+ carriedOver: 2,
+ originalWeek: "2026-05-11",
+ });
+ expect(row.carriedOver).toBe(2);
+ expect(row.originalWeek).toBe("2026-05-11");
+ expect(row.scheduledOn).toBe("2026-05-13");
+ });
+
+ it("shows an unresolved prior-week row on the next week's matching day tab", () => {
+ const row = mapWorkOrderTableRow({
+ id: 42,
+ workOrderType: WORK_ORDER_TYPE_API.PM,
+ scheduledDate: "2026-05-13",
+ lifecycleStatus: 3,
+ carriedOver: 2,
+ originalWeek: "2026-05-11",
+ });
+ const group = buildSelectedDayGroup("2026-05-18", "2026-05-20", [row]);
+ expect(row.status).toBe("Scheduled");
+ expect(group.items.map((item) => item.id)).toEqual([42]);
+ });
+});
diff --git a/src/test/domain/work-orders/utils/carry-over-display.test.ts b/src/test/domain/work-orders/utils/carry-over-display.test.ts
new file mode 100644
index 00000000..00b54dfe
--- /dev/null
+++ b/src/test/domain/work-orders/utils/carry-over-display.test.ts
@@ -0,0 +1,94 @@
+import { describe, expect, it } from "vitest";
+import {
+ applyCarryOverDisplayDayFields,
+ formatCarriedOverHover,
+ isCarryOverDisplayRow,
+ mapCarryOverDisplayDay,
+ resolveCarryOverSourceWeek,
+ shouldShowCarriedOverPill,
+} from "@/domain/work-orders/utils/carry-over-display";
+
+describe("carry-over-display", () => {
+ it("hides the pill below 2 and on terminal statuses", () => {
+ expect(shouldShowCarriedOverPill(1, "Scheduled")).toBe(false);
+ expect(shouldShowCarriedOverPill(2, "Scheduled")).toBe(true);
+ expect(shouldShowCarriedOverPill(3, "Completed")).toBe(false);
+ expect(shouldShowCarriedOverPill(3, "Canceled")).toBe(false);
+ });
+
+ it("formats hover without a source week", () => {
+ expect(formatCarriedOverHover(2)).toBe(
+ "Carried over 2 times — this WO was not completed and moved to the next period",
+ );
+ });
+
+ it("appends the source week label from originalWeek", () => {
+ expect(formatCarriedOverHover(3, "2026-05-04")).toBe(
+ "Carried over 3 times — this WO was not completed and moved to the next period. Source week: 5/4–5/8.",
+ );
+ });
+
+ it("resolves source week from originalWeek, then originalDate, then scheduledOn", () => {
+ expect(resolveCarryOverSourceWeek({ originalWeek: "2026-05-04" })).toBe("2026-05-04");
+ expect(resolveCarryOverSourceWeek({ originalDate: "2026-05-06" })).toBe("2026-05-04");
+ expect(resolveCarryOverSourceWeek({ scheduledOn: "2026-05-13" })).toBe("2026-05-11");
+ expect(resolveCarryOverSourceWeek({})).toBe("");
+ });
+
+ it("treats non-terminal rows scheduled before the viewed week as carry-over", () => {
+ expect(
+ isCarryOverDisplayRow(
+ { scheduledOn: "2026-05-13", status: "Scheduled", carriedOver: 1 },
+ "2026-05-18",
+ ),
+ ).toBe(true);
+ expect(
+ isCarryOverDisplayRow(
+ { scheduledOn: "2026-05-20", status: "Scheduled", carriedOver: 2 },
+ "2026-05-18",
+ ),
+ ).toBe(false);
+ expect(
+ isCarryOverDisplayRow(
+ { scheduledOn: "2026-05-13", status: "Completed", carriedOver: 2 },
+ "2026-05-18",
+ ),
+ ).toBe(false);
+ });
+
+ it("maps the original weekday onto the viewed week and weekends onto Monday", () => {
+ expect(mapCarryOverDisplayDay("2026-05-13", "2026-05-18")).toBe("2026-05-20");
+ expect(mapCarryOverDisplayDay("2026-05-16", "2026-05-18")).toBe("2026-05-18");
+ expect(mapCarryOverDisplayDay("2026-05-17", "2026-05-18")).toBe("2026-05-18");
+ });
+
+ it("remaps dayGroup to the viewed weekday without changing scheduledOn", () => {
+ const row = {
+ scheduledOn: "2026-05-13",
+ status: "Scheduled",
+ dayGroup: "05/13",
+ dayLabel: "WED · 05/13",
+ };
+ const next = applyCarryOverDisplayDayFields(row, "2026-05-18");
+ expect(next.scheduledOn).toBe("2026-05-13");
+ expect(next.dayGroup).toBe("05/20");
+ expect(next.dayLabel).toBe("WED · 05/20");
+ });
+
+ it("leaves in-week and terminal rows' dayGroup unchanged", () => {
+ const inWeek = {
+ scheduledOn: "2026-05-20",
+ status: "Scheduled",
+ dayGroup: "05/20",
+ dayLabel: "WED · 05/20",
+ };
+ const completed = {
+ scheduledOn: "2026-05-13",
+ status: "Completed",
+ dayGroup: "05/13",
+ dayLabel: "WED · 05/13",
+ };
+ expect(applyCarryOverDisplayDayFields(inWeek, "2026-05-18")).toEqual(inWeek);
+ expect(applyCarryOverDisplayDayFields(completed, "2026-05-18")).toEqual(completed);
+ });
+});
diff --git a/src/test/domain/work-orders/utils/table-filter-utils.test.ts b/src/test/domain/work-orders/utils/table-filter-utils.test.ts
index fb68c6f4..85651d2c 100644
--- a/src/test/domain/work-orders/utils/table-filter-utils.test.ts
+++ b/src/test/domain/work-orders/utils/table-filter-utils.test.ts
@@ -137,6 +137,28 @@ describe("filterByBarAndSearch", () => {
expect(ids).toEqual(["in"]);
});
+ it("keeps prior-week unresolved carry-over rows that match a one-character search", () => {
+ const carried = makeRow({
+ id: "co",
+ woNumber: "WO-CARRY",
+ scheduledOn: "2026-05-13",
+ status: "Scheduled",
+ ...deriveDayFields("2026-05-13"),
+ });
+ const completed = makeRow({
+ id: "done",
+ woNumber: "WO-DONE",
+ scheduledOn: "2026-05-13",
+ status: "Completed",
+ ...deriveDayFields("2026-05-13"),
+ });
+
+ const ids = filterByBarAndSearch([carried, completed], bar("2026-05-18"), "W").map(
+ (row) => row.id,
+ );
+ expect(ids).toEqual(["co"]);
+ });
+
it("keeps the union of selected types (OR)", () => {
const pm = makeRow({ id: "pm", type: "PM", scheduledOn: "2026-06-03" });
const emergency = makeRow({ id: "em", type: "Emergency", scheduledOn: "2026-06-03" });
diff --git a/src/test/domain/work-orders/utils/week-day-nav-utils.test.ts b/src/test/domain/work-orders/utils/week-day-nav-utils.test.ts
index 0439dc61..ec6c9bc3 100644
--- a/src/test/domain/work-orders/utils/week-day-nav-utils.test.ts
+++ b/src/test/domain/work-orders/utils/week-day-nav-utils.test.ts
@@ -179,3 +179,44 @@ describe("week-day-nav-utils (SH-240)", () => {
).toBe(false);
});
});
+
+describe("week-day-nav-utils carry-over display", () => {
+ it("places a prior-week unresolved WO on the same weekday of the viewed week", () => {
+ const carried = row({
+ id: "co",
+ scheduledOn: "2026-05-13",
+ dayGroup: "05/13",
+ status: "Scheduled",
+ carriedOver: 2,
+ originalWeek: "2026-05-11",
+ });
+ const nativeWed = row({
+ id: "native",
+ scheduledOn: "2026-05-20",
+ dayGroup: "05/20",
+ status: "Scheduled",
+ });
+
+ expect(rowMatchesSelectedDay(carried, "2026-05-20", "2026-05-18")).toBe(true);
+ expect(rowMatchesSelectedDay(carried, "2026-05-18", "2026-05-18")).toBe(false);
+
+ const group = buildSelectedDayGroup("2026-05-18", "2026-05-20", [carried, nativeWed]);
+ expect(group.items.map((item) => item.id)).toEqual(["co", "native"]);
+ });
+
+ it("does not map completed carry-overs onto the viewed week", () => {
+ const completed = row({
+ id: "done",
+ scheduledOn: "2026-05-13",
+ status: "Completed",
+ carriedOver: 2,
+ });
+ expect(rowMatchesSelectedDay(completed, "2026-05-20", "2026-05-18")).toBe(false);
+ expect(buildSelectedDayGroup("2026-05-18", "2026-05-20", [completed]).items).toEqual([]);
+ });
+
+ it("keeps unscheduled rows out of day groups", () => {
+ const unscheduled = row({ id: "us", scheduledOn: "", scheduleWeekOnly: true, carriedOver: 2 });
+ expect(rowMatchesSelectedDay(unscheduled, "2026-05-18", "2026-05-18")).toBe(false);
+ });
+});