Table POS — House of Legends
Documented: 2026-05-11
Overview
Guests order food and drinks via a table PWA. Staff fulfill orders via kitchen/reception POS views.
Table PWA Flow
Guest Seated
│
▼
Scan QR on Table → /table/{tableId}
│
▼
Menu Displayed (grouped by category)
│
├──► Browse menu
├──► Add items to cart
└──► View cart → Submit order
│
▼
Order Sent to Kitchen
│
▼
Staff POS Alerts
│
▼
Kitchen marks items READY
│
▼
Reception marks items SERVEDTables
// tables
tables: defineTable({
name: v.string(), // "A1", "VIP-1"
zone: v.union(v.literal("VIP"), v.literal("PREMIUM"), v.literal("STANDARD")),
capacity: v.number(),
status: v.union(v.literal("ACTIVE"), v.literal("INACTIVE")),
floorPlanX: v.number(),
floorPlanY: v.number(),
})
.index("by_zone", ["zone"])
.index("by_status", ["status"]);
// menuItems
menuItems: defineTable({
name: v.string(),
description: v.string(),
price: v.number(),
category: v.union(...),
station: v.union(v.literal("KITCHEN"), v.literal("BAR")),
available: v.boolean(),
})
.index("by_category", ["category"])
.index("by_available", ["available"]);
// orders
orders: defineTable({
tableId: v.id("tables"),
reservationId: v.optional(v.id("reservations")),
status: v.union(
v.literal("OPEN"),
v.literal("SUBMITTED"),
v.literal("COMPLETED"),
v.literal("CANCELLED"),
),
totalAmount: v.number(),
})
.index("by_table", ["tableId"])
.index("by_status", ["status"]);
// orderItems
orderItems: defineTable({
orderId: v.id("orders"),
menuItemId: v.id("menuItems"),
quantity: v.number(),
unitPrice: v.number(),
status: v.union(
v.literal("PENDING"),
v.literal("SUBMITTED"),
v.literal("PREPARING"),
v.literal("READY"),
v.literal("SERVED"),
v.literal("CANCELLED"),
),
station: v.union(v.literal("KITCHEN"), v.literal("BAR")),
isComp: v.boolean(),
compSource: v.optional(
v.union(v.literal("SPIN"), v.literal("PHOTO_WIN"), v.literal("GOOGLE_REVIEW")),
),
})
.index("by_order", ["orderId"])
.index("by_station_status", ["station", "status"]);POS Views
Kitchen Display (KDS)
Located at /dashboard/pos/kitchen:
- Orders grouped by station (KITCHEN vs BAR)
- Shows: table name, item names, quantity, notes
- Color-coded status: PENDING (yellow), PREPARING (blue), READY (green)
- Audio ping on new orders
- "Mark Preparing" / "Mark Ready" actions
Reception View
Located at /dashboard/pos/reception:
- Table grid layout with status indicators
- Shows: table name, open order total, order status
- Click table → see order details
- "Mark Served" action for items
Staff POS
Located at /dashboard/pos/staff:
- Per-table order summary
- Add items to open orders
- Apply comp items (from spin/photo/review wins)
- Tab accumulation across multiple orders
Comp Item Flow
When guest wins a challenge:
spinResults/challengeSubmissionscreated withisComp: true- Staff adds comp item in POS
- Item appears with
isComp: trueflag and source - Comp items don't count toward revenue
Components
| Component | Purpose |
|---|---|
table-menu.tsx | Menu grid with categories |
table-cart.tsx | Cart sidebar with items |
table-order-confirm.tsx | Order confirmation modal |
kitchen-display.tsx | KDS grid view |
reception-display.tsx | Table status grid |
order-item-row.tsx | Single item in order list |
Backend Functions
| Function | Purpose |
|---|---|
payments.createOrder | Create new order |
payments.addItemToOrder | Add item to open order |
payments.updateItemStatus | Mark item as preparing/ready/served |
payments.applyComp | Apply comp item to order |
payments.getTableOrders | Get orders for table |
payments.getKitchenQueue | Get all pending items by station |