import type { QueryClient } from "@tanstack/react-query"; import { toast } from "react-toastify"; import { showCompDocDraftSavedToast } from "@/app/(protected)/workorders/_components/comp-doc/comp-doc-draft-toast"; import { locationsApi } from "@/domain/locations/api/locations-api"; import type { Location } from "@/domain/locations/types/location"; import type { CompDocData, CompDocSaveStatus, DocFields, DocState, } from "@/domain/work-orders/types/completion-doc"; import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row"; import { buildDocHtml, printDoc } from "@/domain/work-orders/utils/completion-doc-html"; import { fmtDocNow } from "@/domain/work-orders/utils/completion-doc-state"; import { queryKeys } from "@/infra/query-key/query-key"; type CompDocSite = { street: string; fullAddress: string }; export function buildCompDocAddressLine(site: CompDocSite): string { if (site.street.trim()) return site.street.trim(); return site.fullAddress !== "—" ? site.fullAddress : ""; } function locationUpdatePayload(location: Location, address: string): Location { return { ...location, address, }; } type CreateCompDocDialogActionsParams = { row: WorkOrderTableRow; notes: string; generatedAt: string; editDateCompleted: string; editAddress: string; docState: DocState; docFields: DocFields; site: CompDocSite; locId: string; isMockLocation: boolean; isSiteFrozen: boolean; locationDetail: Location | undefined; queryClient: QueryClient; onSave: (data: CompDocData, status: CompDocSaveStatus) => void | Promise; onDateCompletedChange?: (date: string) => void | Promise; setDocState: (state: DocState) => void; setGeneratedAt: (value: string) => void; }; export function createCompDocDialogActions({ row, notes, generatedAt, editDateCompleted, editAddress, docState, docFields, site, locId, isMockLocation, isSiteFrozen, locationDetail, queryClient, onSave, onDateCompletedChange, setDocState, setGeneratedAt, }: CreateCompDocDialogActionsParams) { async function persist(extra: Partial, status: CompDocSaveStatus): Promise { await onSave({ notes, generatedAt, ...extra }, status); } async function syncCompletedDateIfChanged(): Promise { if (editDateCompleted !== (row.completedDate || "")) { await onDateCompletedChange?.(editDateCompleted); } } async function syncAddressIfChanged(): Promise { const next = editAddress.trim(); const current = buildCompDocAddressLine(site); if (next === current) return; if (isSiteFrozen || isMockLocation || !locId) return; if (!locationDetail) { throw new Error("Location details are still loading. Try saving again."); } await locationsApi.update(locId, locationUpdatePayload(locationDetail, next)); void queryClient.invalidateQueries({ queryKey: queryKeys.locations.detail(locId) }); void queryClient.invalidateQueries({ queryKey: queryKeys.locations.all }); void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.all }); } function refreshAuthoritativeState() { void queryClient.invalidateQueries({ queryKey: queryKeys.workOrders.all }); if (locId && !isMockLocation) { void queryClient.invalidateQueries({ queryKey: queryKeys.locations.detail(locId) }); void queryClient.invalidateQueries({ queryKey: queryKeys.locations.all }); } } async function handleMarkNotRequired() { const previous = docState; setDocState("not-required"); try { await persist({}, "NN"); } catch (error) { setDocState(previous); refreshAuthoritativeState(); toast.error( error instanceof Error ? error.message : "Failed to mark document as not required", ); } } async function handleUndo() { const previous = docState; setDocState("draft"); try { await persist({}, "No"); } catch (error) { setDocState(previous); refreshAuthoritativeState(); toast.error(error instanceof Error ? error.message : "Failed to undo not-required"); } } async function handleSaveDraft() { try { await syncCompletedDateIfChanged(); await syncAddressIfChanged(); await persist({}, "No"); showCompDocDraftSavedToast(); } catch (error) { refreshAuthoritativeState(); toast.error(error instanceof Error ? error.message : "Failed to save draft"); } } async function handleGenerate() { if (!editDateCompleted.trim()) { toast.error("Date Work Completed is required to generate the document."); return; } const ts = fmtDocNow(); try { // Prerequisites first; only mark Generated after completedDate/address persist. await syncCompletedDateIfChanged(); await syncAddressIfChanged(); await persist({ generatedAt: ts }, "Yes"); setGeneratedAt(ts); setDocState("generated"); } catch (error) { // Keep Draft UI; refresh so any partially persisted date/address is authoritative. refreshAuthoritativeState(); toast.error(error instanceof Error ? error.message : "Failed to generate document"); } } function handleDownloadPdf() { printDoc(buildDocHtml(row, { notes, generatedAt }, docFields)); } // why: Uploading a signed PDF finalizes the document server-side: the endpoint sets DocStatus=Yes and // bumps the work-order row version in one transaction. Reflect that in the UI and refresh // authoritative state, but do not re-run the Generate persist — that patch would carry the // pre-upload row version and 409, surfacing an error right after a successful upload. function markSignedPdfUploaded() { setGeneratedAt(fmtDocNow()); setDocState("generated"); refreshAuthoritativeState(); } return { handleMarkNotRequired, handleUndo, handleSaveDraft, handleGenerate, handleDownloadPdf, markSignedPdfUploaded, }; }