mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-05 23:32:06 +00:00
feat(work-orders): show counter pills from the first occurrence
Dispatchers need reschedule and carried-over counts after one event. Advanced filters and the detail header stay at two or more.
This commit is contained in:
parent
bb9f4b88c1
commit
96beacd6d6
9 changed files with 130 additions and 12 deletions
|
|
@ -36,7 +36,10 @@ export function SlideOverHeader({
|
|||
>
|
||||
#{row.woNumber || "—"}
|
||||
</Text>
|
||||
<RescheduleCounterPill count={row.rescheduleCount} originalDate={row.originalDate} />
|
||||
{/* Column pills start at 1. This header keeps the previous threshold of 2. */}
|
||||
{row.rescheduleCount >= 2 && (
|
||||
<RescheduleCounterPill count={row.rescheduleCount} originalDate={row.originalDate} />
|
||||
)}
|
||||
{Boolean(row.avetaRequired) && (
|
||||
<AvetaBadge size={13} attached={Boolean(row.hasAvetaDocument)} />
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export function RescheduleCounterPill({
|
|||
count: number;
|
||||
originalDate?: string;
|
||||
}) {
|
||||
if (count < 2) {
|
||||
if (count < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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}.`;
|
||||
|
|
|
|||
|
|
@ -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(<CarriedOverCounterPill count={1} />);
|
||||
it("hides the pill when the count is 0", () => {
|
||||
const { container } = renderWithProviders(<CarriedOverCounterPill count={0} />);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("shows the pill from the first carry-over", () => {
|
||||
renderWithProviders(<CarriedOverCounterPill count={1} status="Scheduled" />);
|
||||
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(<CarriedOverCounterPill count={3} />);
|
||||
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(
|
||||
<CarriedOverCounterPill count={3} originalWeek="2026-05-04" status="Completed" />,
|
||||
);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
expect(completed.container).toBeEmptyDOMElement();
|
||||
|
||||
const canceled = renderWithProviders(<CarriedOverCounterPill count={3} status="Canceled" />);
|
||||
expect(canceled.container).toBeEmptyDOMElement();
|
||||
|
||||
const completedOnce = renderWithProviders(
|
||||
<CarriedOverCounterPill count={1} status="Completed" />,
|
||||
);
|
||||
expect(completedOnce.container).toBeEmptyDOMElement();
|
||||
|
||||
const canceledOnce = renderWithProviders(
|
||||
<CarriedOverCounterPill count={1} status="Canceled" />,
|
||||
);
|
||||
expect(canceledOnce.container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("is distinguishable from the reschedule pill beside it", () => {
|
||||
|
|
|
|||
|
|
@ -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(<RescheduleCounterPill count={1} />);
|
||||
it("hides the pill when the count is 0", () => {
|
||||
const { container } = renderWithProviders(<RescheduleCounterPill count={0} />);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("shows the pill from the first reschedule", () => {
|
||||
renderWithProviders(<RescheduleCounterPill count={1} />);
|
||||
const pill = screen.getByLabelText(/^Rescheduled 1 time/i);
|
||||
expect(pill).toHaveTextContent("1");
|
||||
});
|
||||
|
||||
it("renders a purple count pill with history tooltip from originalDate", () => {
|
||||
renderWithProviders(<RescheduleCounterPill count={4} originalDate="2026-05-08" />);
|
||||
const pill = screen.getByLabelText(/Rescheduled 4 times.*Original date: May 8, 2026/i);
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
<SlideOverHeader row={rowWithReschedules(1)} isCancelled={false} onClose={() => 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(
|
||||
<SlideOverHeader row={rowWithReschedules(2)} isCancelled={false} onClose={() => undefined} />,
|
||||
);
|
||||
|
||||
expect(screen.getByLabelText(/^Rescheduled 2 times/i)).toHaveTextContent("2");
|
||||
});
|
||||
});
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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", () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue