shoc-frontend-new/src/app/(protected)/workorders/_hooks/use-comp-doc-dialog.ts

187 lines
5.6 KiB
TypeScript
Raw Normal View History

import { useEffect, useMemo, useState } from "react";
import { useQueryClient } 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 { useLocationDetail } from "@/domain/locations/use-cases/use-location-detail";
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 { getMockWoWizardLocationDetail } from "@/domain/work-orders/mocks/wo-wizard-mock-data";
import { buildDocHtml, printDoc } from "@/domain/work-orders/utils/completion-doc-html";
import { resolveCompDocSite } from "@/domain/work-orders/utils/completion-doc-site";
import {
DOC_STATE_CFG,
docStateOf,
fmtDocNow,
} from "@/domain/work-orders/utils/completion-doc-state";
import { queryKeys } from "@/infra/query-key/query-key";
type UseCompDocDialogParams = {
row: WorkOrderTableRow;
open: boolean;
data?: CompDocData;
onSave: (data: CompDocData, status: CompDocSaveStatus) => void;
onDateCompletedChange?: (date: string) => void;
};
function buildAddressLine(site: { street: string; fullAddress: string }): string {
if (site.street.trim()) return site.street.trim();
return site.fullAddress !== "—" ? site.fullAddress : "";
}
function locationUpdatePayload(location: Location, address: string): Location {
return {
...location,
address,
};
}
export function useCompDocDialog({
row,
open,
data,
onSave,
onDateCompletedChange,
}: UseCompDocDialogParams) {
const queryClient = useQueryClient();
const locId = String(row.locationId || "");
const isMockLocation = locId.startsWith("mock-");
const { data: apiLocationDetail } = useLocationDetail(
isMockLocation || !locId ? undefined : locId,
);
const locationDetail = isMockLocation ? getMockWoWizardLocationDetail(locId) : apiLocationDetail;
const site = useMemo(() => resolveCompDocSite(row, locationDetail), [row, locationDetail]);
const [notes, setNotes] = useState("");
const [docState, setDocState] = useState<DocState>("draft");
const [generatedAt, setGeneratedAt] = useState("");
const [editDateCompleted, setEditDateCompleted] = useState("");
const [editAddress, setEditAddress] = useState("");
useEffect(() => {
if (!open) return;
setNotes(data?.notes ?? "");
setDocState(docStateOf(row.docStatus));
setGeneratedAt(data?.generatedAt ?? "");
setEditDateCompleted(row.completedDate || "");
setEditAddress(buildAddressLine(resolveCompDocSite(row, locationDetail)));
}, [open, row, data?.notes, data?.generatedAt, locationDetail]);
const amazonRep = row.pocName || "";
const seaRep = row.tech || "";
const cfg = DOC_STATE_CFG[docState];
const isNotRequired = docState === "not-required";
const isGenerated = docState === "generated";
const effectiveStreet = editAddress.trim() || site.street;
const docFields: DocFields = {
street: effectiveStreet,
city: site.city,
state: site.state,
zip: site.zip,
dateCompleted: editDateCompleted,
amazonRep,
seaRep,
};
const previewSite = useMemo(
() => ({
...site,
street: effectiveStreet,
fullAddress:
[effectiveStreet, site.label, site.zip].filter(Boolean).join(", ") ||
effectiveStreet ||
"—",
}),
[site, effectiveStreet],
);
function persist(extra: Partial<CompDocData>, status: CompDocSaveStatus) {
onSave({ notes, generatedAt, ...extra }, status);
}
function syncCompletedDateIfChanged() {
if (editDateCompleted !== (row.completedDate || "")) {
onDateCompletedChange?.(editDateCompleted);
}
}
async function syncAddressIfChanged(): Promise<void> {
const next = editAddress.trim();
const current = buildAddressLine(site);
if (next === current) return;
if (isMockLocation || !locId) return;
if (!locationDetail) {
toast.error("Location details are still loading. Try saving again.");
return;
}
try {
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 });
} catch (error) {
toast.error(error instanceof Error ? error.message : "Failed to update address");
}
}
function handleMarkNotRequired() {
setDocState("not-required");
persist({}, "NN");
}
function handleUndo() {
setDocState("draft");
persist({}, "No");
}
function handleSaveDraft() {
persist({}, "No");
syncCompletedDateIfChanged();
void syncAddressIfChanged();
showCompDocDraftSavedToast();
}
function handleGenerate() {
const ts = fmtDocNow();
setGeneratedAt(ts);
setDocState("generated");
persist({ generatedAt: ts }, "Yes");
syncCompletedDateIfChanged();
void syncAddressIfChanged();
}
function handleDownloadPdf() {
printDoc(buildDocHtml(row, { notes, generatedAt }, docFields));
}
return {
site: previewSite,
notes,
setNotes,
docState,
generatedAt,
editAddress,
setEditAddress,
editDateCompleted,
setEditDateCompleted,
amazonRep,
seaRep,
cfg,
isNotRequired,
isGenerated,
handleMarkNotRequired,
handleUndo,
handleSaveDraft,
handleGenerate,
handleDownloadPdf,
};
}