From 9cc9bc4dfb47fdec65690210c1f1299b13e295b9 Mon Sep 17 00:00:00 2001 From: Arthur Bassi Date: Wed, 2 Sep 2026 15:11:31 -0300 Subject: [PATCH 1/5] feat(work-orders): align inline lifecycle transitions with AAP Use the prototype status list and gates on the board and wizard. Auto-derive Scheduled or Pending from a concrete schedule date. --- .../list/table/cells/status-cell.tsx | 36 ++-- .../list/table/to-inline-draft-row.ts | 2 + .../list/table/wo-table-inline-row-cells.tsx | 2 + .../wizard/wizard-step-vendor-time.tsx | 36 ++-- .../use-work-order-table-mutations.ts | 25 ++- .../utils/wo-lifecycle-transitions.ts | 169 ++++++++++++++++++ .../status-cell-closability.test.tsx | 41 ++++- .../status-cell-past-due-copy.test.tsx | 10 +- .../status-cell-pending-uplift.test.tsx | 9 +- ...zard-step-vendor-time-closability.test.tsx | 39 ++-- .../use-work-order-table-mutations.test.tsx | 70 ++++++++ .../utils/format-board-audit-action.test.ts | 12 ++ .../utils/wo-lifecycle-transitions.test.ts | 125 +++++++++++++ 13 files changed, 504 insertions(+), 72 deletions(-) create mode 100644 src/domain/work-orders/utils/wo-lifecycle-transitions.ts create mode 100644 src/test/domain/work-orders/utils/wo-lifecycle-transitions.test.ts diff --git a/src/app/(protected)/workorders/_components/list/table/cells/status-cell.tsx b/src/app/(protected)/workorders/_components/list/table/cells/status-cell.tsx index d55176bd..a9ee2647 100644 --- a/src/app/(protected)/workorders/_components/list/table/cells/status-cell.tsx +++ b/src/app/(protected)/workorders/_components/list/table/cells/status-cell.tsx @@ -2,16 +2,19 @@ import { useState } from "react"; import { AlertTriangle, Check } from "lucide-react"; import { Popover } from "@mui/material"; import { Text } from "@/components/ui/text"; -import { - ALL_WIZARD_STATUSES, - type WizardWOStatus, -} from "@/domain/work-orders/types/work-order-wizard"; +import type { WizardWOStatus } from "@/domain/work-orders/types/work-order-wizard"; import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row"; import { - formatClosabilityMissingTitle, getClosabilityGaps, tableRowToClosabilityInput, } from "@/domain/work-orders/utils/wo-closability"; +import { + BOARD_INLINE_STATUSES, + PAST_DUE_BADGE_TOOLTIP, + PAST_DUE_POPOVER_BODY, + PAST_DUE_RESCHEDULE_CTA, + getStatusOptionState, +} from "@/domain/work-orders/utils/wo-lifecycle-transitions"; import { HoverChevron } from "./hover-chevron"; import { WoTableStatusLabel } from "./wo-table-status-label"; @@ -24,6 +27,7 @@ type StatusCellProps = { onPastDueAttempt?: () => void; onRequestComplete?: () => void; readOnly?: boolean; + statusOptions?: readonly WizardWOStatus[]; }; function ReadOnlyStatusLabel({ status }: { status: string }) { @@ -64,7 +68,7 @@ function PastDueStatusCell({ onPastDueAttempt?.(); setAnchorEl(e.currentTarget); }} - title="This WO missed its deadline. Update Schedule On to unlock status updates." + title={PAST_DUE_BADGE_TOOLTIP} className="group/cell flex w-full cursor-pointer items-center gap-1.5 whitespace-nowrap leading-none transition-opacity hover:opacity-80" style={{ fontSize: 12.5, fontWeight: 600, color: "var(--status-pastdue-text)" }} > @@ -97,7 +101,7 @@ function PastDueStatusCell({ color: "var(--status-pending-text)", }} > - This work order is past due. Update Schedule On before the status can be updated. + {PAST_DUE_POPOVER_BODY} @@ -124,6 +128,7 @@ export function StatusCell({ onPastDueAttempt, onRequestComplete, readOnly = false, + statusOptions = BOARD_INLINE_STATUSES, }: StatusCellProps) { const [anchorEl, setAnchorEl] = useState(null); const closabilityGaps = rowClosabilityGaps(wo); @@ -164,20 +169,17 @@ export function StatusCell({ onClick={(e) => e.stopPropagation()} slotProps={{ paper: { className: "min-w-[168px] rounded-lg border border-border p-1" } }} > - {ALL_WIZARD_STATUSES.map((status) => { - const isCompletedBlocked = status === "Completed" && closabilityGaps.length > 0; - const missingTitle = isCompletedBlocked - ? formatClosabilityMissingTitle(closabilityGaps) - : undefined; + {statusOptions.map((status) => { + const option = getStatusOptionState(wo, status, closabilityGaps); return (