feat(stock): beforeunload warning when pricing draft is dirty
This commit is contained in:
parent
7aaed60860
commit
9f27e9d4f1
2 changed files with 38 additions and 0 deletions
|
|
@ -428,4 +428,25 @@ describe("StockListPage", () => {
|
|||
expect(await screen.findByText("Missing both")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Stock list — beforeunload while dirty", () => {
|
||||
beforeEach(() => {
|
||||
_resetState();
|
||||
_state.items = [
|
||||
{ id: "aaaaaaaaaaaaaaa", sku: "B-001", name: "M10 Hex Bolt", selling_price: 0, cost_price: 0, markup: 0, current_qty: 5, min_qty: 0, is_active: true, supplier: "" },
|
||||
];
|
||||
window.localStorage.clear();
|
||||
});
|
||||
afterEach(() => cleanup());
|
||||
|
||||
it("registers a beforeunload listener when there are dirty rows", async () => {
|
||||
const addSpy = vi.spyOn(window, "addEventListener");
|
||||
render(<Page />);
|
||||
await screen.findByText("M10 Hex Bolt"); // wait for items
|
||||
fireEvent.click(screen.getByRole("button", { name: /Edit pricing/i }));
|
||||
const costInput = await screen.findByLabelText(/Cost for B-001/i);
|
||||
fireEvent.change(costInput, { target: { value: "5" } });
|
||||
expect(addSpy).toHaveBeenCalledWith("beforeunload", expect.any(Function));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -177,6 +177,23 @@ export default function StockListPage() {
|
|||
}
|
||||
}, [pricingDraft]);
|
||||
|
||||
// Warn the operator before they navigate away with unsaved pricing
|
||||
// changes. Only registered when edit mode is on AND there's actually
|
||||
// something in the draft — the bar already gates the apply button
|
||||
// on the same condition, so a Cmd-W / tab close here would silently
|
||||
// lose the work otherwise.
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined") return;
|
||||
const isDirty = Object.keys(pricingDraft).length > 0 && editPricing;
|
||||
if (!isDirty) return;
|
||||
const handler = (e: BeforeUnloadEvent) => {
|
||||
e.preventDefault();
|
||||
e.returnValue = "";
|
||||
};
|
||||
window.addEventListener("beforeunload", handler);
|
||||
return () => window.removeEventListener("beforeunload", handler);
|
||||
}, [pricingDraft, editPricing]);
|
||||
|
||||
// Debounce the search input → 250ms after the last keystroke. Same
|
||||
// pattern as /orders and /quotes — the operator is hunting for a
|
||||
// match, not paging through.
|
||||
|
|
|
|||
Loading…
Reference in a new issue