diff --git a/src/app/(protected)/workorders/_components/list/table/cells/due-date-cell.tsx b/src/app/(protected)/workorders/_components/list/table/cells/due-date-cell.tsx
index c212b795..1c30600c 100644
--- a/src/app/(protected)/workorders/_components/list/table/cells/due-date-cell.tsx
+++ b/src/app/(protected)/workorders/_components/list/table/cells/due-date-cell.tsx
@@ -10,6 +10,12 @@ type DueDateCellProps = {
value: string;
q?: string;
highlight?: boolean;
+ /**
+ * Past-due is durable business state, unlike `highlight`, which is also raised
+ * transiently to draw attention to a row. Announce it so the state does not
+ * read by colour alone.
+ */
+ pastDue?: boolean;
onSave: (date: string) => void;
};
@@ -17,11 +23,23 @@ type DueDateCellProps = {
* Board / slide-over Due Date editor.
* Emergency → dedicated calendar Dialog; other types → standard DateCell popover.
*/
-export function DueDateCell({ type, value, q, highlight, onSave }: DueDateCellProps) {
+export function DueDateCell({
+ type,
+ value,
+ q,
+ highlight,
+ pastDue = false,
+ onSave,
+}: DueDateCellProps) {
const [open, setOpen] = useState(false);
if (type !== "Emergency") {
- return ;
+ return (
+ <>
+
+ {pastDue && Past due}
+ >
+ );
}
return (
@@ -43,7 +61,7 @@ export function DueDateCell({ type, value, q, highlight, onSave }: DueDateCellPr
}
: undefined
}
- aria-label="Due Date"
+ aria-label={pastDue ? "Due Date, past due" : "Due Date"}
aria-haspopup="dialog"
>
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 3dad9c2d..a98552ae 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
@@ -51,14 +51,18 @@ export function CarriedOverCounterPill({ count }: { count: number }) {
return null;
}
+ const label = `Carried over ${count} times`;
+
return (
-
- {count}
-
+
+
+ {count}
+
+
);
}
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 bb904aaa..aac347e1 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
@@ -141,6 +141,7 @@ export function WoTableRowCells({
value={row.dueDate}
q={search}
highlight={forceDueDateHighlight || row.isPastDue}
+ pastDue={row.isPastDue}
onSave={(date) => onPatchRow({ dueDate: date })}
/>
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
new file mode 100644
index 00000000..277f4a73
--- /dev/null
+++ b/src/test/app/(protected)/workorders/carried-over-counter-pill.test.tsx
@@ -0,0 +1,34 @@
+import { screen } from "@testing-library/react";
+import { describe, expect, it } from "vitest";
+import {
+ CarriedOverCounterPill,
+ RescheduleCounterPill,
+} from "@/app/(protected)/workorders/_components/list/table/cells/wo-counter-pills";
+import { renderWithProviders } from "@/test/test-utils";
+
+describe("CarriedOverCounterPill", () => {
+ it("hides the pill when the count is below 2", () => {
+ const { container } = renderWithProviders();
+ expect(container).toBeEmptyDOMElement();
+ });
+
+ it("names the count so it is not a bare number", () => {
+ renderWithProviders();
+ const pill = screen.getByLabelText(/Carried over 3 times/i);
+ expect(pill).toHaveTextContent("3");
+ });
+
+ it("is distinguishable from the reschedule pill beside it", () => {
+ renderWithProviders(
+ <>
+
+
+ >,
+ );
+
+ // Both render the same visible digit, so the accessible names are the only
+ // thing telling a non-sighted user which counter is which.
+ expect(screen.getByLabelText(/Rescheduled 2 times/i)).toBeInTheDocument();
+ expect(screen.getByLabelText(/Carried over 2 times/i)).toBeInTheDocument();
+ });
+});
diff --git a/src/test/app/(protected)/workorders/due-date-past-due-announcement.test.tsx b/src/test/app/(protected)/workorders/due-date-past-due-announcement.test.tsx
new file mode 100644
index 00000000..9606c43a
--- /dev/null
+++ b/src/test/app/(protected)/workorders/due-date-past-due-announcement.test.tsx
@@ -0,0 +1,33 @@
+import { screen } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+import { DueDateCell } from "@/app/(protected)/workorders/_components/list/table/cells/due-date-cell";
+import { renderWithProviders } from "@/test/test-utils";
+
+describe("Due Date past-due announcement", () => {
+ it("names the past-due state on the Emergency due date control", () => {
+ renderWithProviders(
+ ,
+ );
+ expect(screen.getByLabelText("Due Date, past due")).toBeInTheDocument();
+ });
+
+ it("keeps the plain name when the work order is not past due", () => {
+ renderWithProviders();
+ expect(screen.getByLabelText("Due Date")).toBeInTheDocument();
+ expect(screen.queryByLabelText("Due Date, past due")).not.toBeInTheDocument();
+ });
+
+ it("does not announce past due for a transient highlight alone", () => {
+ // `highlight` is also raised to draw attention to a row, which is not a
+ // durable state and must not be announced as one.
+ renderWithProviders(
+ ,
+ );
+ expect(screen.getByLabelText("Due Date")).toBeInTheDocument();
+ });
+
+ it("announces past due on non-Emergency types too", () => {
+ renderWithProviders();
+ expect(screen.getByText("Past due")).toBeInTheDocument();
+ });
+});