fix(vendors): persist technician removal

This commit is contained in:
Codex Review Integration 2026-08-20 20:43:52 -03:00
parent 35bfb9b7c8
commit 146491fafb
4 changed files with 114 additions and 25 deletions

View file

@ -8,19 +8,35 @@ import {
import { getSingleStatusOnlyChange } from "@/domain/vendors/use-cases/use-save-vendor-company-roster";
import type { VendorCompanyRoster } from "@/domain/vendors/types/vendor";
function withoutTechnicianStubs(
values: VendorCompanyRosterFormValues,
): VendorCompanyRosterFormValues {
return {
...values,
technicians: values.technicians.filter((technician) => {
if (technician === null || typeof technician !== "object" || Array.isArray(technician)) {
return true;
}
const candidate = technician as unknown as Record<string, unknown>;
const keys = Object.keys(candidate);
const isGeneratedStatusStub =
keys.length === 1 && keys[0] === "isActive" && typeof candidate.isActive === "boolean";
return !isGeneratedStatusStub;
}),
};
}
export function useVendorRosterResolver(
routeRoster: VendorCompanyRoster | undefined,
): Resolver<VendorCompanyRosterFormValues> {
const strictResolver = useMemo(() => zodResolver(vendorCompanyRosterSchema), []);
return useMemo<Resolver<VendorCompanyRosterFormValues>>(
() => async (values, context, options) => {
if (
routeRoster &&
getSingleStatusOnlyChange(routeRoster, values as VendorCompanyRosterFormValues)
) {
return { values, errors: {} };
const normalizedValues = withoutTechnicianStubs(values);
if (routeRoster && getSingleStatusOnlyChange(routeRoster, normalizedValues)) {
return { values: normalizedValues, errors: {} };
}
return strictResolver(values, context, options);
return strictResolver(normalizedValues, context, options);
},
[routeRoster, strictResolver],
);

View file

@ -315,6 +315,7 @@ function DrawerEditor({
<Controller
control={form.control}
name={`technicians.${selectedIndex}.isActive`}
shouldUnregister
render={({ field }) => (
<Stack direction="row" spacing={1} sx={{ alignItems: "center" }}>
<Text variant="description">{field.value ? "Active" : "Inactive"}</Text>

View file

@ -348,23 +348,22 @@ function TechnicianRow({
tradeOptions={tradeOptions}
tradeOptionsLoading={tradeOptionsLoading}
/>
{showStatus && (
<Controller
control={control}
name={`technicians.${index}.isActive`}
render={({ field }) => (
<FormControlLabel
control={
<Switch
checked={field.value}
onChange={(_event, checked) => field.onChange(checked)}
/>
}
label={field.value ? "Active" : "Inactive"}
/>
)}
/>
)}
<Controller
control={control}
name={`technicians.${index}.isActive`}
render={({ field }) => (
<FormControlLabel
sx={{ display: showStatus ? undefined : "none" }}
control={
<Switch
checked={Boolean(field.value)}
onChange={(_event, checked) => field.onChange(checked)}
/>
}
label={field.value ? "Active" : "Inactive"}
/>
)}
/>
</Stack>
);
}

View file

@ -1,16 +1,27 @@
import { screen } from "@testing-library/react";
import { screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { VendorDetailDrawer } from "@/app/(protected)/vendors/_components/vendor-detail-drawer";
import { renderWithProviders } from "@/test/test-utils";
import type { VendorListItem } from "@/domain/vendors/types/vendor";
const useVendorCompanyRoster = vi.fn();
const saveMutate = vi.fn();
vi.mock("@/domain/vendors/use-cases/use-vendor-company-roster", () => ({
useVendorCompanyRoster: (...args: unknown[]) => useVendorCompanyRoster(...args),
}));
vi.mock("@/domain/vendors/use-cases/use-save-vendor-company-roster", async () => {
const actual = await vi.importActual<
typeof import("@/domain/vendors/use-cases/use-save-vendor-company-roster")
>("@/domain/vendors/use-cases/use-save-vendor-company-roster");
return {
...actual,
useSaveVendorCompanyRoster: () => ({ mutate: saveMutate, isPending: false }),
};
});
const vendor: VendorListItem = {
id: 1,
companyId: "co-1",
@ -50,7 +61,69 @@ function rosterWith(technicians: Array<Record<string, unknown>>) {
};
}
beforeEach(() => {
saveMutate.mockReset();
});
describe("VendorDetailDrawer selected-technician display", () => {
it.each([
{ removedPosition: 1, remainingId: 2, remainingName: "Beth" },
{ removedPosition: 2, remainingId: 1, remainingName: "Adam" },
])(
"reconciles the remaining roster after removing technician $removedPosition",
async ({ removedPosition, remainingId, remainingName }) => {
useVendorCompanyRoster.mockReturnValue(
rosterWith([
{
id: 1,
contactName: "Adam",
phone: "314-555-0198",
email: "",
tradeSpecialties: "",
isActive: removedPosition === 1,
totalJobs: 5,
},
{
id: 2,
contactName: "Beth",
phone: "314-555-0199",
email: "",
tradeSpecialties: "",
isActive: removedPosition === 2,
totalJobs: 2,
},
]),
);
renderWithProviders(
<VendorDetailDrawer open={true} vendor={vendor} startInEdit={true} onClose={vi.fn()} />,
{ route: "/vendors", withAuth: false },
);
await userEvent.click(
screen.getByRole("button", { name: `Remove technician ${removedPosition}` }),
);
await userEvent.click(screen.getByRole("button", { name: "Save changes" }));
await waitFor(() => expect(saveMutate).toHaveBeenCalledTimes(1));
expect(saveMutate).toHaveBeenCalledWith(
expect.objectContaining({
mode: "update",
values: expect.objectContaining({
technicians: [
expect.objectContaining({
id: remainingId,
contactName: remainingName,
isActive: false,
}),
],
}),
}),
expect.any(Object),
);
},
);
it("keeps Save changes available after removing a technician from a legacy roster", async () => {
const roster = rosterWith([
{