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 = {
summary: UpliftSummary;
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);
if (!onOpen) {
@ -30,7 +32,7 @@ export function UpliftCell({ summary, onOpen }: UpliftCellProps) {
e.stopPropagation();
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"
>
{label && pillStyle ? (
@ -40,6 +42,8 @@ export function UpliftCell({ summary, onOpen }: UpliftCellProps) {
>
{label}
</span>
) : readOnly ? (
<span className="text-[11px] text-[var(--color-text-muted)]">—</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 { VendorCell } from "./cells/vendor-cell";
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 { effectiveCompany } from "@/domain/work-orders/utils/effective-completion-snapshot";
import type { WoTableRowHandlers } from "./wo-table-row";
@ -132,12 +132,8 @@ export function WoTableRowServiceCells({
<td style={{ padding: "0 14px" }} onClick={(e) => e.stopPropagation()}>
<UpliftCell
summary={row.upliftSummary ?? EMPTY_UPLIFT_SUMMARY}
onOpen={
canOpenUpliftsDialog(row.upliftSummary ?? EMPTY_UPLIFT_SUMMARY, row.status) &&
handlers.onOpenUplifts
? () => handlers.onOpenUplifts!(row)
: undefined
}
onOpen={handlers.onOpenUplifts ? () => handlers.onOpenUplifts!(row) : undefined}
readOnly={isWorkOrderUpliftsReadOnly(row.status)}
/>
</td>
</>

View file

@ -74,16 +74,6 @@ export function isWorkOrderUpliftsReadOnly(status: WorkOrderTableRow["status"]):
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(
uplift: Pick<WorkOrderUplift, "status" | "requestedById">,
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);
});
});