Skip to content

helgiv/vendral-dashboard

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

4 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🧊 Vendral – IoT Vending Machine Fleet Dashboard

A futuristic prototype dashboard for managing a fleet of smart vending machines across Iceland. Built with Next.js 16, React 19, TypeScript, Tailwind CSS v4, shadcn/ui, Recharts, TanStack Table, TanStack Query, and Framer Motion.

Dashboard Next.js TypeScript Tailwind

Vendral Dashboard Overview


πŸ“– Table of Contents

  1. Quick Start
  2. Project Overview
  3. Tech Stack Explained
  4. Architecture
  5. Folder Structure
  6. Dashboard Sections
  7. Data Simulation
  8. Key Patterns & Concepts
  9. Deployment
  10. Troubleshooting
  11. Learning Resources

πŸš€ Quick Start

# 1. Install dependencies
npm install

# 2. Start the development server
npm run dev

# 3. Open in your browser
#    β†’ http://localhost:3000

That's it! No environment variables, API keys, or databases needed. The dashboard runs entirely in the browser using simulated data.


🌐 Project Overview

Vendral is a fictional IoT fleet management system for vending machines in Iceland. This dashboard is a fully self-contained prototype that simulates:

  • 20 vending machines across 5 Icelandic locations
  • 50 products with realistic pricing in ISK (Icelandic KrΓ³na)
  • Live transactions (card payments) generated every 2–4 seconds
  • Hardware events (motor jams, connectivity, temperature) every 5–10 seconds
  • Stock depletion that triggers low-stock and out-of-stock alerts

All payments are simulated as card transactions (no cash).


πŸ›  Tech Stack Explained

If you're new to any of these technologies, here's what each one does:

Next.js (v16)

The React framework that handles routing, server-side rendering, and building.

  • We use the App Router (the src/app/ directory).
  • layout.tsx wraps every page; page.tsx is the main route.
  • "use client" directive marks components that need browser APIs (hooks, events).

TypeScript – Type Safety

JavaScript with types. Every variable, function parameter, and return value has a declared type. This catches bugs at compile time instead of runtime. Look for interface and type keywords in the code.

Tailwind CSS (v4)

A utility-first CSS framework. Instead of writing CSS classes like .card, you compose styles directly: className="bg-white/5 rounded-lg p-4 border". Tailwind v4 uses CSS-native config (in globals.css) instead of tailwind.config.js.

shadcn/ui – Component Library

A collection of copy-paste React components (Button, Badge, Progress, Tooltip, etc.) built on Radix UI primitives. They live in src/components/ui/ and are fully customizable.

Recharts – Charting

A charting library for React. We use it for area charts, and the concepts apply to bar charts, line charts, pie charts, etc. Key components:

  • <ResponsiveContainer> β†’ auto-sizes the chart to its parent
  • <AreaChart data={...}> β†’ the chart type
  • <Area>, <XAxis>, <YAxis>, <Tooltip> β†’ composable building blocks

TanStack Table (v8)

A headless table library. "Headless" means it provides the logic (sorting, filtering, pagination) but zero UI β€” you bring your own markup and styling. See src/components/transaction-table.tsx for a full working example.

TanStack Query (v5)

A data-fetching & caching library. It manages loading states, caching, background refetching, and error handling. Key hook: useQuery({ queryKey, queryFn }). See src/lib/queries.ts for our custom hooks and src/components/settings-page.tsx for a live demo comparing TanStack Query vs React Context.

Framer Motion – Animations

An animation library for React. We use it for:

  • Page transitions (AnimatePresence + motion.div in page.tsx)
  • List animations (event ticker items sliding in)
  • Hover/tap effects (planogram slot scaling)

Lucide React

An icon library with 1000+ clean SVG icons. Usage: <Monitor className="w-4 h-4" />.


πŸ— Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                Browser (Client)                   β”‚
β”‚                                                   β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚     ClientProviders (client-providers.tsx)    β”‚ β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚ β”‚
β”‚  β”‚  β”‚   QueryClientProvider (TanStack Q)   β”‚   β”‚ β”‚
β”‚  β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚   β”‚ β”‚
β”‚  β”‚  β”‚  β”‚  SimulationProvider (Context)  β”‚  β”‚   β”‚ β”‚
β”‚  β”‚  β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚  β”‚   β”‚ β”‚
β”‚  β”‚  β”‚  β”‚  β”‚Simulationβ”‚β†’β”‚  Context   β”‚  β”‚  β”‚   β”‚ β”‚
β”‚  β”‚  β”‚  β”‚  β”‚ Engine   β”‚ β”‚  (State)   β”‚  β”‚  β”‚   β”‚ β”‚
β”‚  β”‚  β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜  β”‚  β”‚   β”‚ β”‚
β”‚  β”‚  β”‚  β”‚                     β”‚         β”‚  β”‚   β”‚ β”‚
β”‚  β”‚  β”‚  β”‚    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”     β”‚  β”‚   β”‚ β”‚
β”‚  β”‚  β”‚  β”‚    β”‚  Dashboard Pages   β”‚     β”‚  β”‚   β”‚ β”‚
β”‚  β”‚  β”‚  β”‚    β”‚  (Overview, Sales, β”‚     β”‚  β”‚   β”‚ β”‚
β”‚  β”‚  β”‚  β”‚    β”‚   Map, Planogram)  β”‚     β”‚  β”‚   β”‚ β”‚
β”‚  β”‚  β”‚  β”‚    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β”‚  β”‚   β”‚ β”‚
β”‚  β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚   β”‚ β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Data flow:

  1. simulation.ts runs intervals that generate transactions + events
  2. simulation-context.tsx wraps the data in React Context (1-second tick)
  3. Components call useSimulation() to read machines, transactions, events
  4. Alternatively, components can use TanStack Query hooks from queries.ts

