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)}
+
+ >
+ )}