Stock Management
Distributor operational tool for receiving stock from suppliers, selling to retailers and proprietors, transfers, returns, and printed invoices. Built around how wholesale actually moves. Separate Vite + React 19 SPA and backend repos, both deployed.

The brief
A distributor was running wholesale inventory in three spreadsheets and a Telegram group. Stock counts disagreed across them weekly. Retailers got the wrong invoice. Receiving from suppliers and selling to shop owners were both manual data entry into the same file. They asked for “a simple inventory app.”
What they actually needed was a tool that mirrored how stock physically moves through wholesale: procurement, transfers, sales to retailers, returns, printing. Without making someone type the same SKU twice.
The problem
The status quo:
- Two systems of record: physical stock on the shelf and a spreadsheet that's always one delivery behind.
- Invoicing as an afterthought: printed from a different tool, manually keyed in, often wrong.
- No transfer concept: when stock moves between locations it just “disappears” and reappears.
- No audit trail: when a count is wrong, no one can tell when it went wrong.
The approach
I shipped this as two separate repos: a Vite + React 19 SPA and a TypeScript backend, both deployed on Vercel. That split was deliberate. The frontend is the high-iteration surface the operations team uses daily; the backend is the slower-changing source of truth.
The data model treats every stock change as an immutable movement event (receiving, sale, transfer, adjustment). The current count is derived from the sum. Nothing “updates” a stock level directly. That single decision makes the audit trail free and the bugs visible.
The hard parts
Printing invoices that look right
Wholesale invoices are a layout problem disguised as a data problem. Headers, line items, totals, footer notes, signature block. It has to print to whatever cheap thermal or A4 printer is on the desk. I render them in-browser with react-pdf, preview them in a react-to-print flow, and let the user save or print. Layout lives in code, not in a template the client has to maintain.
Optimistic UI with a real audit trail
The operations staff don't want to wait for a server round-trip every time they record a sale to a retailer. TanStack Query's optimistic mutations let the UI update instantly while the movement event posts in the background. If the server rejects (price mismatch, out of stock), the optimistic state rolls back and a toast explains why. The movement log records both attempts, so an audit shows what happened.
State that survives a refresh
Carts, draft transfers, draft returns. Anything mid-flight lives in Zustand with localStorage persistence. A staff member can start a sale, get pulled away, come back twenty minutes later, and find the cart exactly as they left it. Small detail, but it determines whether software gets used.
- Vite + React 19shell
- Fast HMR, fast page load on the operations PC.
- TanStack Querydata
- Server state + optimistic mutations.
- TanStack Tableui
- Dense, sortable tables for stock + sales lists.
- Zustandstate
- Persisted local state for in-flight carts and drafts.
- react-pdf + react-to-printprint
- Layout-driven invoices, no templates to maintain.
- Better-Authauth
- Role-scoped sessions: cashier vs admin.
What's running today
Both the frontend and the backend are deployed and in daily use for the distributor's wholesale operations.
What I'd do differently
The two-repo split was right for this team, but it added deploy-coordination friction. Next time I'd reach for a monorepo from day one. Turbo's pipelines catch the “don't ship the frontend if the backend types changed” case more reliably than a careful git habit.