fix(vendors): preserve current roster selection

This commit is contained in:
Alexandre Brandizzi 2026-08-03 18:56:34 -03:00
parent 8b07015c26
commit 59d6749d71
4 changed files with 126 additions and 2 deletions

View file

@ -419,6 +419,13 @@ test.describe("Vendor directory prototype parity", () => {
}), }),
]), ]),
); );
const submittedTechnicians = Array.isArray(state.updatedBody?.technicians)
? (state.updatedBody.technicians as Array<Record<string, unknown>>)
: [];
const newTechnician = submittedTechnicians.find(
(technician) => technician.contactName === "New Technician",
);
expect(newTechnician?.preferredContact).toBeUndefined();
await page.getByRole("button", { name: "View vendor Gateway Plumbing" }).click(); await page.getByRole("button", { name: "View vendor Gateway Plumbing" }).click();
const detailDrawer = page.locator(".MuiDrawer-paper").last(); const detailDrawer = page.locator(".MuiDrawer-paper").last();

View file

@ -65,6 +65,7 @@ export function useVendorRosterSelection({
const retryTargetRef = useRef< const retryTargetRef = useRef<
{ kind: "select"; company: VendorFacetCompany } | { kind: "reload" } | null { kind: "select"; company: VendorFacetCompany } | { kind: "reload" } | null
>(null); >(null);
const requestIdRef = useRef(0);
const fetchRosterByCompany = useCallback( const fetchRosterByCompany = useCallback(
async (id: string | number): Promise<VendorCompanyRoster> => async (id: string | number): Promise<VendorCompanyRoster> =>
@ -78,20 +79,24 @@ export function useVendorRosterSelection({
const selectCompany = useCallback( const selectCompany = useCallback(
async (company: VendorFacetCompany | null) => { async (company: VendorFacetCompany | null) => {
if (!company || company.companyId == null) { if (!company || company.companyId == null) {
requestIdRef.current += 1;
setSelectedRoster(null); setSelectedRoster(null);
setLoadErrorMessage(null); setLoadErrorMessage(null);
retryTargetRef.current = null; retryTargetRef.current = null;
return; return;
} }
const requestId = (requestIdRef.current += 1);
setLoadErrorMessage(null); setLoadErrorMessage(null);
retryTargetRef.current = { kind: "select", company }; retryTargetRef.current = { kind: "select", company };
try { try {
const roster = await fetchRosterByCompany(company.companyId); const roster = await fetchRosterByCompany(company.companyId);
if (requestIdRef.current !== requestId) return;
reset(toFormValues(roster)); reset(toFormValues(roster));
setSelectedRoster(roster); setSelectedRoster(roster);
clearConflict(); clearConflict();
retryTargetRef.current = null; retryTargetRef.current = null;
} catch { } catch {
if (requestIdRef.current !== requestId) return;
setLoadErrorMessage(SELECT_ERROR_MESSAGE); setLoadErrorMessage(SELECT_ERROR_MESSAGE);
} }
}, },
@ -100,6 +105,7 @@ export function useVendorRosterSelection({
const clearSelectedCompany = useCallback( const clearSelectedCompany = useCallback(
(nextName = "") => { (nextName = "") => {
requestIdRef.current += 1;
if (selectedRoster) { if (selectedRoster) {
reset({ ...emptyVendorCompanyRosterForm, name: nextName }); reset({ ...emptyVendorCompanyRosterForm, name: nextName });
} }
@ -112,11 +118,13 @@ export function useVendorRosterSelection({
); );
const reload = useCallback(() => { const reload = useCallback(() => {
const requestId = (requestIdRef.current += 1);
clearConflict(); clearConflict();
setLoadErrorMessage(null); setLoadErrorMessage(null);
retryTargetRef.current = { kind: "reload" }; retryTargetRef.current = { kind: "reload" };
if (mode === "update") { if (mode === "update") {
void query.refetch().then((result) => { void query.refetch().then((result) => {
if (requestIdRef.current !== requestId) return;
if (result.isError) setLoadErrorMessage(RELOAD_ERROR_MESSAGE); if (result.isError) setLoadErrorMessage(RELOAD_ERROR_MESSAGE);
else retryTargetRef.current = null; else retryTargetRef.current = null;
}); });
@ -125,11 +133,15 @@ export function useVendorRosterSelection({
if (selectedRoster?.companyId != null) { if (selectedRoster?.companyId != null) {
void fetchRosterByCompany(selectedRoster.companyId) void fetchRosterByCompany(selectedRoster.companyId)
.then((roster) => { .then((roster) => {
if (requestIdRef.current !== requestId) return;
reset(toFormValues(roster)); reset(toFormValues(roster));
setSelectedRoster(roster); setSelectedRoster(roster);
retryTargetRef.current = null; retryTargetRef.current = null;
}) })
.catch(() => setLoadErrorMessage(RELOAD_ERROR_MESSAGE)); .catch(() => {
if (requestIdRef.current !== requestId) return;
setLoadErrorMessage(RELOAD_ERROR_MESSAGE);
});
} }
}, [clearConflict, fetchRosterByCompany, mode, query, reset, selectedRoster]); }, [clearConflict, fetchRosterByCompany, mode, query, reset, selectedRoster]);
@ -141,6 +153,7 @@ export function useVendorRosterSelection({
}, [reload, selectCompany]); }, [reload, selectCompany]);
const resetSelection = useCallback(() => { const resetSelection = useCallback(() => {
requestIdRef.current += 1;
setSelectedRoster(null); setSelectedRoster(null);
setLoadErrorMessage(null); setLoadErrorMessage(null);
retryTargetRef.current = null; retryTargetRef.current = null;

View file

@ -343,7 +343,6 @@ function TechniciansFieldArray({
contactName: "", contactName: "",
phone: "", phone: "",
email: "", email: "",
preferredContact: "Phone",
tradeSpecialties: "", tradeSpecialties: "",
isActive: true, isActive: true,
}) })

View file

@ -61,6 +61,22 @@ const roster: VendorCompanyRoster = {
technicians: [], technicians: [],
}; };
const secondCompany: VendorFacetCompany = {
...company,
companyId: 6,
name: "Metro HVAC",
companyPhone: "(314) 555-0110",
email: "dispatch@metro.test",
};
const secondRoster: VendorCompanyRoster = {
...roster,
companyId: 6,
name: "Metro HVAC",
companyPhone: "(314) 555-0110",
email: "dispatch@metro.test",
};
function createClient(): QueryClient { function createClient(): QueryClient {
return new QueryClient({ defaultOptions: { queries: { retry: false } } }); return new QueryClient({ defaultOptions: { queries: { retry: false } } });
} }
@ -71,6 +87,17 @@ function makeWrapper(client: QueryClient) {
}; };
} }
function deferRoster(): {
promise: Promise<VendorCompanyRoster>;
resolve: (value: VendorCompanyRoster) => void;
} {
let resolve!: (value: VendorCompanyRoster) => void;
const promise = new Promise<VendorCompanyRoster>((resolver) => {
resolve = resolver;
});
return { promise, resolve };
}
describe("useVendorRosterForm load-error handling", () => { describe("useVendorRosterForm load-error handling", () => {
beforeEach(() => { beforeEach(() => {
rosterGet.mockReset(); rosterGet.mockReset();
@ -168,3 +195,81 @@ describe("useVendorRosterForm load-error handling", () => {
expect(result.current.loadErrorMessage).not.toContain("10.0.0.1"); expect(result.current.loadErrorMessage).not.toContain("10.0.0.1");
}); });
}); });
describe("useVendorRosterForm stale-selection handling", () => {
beforeEach(() => {
rosterGet.mockReset();
});
it("keeps the newest company roster when two lookups resolve out of order", async () => {
const first = deferRoster();
const second = deferRoster();
rosterGet.mockImplementationOnce(() => first.promise);
rosterGet.mockImplementationOnce(() => second.promise);
const { result } = renderHook(
() => {
const form = useVendorRosterForm({ mode: "create" });
const name = useController({ control: form.control, name: "name" });
return { form, name };
},
{ wrapper: makeWrapper(createClient()) },
);
let firstSelect!: Promise<void>;
let secondSelect!: Promise<void>;
act(() => {
firstSelect = result.current.form.selectCompany(company);
});
act(() => {
secondSelect = result.current.form.selectCompany(secondCompany);
});
await act(async () => {
second.resolve(secondRoster);
await secondSelect;
});
await act(async () => {
first.resolve(roster);
await firstSelect;
});
expect(result.current.form.selectedCompanyId).toBe(6);
expect(result.current.name.field.value).toBe("Metro HVAC");
});
it("keeps a free-text draft when a pending lookup resolves after clearing to free text", async () => {
const lookup = deferRoster();
rosterGet.mockImplementationOnce(() => lookup.promise);
const { result } = renderHook(
() => {
const form = useVendorRosterForm({ mode: "create" });
const name = useController({ control: form.control, name: "name" });
return { form, name };
},
{ wrapper: makeWrapper(createClient()) },
);
let pendingSelect!: Promise<void>;
act(() => {
pendingSelect = result.current.form.selectCompany(company);
});
act(() => {
result.current.form.clearSelectedCompany("Draft Vendor");
});
await act(async () => {
result.current.name.field.onChange("Draft Vendor");
});
expect(result.current.name.field.value).toBe("Draft Vendor");
await act(async () => {
lookup.resolve(roster);
await pendingSelect;
});
expect(result.current.form.selectedCompanyId).toBeNull();
expect(result.current.name.field.value).toBe("Draft Vendor");
});
});