fix(work-orders): seed CompDoc address after location settles [SH-116]

This commit is contained in:
arthur.bassi 2026-08-06 10:14:29 -03:00
parent bfc230d421
commit cd4b8d27fd
2 changed files with 182 additions and 8 deletions

View file

@ -46,22 +46,48 @@ export function useCompDocDialog({
const [editDateCompleted, setEditDateCompleted] = useState("");
const [editAddress, setEditAddress] = useState("");
const seedKeyRef = useRef<string | null>(null);
const addressSeededRef = useRef(false);
const addressTouchedRef = useRef(false);
// Seed once per open/WO — row/detail refetches must not wipe in-progress edits.
// Address waits for authoritative locationDetail (when a real location id exists)
// and still accepts that first response without overwriting later user edits.
useEffect(() => {
if (!open) {
seedKeyRef.current = null;
addressSeededRef.current = false;
addressTouchedRef.current = false;
return;
}
const key = String(row.id);
if (seedKeyRef.current === key) return;
seedKeyRef.current = key;
setNotes(data?.notes ?? "");
setDocState(docStateOf(row.docStatus));
setGeneratedAt(data?.generatedAt ?? "");
setEditDateCompleted(row.completedDate || "");
if (seedKeyRef.current !== key) {
seedKeyRef.current = key;
addressSeededRef.current = false;
addressTouchedRef.current = false;
setNotes(data?.notes ?? "");
setDocState(docStateOf(row.docStatus));
setGeneratedAt(data?.generatedAt ?? "");
setEditDateCompleted(row.completedDate || "");
}
if (addressTouchedRef.current || addressSeededRef.current) {
return;
}
const awaitingLocation = Boolean(locId) && !isMockLocation && !locationDetail;
if (awaitingLocation) {
return;
}
setEditAddress(buildCompDocAddressLine(resolveCompDocSite(row, locationDetail)));
}, [open, row, data?.notes, data?.generatedAt, locationDetail]);
addressSeededRef.current = true;
}, [open, row, data?.notes, data?.generatedAt, locationDetail, locId, isMockLocation]);
const handleAddressChange = (value: string) => {
addressTouchedRef.current = true;
setEditAddress(value);
};
const amazonRep = row.pocName || "";
const seaRep = row.tech || "";
@ -118,7 +144,7 @@ export function useCompDocDialog({
docState,
generatedAt,
editAddress,
setEditAddress,
setEditAddress: handleAddressChange,
editDateCompleted,
setEditDateCompleted,
amazonRep,

View file

@ -0,0 +1,148 @@
import { act, renderHook, waitFor } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { ReactNode } from "react";
import { useCompDocDialog } from "@/app/(protected)/workorders/_hooks/use-comp-doc-dialog";
import type { Location } from "@/domain/locations/types/location";
import type { WorkOrderTableRow } from "@/domain/work-orders/types/work-order-table-row";
const locationDetailState = vi.hoisted(() => ({
data: undefined as Location | undefined,
}));
vi.mock("@/domain/locations/use-cases/use-location-detail", () => ({
useLocationDetail: () => ({ data: locationDetailState.data }),
}));
function baseRow(overrides: Partial<WorkOrderTableRow> = {}): WorkOrderTableRow {
return {
id: 42,
woNumber: "WO-42",
site: "SEA1",
type: "PM",
dispatcherId: "u1",
dispatcherName: "Dispatcher",
scheduledOn: "2026-06-01",
completedDate: "",
targetWeek: "",
dayGroup: "",
dayLabel: "",
location: "Seattle",
locationId: "loc-real-1",
pm: "HVAC",
serviceNotes: "",
extraServices: [],
vendorId: "",
company: "Vendor Co",
tech: "Tech One",
techPhone: "",
apptStart: "",
apptEnd: "",
apptTime: "",
status: "Scheduled",
docStatus: "No",
pocName: "Amazon Rep",
pocPhone: "",
pocNotes: "",
dueDate: "",
rescheduleCount: 0,
carriedOver: 0,
originalWeek: "",
originalDate: "",
isPastDue: false,
scheduleWeekOnly: false,
mediaCount: 0,
title: "PM — SEA1",
priority: "Sev 3",
primaryDispatchId: null,
rowVersion: "",
dispatchRowVersion: "",
flagColor: null,
...overrides,
};
}
function wrapper({ children }: { children: ReactNode }) {
const client = new QueryClient({
defaultOptions: { queries: { retry: false }, mutations: { retry: false } },
});
return <QueryClientProvider client={client}>{children}</QueryClientProvider>;
}
describe("useCompDocDialog address seeding", () => {
beforeEach(() => {
locationDetailState.data = undefined;
});
it("seeds address from the first authoritative locationDetail without locking on the empty fallback", async () => {
const row = baseRow();
const { result, rerender } = renderHook(
() =>
useCompDocDialog({
row,
open: true,
onSave: vi.fn(),
}),
{ wrapper },
);
expect(result.current.editAddress).toBe("");
locationDetailState.data = {
id: "loc-real-1",
name: "SEA1",
address: "3811 Distribution Dr",
city: "Seattle",
state: "WA",
zipCode: "98101",
} as Location;
rerender();
await waitFor(() => {
expect(result.current.editAddress).toContain("3811 Distribution Dr");
});
});
it("does not overwrite address after the dispatcher edits it", async () => {
locationDetailState.data = {
id: "loc-real-1",
name: "SEA1",
address: "3811 Distribution Dr",
city: "Seattle",
state: "WA",
zipCode: "98101",
} as Location;
const row = baseRow();
const { result, rerender } = renderHook(
() =>
useCompDocDialog({
row,
open: true,
onSave: vi.fn(),
}),
{ wrapper },
);
await waitFor(() => {
expect(result.current.editAddress).toContain("3811 Distribution Dr");
});
act(() => {
result.current.setEditAddress("Dispatcher typed address");
});
locationDetailState.data = {
id: "loc-real-1",
name: "SEA1",
address: "999 Other St",
city: "Seattle",
state: "WA",
zipCode: "98101",
} as Location;
rerender();
expect(result.current.editAddress).toBe("Dispatcher typed address");
});
});