diff --git a/src/app/(protected)/workorders/_components/comp-doc/use-comp-doc-dialog-seed.ts b/src/app/(protected)/workorders/_components/comp-doc/use-comp-doc-dialog-seed.ts new file mode 100644 index 00000000..9ff86a92 --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/use-comp-doc-dialog-seed.ts @@ -0,0 +1,58 @@ +import { useEffect, useRef, type Dispatch, type SetStateAction } from "react"; +import type { CompDocData, DocState } from "@/domain/work-orders/types/completion-doc"; +import { docStateOf } from "@/domain/work-orders/utils/completion-doc-state"; + +type UseCompDocDialogSeedArgs = { + open: boolean; + infoId: string | number; + infoDocStatus?: string | null; + infoCompletedDate?: string | null; + completionDocStatus?: string | null; + data?: CompDocData; + setNotes: Dispatch>; + setDocState: Dispatch>; + setGeneratedAt: Dispatch>; + setEditDateCompleted: Dispatch>; +}; + +/** Prototype seeds once per open/WO session; detail refetches must not wipe edits. */ +export function useCompDocDialogSeed({ + open, + infoId, + infoDocStatus, + infoCompletedDate, + completionDocStatus, + data, + setNotes, + setDocState, + setGeneratedAt, + setEditDateCompleted, +}: UseCompDocDialogSeedArgs) { + const seedKeyRef = useRef(null); + + useEffect(() => { + if (!open) { + seedKeyRef.current = null; + return; + } + const key = String(infoId); + if (seedKeyRef.current === key) return; + seedKeyRef.current = key; + setNotes(data?.notes ?? ""); + setDocState(docStateOf(completionDocStatus || infoDocStatus)); + setGeneratedAt(data?.generatedAt ?? ""); + setEditDateCompleted(infoCompletedDate || ""); + }, [ + open, + infoId, + infoDocStatus, + infoCompletedDate, + completionDocStatus, + data?.notes, + data?.generatedAt, + setNotes, + setDocState, + setGeneratedAt, + setEditDateCompleted, + ]); +} diff --git a/src/app/(protected)/workorders/_components/comp-doc/use-comp-doc-dialog.tsx b/src/app/(protected)/workorders/_components/comp-doc/use-comp-doc-dialog.tsx index 275f676b..365cf856 100644 --- a/src/app/(protected)/workorders/_components/comp-doc/use-comp-doc-dialog.tsx +++ b/src/app/(protected)/workorders/_components/comp-doc/use-comp-doc-dialog.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useRef, useState } from "react"; +import { useMemo, useRef, useState } from "react"; import { toast } from "react-toastify"; import { useLocationDetail } from "@/domain/locations/use-cases/use-location-detail"; import type { @@ -11,8 +11,10 @@ import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-ta import { useWorkOrderBoardDetail } from "@/domain/work-orders/use-cases/use-work-order-detail"; import { useUploadCompletionDoc } from "@/domain/work-orders/use-cases/use-work-order-mutations"; import { resolveCompDocSite } from "@/domain/work-orders/utils/completion-doc-site"; -import { DOC_STATE_CFG, docStateOf } from "@/domain/work-orders/utils/completion-doc-state"; +import { DOC_STATE_CFG } from "@/domain/work-orders/utils/completion-doc-state"; import { isWorkOrderDocumentsReadOnly } from "@/domain/work-orders/utils/work-order-media-limits"; +import { useCompDocDialogSeed } from "./use-comp-doc-dialog-seed"; +import { useCompDocNotRequiredActions } from "./use-comp-doc-not-required-actions"; import { useCompDocPdfActions } from "./use-comp-doc-pdf-actions"; import { useCompDocPersistFlow } from "./use-comp-doc-persist-flow"; @@ -71,45 +73,31 @@ export function useCompDocDialog({ const signOffAttachment = completion?.signOffAttachment; const rowVersion = info.rowVersion || row.rowVersion; const isDocumentsReadOnly = isWorkOrderDocumentsReadOnly(info.status); - const site = useMemo(() => resolveCompDocSite(info, locationDetail), [info, locationDetail]); const [notes, setNotes] = useState(""); const [docState, setDocState] = useState("draft"); const [generatedAt, setGeneratedAt] = useState(""); const [editDateCompleted, setEditDateCompleted] = useState(""); - const seedKeyRef = useRef(null); - // Prototype seeds once per open/WO session. Detail refetches must not wipe - // in-progress PM comments or date edits (Bugbot: dialog effect clears edits). - useEffect(() => { - if (!open) { - seedKeyRef.current = null; - return; - } - const key = String(info.id); - if (seedKeyRef.current === key) return; - seedKeyRef.current = key; - setNotes(data?.notes ?? ""); - setDocState(docStateOf(completion?.docStatus || info.docStatus)); - setGeneratedAt(data?.generatedAt ?? ""); - setEditDateCompleted(info.completedDate || ""); - }, [ + useCompDocDialogSeed({ open, - info.id, - info.docStatus, - info.completedDate, - completion?.docStatus, - data?.notes, - data?.generatedAt, - ]); + infoId: info.id, + infoDocStatus: info.docStatus, + infoCompletedDate: info.completedDate, + completionDocStatus: completion?.docStatus, + data, + setNotes, + setDocState, + setGeneratedAt, + setEditDateCompleted, + }); const amazonRep = info.pocName || ""; const seaRep = info.tech || ""; const cfg = DOC_STATE_CFG[docState]; const isNotRequired = docState === "not-required"; const isGenerated = docState === "generated" || Boolean(signOffAttachment); - const isBusy = uploadCompletionMutation.isPending || isFinalizing; const docFields: DocFields = { street: site.street, @@ -137,37 +125,13 @@ export function useCompDocDialog({ onDraftSaved: notifyDraftSaved, }); - async function handleMarkNotRequired() { - const previous = docState; - setDocState("not-required"); - setIsFinalizing(true); - try { - await persist({}, "NN"); - } catch (error) { - setDocState(previous); - refreshAuthoritativeState(); - toast.error( - error instanceof Error ? error.message : "Failed to mark document as not required", - ); - } finally { - setIsFinalizing(false); - } - } - - async function handleUndo() { - const previous = docState; - setDocState("draft"); - setIsFinalizing(true); - try { - await persist({}, "No"); - } catch (error) { - setDocState(previous); - refreshAuthoritativeState(); - toast.error(error instanceof Error ? error.message : "Failed to undo not-required"); - } finally { - setIsFinalizing(false); - } - } + const { handleMarkNotRequired, handleUndo } = useCompDocNotRequiredActions({ + docState, + setDocState, + persist, + refreshAuthoritativeState, + setIsFinalizing, + }); const { handleDownloadPdf, handleUploadPdf } = useCompDocPdfActions({ info, @@ -203,7 +167,7 @@ export function useCompDocDialog({ cfg, isNotRequired, isGenerated, - isUploading: isBusy, + isUploading: uploadCompletionMutation.isPending || isFinalizing, pdfInputRef, handleMarkNotRequired, handleUndo, diff --git a/src/app/(protected)/workorders/_components/comp-doc/use-comp-doc-not-required-actions.ts b/src/app/(protected)/workorders/_components/comp-doc/use-comp-doc-not-required-actions.ts new file mode 100644 index 00000000..035e78d5 --- /dev/null +++ b/src/app/(protected)/workorders/_components/comp-doc/use-comp-doc-not-required-actions.ts @@ -0,0 +1,54 @@ +import { toast } from "react-toastify"; +import type { CompDocSaveStatus, DocState } from "@/domain/work-orders/types/completion-doc"; + +type PersistFn = (data: Record, status: CompDocSaveStatus) => void | Promise; + +type UseCompDocNotRequiredActionsArgs = { + docState: DocState; + setDocState: (state: DocState) => void; + persist: PersistFn; + refreshAuthoritativeState: () => void; + setIsFinalizing: (value: boolean) => void; +}; + +export function useCompDocNotRequiredActions({ + docState, + setDocState, + persist, + refreshAuthoritativeState, + setIsFinalizing, +}: UseCompDocNotRequiredActionsArgs) { + async function handleMarkNotRequired() { + const previous = docState; + setDocState("not-required"); + setIsFinalizing(true); + try { + await persist({}, "NN"); + } catch (error) { + setDocState(previous); + refreshAuthoritativeState(); + toast.error( + error instanceof Error ? error.message : "Failed to mark document as not required", + ); + } finally { + setIsFinalizing(false); + } + } + + async function handleUndo() { + const previous = docState; + setDocState("draft"); + setIsFinalizing(true); + try { + await persist({}, "No"); + } catch (error) { + setDocState(previous); + refreshAuthoritativeState(); + toast.error(error instanceof Error ? error.message : "Failed to undo not-required"); + } finally { + setIsFinalizing(false); + } + } + + return { handleMarkNotRequired, handleUndo }; +}