fix(work-orders): keep uplift history reachable on closed work orders

The Uplift column hid its trigger on a Completed or Canceled work order
with no live uplift, so once cancelled/revoked uplifts stop counting as
"has uplift", their read-only history had no way in. The cell now always
opens the uplifts dialog; closed work orders show a dash instead of Manage.
This commit is contained in:
Alexandre Brandizzi 2026-09-25 18:14:53 -03:00
parent f068beb45e
commit a2d03f9461
4 changed files with 90 additions and 19 deletions

View file

@ -7,9 +7,11 @@ import {
type UpliftCellProps = { type UpliftCellProps = {
summary: UpliftSummary; summary: UpliftSummary;
onOpen?: () => void; onOpen?: () => void;
/** Closed work orders show a dash (not "Manage") that still opens the read-only history. */
readOnly?: boolean;
}; };
export function UpliftCell({ summary, onOpen }: UpliftCellProps) { export function UpliftCell({ summary, onOpen, readOnly = false }: UpliftCellProps) {
const label = getUpliftCellLabel(summary); const label = getUpliftCellLabel(summary);
if (!onOpen) { if (!onOpen) {
@ -30,7 +32,7 @@ export function UpliftCell({ summary, onOpen }: UpliftCellProps) {
e.stopPropagation(); e.stopPropagation();
onOpen(); onOpen();
}} }}
title={label ? "View uplifts" : "Manage uplifts"} title={label || readOnly ? "View uplifts" : "Manage uplifts"}
className="flex w-full items-center justify-start transition-opacity hover:opacity-80" className="flex w-full items-center justify-start transition-opacity hover:opacity-80"
> >
{label && pillStyle ? ( {label && pillStyle ? (
@ -40,6 +42,8 @@ export function UpliftCell({ summary, onOpen }: UpliftCellProps) {
> >
{label} {label}
</span> </span>
) : readOnly ? (
<span className="text-[11px] text-[var(--color-text-muted)]">—</span>
) : ( ) : (
<span className="text-[11px] font-semibold text-[var(--color-primary)]">Manage</span> <span className="text-[11px] font-semibold text-[var(--color-primary)]">Manage</span>
)} )}

View file

@ -9,7 +9,7 @@ import { StatusCell } from "./cells/status-cell";
import { UpliftCell } from "./cells/uplift-cell"; import { UpliftCell } from "./cells/uplift-cell";
import { VendorCell } from "./cells/vendor-cell"; import { VendorCell } from "./cells/vendor-cell";
import { EMPTY_UPLIFT_SUMMARY } from "@/domain/work-orders/types/work-order-uplift"; import { EMPTY_UPLIFT_SUMMARY } from "@/domain/work-orders/types/work-order-uplift";
import { canOpenUpliftsDialog } from "@/domain/work-orders/utils/uplift-display-utils"; import { isWorkOrderUpliftsReadOnly } from "@/domain/work-orders/utils/uplift-display-utils";
import { applyVendorTableSave } from "@/domain/work-orders/utils/work-order-feedback-toasts"; import { applyVendorTableSave } from "@/domain/work-orders/utils/work-order-feedback-toasts";
import { effectiveCompany } from "@/domain/work-orders/utils/effective-completion-snapshot"; import { effectiveCompany } from "@/domain/work-orders/utils/effective-completion-snapshot";
import type { WoTableRowHandlers } from "./wo-table-row"; import type { WoTableRowHandlers } from "./wo-table-row";
@ -132,12 +132,8 @@ export function WoTableRowServiceCells({
<td style={{ padding: "0 14px" }} onClick={(e) => e.stopPropagation()}> <td style={{ padding: "0 14px" }} onClick={(e) => e.stopPropagation()}>
<UpliftCell <UpliftCell
summary={row.upliftSummary ?? EMPTY_UPLIFT_SUMMARY} summary={row.upliftSummary ?? EMPTY_UPLIFT_SUMMARY}
onOpen={ onOpen={handlers.onOpenUplifts ? () => handlers.onOpenUplifts!(row) : undefined}
canOpenUpliftsDialog(row.upliftSummary ?? EMPTY_UPLIFT_SUMMARY, row.status) && readOnly={isWorkOrderUpliftsReadOnly(row.status)}
handlers.onOpenUplifts
? () => handlers.onOpenUplifts!(row)
: undefined
}
/> />
</td> </td>
</> </>

View file

@ -74,16 +74,6 @@ export function isWorkOrderUpliftsReadOnly(status: WorkOrderTableRow["status"]):
return status === "Completed" || status === "Canceled"; return status === "Completed" || status === "Canceled";
} }
export function canOpenUpliftsDialog(
summary: UpliftSummary,
status: WorkOrderTableRow["status"],
): boolean {
if (summary.hasUplift || summary.pendingCount > 0) {
return true;
}
return !isWorkOrderUpliftsReadOnly(status);
}
export function canRevokeWorkOrderUplift( export function canRevokeWorkOrderUplift(
uplift: Pick<WorkOrderUplift, "status" | "requestedById">, uplift: Pick<WorkOrderUplift, "status" | "requestedById">,
currentUserId: string | number | null | undefined, currentUserId: string | number | null | undefined,

View file

@ -0,0 +1,81 @@
import { fireEvent, screen } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import { WoTableRowServiceCells } from "@/app/(protected)/workorders/_components/list/table/wo-table-row-service-cells";
import type { WoTableRowHandlers } from "@/app/(protected)/workorders/_components/list/table/wo-table-row";
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
import { renderWithProviders } from "@/test/test-utils";
vi.mock("@/app/(protected)/workorders/_components/list/table/cells/pm-type-cell", () => ({
PMTypeCell: () => null,
}));
vi.mock("@/app/(protected)/workorders/_components/list/table/cells/appt-time-cell", () => ({
ApptTimeCell: () => null,
}));
vi.mock("@/app/(protected)/workorders/_components/list/table/cells/status-cell", () => ({
StatusCell: () => null,
}));
vi.mock("@/app/(protected)/workorders/_components/list/table/cells/comp-doc-cell", () => ({
CompDocCell: () => null,
}));
vi.mock("@/app/(protected)/workorders/_components/list/table/cells/date-cell", () => ({
DateCell: () => null,
}));
vi.mock("@/app/(protected)/workorders/_components/list/table/cells/vendor-cell", () => ({
VendorCell: () => null,
}));
function renderCells(row: WorkOrderTableRow, onOpenUplifts = vi.fn()) {
const handlers = { onOpenUplifts } as unknown as WoTableRowHandlers;
renderWithProviders(
<table>
<tbody>
<tr>
<WoTableRowServiceCells
row={row}
vendors={[]}
pmTypes={[]}
handlers={handlers}
onPatchRow={vi.fn()}
/>
</tr>
</tbody>
</table>,
{ withAuth: false },
);
return onOpenUplifts;
}
describe("WoTableRowServiceCells uplift column", () => {
// A closed work order whose uplifts were all cancelled or revoked has no live uplift,
// but its read-only uplift history must stay one click away.
it.each(["Completed", "Canceled"] as const)(
"opens the read-only uplift history from a dash on a %s work order with no live uplift",
(status) => {
const row = {
id: 7,
status,
upliftSummary: { hasUplift: false, pendingCount: 0 },
} as WorkOrderTableRow;
const onOpenUplifts = renderCells(row);
const trigger = screen.getByRole("button", { name: "—" });
expect(screen.queryByText("Manage")).not.toBeInTheDocument();
fireEvent.click(trigger);
expect(onOpenUplifts).toHaveBeenCalledWith(row);
},
);
it("offers Manage on an open work order with no live uplift", () => {
const row = {
id: 8,
status: "Scheduled",
upliftSummary: { hasUplift: false, pendingCount: 0 },
} as WorkOrderTableRow;
const onOpenUplifts = renderCells(row);
fireEvent.click(screen.getByRole("button", { name: "Manage" }));
expect(onOpenUplifts).toHaveBeenCalledWith(row);
});
});