Compare commits

...

2 commits

Author SHA1 Message Date
Alexandre Brandizzi
db3ae2ca3f fix(vendors): keep company input in sync 2026-07-22 11:30:02 -03:00
Alexandre Brandizzi
ff10bbd8ec fix(vendors): report the filtered total 2026-07-22 11:00:08 -03:00
4 changed files with 172 additions and 8 deletions

View file

@ -46,7 +46,6 @@ export function VendorFormFields({
onCompanySelected,
showTotalJobs = false,
}: VendorFormFieldsProps) {
const [companyInput, setCompanyInput] = useState("");
const [tradeInput, setTradeInput] = useState("");
return (
@ -99,28 +98,24 @@ export function VendorFormFields({
: option.name === value.name
}
value={field.value ?? ""}
inputValue={companyInput || (typeof field.value === "string" ? field.value : "")}
inputValue={field.value ?? ""}
onInputChange={(_event, value, reason) => {
if (reason === "input") {
setCompanyInput(value);
field.onChange(value);
}
}}
onChange={(_event, value, reason) => {
if (typeof value === "string") {
field.onChange(value);
setCompanyInput(value);
return;
}
if (value) {
field.onChange(value.name);
setCompanyInput(value.name);
if (reason === "selectOption") {
onCompanySelected(value);
}
} else {
field.onChange("");
setCompanyInput("");
}
}}
renderInput={(params) => (

View file

@ -416,8 +416,8 @@ export default function VendorsListPage() {
}}
>
<Typography variant="body2" sx={{ color: "text.secondary" }}>
{rows.length} {isActive ? "active" : "inactive"}{" "}
{rows.length === 1 ? "technician" : "technicians"}
{totalCount} {isActive ? "active" : "inactive"}{" "}
{totalCount === 1 ? "technician" : "technicians"}
{(appliedChips.length > 0 || debouncedSearch) && " · filtered"}
{` · ${activeCount} active · ${inactiveCount} inactive`}
</Typography>

View file

@ -0,0 +1,151 @@
import { screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm, type FieldErrors } from "react-hook-form";
import { describe, expect, it } from "vitest";
import { VendorFormFields } from "@/app/(protected)/vendors/_components/vendor-form-fields";
import { vendorSchema, type VendorFormValues } from "@/domain/vendors/schemas/vendor-schema";
import type { VendorFacetCompany } from "@/domain/vendors/types/vendor";
import { renderWithProviders } from "@/test/test-utils";
const companies: VendorFacetCompany[] = [
{
name: "Gateway Plumbing",
companyPhone: "(314) 555-0198",
email: "dispatch@gatewayplumbing.com",
address: "1 Main St",
city: "St. Louis",
state: "MO",
zip: "63101",
googleMapsUrl: "https://maps.google.com/gateway",
},
];
const emptyForm: VendorFormValues = {
companyName: "",
contactName: "Tech Person",
email: "",
phone: "",
companyPhone: "",
googleMapsUrl: "",
notes: "",
totalJobs: 0,
address: "",
city: "",
state: "",
zip: "",
tradeSpecialties: "",
isActive: true,
};
function Harness({
defaultValues,
resetTarget,
}: {
defaultValues: VendorFormValues;
resetTarget: VendorFormValues;
}) {
const { control, reset, setValue } = useForm<VendorFormValues>({
resolver: zodResolver(vendorSchema),
defaultValues,
});
const onCompanySelected = (company: VendorFacetCompany | null) => {
if (!company) return;
setValue("companyPhone", company.companyPhone);
setValue("email", company.email);
setValue("address", company.address);
setValue("city", company.city);
setValue("state", company.state);
setValue("zip", company.zip);
setValue("googleMapsUrl", company.googleMapsUrl);
};
return (
<>
<VendorFormFields
control={control}
errors={{} as FieldErrors<VendorFormValues>}
companies={companies}
tradeOptions={[]}
onCompanySelected={onCompanySelected}
/>
<button type="button" onClick={() => reset(resetTarget)}>
reset-form
</button>
</>
);
}
const companyField = () => screen.getByRole("combobox", { name: "Company" }) as HTMLInputElement;
describe("VendorFormFields company autocomplete state", () => {
it("clears a previously typed company when the form is reset (create-form reopen)", async () => {
renderWithProviders(<Harness defaultValues={emptyForm} resetTarget={emptyForm} />, {
withAuth: false,
});
await userEvent.type(companyField(), "Leftover Co");
expect(companyField()).toHaveValue("Leftover Co");
await userEvent.click(screen.getByRole("button", { name: "reset-form" }));
expect(companyField()).toHaveValue("");
});
it("shows the switched company value instead of previously typed text when the form is reset", async () => {
renderWithProviders(
<Harness
defaultValues={emptyForm}
resetTarget={{ ...emptyForm, companyName: "Beta LLC", contactName: "Other Tech" }}
/>,
{ withAuth: false },
);
await userEvent.type(companyField(), "Acme");
expect(companyField()).toHaveValue("Acme");
await userEvent.click(screen.getByRole("button", { name: "reset-form" }));
expect(companyField()).toHaveValue("Beta LLC");
});
it("reflects the company provided by an external reset even when nothing was typed", async () => {
renderWithProviders(
<Harness
defaultValues={{ ...emptyForm, companyName: "Acme Corp" }}
resetTarget={{ ...emptyForm, companyName: "Beta LLC" }}
/>,
{ withAuth: false },
);
expect(companyField()).toHaveValue("Acme Corp");
await userEvent.click(screen.getByRole("button", { name: "reset-form" }));
expect(companyField()).toHaveValue("Beta LLC");
});
it("updates the displayed company as the user types (freeSolo)", async () => {
renderWithProviders(<Harness defaultValues={emptyForm} resetTarget={emptyForm} />, {
withAuth: false,
});
await userEvent.type(companyField(), "Acme");
expect(companyField()).toHaveValue("Acme");
});
it("selects a company option, autofills company details, and shows the company name", async () => {
renderWithProviders(<Harness defaultValues={emptyForm} resetTarget={emptyForm} />, {
withAuth: false,
});
await userEvent.type(companyField(), "Gate");
const option = await screen.findByRole("option", { name: "Gateway Plumbing" });
await userEvent.click(option);
expect(companyField()).toHaveValue("Gateway Plumbing");
expect(screen.getByRole("textbox", { name: "Company Phone" })).toHaveValue("(314) 555-0198");
});
});

View file

@ -65,4 +65,22 @@ describe("VendorsListPage", () => {
expect(screen.getByText("Drain Cleaning Bros")).toBeInTheDocument();
expect(screen.queryByRole("button", { name: "Deactivate vendor" })).not.toBeInTheDocument();
});
it("reports the filtered totalCount in the footer when page rows are fewer than total", () => {
const pageVendors = Array.from({ length: 3 }, (_, index) => ({
...activeVendor,
id: 100 + index,
companyName: `Vendor ${index + 1}`,
}));
useVendorsList.mockImplementation((params: { isActive?: boolean; pageSize?: number }) => {
if (params.pageSize === 1) return result([], params.isActive ? 25 : 0);
return result(pageVendors, 25);
});
renderWithProviders(<VendorsListPage />, { route: "/vendors", withAuth: false });
expect(screen.getByText(/25 active technicians/)).toBeInTheDocument();
expect(screen.queryByText(/3 active technicians/)).not.toBeInTheDocument();
});
});