diff --git a/src/app/(protected)/workorders/_components/detail/slide-over-header.tsx b/src/app/(protected)/workorders/_components/detail/slide-over-header.tsx
index 32facebb..ff9b80e1 100644
--- a/src/app/(protected)/workorders/_components/detail/slide-over-header.tsx
+++ b/src/app/(protected)/workorders/_components/detail/slide-over-header.tsx
@@ -36,7 +36,10 @@ export function SlideOverHeader({
>
#{row.woNumber || "—"}
-
+ {/* Column pills start at 1. This header keeps the previous threshold of 2. */}
+ {row.rescheduleCount >= 2 && (
+
+ )}
{Boolean(row.avetaRequired) && (
)}
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 32c13d49..4bf19890 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
@@ -31,7 +31,7 @@ export function RescheduleCounterPill({
count: number;
originalDate?: string;
}) {
- if (count < 2) {
+ if (count < 1) {
return null;
}
diff --git a/src/domain/work-orders/utils/carry-over-display.ts b/src/domain/work-orders/utils/carry-over-display.ts
index dd92b2d3..8b14ddfa 100644
--- a/src/domain/work-orders/utils/carry-over-display.ts
+++ b/src/domain/work-orders/utils/carry-over-display.ts
@@ -18,7 +18,7 @@ export function isTerminalCarryOverStatus(status?: string): boolean {
}
export function shouldShowCarriedOverPill(count: number, status?: string): boolean {
- return count >= 2 && !isTerminalCarryOverStatus(status);
+ return count >= 1 && !isTerminalCarryOverStatus(status);
}
export function toIsoDate(value?: string): string {
diff --git a/src/domain/work-orders/utils/reschedule-indicator.ts b/src/domain/work-orders/utils/reschedule-indicator.ts
index 26bee35e..4ca4ac29 100644
--- a/src/domain/work-orders/utils/reschedule-indicator.ts
+++ b/src/domain/work-orders/utils/reschedule-indicator.ts
@@ -4,7 +4,7 @@ export function formatRescheduleCountLabel(count: number): string {
return count === 1 ? "1 time" : `${count} times`;
}
-/** Hover copy for the Reschedule pill (count ≥ 2). Uses originalDate when the API sends it. */
+/** Hover copy for the Reschedule pill (count ≥ 1). Uses originalDate when the API sends it. */
export function formatRescheduleHover(count: number, originalDate?: string): string {
const times = formatRescheduleCountLabel(count);
const base = `Rescheduled ${times} — this WO has had its date changed ${times}.`;
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 d0059873..343ddc41 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
@@ -7,11 +7,17 @@ import {
import { renderWithProviders } from "@/test/test-utils";
describe("CarriedOverCounterPill", () => {
- it("hides the pill when the count is below 2", () => {
- const { container } = renderWithProviders();
+ it("hides the pill when the count is 0", () => {
+ const { container } = renderWithProviders();
expect(container).toBeEmptyDOMElement();
});
+ it("shows the pill from the first carry-over", () => {
+ renderWithProviders();
+ const pill = screen.getByLabelText(/^Carried over 1 time/i);
+ expect(pill).toHaveTextContent("1");
+ });
+
it("names the count so it is not a bare number", () => {
renderWithProviders();
const pill = screen.getByLabelText(/Carried over 3 times/i);
@@ -26,10 +32,23 @@ describe("CarriedOverCounterPill", () => {
});
it("hides after resolution even when the counter remains", () => {
- const { container } = renderWithProviders(
+ const completed = renderWithProviders(
,
);
- expect(container).toBeEmptyDOMElement();
+ expect(completed.container).toBeEmptyDOMElement();
+
+ const canceled = renderWithProviders();
+ expect(canceled.container).toBeEmptyDOMElement();
+
+ const completedOnce = renderWithProviders(
+ ,
+ );
+ expect(completedOnce.container).toBeEmptyDOMElement();
+
+ const canceledOnce = renderWithProviders(
+ ,
+ );
+ expect(canceledOnce.container).toBeEmptyDOMElement();
});
it("is distinguishable from the reschedule pill beside it", () => {
diff --git a/src/test/app/(protected)/workorders/reschedule-counter-pill.test.tsx b/src/test/app/(protected)/workorders/reschedule-counter-pill.test.tsx
index 8f81de8d..10742ffd 100644
--- a/src/test/app/(protected)/workorders/reschedule-counter-pill.test.tsx
+++ b/src/test/app/(protected)/workorders/reschedule-counter-pill.test.tsx
@@ -4,11 +4,17 @@ import { RescheduleCounterPill } from "@/app/(protected)/workorders/_components/
import { renderWithProviders } from "@/test/test-utils";
describe("RescheduleCounterPill", () => {
- it("hides the pill when the count is below 2", () => {
- const { container } = renderWithProviders();
+ it("hides the pill when the count is 0", () => {
+ const { container } = renderWithProviders();
expect(container).toBeEmptyDOMElement();
});
+ it("shows the pill from the first reschedule", () => {
+ renderWithProviders();
+ const pill = screen.getByLabelText(/^Rescheduled 1 time/i);
+ expect(pill).toHaveTextContent("1");
+ });
+
it("renders a purple count pill with history tooltip from originalDate", () => {
renderWithProviders();
const pill = screen.getByLabelText(/Rescheduled 4 times.*Original date: May 8, 2026/i);
diff --git a/src/test/app/(protected)/workorders/slide-over-header-reschedule-pill.test.tsx b/src/test/app/(protected)/workorders/slide-over-header-reschedule-pill.test.tsx
new file mode 100644
index 00000000..f06f5fe3
--- /dev/null
+++ b/src/test/app/(protected)/workorders/slide-over-header-reschedule-pill.test.tsx
@@ -0,0 +1,37 @@
+import { screen } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+import { SlideOverHeader } from "@/app/(protected)/workorders/_components/detail/slide-over-header";
+import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
+import { renderWithProviders } from "@/test/test-utils";
+
+function rowWithReschedules(count: number): WorkOrderTableRow {
+ return {
+ woNumber: "24880",
+ rescheduleCount: count,
+ originalDate: "2026-05-19",
+ avetaRequired: false,
+ site: "",
+ isPastDue: false,
+ status: "",
+ type: "",
+ } as WorkOrderTableRow;
+}
+
+describe("SlideOverHeader reschedule pill", () => {
+ it("keeps a single reschedule out of the detail header", () => {
+ renderWithProviders(
+ undefined} />,
+ );
+
+ expect(screen.getByRole("heading", { name: "#24880" })).toBeInTheDocument();
+ expect(screen.queryByLabelText(/^Rescheduled 1 time/i)).not.toBeInTheDocument();
+ });
+
+ it("still shows the pill in the detail header from the second reschedule", () => {
+ renderWithProviders(
+ undefined} />,
+ );
+
+ expect(screen.getByLabelText(/^Rescheduled 2 times/i)).toHaveTextContent("2");
+ });
+});
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
index 00b54dfe..db449608 100644
--- a/src/test/domain/work-orders/utils/carry-over-display.test.ts
+++ b/src/test/domain/work-orders/utils/carry-over-display.test.ts
@@ -9,10 +9,13 @@ import {
} 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);
+ it("shows the pill from 1 and hides it on terminal statuses", () => {
+ expect(shouldShowCarriedOverPill(0, "Scheduled")).toBe(false);
+ expect(shouldShowCarriedOverPill(1, "Scheduled")).toBe(true);
expect(shouldShowCarriedOverPill(2, "Scheduled")).toBe(true);
+ expect(shouldShowCarriedOverPill(1, "Completed")).toBe(false);
expect(shouldShowCarriedOverPill(3, "Completed")).toBe(false);
+ expect(shouldShowCarriedOverPill(1, "Canceled")).toBe(false);
expect(shouldShowCarriedOverPill(3, "Canceled")).toBe(false);
});
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 56097d24..51a9e7f9 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
@@ -565,6 +565,56 @@ describe("matchesAdvancedFilters", () => {
expect(matchesAdvancedFilters(completedUndated, unscheduledOnly)).toBe(false);
expect(matchesAdvancedFilters(canceledUndated, unscheduledOnly)).toBe(false);
});
+
+ it("keeps Rescheduled and Carried Over at count 2 or more", () => {
+ const weekMonday = mondayOf(todayIso());
+ const dated = { scheduledOn: weekMonday, ...deriveDayFields(weekMonday) };
+ const openRange = {
+ ...EMPTY_ADVANCED_FILTERS,
+ dateRange: "custom" as const,
+ customFrom: "0000-01-01",
+ customTo: "9999-12-31",
+ };
+
+ expect(
+ matchesAdvancedFilters(makeRow({ ...dated, rescheduleCount: 1 }), {
+ ...openRange,
+ rescheduled: true,
+ }),
+ ).toBe(false);
+ expect(
+ matchesAdvancedFilters(makeRow({ ...dated, rescheduleCount: 2 }), {
+ ...openRange,
+ rescheduled: true,
+ }),
+ ).toBe(true);
+ expect(
+ matchesAdvancedFilters(makeRow({ ...dated, carriedOver: 1 }), {
+ ...openRange,
+ carriedOver: true,
+ }),
+ ).toBe(false);
+ expect(
+ matchesAdvancedFilters(makeRow({ ...dated, carriedOver: 2 }), {
+ ...openRange,
+ carriedOver: true,
+ }),
+ ).toBe(true);
+ expect(
+ matchesAdvancedFilters(makeRow({ ...dated, rescheduleCount: 2, carriedOver: 1 }), {
+ ...openRange,
+ rescheduled: true,
+ carriedOver: true,
+ }),
+ ).toBe(false);
+ expect(
+ matchesAdvancedFilters(makeRow({ ...dated, rescheduleCount: 2, carriedOver: 2 }), {
+ ...openRange,
+ rescheduled: true,
+ carriedOver: true,
+ }),
+ ).toBe(true);
+ });
});
describe("computeIsPastDue", () => {