πŸ“ Folder Structure

src/
β”œβ”€β”€ app/                          # Next.js App Router
β”‚   β”œβ”€β”€ globals.css               # Tailwind v4 theme + glassmorphism utilities
β”‚   β”œβ”€β”€ layout.tsx                # Root layout (fonts, providers)
β”‚   └── page.tsx                  # Main dashboard shell (sidebar + tabs)
β”‚
β”œβ”€β”€ components/                   # React components
β”‚   β”œβ”€β”€ ui/                       # shadcn/ui primitives (Button, Badge, etc.)
β”‚   β”œβ”€β”€ client-providers.tsx      # Client-side providers (Query + Simulation + Tooltip)
β”‚   β”œβ”€β”€ sidebar.tsx               # Collapsible navigation sidebar
β”‚   β”œβ”€β”€ stats-ribbon.tsx          # Top KPI ribbon (Revenue, Machines, Alerts)
β”‚   β”œβ”€β”€ overview-dashboard.tsx    # Bento-box overview (fleet health, map, charts)
β”‚   β”œβ”€β”€ fleet-health-bar.tsx      # Stacked bar: online/warning/error/offline
β”‚   β”œβ”€β”€ event-ticker.tsx          # Live scrolling event feed
β”‚   β”œβ”€β”€ geo-map.tsx               # SVG map of Iceland with cluster pins
β”‚   β”œβ”€β”€ device-health.tsx         # Machine drill-down (3D kiosk, hardware stack, terminal)
β”‚   β”œβ”€β”€ sales-dashboard.tsx       # Sales analytics (charts, heatmap, products)
β”‚   β”œβ”€β”€ transaction-table.tsx     # TanStack Table: sortable/filterable TX log
β”‚   β”œβ”€β”€ map-view.tsx              # Full map page with search + machine list
β”‚   β”œβ”€β”€ planogram-view.tsx        # Virtual planogram grid with restock optimizer
β”‚   └── settings-page.tsx         # Settings + TanStack Query demo
β”‚
β”œβ”€β”€ lib/                          # Shared utilities and data
β”‚   β”œβ”€β”€ data.ts                   # Types, product catalog, machine generator
β”‚   β”œβ”€β”€ simulation.ts             # Singleton simulation engine
β”‚   β”œβ”€β”€ simulation-context.tsx    # React Context provider
β”‚   β”œβ”€β”€ queries.ts                # TanStack Query hooks
β”‚   └── utils.ts                  # cn() utility (clsx + tailwind-merge)

πŸ–₯ Dashboard Sections

1. System Overview (Default)

  • Fleet Health Bar: Stacked horizontal bar showing machine status proportions
  • Mini Revenue Chart: 24-hour area chart (Recharts)
  • Event Ticker: Live feed of transactions and system events
  • SVG Map: Iceland with color-coded location clusters
  • Top Products: Best sellers with stock bars
  • Machine Status: Quick list sorted by urgency

2. Technical Monitoring

  • Machine Picker: Grid of all 20 machines to select from
  • 3D Kiosk SVG: Visual representation of the selected machine
  • Hardware Stack: 6-component status list (bill validator, card reader, temp, etc.)
  • Terminal Log: Monospace event log with timestamps and error codes

3. Sales & Marketing

  • KPI Tiles: Revenue, MTD, ATV, Conversion Rate with trend arrows
  • Dual-Axis Area Chart: Revenue (ISK) vs Foot Traffic overlaid
  • Sales Heatmap: Hour Γ— Day-of-week grid (cyan intensity = sales volume)
  • Top/Bottom Products: Best and worst sellers with stock indicators
  • Transaction Table: Sortable, searchable table (TanStack Table)

4. Map & Location Filter

  • Full SVG Map: Larger map with connection lines between locations
  • Search Bar: Filter machines by ID, name, or city
  • Location Chips: Quick filter by location
  • Machine Cards: Detailed status with glow indicators and quick actions

