feat(stock): persist pricing draft to localStorage

This commit is contained in:
Dave 2026-08-31 17:14:34 +02:00
parent 759e05048c
commit 7aaed60860
2 changed files with 50 additions and 0 deletions

View file

@ -379,6 +379,30 @@ describe("StockListPage", () => {
});
});
describe("Stock list — localStorage draft persistence", () => {
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("survives a page remount via localStorage", async () => {
const { unmount } = render(<Page />);
await screen.findByText("M10 Hex Bolt");
fireEvent.click(screen.getByRole("button", { name: /Edit pricing/i }));
const costInput = await screen.findByLabelText(/Cost for B-001/i);
fireEvent.change(costInput, { target: { value: "1.23" } });
unmount();
render(<Page />);
await screen.findByText("M10 Hex Bolt");
fireEvent.click(screen.getByRole("button", { name: /Edit pricing/i }));
expect(await screen.findByText(/Apply changes/i)).toBeInTheDocument();
});
});
describe("Stock list — missing-pricing-only filter", () => {
beforeEach(() => {
_resetState();

View file

@ -156,6 +156,27 @@ export default function StockListPage() {
window.localStorage.setItem("wfdash.stock.showPricing", String(showPricingColumns));
}, [showPricingColumns]);
// Rehydrate draft from localStorage on mount.
useEffect(() => {
if (typeof window === "undefined") return;
try {
const raw = window.localStorage.getItem("wfdash.stock.pricingDraft");
if (raw) setPricingDraft(JSON.parse(raw));
} catch {
// ignore corrupt draft
}
}, []);
// Persist draft every change.
useEffect(() => {
if (typeof window === "undefined") return;
try {
window.localStorage.setItem("wfdash.stock.pricingDraft", JSON.stringify(pricingDraft));
} catch {
// localStorage quota — best-effort
}
}, [pricingDraft]);
// 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.
@ -250,6 +271,11 @@ export default function StockListPage() {
Object.entries(pricingDraft).filter(([id]) => failedIds.has(id)),
);
setPricingDraft(failedDraftEntries);
if (failedIds.size === 0) {
window.localStorage.removeItem("wfdash.stock.pricingDraft");
} else {
window.localStorage.setItem("wfdash.stock.pricingDraft", JSON.stringify(failedDraftEntries));
}
setPricingApplyState(failedIds.size > 0 ? "partial" : "applied");
toast.success(
failedIds.size > 0