From 9225f2345cd6b483777961d20ab0375e8c34633b Mon Sep 17 00:00:00 2001 From: Codex Review Integration Date: Wed, 16 Sep 2026 15:23:17 -0300 Subject: [PATCH] fix(work-orders): gate completion document button on service selection (SH-369) Co-Authored-By: Claude Opus 5 (1M context) --- .../tabs/slide-over-completion-actions.tsx | 28 ++++++++++- .../detail/tabs/slide-over-completion-tab.tsx | 2 + ...lide-over-completion-service-gate.test.tsx | 46 +++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) create mode 100644 src/test/app/(protected)/workorders/slide-over-completion-service-gate.test.tsx diff --git a/src/app/(protected)/workorders/_components/detail/tabs/slide-over-completion-actions.tsx b/src/app/(protected)/workorders/_components/detail/tabs/slide-over-completion-actions.tsx index 51c262be..2853c205 100644 --- a/src/app/(protected)/workorders/_components/detail/tabs/slide-over-completion-actions.tsx +++ b/src/app/(protected)/workorders/_components/detail/tabs/slide-over-completion-actions.tsx @@ -8,13 +8,29 @@ type SlideOverCompletionActionsProps = { docStatus: CompletionDocStatus; rowVersion?: string | number | null; completionPending: boolean; + /** Completion documents require a selected primary service. */ + hasService: boolean; hasUploadHandler: boolean; pdfInputRef: RefObject; onPdfSelected: (files: FileList | null) => void; onOpenCompletionDoc: () => void; }; -function DocStatusButtonContent({ docStatus }: { docStatus: CompletionDocStatus }) { +function DocStatusButtonContent({ + docStatus, + hasService, +}: { + docStatus: CompletionDocStatus; + hasService: boolean; +}) { + if (!hasService) { + return ( + <> + + Select service + + ); + } if (docStatus === "Yes") { return ( <> @@ -43,6 +59,7 @@ export function SlideOverCompletionActions({ docStatus, rowVersion, completionPending, + hasService, hasUploadHandler, pdfInputRef, onPdfSelected, @@ -83,10 +100,17 @@ export function SlideOverCompletionActions({ + {!hasService && ( + + Select a service before opening the completion document. + + )} ); } diff --git a/src/app/(protected)/workorders/_components/detail/tabs/slide-over-completion-tab.tsx b/src/app/(protected)/workorders/_components/detail/tabs/slide-over-completion-tab.tsx index 290ec603..adcf3e20 100644 --- a/src/app/(protected)/workorders/_components/detail/tabs/slide-over-completion-tab.tsx +++ b/src/app/(protected)/workorders/_components/detail/tabs/slide-over-completion-tab.tsx @@ -7,6 +7,7 @@ import type { WorkOrderBoardCompletion } from "@/domain/work-orders/types/work-o import type { MediaFile } from "@/domain/work-orders/types/work-order-media"; import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row"; import type { WOType } from "@/domain/work-orders/types/work-order-wizard"; +import { hasCompletionDocService } from "@/domain/work-orders/utils/completion-doc-service-gate"; import { formatMediaSizeLimit, isWorkOrderFileTooLarge, @@ -130,6 +131,7 @@ export function SlideOverCompletionTab({ docStatus={docStatus} rowVersion={row.rowVersion} completionPending={completionPending} + hasService={hasCompletionDocService(row.pm)} hasUploadHandler={Boolean(onUploadCompletionPdf)} pdfInputRef={pdfInputRef} onPdfSelected={handlePdfSelected} diff --git a/src/test/app/(protected)/workorders/slide-over-completion-service-gate.test.tsx b/src/test/app/(protected)/workorders/slide-over-completion-service-gate.test.tsx new file mode 100644 index 00000000..d87162fe --- /dev/null +++ b/src/test/app/(protected)/workorders/slide-over-completion-service-gate.test.tsx @@ -0,0 +1,46 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import { describe, expect, it, vi } from "vitest"; +import { SlideOverCompletionTab } from "@/app/(protected)/workorders/_components/detail/tabs/slide-over-completion-tab"; +import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row"; + +function renderTab(pm: string, onOpenCompletionDoc = vi.fn()) { + const row = { id: "1", type: "PM", pm, docStatus: "No", rowVersion: "1" } as WorkOrderTableRow; + render( + , + ); + return onOpenCompletionDoc; +} + +describe("SlideOverCompletionTab service gate", () => { + it("disables the completion document button until a service is selected", () => { + const onOpen = renderTab(" "); + + const button = screen.getByRole("button", { name: /select service/i }); + expect(button).toBeDisabled(); + expect( + screen.getByText("Select a service before opening the completion document."), + ).toBeInTheDocument(); + expect( + screen.queryByRole("button", { name: /open completion document/i }), + ).not.toBeInTheDocument(); + + fireEvent.click(button); + expect(onOpen).not.toHaveBeenCalled(); + }); + + it("opens the completion document when a service is selected", () => { + const onOpen = renderTab("HVAC Inspection"); + + const button = screen.getByRole("button", { name: /open completion document/i }); + expect(button).toBeEnabled(); + expect(screen.queryByText(/select service/i)).not.toBeInTheDocument(); + + fireEvent.click(button); + expect(onOpen).toHaveBeenCalledTimes(1); + }); +});