5. Planogram View

  • 10Γ—6 Grid: 60 slots with product icons, prices, stock bars
  • Color Coding: πŸ”΄ Empty, 🟠 Low (≀2), πŸ”΅ Adequate (3+)
  • Restock Optimizer: Toggle to highlight and pulse slots needing refill
  • Restock Summary: Priority list grouped by product

6. Settings

  • System info cards
  • TanStack Query Demo: Live comparison of Context vs Query data fetching

🎲 Data Simulation

The simulation runs entirely in the browser (no server needed):

Parameter Value
Products 50 (ISK 300-2,000, weights 1-3)
Machines 20 (4 per location)
Locations 5 (Reykjavik, Kopavogur, Akureyri, Keflavik)
Transaction rate Every 2-4 seconds
Event rate Every 5-10 seconds
Payment method Card only (all transactions)
Currency ISK (Icelandic Krona)
Planogram 10 rows x 6 columns = 60 slots

Weighted Random Selection

Products have a weight property (1–3). Popular items like Coca-Cola (weight 3) are 3Γ— more likely to be "sold" than niche items like AΓ§aΓ­ Bowl (weight 1). See weightedRandomProduct() in src/lib/data.ts.


πŸ”‘ Key Patterns & Concepts

Glassmorphism UI

The dark theme uses semi-transparent backgrounds with backdrop blur:

.glass-card {
  background: rgba(15, 23, 42, 0.6);
  backdrop-filter: blur(12px);
  border: 1px solid rgba(148, 163, 184, 0.1);
}

Provider Pattern (Next.js)

layout.tsx is a Server Component, but providers need client features. Solution: wrap providers in a "use client" component (client-providers.tsx).

Derived State vs. Effects

Instead of useEffect(() => setState(prop), [prop]) (which causes cascading renders), we use useMemo or direct computation to derive state from props. See device-health.tsx and planogram-view.tsx.

Headless Table (TanStack Table)

TanStack Table separates logic from UI. You define columns with ColumnDef, create a table instance with useReactTable(), then render with flexRender(). See transaction-table.tsx for a complete annotated example.

TanStack Query Caching

Every useQuery call specifies a queryKey (cache key) and queryFn (data fetcher). Multiple components using the same key share one cached response. See src/lib/queries.ts for all hooks.


🚒 Deployment

Option 1: Vercel (Recommended – Free Tier)

Vercel is the company behind Next.js and offers the smoothest deployment:

# 1. Install Vercel CLI
npm install -g vercel

# 2. Deploy (follow the prompts)
vercel

# 3. For production deployment
vercel --prod

Or use the Vercel Dashboard:

  1. Push your code to GitHub / GitLab / Bitbucket
  2. Go to vercel.com β†’ New Project β†’ Import your repo
  3. Vercel auto-detects Next.js and deploys automatically

Option 2: Static Export (Any static host)

Since this dashboard is entirely client-side, you can export it as static HTML:

# 1. Add output: 'export' to next.config.ts (see below)
# 2. Build the static export
npm run build
# 3. The output is in the `out/` folder
#    Upload to Netlify, Cloudflare Pages, GitHub Pages, S3, etc.

To enable static export, update next.config.ts:

const nextConfig = {
  output: 'export',
  images: { unoptimized: true },
};
export default nextConfig;

Option 3: Docker

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
EXPOSE 3000
CMD ["node", "server.js"]

For the standalone Docker output, add to next.config.ts:

const nextConfig = { output: 'standalone' };

Then build and run:

docker build -t vendral .
docker run -p 3000:3000 vendral

Option 4: Node.js Server (Self-hosted)

# Build for production
npm run build

# Start the production server (port 3000)
npm start

πŸ”§ Troubleshooting

Recharts width/height warning

The width(-1) and height(-1) of chart should be greater than 0

This is harmless β€” it happens during static page generation when there's no real DOM to measure. The charts work perfectly in the browser.

"Cannot find module" errors in VS Code

If VS Code shows red squiggles on imports but npm run build succeeds:

  1. Restart TypeScript: Ctrl+Shift+P β†’ "TypeScript: Restart TS Server"
  2. Rebuild: npm run build to regenerate type definitions

Port 3000 already in use

# Windows PowerShell: find and kill the process
netstat -ano | findstr :3000
taskkill /PID <PID_NUMBER> /F

# Or just use a different port
npm run dev -- --port 3001

πŸ“š Learning Resources

Next.js – Learn More

TypeScript – Learn More

Tailwind CSS – Learn More

shadcn/ui – Learn More

Recharts – Learn More

TanStack Table – Learn More

TanStack Query – Learn More

Framer Motion – Learn More


πŸ“„ License

This is a prototype / educational project. Use it however you like.


Built with ❄️ in Iceland

About

Futuristic IoT fleet management dashboard for vending machines in Iceland. Next.js 16, React 19, TypeScript, Tailwind v4, shadcn/ui, Recharts, TanStack Table/Query, Framer Motion.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors