mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-07 12:49:00 +00:00
fix(vendors): persist technician removal
This commit is contained in:
parent
35bfb9b7c8
commit
146491fafb
4 changed files with 114 additions and 25 deletions
|
|
@ -8,19 +8,35 @@ import {
|
||||||
import { getSingleStatusOnlyChange } from "@/domain/vendors/use-cases/use-save-vendor-company-roster";
|
import { getSingleStatusOnlyChange } from "@/domain/vendors/use-cases/use-save-vendor-company-roster";
|
||||||
import type { VendorCompanyRoster } from "@/domain/vendors/types/vendor";
|
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(
|
export function useVendorRosterResolver(
|
||||||
routeRoster: VendorCompanyRoster | undefined,
|
routeRoster: VendorCompanyRoster | undefined,
|
||||||
): Resolver<VendorCompanyRosterFormValues> {
|
): Resolver<VendorCompanyRosterFormValues> {
|
||||||
const strictResolver = useMemo(() => zodResolver(vendorCompanyRosterSchema), []);
|
const strictResolver = useMemo(() => zodResolver(vendorCompanyRosterSchema), []);
|
||||||
return useMemo<Resolver<VendorCompanyRosterFormValues>>(
|
return useMemo<Resolver<VendorCompanyRosterFormValues>>(
|
||||||
() => async (values, context, options) => {
|
() => async (values, context, options) => {
|
||||||
if (
|
const normalizedValues = withoutTechnicianStubs(values);
|
||||||
routeRoster &&
|
if (routeRoster && getSingleStatusOnlyChange(routeRoster, normalizedValues)) {
|
||||||
getSingleStatusOnlyChange(routeRoster, values as VendorCompanyRosterFormValues)
|
return { values: normalizedValues, errors: {} };
|
||||||
) {
|
|
||||||
return { values, errors: {} };
|
|
||||||
}
|
}
|
||||||
return strictResolver(values, context, options);
|
return strictResolver(normalizedValues, context, options);
|
||||||
},
|
},
|
||||||
[routeRoster, strictResolver],
|
[routeRoster, strictResolver],
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -315,6 +315,7 @@ function DrawerEditor({
|
||||||
<Controller
|
<Controller
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name={`technicians.${selectedIndex}.isActive`}
|
name={`technicians.${selectedIndex}.isActive`}
|
||||||
|
shouldUnregister
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Stack direction="row" spacing={1} sx={{ alignItems: "center" }}>
|
<Stack direction="row" spacing={1} sx={{ alignItems: "center" }}>
|
||||||
<Text variant="description">{field.value ? "Active" : "Inactive"}</Text>
|
<Text variant="description">{field.value ? "Active" : "Inactive"}</Text>
|
||||||
|
|
|
||||||
|
|
@ -348,23 +348,22 @@ function TechnicianRow({
|
||||||
tradeOptions={tradeOptions}
|
tradeOptions={tradeOptions}
|
||||||
tradeOptionsLoading={tradeOptionsLoading}
|
tradeOptionsLoading={tradeOptionsLoading}
|
||||||
/>
|
/>
|
||||||
{showStatus && (
|
<Controller
|
||||||
<Controller
|
control={control}
|
||||||
control={control}
|
name={`technicians.${index}.isActive`}
|
||||||
name={`technicians.${index}.isActive`}
|
render={({ field }) => (
|
||||||
render={({ field }) => (
|
<FormControlLabel
|
||||||
<FormControlLabel
|
sx={{ display: showStatus ? undefined : "none" }}
|
||||||
control={
|
control={
|
||||||
<Switch
|
<Switch
|
||||||
checked={field.value}
|
checked={Boolean(field.value)}
|
||||||
onChange={(_event, checked) => field.onChange(checked)}
|
onChange={(_event, checked) => field.onChange(checked)}
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
label={field.value ? "Active" : "Inactive"}
|
label={field.value ? "Active" : "Inactive"}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 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 { VendorDetailDrawer } from "@/app/(protected)/vendors/_components/vendor-detail-drawer";
|
||||||
import { renderWithProviders } from "@/test/test-utils";
|
import { renderWithProviders } from "@/test/test-utils";
|
||||||
import type { VendorListItem } from "@/domain/vendors/types/vendor";
|
import type { VendorListItem } from "@/domain/vendors/types/vendor";
|
||||||
|
|
||||||
const useVendorCompanyRoster = vi.fn();
|
const useVendorCompanyRoster = vi.fn();
|
||||||
|
const saveMutate = vi.fn();
|
||||||
|
|
||||||
vi.mock("@/domain/vendors/use-cases/use-vendor-company-roster", () => ({
|
vi.mock("@/domain/vendors/use-cases/use-vendor-company-roster", () => ({
|
||||||
useVendorCompanyRoster: (...args: unknown[]) => useVendorCompanyRoster(...args),
|
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 = {
|
const vendor: VendorListItem = {
|
||||||
id: 1,
|
id: 1,
|
||||||
companyId: "co-1",
|
companyId: "co-1",
|
||||||
|
|
@ -50,7 +61,69 @@ function rosterWith(technicians: Array<Record<string, unknown>>) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
saveMutate.mockReset();
|
||||||
|
});
|
||||||
|
|
||||||
describe("VendorDetailDrawer selected-technician display", () => {
|
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 () => {
|
it("keeps Save changes available after removing a technician from a legacy roster", async () => {
|
||||||
const roster = rosterWith([
|
const roster = rosterWith([
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue