- {/* 1. Header (Sticky) */}
-
-
- {/* 2. Content Tabs */}
-
- {/* Simple Tab Switcher */}
-
- setActiveTab('menu')}
- label="Menu"
- icon={}
- />
- setActiveTab('orders')}
- label="My Orders"
- icon={}
- />
-
+
+
+ {/* 1. Header (Sticky) */}
+
- {/* Tab Content */}
-
- {activeTab === 'menu' && (
-
-
-
Hungry? 😋
-
Select items to add to your table order.
-
+ {/* 2. Content Tabs */}
+
+ {/* Simple Tab Switcher */}
+
+ setActiveTab('menu')}
+ label="Menu"
+ icon={}
+ />
+ setActiveTab('orders')}
+ label="My Orders"
+ icon={}
+ />
+
- {/* Trending Items Section - only show when not viewing full menu */}
- {!showFullMenu &&
}
-
- {/* All Menu Items */}
-
-
-
{showFullMenu ? 'Full Menu' : 'Our Menu'}
-
+ {/* Tab Content */}
+
+ {activeTab === 'menu' && (
+
+
+
Hungry? 😋
+
Select items to add to your table order.
-
-
-
- )}
-
- {activeTab === 'orders' && (
-
-
- No active orders yet.
-
+ )}
+
+ {activeTab === 'orders' && (
+
- Go to Menu
-
-
- )}
-
-
+
+
+
My Orders
+
Most recent first
+
+
+ {isOrdersLoading ? 'Refreshing...' : 'Refresh'}
+
+
- {/* 3. Floating Cart Button (FAB) */}
- {cartItems.length > 0 && (
-
- setIsCartOpen(true)}
- size="lg"
- className="w-full py-8 px-6 rounded-2xl shadow-xl shadow-amber-500/20 flex items-center justify-between font-bold text-lg"
- >
-
-
{cartItems.length}
-
View Cart
-
-
- ₹{cartItems.reduce((acc, item) => acc + item.price * item.quantity, 0)}
-
-
-
-
- )}
+ {isOrdersLoading ? (
+
+
+ Loading orders...
+
+ ) : orders.length === 0 ? (
+
+
+
No orders yet for this table.
+
setActiveTab('menu')}
+ variant="link"
+ className="mt-4 text-amber-500 font-medium text-sm"
+ >
+ Go to Menu
+
+
+ ) : (
+
+ {orders.map((order) => {
+ const isOpen = expandedOrders.includes(order._id);
+ return (
+
+
toggleOrder(order._id)}
+ >
+
+
Order #{order._id?.slice(-6)}
+
{new Date(order.createdAt).toLocaleString()}
+
{(order.items || []).length} item(s)
+
+
+
+ {order.status}
+
+
+
+
- {/* Drawers/Modals */}
-
+ {!isOpen && (order.items || []).length > 0 && (
+
+ {(order.items || [])
+ .slice(0, 2)
+ .map((item) => item.name || 'Item')
+ .join(', ')}
+ {(order.items || []).length > 2 ? '…' : ''}
+
+ )}
+
+ {isOpen && (
+
+ {(order.items || []).map((item, idx) => (
+
+
+
+ {item.name || item.menuItem?.name || 'Item'}
+
+ {Array.isArray(item.modifiers) && item.modifiers.length > 0 && (
+
+ {item.modifiers.map((mod, mIdx) => (
+
+ {mod}
+
+ ))}
+
+ )}
+
+
+
x{item.quantity}
+ {item.price !== undefined && (
+
+ ${item.price?.toFixed?.(2) ?? item.price}
+
+ )}
+
+
+ ))}
+
+ )}
+
+
+
Total
+
+ ${order.totalAmount?.toFixed?.(2) ?? order.totalAmount}
+
+
+
+ );
+ })}
+
+ )}
+
+ )}
+
+
+
+ {/* 3. Floating Cart Button (FAB) */}
+ {cartItems.length > 0 && (
+
+ setIsCartOpen(true)}
+ size="lg"
+ className="w-full py-6 sm:py-7 px-4 sm:px-6 rounded-2xl shadow-xl shadow-amber-500/20 flex items-center justify-between font-bold text-base sm:text-lg"
+ >
+
+
{cartItems.length}
+
View Cart
+
+
+ ₹{cartItems.reduce((acc, item) => acc + item.price * item.quantity, 0)}
+
+
+
+
+ )}
+
+ {/* Drawers/Modals */}
+
+
);
};
diff --git a/server/controllers/orderController.js b/server/controllers/orderController.js
index 8278350..e39590c 100644
--- a/server/controllers/orderController.js
+++ b/server/controllers/orderController.js
@@ -55,15 +55,61 @@ export const createOrder = async (req, res) => {
}
}
+ // Enrich items with snapshots (name, price, normalized modifiers)
+ const menuIds = items.map((i) => i.menuItem).filter(Boolean);
+ const menus = await MenuItem.find({ _id: { $in: menuIds } }).lean();
+ const configs = await OutletItemConfig.find({
+ outletId,
+ menuItemId: { $in: menuIds },
+ }).lean();
+ const configMap = new Map();
+ configs.forEach((c) => configMap.set(c.menuItemId.toString(), c));
+
+ const normalizedItems = items.map((item) => {
+ const menu = menus.find(
+ (m) => m._id.toString() === String(item.menuItem)
+ );
+ const cfg = configMap.get(String(item.menuItem));
+ const priceFromConfig =
+ cfg && cfg.customPrice !== undefined && cfg.customPrice !== null
+ ? cfg.customPrice
+ : undefined;
+ const priceSnapshot =
+ typeof item.price === 'number'
+ ? item.price
+ : (priceFromConfig ?? menu?.basePrice ?? 0);
+ const nameSnapshot = item.name || menu?.name || 'Menu Item';
+ const modifiersList = Array.isArray(item.modifiers)
+ ? item.modifiers.map((mod) => {
+ if (typeof mod === 'string') return mod;
+ const label = mod?.option || mod?.label || '';
+ const key = mod?.name || mod?.variant || 'Option';
+ const priceAdj = mod?.priceAdjustment
+ ? ` (${mod.priceAdjustment > 0 ? '+' : ''}${mod.priceAdjustment})`
+ : '';
+ return `${key}: ${label}${priceAdj}`.trim();
+ })
+ : [];
+
+ return {
+ menuItem: item.menuItem,
+ name: nameSnapshot,
+ price: priceSnapshot,
+ quantity: item.quantity || 1,
+ modifiers: modifiersList,
+ };
+ });
+
// Generate unique token for this customer-table combination
const newCustomerToken = generateSessionToken();
const newOrder = await Order.create({
outletId,
- items,
+ items: normalizedItems,
totalAmount,
status: 'placed',
tableId: tableId || null,
+ tableSessionId: table?.sessionId || null,
notes: notes || '',
customerToken: newCustomerToken, // Store the unique token for this customer
takenBy: req.user ? req.user._id : null, // If logged in
@@ -130,13 +176,29 @@ export const getOutletOrders = async (req, res) => {
};
// @desc Get orders for a specific table
-// @route GET /api/waiter/table/:tableId/orders
-// @access Private (Waiter)
+// @route GET /api/waiter/table/:tableId/orders OR GET /api/public/orders/table/:tableId?customerToken=xxx
+// @access Private (Waiter) or Public (with customerToken)
export const getTableOrders = async (req, res) => {
try {
const { tableId } = req.params;
+ const { customerToken } = req.query;
- const orders = await Order.find({ tableId })
+ // Get current table session
+ const table = await Table.findById(tableId);
+ if (!table) {
+ return res
+ .status(404)
+ .json({ success: false, message: 'Table not found' });
+ }
+
+ const query = { tableId, tableSessionId: table.sessionId };
+ // If customerToken is provided, filter by it (for returning customers)
+ if (customerToken) {
+ query.customerToken = customerToken;
+ }
+ // Otherwise return all orders for current table session (new customers see all current session orders)
+
+ const orders = await Order.find(query)
.sort({ createdAt: -1 })
.populate('items.menuItem', 'name');
diff --git a/server/controllers/tableController.js b/server/controllers/tableController.js
index e863279..c1a2a8c 100644
--- a/server/controllers/tableController.js
+++ b/server/controllers/tableController.js
@@ -161,9 +161,12 @@ export const releaseTable = async (req, res) => {
try {
const { tableId } = req.params;
+ // Generate new sessionId to invalidate old customer sessions
+ const newSessionId = `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
+
const table = await Table.findByIdAndUpdate(
tableId,
- { isOccupied: false, currentOrderId: null },
+ { isOccupied: false, currentOrderId: null, sessionId: newSessionId },
{ new: true }
);
diff --git a/server/models/Order.js b/server/models/Order.js
index 89609bb..2aadac8 100644
--- a/server/models/Order.js
+++ b/server/models/Order.js
@@ -47,6 +47,11 @@ const orderSchema = new mongoose.Schema(
type: mongoose.Schema.Types.ObjectId,
ref: 'Table',
},
+ // Table session ID at time of order (changes when table is released)
+ tableSessionId: {
+ type: String,
+ default: null,
+ },
// Customer identifier (for unauthenticated orders)
customerId: {
type: String,
diff --git a/server/models/Table.js b/server/models/Table.js
index c7c27ae..289c5a1 100644
--- a/server/models/Table.js
+++ b/server/models/Table.js
@@ -39,6 +39,11 @@ const tableSchema = new mongoose.Schema(
ref: 'Order',
default: null,
},
+ sessionId: {
+ type: String,
+ default: () =>
+ `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
+ },
assignedWaiterId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
diff --git a/server/routes/orderRoutes.js b/server/routes/orderRoutes.js
index 0eec674..42c364d 100644
--- a/server/routes/orderRoutes.js
+++ b/server/routes/orderRoutes.js
@@ -2,6 +2,7 @@ import express from 'express';
import {
createOrder,
getOutletOrders,
+ getTableOrders,
updateOrderStatus,
} from '../controllers/orderController.js';
import { protect } from '../middleware/authMiddleware.js';
@@ -11,6 +12,7 @@ const router = express.Router();
// Public
router.post('/public/orders', createOrder);
+router.get('/public/orders/table/:tableId', getTableOrders);
// Manager, Waiter & Kitchen
router.get(