mirror of
https://github.com/Sea-Haven-Industries/shoc-frontend-new.git
synced 2026-10-06 15:52:04 +00:00
fix(vendors): preserve the selected edit state
This commit is contained in:
parent
ff2e04b30e
commit
ec203e1f5b
5 changed files with 82 additions and 22 deletions
|
|
@ -15,7 +15,7 @@ export interface VendorDeactivationState {
|
||||||
confirm: () => void;
|
confirm: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useVendorDeactivation(): VendorDeactivationState {
|
export function useVendorDeactivation(onSuccess?: () => void): VendorDeactivationState {
|
||||||
const [target, setTarget] = useState<VendorListItem | null>(null);
|
const [target, setTarget] = useState<VendorListItem | null>(null);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const deleteVendor = useDeleteVendor();
|
const deleteVendor = useDeleteVendor();
|
||||||
|
|
@ -39,7 +39,10 @@ export function useVendorDeactivation(): VendorDeactivationState {
|
||||||
if (!target || target.id == null) return;
|
if (!target || target.id == null) return;
|
||||||
setError(null);
|
setError(null);
|
||||||
deleteVendor.mutate(target.id, {
|
deleteVendor.mutate(target.id, {
|
||||||
onSuccess: () => setTarget(null),
|
onSuccess: () => {
|
||||||
|
setTarget(null);
|
||||||
|
onSuccess?.();
|
||||||
|
},
|
||||||
onError: (err: Error) => setError(err.message || "Failed to deactivate vendor"),
|
onError: (err: Error) => setError(err.message || "Failed to deactivate vendor"),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { useEffect, useState, type ReactNode } from "react";
|
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 CloseIcon from "@mui/icons-material/Close";
|
||||||
import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
|
import EditOutlinedIcon from "@mui/icons-material/EditOutlined";
|
||||||
import LaunchIcon from "@mui/icons-material/Launch";
|
import LaunchIcon from "@mui/icons-material/Launch";
|
||||||
|
|
@ -250,13 +250,14 @@ function DrawerEditor({
|
||||||
onSuccess: onSaved,
|
onSuccess: onSaved,
|
||||||
});
|
});
|
||||||
const roster = form.roster;
|
const roster = form.roster;
|
||||||
const selectedIndex = Math.max(
|
const technicians = useWatch({ control: form.control, name: "technicians" }) ?? [];
|
||||||
0,
|
const selectedIndex = technicians.findIndex(
|
||||||
roster?.technicians.findIndex(
|
(technician) => technician.id != null && String(technician.id) === String(vendor.id),
|
||||||
(technician) => technician.id != null && String(technician.id) === String(vendor.id),
|
|
||||||
) ?? 0,
|
|
||||||
);
|
);
|
||||||
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) {
|
if (form.isLoading) {
|
||||||
return <CircularProgress size={28} sx={{ m: "auto" }} />;
|
return <CircularProgress size={28} sx={{ m: "auto" }} />;
|
||||||
|
|
@ -289,12 +290,12 @@ function DrawerEditor({
|
||||||
tradeOptions={form.trades}
|
tradeOptions={form.trades}
|
||||||
showTechnicianStatus={false}
|
showTechnicianStatus={false}
|
||||||
/>
|
/>
|
||||||
{selected != null && (
|
{selectedIndex >= 0 && (
|
||||||
<>
|
<>
|
||||||
<Divider />
|
<Divider />
|
||||||
<Stack direction="row" sx={{ alignItems: "center", justifyContent: "space-between" }}>
|
<Stack direction="row" sx={{ alignItems: "center", justifyContent: "space-between" }}>
|
||||||
<Box>
|
<Box>
|
||||||
<Text variant="title">{selected.totalJobs ?? 0}</Text>
|
<Text variant="title">{selectedTotalJobs}</Text>
|
||||||
<Text variant="caption" tone="muted">
|
<Text variant="caption" tone="muted">
|
||||||
Total Jobs
|
Total Jobs
|
||||||
</Text>
|
</Text>
|
||||||
|
|
|
||||||
17
src/app/(protected)/vendors/index.tsx
vendored
17
src/app/(protected)/vendors/index.tsx
vendored
|
|
@ -116,7 +116,11 @@ export default function VendorsListPage() {
|
||||||
const [createOpen, setCreateOpen] = useState(false);
|
const [createOpen, setCreateOpen] = useState(false);
|
||||||
const [detailTarget, setDetailTarget] = useState<VendorListItem | null>(null);
|
const [detailTarget, setDetailTarget] = useState<VendorListItem | null>(null);
|
||||||
const [detailStartsInEdit, setDetailStartsInEdit] = useState(false);
|
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: facets } = useVendorFacets(filters.isActive);
|
||||||
const { data, isLoading, isFetching, error } = useVendorsList(filters.listParams);
|
const { data, isLoading, isFetching, error } = useVendorsList(filters.listParams);
|
||||||
|
|
@ -213,15 +217,8 @@ export default function VendorsListPage() {
|
||||||
open={Boolean(detailTarget)}
|
open={Boolean(detailTarget)}
|
||||||
vendor={detailTarget}
|
vendor={detailTarget}
|
||||||
startInEdit={detailStartsInEdit}
|
startInEdit={detailStartsInEdit}
|
||||||
onRequestDeactivation={(row) => {
|
onRequestDeactivation={deactivation.open}
|
||||||
setDetailTarget(null);
|
onClose={closeDetail}
|
||||||
setDetailStartsInEdit(false);
|
|
||||||
deactivation.open(row);
|
|
||||||
}}
|
|
||||||
onClose={() => {
|
|
||||||
setDetailTarget(null);
|
|
||||||
setDetailStartsInEdit(false);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<VendorDeactivationDialog
|
<VendorDeactivationDialog
|
||||||
|
|
|
||||||
|
|
@ -110,4 +110,27 @@ describe("VendorDetailDrawer selected-technician display", () => {
|
||||||
expect(screen.getByText("0")).toBeInTheDocument();
|
expect(screen.getByText("0")).toBeInTheDocument();
|
||||||
expect(screen.queryByText("999-999-9999")).not.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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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 userEvent from "@testing-library/user-event";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { describe, expect, it, vi } from "vitest";
|
||||||
import VendorsListPage from "@/app/(protected)/vendors/index";
|
import VendorsListPage from "@/app/(protected)/vendors/index";
|
||||||
|
|
@ -220,6 +220,42 @@ describe("VendorsListPage", () => {
|
||||||
expect(mutate).not.toHaveBeenCalled();
|
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 () => {
|
it("opens inline edit when the table Edit control is clicked", async () => {
|
||||||
setupDefaults();
|
setupDefaults();
|
||||||
useVendorCompanyRoster.mockReturnValue({
|
useVendorCompanyRoster.mockReturnValue({
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue