🍗 wh-pos — System Architecture

End-to-end technical overview · Every component, data layer, and integration
https://wh-pos.pages.dev/?ws={workspaceId}#{/route}
👥 Layer 1 — User Devices  ·  Any browser, no install required
🖥️
Staff — Counter / Manager
Full POS interface. Places orders, manages the live dashboard, and accesses all admin sections. Uses the desktop sidebar for navigation.
Desktop / Tablet Full access Admin PIN unlock
📺
Kitchen Display Screen
Dashboard open on a wall-mounted screen. Live order board updates instantly via Firestore real-time push — no manual refresh ever needed.
Read-only view Real-time push Same workspace URL
📱
Customer Phone (QR Ordering)
Scans a table QR code → opens a clean ordering page. No app install needed. "Order More" appends to the same open order — no new order created.
QR scan Mobile sheet UI No staff nav
HTTPS · Any modern browser
☁️ Layer 2 — Cloudflare Pages  ·  Global CDN · Static hosting
wh-pos.pages.dev
wh-pos.pages.dev/?ws={workspaceId}#{route}
Source: winghouse-pos/wh-pos (private GitHub org repo)
Auto Deploy
git push to main → Cloudflare webhook fires → build starts instantly
Env Secrets
VITE_FIREBASE_* set in Cloudflare dashboard → baked into JS at build time
What It Serves
Static HTML + JS + CSS bundles only. No backend server or database here.
Why Cloudflare?
Supports private repos. Clean URL — no personal GitHub username in the address.
Delivers JS bundles · App boots in the browser · No server-side rendering
⚡ Layer 3 — Vue 3 Application  ·  Single-Page App · Runs entirely in the browser
🔑 Multi-Device Sync via URL
wh-pos.pages.dev/?ws={workspaceId}#/dashboard
workspace.js
reads ?ws= param
UUID saved to
localStorage
All Firestore reads/writes
scoped to this UUID
Share the URL =
share live data instantly
🗺 Client-Side Routing
Vue Router 4 — maps URLs to views, no full page reload
  • / → DashboardView
  • /new-order → NewOrderView
  • /past-orders → OrdersView
  • /item-sales → ItemSalesView
  • /analytics → AnalyticsView
  • /admin/menu-mgmt → MenuManagementView
  • /admin/finance → FinanceView
  • /admin/expenses → ExpensesView
  • /admin/employees → EmployeesView
  • /admin/settings → SettingsView
  • /customer/:tableId → CustomerOrderView
