mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 16:19:01 +00:00
Compare commits
2 commits
3fb629dee3
...
481fa29f42
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
481fa29f42 | ||
|
|
d908b7e1da |
8 changed files with 123 additions and 17 deletions
12
.github/workflows/ci.yaml
vendored
12
.github/workflows/ci.yaml
vendored
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
build-and-test:
|
||||
name: Build and test
|
||||
# Org reusable workflow (Node 24): format check, lint, build, unit tests.
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-frontend.yaml@main
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-frontend.yaml@af0f002e14a08cdbfd879c1183bfe7eb2604bce9 # v1.0.8
|
||||
with:
|
||||
node-version: "24"
|
||||
|
||||
|
|
@ -35,10 +35,10 @@ jobs:
|
|||
env:
|
||||
GOVERNANCE_BASE: ${{ github.event_name == 'pull_request' && format('origin/{0}', github.base_ref) || github.event_name == 'push' && github.event.before || 'origin/dev' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: npm
|
||||
|
|
@ -50,8 +50,8 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
container: mcr.microsoft.com/playwright:v1.61.1-noble
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: npm
|
||||
|
|
@ -59,7 +59,7 @@ jobs:
|
|||
- run: npm run test:e2e:visual
|
||||
- name: Upload visual diff artifacts
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: visual-regression-diffs
|
||||
path: |
|
||||
|
|
|
|||
2
.github/workflows/deploy.yml
vendored
2
.github/workflows/deploy.yml
vendored
|
|
@ -27,7 +27,7 @@ concurrency:
|
|||
|
||||
jobs:
|
||||
deploy:
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@main
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@af0f002e14a08cdbfd879c1183bfe7eb2604bce9 # v1.0.8
|
||||
with:
|
||||
node-version: "24"
|
||||
region: us-east-1
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ type DateCellProps = {
|
|||
defaultMode?: "week" | "date";
|
||||
/** When set, shows Clear in the popover so schedule can be removed (e.g. day-group → Unscheduled). */
|
||||
onClear?: () => void;
|
||||
/** Screen-reader-only status appended inside the button so the accessible name keeps the visible date and adds durable state. */
|
||||
srStatusSuffix?: string;
|
||||
};
|
||||
|
||||
export function DateCell({
|
||||
|
|
@ -29,6 +31,7 @@ export function DateCell({
|
|||
onSaveWeek,
|
||||
defaultMode,
|
||||
onClear,
|
||||
srStatusSuffix,
|
||||
}: DateCellProps) {
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
||||
const [cellMode, setCellMode] = useState<"week" | "date">(
|
||||
|
|
@ -81,6 +84,7 @@ export function DateCell({
|
|||
</>
|
||||
)}
|
||||
</span>
|
||||
{Boolean(srStatusSuffix) && <span className="sr-only">{srStatusSuffix}</span>}
|
||||
<ChevronDown
|
||||
size={14}
|
||||
className="ml-2 shrink-0 opacity-0 transition-opacity group-hover/cell:opacity-100"
|
||||
|
|
|
|||
|
|
@ -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,26 @@ 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 <DateCell value={value} q={q} highlight={highlight} onSave={onSave} />;
|
||||
return (
|
||||
<DateCell
|
||||
value={value}
|
||||
q={q}
|
||||
highlight={highlight}
|
||||
onSave={onSave}
|
||||
srStatusSuffix={pastDue ? "Past due" : undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -43,7 +64,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"
|
||||
>
|
||||
<span className="flex min-w-0 flex-1 items-center gap-1.5 overflow-hidden">
|
||||
|
|
|
|||
|
|
@ -51,14 +51,18 @@ export function CarriedOverCounterPill({ count }: { count: number }) {
|
|||
return null;
|
||||
}
|
||||
|
||||
const label = `Carried over ${count} times`;
|
||||
|
||||
return (
|
||||
<span
|
||||
title="Carried over"
|
||||
className="inline-flex h-[18px] items-center rounded px-1.5 text-[10px] font-semibold"
|
||||
style={MUTED_PILL_STYLE}
|
||||
>
|
||||
{count}
|
||||
</span>
|
||||
<Tooltip title="Carried over" arrow placement="top">
|
||||
<span
|
||||
aria-label={label}
|
||||
className="inline-flex h-[18px] items-center rounded px-1.5 text-[10px] font-semibold"
|
||||
style={MUTED_PILL_STYLE}
|
||||
>
|
||||
{count}
|
||||
</span>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ export function WoTableRowCells({
|
|||
value={row.dueDate}
|
||||
q={search}
|
||||
highlight={forceDueDateHighlight || row.isPastDue}
|
||||
pastDue={row.isPastDue}
|
||||
onSave={(date) => onPatchRow({ dueDate: date })}
|
||||
/>
|
||||
</td>
|
||||
|
|
|
|||
|
|
@ -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(<CarriedOverCounterPill count={1} />);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("names the count so it is not a bare number", () => {
|
||||
renderWithProviders(<CarriedOverCounterPill count={3} />);
|
||||
const pill = screen.getByLabelText(/Carried over 3 times/i);
|
||||
expect(pill).toHaveTextContent("3");
|
||||
});
|
||||
|
||||
it("is distinguishable from the reschedule pill beside it", () => {
|
||||
renderWithProviders(
|
||||
<>
|
||||
<RescheduleCounterPill count={2} />
|
||||
<CarriedOverCounterPill count={2} />
|
||||
</>,
|
||||
);
|
||||
|
||||
// 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();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
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(
|
||||
<DueDateCell type="Emergency" value="2026-07-03" pastDue highlight onSave={vi.fn()} />,
|
||||
);
|
||||
expect(screen.getByLabelText("Due Date, past due")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("keeps the plain name when the work order is not past due", () => {
|
||||
renderWithProviders(<DueDateCell type="Emergency" value="2026-07-03" onSave={vi.fn()} />);
|
||||
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(
|
||||
<DueDateCell type="Emergency" value="2026-07-03" highlight onSave={vi.fn()} />,
|
||||
);
|
||||
expect(screen.getByLabelText("Due Date")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("announces past due on non-Emergency types too", () => {
|
||||
renderWithProviders(<DueDateCell type="PM" value="2026-07-03" pastDue onSave={vi.fn()} />);
|
||||
// The visible date stays the leading part of the button's accessible name
|
||||
// and the durable past-due state is appended to it.
|
||||
expect(screen.getByRole("button", { name: /07-03\s*Past due/ })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not announce past due for a non-Emergency 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(<DueDateCell type="PM" value="2026-07-03" highlight onSave={vi.fn()} />);
|
||||
expect(screen.getByRole("button", { name: "07-03" })).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue