Merge branch 'dev' into feat/ab/sh-278-vendor-service-area
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 139 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 112 KiB |
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 117 KiB |
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 185 KiB |
|
|
@ -1,4 +1,5 @@
|
||||||
import { WO_TABLE_COLS } from "./wo-table-cols";
|
import { WO_TABLE_COLS } from "./wo-table-cols";
|
||||||
|
import { WoTableHeaderLabel } from "./wo-table-header-label";
|
||||||
|
|
||||||
export function WoTableHead() {
|
export function WoTableHead() {
|
||||||
return (
|
return (
|
||||||
|
|
@ -25,7 +26,7 @@ export function WoTableHead() {
|
||||||
borderBottom: "1px solid var(--border)",
|
borderBottom: "1px solid var(--border)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{col.label}
|
<WoTableHeaderLabel colKey={col.key} label={col.label} />
|
||||||
{Boolean(srLabel) && <span className="sr-only">{srLabel}</span>}
|
{Boolean(srLabel) && <span className="sr-only">{srLabel}</span>}
|
||||||
</th>
|
</th>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
import { Tooltip } from "@mui/material";
|
||||||
|
import { CircleQuestionMark } from "lucide-react";
|
||||||
|
|
||||||
|
import { getWoTableHeaderTooltip } from "@/domain/work-orders/constants/wo-table-header-tooltips";
|
||||||
|
|
||||||
|
type WoTableHeaderLabelProps = {
|
||||||
|
colKey: string;
|
||||||
|
label: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function WoTableHeaderLabel({ colKey, label }: WoTableHeaderLabelProps) {
|
||||||
|
const tooltip = getWoTableHeaderTooltip(colKey);
|
||||||
|
|
||||||
|
if (!tooltip) {
|
||||||
|
return <>{label}</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span style={{ display: "inline-flex", alignItems: "center", gap: 4 }}>
|
||||||
|
{label}
|
||||||
|
<Tooltip title={tooltip} arrow placement="top">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
aria-label={`${label} column help`}
|
||||||
|
style={{
|
||||||
|
display: "inline-flex",
|
||||||
|
cursor: "help",
|
||||||
|
color: "var(--color-text-muted)",
|
||||||
|
padding: 0,
|
||||||
|
border: 0,
|
||||||
|
background: "transparent",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CircleQuestionMark size={13} aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</Tooltip>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
17
src/domain/work-orders/constants/wo-table-header-tooltips.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
/**
|
||||||
|
* Help copy for date-column headers on the work orders board table.
|
||||||
|
* Keyed by the `WO_TABLE_COLS` column key; only columns with copy here show a
|
||||||
|
* header help icon. `appt` (Appt Time) is an intentional open point — do not
|
||||||
|
* add it without a business-rule decision.
|
||||||
|
*/
|
||||||
|
export const WO_TABLE_HEADER_TOOLTIPS = {
|
||||||
|
sched: "The date the work is planned to happen. Setting it moves the work order to Scheduled.",
|
||||||
|
due: "The deadline for the work — when it must be completed by.",
|
||||||
|
completed: "The date the vendor actually finished the work. Required to complete the work order.",
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export function getWoTableHeaderTooltip(colKey: string): string | undefined {
|
||||||
|
return colKey in WO_TABLE_HEADER_TOOLTIPS
|
||||||
|
? WO_TABLE_HEADER_TOOLTIPS[colKey as keyof typeof WO_TABLE_HEADER_TOOLTIPS]
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { screen, within } from "@testing-library/react";
|
||||||
|
import userEvent from "@testing-library/user-event";
|
||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
|
||||||
|
import { WoTableHead } from "@/app/(protected)/workorders/_components/list/table/wo-table-head";
|
||||||
|
import { getWoTableHeaderTooltip } from "@/domain/work-orders/constants/wo-table-header-tooltips";
|
||||||
|
import { renderWithProviders } from "@/test/test-utils";
|
||||||
|
|
||||||
|
const CASES = [
|
||||||
|
{
|
||||||
|
columnName: "SCHEDULE ON",
|
||||||
|
tooltip:
|
||||||
|
"The date the work is planned to happen. Setting it moves the work order to Scheduled.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
columnName: "DUE DATE",
|
||||||
|
tooltip: "The deadline for the work — when it must be completed by.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
columnName: "COMPLETED DATE",
|
||||||
|
tooltip: "The date the vendor actually finished the work. Required to complete the work order.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function renderHead() {
|
||||||
|
return renderWithProviders(
|
||||||
|
<table>
|
||||||
|
<WoTableHead />
|
||||||
|
</table>,
|
||||||
|
{ withAuth: false },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("WoTableHead date column header tooltips (SH-195)", () => {
|
||||||
|
it.each(CASES)(
|
||||||
|
"shows the $columnName help icon and tooltip copy on hover",
|
||||||
|
async ({ columnName, tooltip }) => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
renderHead();
|
||||||
|
|
||||||
|
const header = screen.getByRole("columnheader", { name: new RegExp(`^${columnName}`) });
|
||||||
|
const icon = within(header).getByRole("button", { name: `${columnName} column help` });
|
||||||
|
|
||||||
|
await user.hover(icon);
|
||||||
|
|
||||||
|
const tip = await screen.findByText(tooltip);
|
||||||
|
expect(tip).toBeInTheDocument();
|
||||||
|
},
|
||||||
|
15000,
|
||||||
|
);
|
||||||
|
|
||||||
|
it("moves focus to the help icon on tab and keeps it keyboard reachable", async () => {
|
||||||
|
const user = userEvent.setup();
|
||||||
|
renderHead();
|
||||||
|
|
||||||
|
const header = screen.getByRole("columnheader", { name: /^SCHEDULE ON/ });
|
||||||
|
const icon = within(header).getByRole("button", { name: "SCHEDULE ON column help" });
|
||||||
|
|
||||||
|
await user.tab();
|
||||||
|
|
||||||
|
expect(icon).toHaveFocus();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not add a help icon to Appt Time (open point, not implemented)", () => {
|
||||||
|
renderHead();
|
||||||
|
|
||||||
|
const header = screen.getByRole("columnheader", { name: "APPT TIME" });
|
||||||
|
expect(within(header).queryByRole("img")).not.toBeInTheDocument();
|
||||||
|
expect(getWoTableHeaderTooltip("appt")).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||