fix(vendors): preserve the selected edit state

This commit is contained in:
Alexandre Brandizzi 2026-08-06 22:39:11 -03:00
parent ff2e04b30e
commit ec203e1f5b
5 changed files with 82 additions and 22 deletions

View file

@ -15,7 +15,7 @@ export interface VendorDeactivationState {
confirm: () => void;
}
export function useVendorDeactivation(): VendorDeactivationState {
export function useVendorDeactivation(onSuccess?: () => void): VendorDeactivationState {
const [target, setTarget] = useState<VendorListItem | null>(null);
const [error, setError] = useState<string | null>(null);
const deleteVendor = useDeleteVendor();
@ -39,7 +39,10 @@ export function useVendorDeactivation(): VendorDeactivationState {
if (!target || target.id == null) return;
setError(null);
deleteVendor.mutate(target.id, {
onSuccess: () => setTarget(null),
onSuccess: () => {
setTarget(null);
onSuccess?.();
},
onError: (err: Error) => setError(err.message || "Failed to deactivate vendor"),
});
};

View file

@ -1,5 +1,5 @@
import { useEffect, useState, type ReactNode } from "react";
import { Controller } from "react-hook-form";
import { Controller, useWatch } from "react-hook-form";
import CloseIcon from "@mui/icons-material/Close";
import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
import LaunchIcon from "@mui/icons-material/Launch";
@ -250,13 +250,14 @@ function DrawerEditor({
onSuccess: onSaved,
});
const roster = form.roster;
const selectedIndex = Math.max(
0,
roster?.technicians.findIndex(
(technician) => technician.id != null && String(technician.id) === String(vendor.id),
) ?? 0,
const technicians = useWatch({ control: form.control, name: "technicians" }) ?? [];
const selectedIndex = technicians.findIndex(
(technician) => technician.id != null && String(technician.id) === String(vendor.id),
);
const selected = roster?.technicians[selectedIndex];
const selectedTotalJobs =
roster?.technicians.find(
(technician) => technician.id != null && String(technician.id) === String(vendor.id),
)?.totalJobs ?? vendor.totalJobs;
if (form.isLoading) {
return <CircularProgress size={28} sx={{ m: "auto" }} />;
@ -289,12 +290,12 @@ function DrawerEditor({
tradeOptions={form.trades}
showTechnicianStatus={false}
/>
{selected != null && (
{selectedIndex >= 0 && (
<>
<Divider />
<Stack direction="row" sx={{ alignItems: "center", justifyContent: "space-between" }}>
<Box>
<Text variant="title">{selected.totalJobs ?? 0}</Text>
<Text variant="title">{selectedTotalJobs}</Text>
<Text variant="caption" tone="muted">
Total Jobs
</Text>

View file

@ -116,7 +116,11 @@ export default function VendorsListPage() {
const [createOpen, setCreateOpen] = useState(false);
const [detailTarget, setDetailTarget] = useState<VendorListItem | null>(null);
const [detailStartsInEdit, setDetailStartsInEdit] = useState(false);
const deactivation = useVendorDeactivation();
const closeDetail = () => {
setDetailTarget(null);
setDetailStartsInEdit(false);
};
const deactivation = useVendorDeactivation(closeDetail);
const { data: facets } = useVendorFacets(filters.isActive);
const { data, isLoading, isFetching, error } = useVendorsList(filters.listParams);
@ -213,15 +217,8 @@ export default function VendorsListPage() {
open={Boolean(detailTarget)}
vendor={detailTarget}
startInEdit={detailStartsInEdit}
onRequestDeactivation={(row) => {
setDetailTarget(null);
setDetailStartsInEdit(false);
deactivation.open(row);
}}
onClose={() => {
setDetailTarget(null);
setDetailStartsInEdit(false);
}}
onRequestDeactivation={deactivation.open}
onClose={closeDetail}
/>
<VendorDeactivationDialog

View file

@ -110,4 +110,27 @@ describe("VendorDetailDrawer selected-technician display", () => {
expect(screen.getByText("0")).toBeInTheDocument();
expect(screen.queryByText("999-999-9999")).not.toBeInTheDocument();
});
it("does not expose a status control for a different technician", () => {
useVendorCompanyRoster.mockReturnValue(
rosterWith([
{
id: 7,
contactName: "Different Technician",
phone: "314-555-0101",
email: "",
tradeSpecialties: "Plumbing",
isActive: true,
totalJobs: 10,
},
]),
);
renderWithProviders(
<VendorDetailDrawer open={true} vendor={vendor} startInEdit={true} onClose={vi.fn()} />,
{ route: "/vendors", withAuth: false },
);
expect(screen.queryByRole("switch", { name: "Active status" })).not.toBeInTheDocument();
});
});

View file

@ -1,4 +1,4 @@
import { screen } from "@testing-library/react";
import { screen, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import VendorsListPage from "@/app/(protected)/vendors/index";
@ -220,6 +220,42 @@ describe("VendorsListPage", () => {
expect(mutate).not.toHaveBeenCalled();
});
it("preserves inline edits when deactivation is cancelled", async () => {
setupDefaults();
useVendorCompanyRoster.mockReturnValue({
data: activeRoster,
isLoading: false,
isError: false,
error: null,
refetch: vi.fn(),
});
useVendorDeactivationImpact.mockReturnValue({
data: { vendorId: 1, canDeactivate: true, openWorkOrders: [] },
isLoading: false,
error: null,
});
useVendorsList.mockImplementation((params: { isActive?: boolean; pageSize?: number }) => {
if (params.pageSize === 1) return result([], 1);
return params.isActive ? result([activeVendor], 1) : result([], 0);
});
renderWithProviders(<VendorsListPage />, { route: "/vendors", withAuth: false });
await userEvent.click(screen.getByRole("button", { name: "Edit vendor Gateway Plumbing" }));
const company = screen.getByRole("textbox", { name: "Company" });
await userEvent.clear(company);
await userEvent.type(company, "Draft Company Name");
await userEvent.click(screen.getByRole("switch", { name: "Active status" }));
await userEvent.click(
within(screen.getByRole("dialog", { name: "Deactivate Vendor" })).getByRole("button", {
name: "Cancel",
}),
);
expect(company).toBeInTheDocument();
expect(company).toHaveValue("Draft Company Name");
});
it("opens inline edit when the table Edit control is clicked", async () => {
setupDefaults();
useVendorCompanyRoster.mockReturnValue({