mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 16:19:01 +00:00
fix(vendors): preserve current roster selection
This commit is contained in:
parent
8b07015c26
commit
59d6749d71
4 changed files with 126 additions and 2 deletions
7
e2e/vendors/vendors.spec.ts
vendored
7
e2e/vendors/vendors.spec.ts
vendored
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -343,7 +343,6 @@ function TechniciansFieldArray({
|
|||
contactName: "",
|
||||
phone: "",
|
||||
email: "",
|
||||
preferredContact: "Phone",
|
||||
tradeSpecialties: "",
|
||||
isActive: true,
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue