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();
|
await page.getByRole("button", { name: "View vendor Gateway Plumbing" }).click();
|
||||||
const detailDrawer = page.locator(".MuiDrawer-paper").last();
|
const detailDrawer = page.locator(".MuiDrawer-paper").last();
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -343,7 +343,6 @@ function TechniciansFieldArray({
|
||||||
contactName: "",
|
contactName: "",
|
||||||
phone: "",
|
phone: "",
|
||||||
email: "",
|
email: "",
|
||||||
preferredContact: "Phone",
|
|
||||||
tradeSpecialties: "",
|
tradeSpecialties: "",
|
||||||
isActive: true,
|
isActive: true,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue