diff --git a/src/app/(app)/stock/page.test.tsx b/src/app/(app)/stock/page.test.tsx index 353baba..c5aa036 100644 --- a/src/app/(app)/stock/page.test.tsx +++ b/src/app/(app)/stock/page.test.tsx @@ -302,4 +302,39 @@ describe("StockListPage", () => { }); expect(submit).not.toBeDisabled(); }); + + describe("Stock list — show pricing columns toggle", () => { + beforeEach(() => { + _resetState(); + _state.items = [ + { + id: "abcdefghijklmno", + sku: "B-001", + name: "M10 Hex Bolt", + current_qty: 42, + min_qty: 10, + target_qty: 0, + is_active: true, + supplier: "", + }, + ]; + }); + + it("hides pricing columns by default", async () => { + render(); + expect(screen.queryByText("Cost")).toBeNull(); + expect(screen.queryByText("Markup")).toBeNull(); + expect(screen.queryByText("Selling")).toBeNull(); + }); + + it("shows pricing columns after toggling Show pricing on", async () => { + render(); + fireEvent.click( + screen.getByRole("switch", { name: /Show pricing columns/i }), + ); + expect(await screen.findByText("Cost (R)")).toBeInTheDocument(); + expect(screen.getByText("Markup %")).toBeInTheDocument(); + expect(screen.getByText("Selling (R)")).toBeInTheDocument(); + }); + }); }); diff --git a/src/app/(app)/stock/page.tsx b/src/app/(app)/stock/page.tsx index 4246dbd..b3f3cc8 100644 --- a/src/app/(app)/stock/page.tsx +++ b/src/app/(app)/stock/page.tsx @@ -86,6 +86,7 @@ export default function StockListPage() { /** Debounced search — what we actually query for. */ const [search, setSearch] = useState(""); const [lowStockOnly, setLowStockOnly] = useState(false); + const [showPricingColumns, setShowPricingColumns] = useState(false); const [createOpen, setCreateOpen] = useState(false); const fetchItems = useCallback(async () => { @@ -131,6 +132,22 @@ export default function StockListPage() { fetchItems(); }, [fetchItems]); + // Hydrate the "Show pricing columns" toggle from localStorage on mount. + // Defaults to false when the key is missing or the value is anything + // other than the literal "true". + useEffect(() => { + if (typeof window === "undefined") return; + const v = window.localStorage.getItem("wfdash.stock.showPricing"); + if (v === "true") setShowPricingColumns(true); + }, []); + + // Mirror toggle changes back to localStorage so the operator's choice + // persists across page navigations and reloads. + useEffect(() => { + if (typeof window === "undefined") return; + window.localStorage.setItem("wfdash.stock.showPricing", String(showPricingColumns)); + }, [showPricingColumns]); + // 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. @@ -249,6 +266,16 @@ export default function StockListPage() { + setShowPricingColumns(checked === true)} + aria-label="Show pricing columns" + data-testid="show-pricing-toggle" + /> + @@ -296,6 +323,13 @@ export default function StockListPage() { Current Min Target + {showPricingColumns && ( + <> + Cost (R) + Markup % + Selling (R) + + )} Status @@ -305,6 +339,7 @@ export default function StockListPage() { router.push(`/stock/${item.id}`)} /> ))} @@ -341,9 +376,21 @@ export default function StockListPage() { * so the supplier name link inside can stay a real anchor without * triggering the nested-button trap. * ========================================================================== */ -function StockRow({ item, onOpen }: { item: StockItem; onOpen: () => void }) { +function StockRow({ + item, + showPricingColumns, + onOpen, +}: { + item: StockItem; + showPricingColumns: boolean; + onOpen: () => void; +}) { const status = getStockStatus(item.current_qty, item.min_qty, item.is_active); const supplierName = item.expand?.supplier?.suppliername || "—"; + const formatPrice = (n: number | undefined) => + typeof n === "number" && n > 0 ? `R ${n.toFixed(2)}` : "—"; + const formatPercent = (n: number | undefined) => + typeof n === "number" && n > 0 ? `${n}%` : "—"; return ( void }) { — )} + {showPricingColumns && ( + <> + + {formatPrice(item.cost_price)} + + + {formatPercent(item.markup)} + + + {formatPrice(item.selling_price)} + + + )}