🍍 State Management — Pinia Stores
Shared reactive data — any component can read or update these
workspace.js
Holds the active workspace UUID. Reads ?ws= from the URL or generates a new one. Every Firestore operation is scoped to this ID.
app.js
Loads the settings/config Firestore doc — restaurant name, tax rate, payment methods, admin PIN, table count, theme. Also handles toast notifications.
cart.js
Active order in progress — items, order type, table, customer, discount, payment method. placeOrder() writes the completed order to Firestore.
🧩 Layout Shell — Shared Components
UI chrome that wraps every page — nav, header, cart
AppSidebar.vue
Left navigation (desktop). Staff links always visible. Admin links hidden until the correct PIN is entered.
AppHeader.vue
Top bar — restaurant name (from Firestore), current IST time, dark/light theme toggle.
BottomNav.vue
Mobile-only bottom navigation bar. Mirrors the desktop sidebar links.
CartContent.vue
Shared cart UI. Renders as a full-width desktop band (above the menu) or as a mobile bottom sheet (triggered by a floating gold button).
📄 Views — Pages rendered for each route
📋 Staff Views — Always accessible
DashboardView
Live orders board via Firestore real-time push. Elapsed time indicators (white → orange → red blink at 1hr). Inline edit — items, table, type. Mark complete / cancel. 🔔 badge when a customer adds items via QR.
NewOrderView
Guided setup: ① Order type → ② Table picker (Dine In only) → ③ Full menu. Diet filter tabs, category chips, live search. Size picker modal. Add-on upsell popup. Desktop cart band + mobile bottom sheet.
OrdersView (Past Orders)
Full order history. Date presets: Today / Yesterday / This Week / This Month / Custom range. Status filter. Re-print KOT or Bill from any past order.
ItemSalesView
Per-item revenue breakdown pulled from Firestore orders. Sortable by quantity sold or total revenue.
AnalyticsView
Monthly revenue vs expenses (12-month bar). Revenue by day of week. Payment methods donut. Order types donut. Busiest hours bar. All via ApexCharts (lazy-loaded).
🔐 Admin Views — PIN protected
MenuManagementView
Add, edit, or disable menu items. Set prices per size variant. Upload item photos (stored in IndexedDB cache). Configure size options.
FinanceView (Sales)
Date presets: Today / Yesterday / Week / Month / Choose Month / Custom. Revenue summary cards. Area and bar charts via ApexCharts. CSV export.
ExpensesView
Log expenses by date, category, vendor, and amount. Same date presets as Sales. Category filter. CSV export. Data stored locally in IndexedDB.
EmployeesView
Staff register: name, role, salary, join date. Stored in IndexedDB — device-local only, not synced across devices.
SettingsView
Restaurant name, address, GST number, tax rate, payment methods, table count, theme, admin PIN. QR Codes tab (per table, printable A4). Data tab: backup / restore / clear orders.
📱 Customer View — QR-only access
CustomerOrderView
Clean menu-only UI — no staff navigation or header chrome. Customer browses and adds items directly from their phone after scanning the table QR code.
QR Code Generation
Generated per table in Settings → QR Codes. Printable 3-column A4 layout. URL: /customer/{tableId}?ws=...
Session-Persistent Orders
"Order More" appends new items to the same Firestore order — doesn't create a new one. Customers can order across multiple rounds without re-scanning.
Kitchen Notification
Customer additions set a newItemsBadge flag on the Firestore order. Dashboard shows a blinking gold badge. Printing a new KOT clears it.
🔧 Utilities & Cross-cutting Concerns
🖨️
printReceipt.js
Generates KOT (kitchen, no prices) and full Customer Bill HTML. Opens native browser print dialog. USB, Bluetooth, PDF. Addendum print for updated orders.
📊
ApexCharts
Area, Bar, Donut charts in Sales and Analytics views. Lazy-loaded — the ~1 MB bundle only downloads the first time those views are opened.
🖼️
useItemImages
Composable for IndexedDB image caching. Stores menu photos as blobs locally after first upload — avoids re-fetching on every visit.
Lucide Vue Next
Icon library. Tree-shaken at build — only icons that are actually imported are included. Final chunk size: ~7 KB.
🎨
CSS Themes
CSS custom properties throughout. Dark mode default (low-light kitchen). Light mode warm palette for front-of-house. Toggle in the header bar.
Firestore SDK · Real-time
Dexie.js · Device-local
🔥
Firebase Firestore
Google Cloud · Real-time · Multi-device sync via shared workspace URL
workspaces/ (collection)
{workspaceId}/ (UUID — from ?ws= param or localStorage)
orders/ (collection)
{orderId} (e.g. ORD-20260309-KQWB0)
items[] name, size, price, qty, notes
orderType "dine-in" | "parcel" | "call"
table, customerName, phone
subtotal, taxAmount, discount, total
paymentMethod Cash | PhonePe | Card | UPI | Other
status "pending" | "in-progress" | "completed" | "cancelled"
createdAt, updatedAt (IST — Asia/Kolkata)
newItemsBadge boolean — customer added items via QR
settings/ (collection)
config (single document)
restaurantName, address, phone, gstNumber
taxEnabled, taxRate, paymentMethods[]
tableCount, adminPin, theme
Real-time push via onSnapshot() No polling needed All timestamps IST
💾
IndexedDB via Dexie.js 4
Device-local · Instant reads · Offline capable · Zero cloud cost
imageCache
itemId · imageBlob · timestamp
Menu item photos stored as blobs after first upload. Read by useItemImages composable before any remote fetch.
expenses
id · date · description · category · vendor · amount
Filtered by date presets in ExpensesView. CSV export. Device-local — not synced across devices.
employees
id · name · role · salary · joinDate · phone
Staff register in EmployeesView. Local to the device it was entered on.
💡 Why local? Expenses and employees don't need cross-device sync. IndexedDB gives instant reads, works offline, and keeps Firestore usage (and billing) to a minimum.
Offline capable Zero cloud cost ~95 KB bundle
⚙️ Build & Deploy Pipeline
📝
Local Dev
npm run dev
localhost:5173
.env.local secrets
📁
GitHub Org Repo
winghouse-pos/wh-pos
Private · push to main
🔗
Webhook Fires
Cloudflare detects
the push and starts
a build job
📦
Vite 7 Build
npm ci
Firebase secrets
injected at build
📤
dist/ Deployed
Uploaded to
Cloudflare global CDN
~15 sec total
🌐
wh-pos.pages.dev
Live · Edge-cached
Instant loads
Vite 7 Output — Bundle chunks  (manual splitting for optimal caching)
index.js
~23 KB
App entry · routes · views
vendor-firebase
~267 KB
Firebase SDK · Firestore
vendor-vue
~106 KB
Vue 3 · Pinia · Router
vendor-dexie
~95 KB
Dexie.js / IndexedDB
vendor-icons
~7 KB
Lucide (tree-shaken)
vendor-charts ⚡
~1 MB · lazy
ApexCharts — loads only when Sales or Analytics is opened
Lazy-loaded · initial page stays fast
User Devices
Cloudflare / Firebase
Vue 3 App (SPA)
IndexedDB / Dexie
Build Pipeline
📋 How an Order Works — From Tap to Report
A visual walkthrough for anyone using the system
🍽️ Staff taps "New Order" on the POS
What type of order?
🪑 Dine In
Customer at a table
Pick table number
📦 Parcel
Takeaway order
Enter customer name
📞 Call Order
Phone order
Enter name & phone
🍔 Browse Menu — Add items to the cartDiet filter tabs · Category chips · Live search · Tap to add
🛒 Review Cart — Apply discount if any, select payment methodCash · PhonePe · Card · UPI · Other
✅ Tap "Place Order" — Order saved to cloud instantlyOrder ID generated · Visible on all devices sharing this workspace
Print KOT for kitchen?
✅ YES — Print KOT
🖨️ KOT sent to printer
Items only · no price
Kitchen gets paper ticket
⏭ NO — Skip print
Kitchen notified
verbally or via the
dashboard screen
📊 Order appears on the Live Dashboard — visible on all devicesStaff screen · Kitchen display · Any device sharing this workspace URL
⏱️ Elapsed time tracked from the moment the order was placed
Under 30 minutes
Normal — shown in white. No action needed.
🟠
30 – 60 minutes
Attention — timer turns orange. Order taking longer than usual.
🔴
Over 1 hour
Urgent — timer blinks red. Immediate attention required.
Customer orders more via QR?
📱 YES — QR add-on
🔔 Badge blinks gold
on Dashboard
Staff prints New KOT
NO — order complete
No action needed
Proceed when ready
to mark completed
✅ Staff taps "Mark as Completed"Status updates to Completed in Firestore — order leaves the live board
📂 Order saved to Past Orders historyBrowse under Orders · Date and status filters · Re-print KOT or Bill any time
📈 Order data now flows into all reporting modules
💰
Sales / Finance
Revenue charts, daily/monthly totals, CSV export for accounting
🍔
Item Sales
Per-item breakdown — which items sold most, total revenue per dish
📊
Analytics
Busiest hours, day-of-week trends, payment method split, order type split