mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-09-30 09:13:11 +00:00
Merge branch 'dev' into feat/ab/sh-195-date-header-tooltips
This commit is contained in:
commit
c081ea8d97
4 changed files with 87 additions and 15 deletions
|
|
@ -9,10 +9,10 @@ import {
|
|||
tableRowToClosabilityInput,
|
||||
} from "@/domain/work-orders/utils/wo-closability";
|
||||
import {
|
||||
BOARD_INLINE_STATUSES,
|
||||
PAST_DUE_BADGE_TOOLTIP,
|
||||
PAST_DUE_POPOVER_BODY,
|
||||
PAST_DUE_RESCHEDULE_CTA,
|
||||
getInlineStatusTransitions,
|
||||
getStatusOptionState,
|
||||
} from "@/domain/work-orders/utils/wo-lifecycle-transitions";
|
||||
import { HoverChevron } from "./hover-chevron";
|
||||
|
|
@ -27,6 +27,7 @@ type StatusCellProps = {
|
|||
onPastDueAttempt?: () => void;
|
||||
onRequestComplete?: () => void;
|
||||
readOnly?: boolean;
|
||||
/** Explicit list for inline create; existing WOs derive valid transitions from `wo.status`. */
|
||||
statusOptions?: readonly WizardWOStatus[];
|
||||
};
|
||||
|
||||
|
|
@ -128,10 +129,11 @@ export function StatusCell({
|
|||
onPastDueAttempt,
|
||||
onRequestComplete,
|
||||
readOnly = false,
|
||||
statusOptions = BOARD_INLINE_STATUSES,
|
||||
statusOptions,
|
||||
}: StatusCellProps) {
|
||||
const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null);
|
||||
const closabilityGaps = rowClosabilityGaps(wo);
|
||||
const options = statusOptions ?? getInlineStatusTransitions(wo.status);
|
||||
|
||||
if (readOnly) {
|
||||
return <ReadOnlyStatusLabel status={wo.status} />;
|
||||
|
|
@ -169,7 +171,7 @@ export function StatusCell({
|
|||
onClick={(e) => e.stopPropagation()}
|
||||
slotProps={{ paper: { className: "min-w-[190px] rounded-lg border border-border p-1" } }}
|
||||
>
|
||||
{statusOptions.map((status) => {
|
||||
{options.map((status) => {
|
||||
const option = getStatusOptionState(wo, status, closabilityGaps);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -11,8 +11,21 @@ import {
|
|||
UPLIFT_PENDING_COMPLETED_TITLE,
|
||||
} from "@/domain/work-orders/utils/wo-closability";
|
||||
|
||||
/** AAP `ALL_STATUSES` — Canceled is kebab-only. */
|
||||
export const BOARD_INLINE_STATUSES: readonly WizardWOStatus[] = ALL_WIZARD_STATUSES;
|
||||
/**
|
||||
* Lifecycle states the server derives (Incomplete from create, Scheduled from Schedule On).
|
||||
* Past Due is an overlay, not a status. None of these may be picked manually on an existing WO.
|
||||
*/
|
||||
export const AUTOMATIC_LIFECYCLE_STATUSES: readonly WizardWOStatus[] = ["Incomplete", "Scheduled"];
|
||||
|
||||
/** Manually selectable statuses for an existing WO — Canceled is kebab-only. */
|
||||
export const BOARD_INLINE_STATUSES: readonly WizardWOStatus[] = ALL_WIZARD_STATUSES.filter(
|
||||
(status) => !AUTOMATIC_LIFECYCLE_STATUSES.includes(status),
|
||||
);
|
||||
|
||||
/** Inline options for an existing WO never repeat its current status. */
|
||||
export function getInlineStatusTransitions(currentStatus: string): WizardWOStatus[] {
|
||||
return BOARD_INLINE_STATUSES.filter((status) => status !== currentStatus);
|
||||
}
|
||||
|
||||
/** AAP `CREATION_STATUSES` — wizard Step 3 and inline create. */
|
||||
export const CREATION_INLINE_STATUSES: readonly WizardWOStatus[] = [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { StatusCell } from "@/app/(protected)/workorders/_components/list/table/cells/status-cell";
|
||||
import type { WizardWOStatus } from "@/domain/work-orders/types/work-order-wizard";
|
||||
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
||||
|
||||
const incompleteRow = {
|
||||
|
|
@ -43,21 +44,62 @@ describe("StatusCell closability gate", () => {
|
|||
expect(screen.queryByRole("button", { name: /^canceled$/i })).toBeNull();
|
||||
});
|
||||
|
||||
it("disables Scheduled when the row has no concrete date", () => {
|
||||
it.each<WizardWOStatus>([
|
||||
"Pending",
|
||||
"En Route",
|
||||
"On Site",
|
||||
"In Progress",
|
||||
"Rescheduled",
|
||||
"Pending Quote",
|
||||
])("offers only manual transitions away from %s (SH-357, SH-358)", (current) => {
|
||||
const onChangeStatus = vi.fn();
|
||||
render(
|
||||
<StatusCell
|
||||
wo={{ ...incompleteRow, status: "Pending", scheduledOn: "" }}
|
||||
wo={{ ...incompleteRow, status: current, scheduledOn: "2026-06-01" }}
|
||||
onChangeStatus={onChangeStatus}
|
||||
onOpenSlideOver={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: /pending/i }));
|
||||
const scheduled = screen.getByRole("button", { name: /^scheduled$/i });
|
||||
expect(scheduled).toBeDisabled();
|
||||
fireEvent.click(scheduled);
|
||||
expect(onChangeStatus).not.toHaveBeenCalled();
|
||||
fireEvent.click(screen.getByRole("button", { name: new RegExp(current, "i") }));
|
||||
const offered = Array.from(document.querySelectorAll(".MuiPopover-paper button")).map(
|
||||
(button) => button.textContent?.trim(),
|
||||
);
|
||||
|
||||
expect(offered).not.toContain("Incomplete");
|
||||
expect(offered).not.toContain("Scheduled");
|
||||
expect(offered).not.toContain("Past Due");
|
||||
expect(offered).not.toContain("Canceled");
|
||||
expect(offered).not.toContain(current);
|
||||
expect(offered).toEqual(
|
||||
[
|
||||
"Pending",
|
||||
"En Route",
|
||||
"On Site",
|
||||
"In Progress",
|
||||
"Completed",
|
||||
"Rescheduled",
|
||||
"Pending Quote",
|
||||
].filter((status) => status !== current),
|
||||
);
|
||||
});
|
||||
|
||||
it("offers every manual status, and no automatic one, from Incomplete or Scheduled", () => {
|
||||
for (const current of ["Incomplete", "Scheduled"] as const) {
|
||||
const { unmount } = render(
|
||||
<StatusCell
|
||||
wo={{ ...incompleteRow, status: current }}
|
||||
onChangeStatus={vi.fn()}
|
||||
onOpenSlideOver={vi.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole("button", { name: new RegExp(current, "i") }));
|
||||
expect(screen.getByRole("button", { name: /^pending$/i })).toBeTruthy();
|
||||
expect(screen.queryByRole("button", { name: /^incomplete$/i })).toBeNull();
|
||||
expect(screen.queryByRole("button", { name: /^scheduled$/i })).toBeNull();
|
||||
unmount();
|
||||
}
|
||||
});
|
||||
|
||||
it("limits creation options to Incomplete, Pending, and Scheduled", () => {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { PENDING_UPLIFT_GAP } from "@/domain/work-orders/utils/wo-closability";
|
||||
import {
|
||||
AUTOMATIC_LIFECYCLE_STATUSES,
|
||||
BOARD_INLINE_STATUSES,
|
||||
COMPLETED_DOC_BLOCK_MSG,
|
||||
COMPLETED_MEDIA_BLOCK_MSG,
|
||||
CREATION_INLINE_STATUSES,
|
||||
SCHEDULED_NEEDS_DATE_MSG,
|
||||
formatAapCompletedBlockTitle,
|
||||
getInlineStatusTransitions,
|
||||
getStatusOptionState,
|
||||
hasConcreteScheduleDate,
|
||||
maybeAutoSchedulePatch,
|
||||
|
|
@ -17,11 +19,9 @@ import {
|
|||
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
|
||||
|
||||
describe("wo-lifecycle-transitions", () => {
|
||||
it("lists AAP board statuses without Canceled", () => {
|
||||
it("lists only manual board statuses: no automatic states and no Canceled (SH-169)", () => {
|
||||
expect(BOARD_INLINE_STATUSES).toEqual([
|
||||
"Incomplete",
|
||||
"Pending",
|
||||
"Scheduled",
|
||||
"En Route",
|
||||
"On Site",
|
||||
"In Progress",
|
||||
|
|
@ -30,6 +30,21 @@ describe("wo-lifecycle-transitions", () => {
|
|||
"Pending Quote",
|
||||
]);
|
||||
expect(BOARD_INLINE_STATUSES).not.toContain("Canceled");
|
||||
for (const automatic of AUTOMATIC_LIFECYCLE_STATUSES) {
|
||||
expect(BOARD_INLINE_STATUSES).not.toContain(automatic);
|
||||
}
|
||||
});
|
||||
|
||||
it("never offers the current status as a transition (SH-358)", () => {
|
||||
expect(getInlineStatusTransitions("On Site")).toEqual([
|
||||
"Pending",
|
||||
"En Route",
|
||||
"In Progress",
|
||||
"Completed",
|
||||
"Rescheduled",
|
||||
"Pending Quote",
|
||||
]);
|
||||
expect(getInlineStatusTransitions("Incomplete")).toEqual(BOARD_INLINE_STATUSES);
|
||||
});
|
||||
|
||||
it("lists AAP creation statuses only", () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue