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();
const detailDrawer = page.locator(".MuiDrawer-paper").last();

View file

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

View file

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

View file

@ -61,6 +61,22 @@ const roster: VendorCompanyRoster = {
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 {
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", () => {
beforeEach(() => {
rosterGet.mockReset();
@ -168,3 +195,81 @@ describe("useVendorRosterForm load-error handling", () => {
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");
});
});