Intelligent Inventory
Universidad Nacional de Colombia · 2026 - At present
The Prototyping Center lends tools, components and materials out of a set of cabinets, and until now nobody could answer "who has the soldering station and since when?" without asking around. This full-stack application puts the cabinets, the inventory and the reservations in one place, records every movement, and can pop a cabinet open over MQTT from the same screen.
Architecture and Tech Stack
Core Architecture
- Framework: Next.js 16 (App Router), server-first, with the React Compiler enabled
- Language: TypeScript 5.9 in strict mode
- Backend platform: Supabase — PostgreSQL, Auth and Realtime (
@supabase/ssr) - Validation: Zod schemas as the single input gate for every server action
- Hardware bridge:
mqttovermqtts(port 8883) against a HiveMQ-compatible broker - Tables and charts: TanStack Table for the admin grids, Recharts for the dashboard
- Exports: jsPDF + jspdf-autotable for inventory reports
- UI: shadcn/ui (Radix Nova) + Tailwind CSS 4,
next-themesfor light/dark
Layered Architecture
Reads and writes are separated by folder, not by convention: every mutation lives in lib/actions/* and every query in lib/data/*. A page never queries Supabase inline, and a form never reads — which is what keeps authorization checks in exactly one place per operation.
Request Flow
Key Features
Features at a Glance
Cabinets and quick open
Each cabinet is modelled digitally with its contents and state. From the admin panel a cabinet can be opened directly: the action publishes a command to the MQTT broker with its own timeout and error handling, so a broker that is down surfaces as a failed action instead of a spinner that never resolves.
Reservations
Students and staff request materials; the team approves or rejects from the same panel. The reservation carries its own lifecycle and shows up in the item's history, so a piece of equipment that is out on loan cannot be silently double-booked.
Roles and access
Authentication, profiles and role-based permissions (admin / root gates the whole /admin area). Access is checked in three layers: session refresh at the request proxy, a role gate in the admin layout, and assertAdmin() inside the actions themselves.
Access history and sessions
A full audit trail of cabinet openings and material usage, plus a session timeline view — who used what, when, and for how long.
Admin dashboard
KPIs plus five analytics tabs (overview, inventory, maintenance, operations, reservations) built on Recharts, with a data fallback so a failing analytics RPC degrades the numbers instead of taking the page down.
Maintenance
Items carry a maintenance record and history, so a tool that keeps coming back broken is visible as a pattern rather than as a series of unrelated incidents.
Technical Highlights
One cached resolver for the current user
getCurrentUser wraps the auth user + profile lookup in React's cache(), so a render tree that needs the user in the layout, the page and three components pays for exactly one lookup.
Actions return state, they don't throw
Every admin action returns a serializable AdminFormState with error, fieldErrors and success instead of throwing. Validation failures are data, not exceptions — which is what lets the same form component render field-level errors without a try/catch in the UI.
Read models composed in TypeScript
lib/data/* doesn't return raw rows: it queries several tables and composes an enriched view model (a Cabinet with its _count and item_names), so components render one object instead of stitching relations together in JSX.
Realtime as deltas, not refetches
The cabinet grid subscribes through subscribeCabinetsGrid and applies broadcast events as in-memory deltas. Two people working the same cabinet see each other's changes without either of them reloading.
One generic table for every admin module
components/tables/data-table.tsx carries the filtering, sorting and pagination once; each module (cabinets, inventory, users, maintenance, reservations, activity, sessions) only declares its typed columns.
Project Structure
app/ ├── (home)/ # cabinet grid for the whole team ├── (admin)/admin/ # inventory, cabinets, categories, maintenance, │ # reservations, sessions, users, activity ├── (auth)/ # login, register, password reset + callback ├── history/ profile/ # per-user views └── layout.tsx components/ ├── admin/ # one folder per module (table, form, actions) ├── tables/data-table.tsx # generic filtering/sorting/pagination └── ui/ # shadcn primitives lib/ ├── actions/ # writes — one file per operation, Zod-validated │ └── cabinets/open-with-mqtt.ts # publishes the open command ├── data/ # reads — one fetcher per view ├── realtime/cabinets.ts # Supabase Realtime subscriptions ├── schemas/ # Zod schemas ├── types/ # enriched domain models └── supabase/ # browser, server, proxy clients + getCurrentUser proxy.ts # session refresh at the request edge
Impact and Scalability
- Every material movement is traceable: who, what, when and for how long.
- The MQTT bridge is in production code, so wiring a new cabinet is a hardware task, not an application change; the ESP32 / Raspberry Pi units that drive the physical locks are being rolled out.
- The read/write split and the generic table make a new admin module mostly a matter of declaring schema, fetcher, action and columns.
- Honest gaps: there is no automated test suite and no CI pipeline in the repository yet.
Notes
Built with Next.js 16, Supabase (PostgreSQL, Auth, Realtime), MQTT, TanStack Table, Recharts and shadcn/ui. The repository is private — it belongs to the Centro de Prototipado at the National University of Colombia.
🖼️ IMAGE PLACEHOLDER — the cabinet grid on the home screen
🖼️ IMAGE PLACEHOLDER — the admin dashboard with its KPIs and analytics tabs
🖼️ IMAGE PLACEHOLDER — the inventory table with filters and PDF export
🖼️ IMAGE PLACEHOLDER — the access history / session timeline
🖼️ IMAGE PLACEHOLDER — a physical cabinet with its ESP32 lock