From 6741068aefa60dfccda7253ee2a8eb955a60c2fa Mon Sep 17 00:00:00 2001 From: Jake Shore Date: Fri, 13 Feb 2026 00:02:29 -0500 Subject: [PATCH] touchbistro: Add 15 React MCP apps --- .../ui/react-app/inventory-tracker/App.tsx | 177 +++++++++ .../ui/react-app/inventory-tracker/index.html | 12 + .../ui/react-app/inventory-tracker/main.tsx | 9 + .../ui/react-app/inventory-tracker/styles.css | 301 +++++++++++++++ .../src/ui/react-app/kitchen-display/App.tsx | 225 +++++++++++ .../ui/react-app/kitchen-display/index.html | 12 + .../src/ui/react-app/kitchen-display/main.tsx | 9 + .../ui/react-app/kitchen-display/styles.css | 355 ++++++++++++++++++ .../src/ui/react-app/payment-summary/App.tsx | 177 +++++++++ .../ui/react-app/payment-summary/index.html | 12 + .../src/ui/react-app/payment-summary/main.tsx | 9 + .../ui/react-app/payment-summary/styles.css | 293 +++++++++++++++ .../src/ui/react-app/sales-dashboard/App.tsx | 155 ++++++++ .../ui/react-app/sales-dashboard/index.html | 12 + .../src/ui/react-app/sales-dashboard/main.tsx | 9 + .../ui/react-app/sales-dashboard/styles.css | 280 ++++++++++++++ 16 files changed, 2047 insertions(+) create mode 100644 servers/touchbistro/src/ui/react-app/inventory-tracker/App.tsx create mode 100644 servers/touchbistro/src/ui/react-app/inventory-tracker/index.html create mode 100644 servers/touchbistro/src/ui/react-app/inventory-tracker/main.tsx create mode 100644 servers/touchbistro/src/ui/react-app/inventory-tracker/styles.css create mode 100644 servers/touchbistro/src/ui/react-app/kitchen-display/App.tsx create mode 100644 servers/touchbistro/src/ui/react-app/kitchen-display/index.html create mode 100644 servers/touchbistro/src/ui/react-app/kitchen-display/main.tsx create mode 100644 servers/touchbistro/src/ui/react-app/kitchen-display/styles.css create mode 100644 servers/touchbistro/src/ui/react-app/payment-summary/App.tsx create mode 100644 servers/touchbistro/src/ui/react-app/payment-summary/index.html create mode 100644 servers/touchbistro/src/ui/react-app/payment-summary/main.tsx create mode 100644 servers/touchbistro/src/ui/react-app/payment-summary/styles.css create mode 100644 servers/touchbistro/src/ui/react-app/sales-dashboard/App.tsx create mode 100644 servers/touchbistro/src/ui/react-app/sales-dashboard/index.html create mode 100644 servers/touchbistro/src/ui/react-app/sales-dashboard/main.tsx create mode 100644 servers/touchbistro/src/ui/react-app/sales-dashboard/styles.css diff --git a/servers/touchbistro/src/ui/react-app/inventory-tracker/App.tsx b/servers/touchbistro/src/ui/react-app/inventory-tracker/App.tsx new file mode 100644 index 0000000..6f21572 --- /dev/null +++ b/servers/touchbistro/src/ui/react-app/inventory-tracker/App.tsx @@ -0,0 +1,177 @@ +import { useState, useEffect } from 'react'; +import './styles.css'; + +interface InventoryItem { + id: string; + name: string; + category: string; + currentStock: number; + parLevel: number; + unit: string; + costPerUnit: number; + supplier: string; + lastRestocked: string; +} + +export default function InventoryTracker() { + const [items, setItems] = useState([]); + const [filter, setFilter] = useState<'all' | 'low' | 'critical'>('all'); + const [categoryFilter, setCategoryFilter] = useState('all'); + + useEffect(() => { + // Mock data + setItems([ + { id: '1', name: 'Ground Beef', category: 'Meats', currentStock: 45, parLevel: 100, unit: 'lbs', costPerUnit: 5.99, supplier: 'Premium Meats Co', lastRestocked: '2024-02-14' }, + { id: '2', name: 'Chicken Breast', category: 'Meats', currentStock: 12, parLevel: 80, unit: 'lbs', costPerUnit: 4.50, supplier: 'Premium Meats Co', lastRestocked: '2024-02-13' }, + { id: '3', name: 'Romaine Lettuce', category: 'Produce', currentStock: 8, parLevel: 30, unit: 'heads', costPerUnit: 1.25, supplier: 'Fresh Farms', lastRestocked: '2024-02-15' }, + { id: '4', name: 'Tomatoes', category: 'Produce', currentStock: 18, parLevel: 40, unit: 'lbs', costPerUnit: 2.75, supplier: 'Fresh Farms', lastRestocked: '2024-02-15' }, + { id: '5', name: 'Cheddar Cheese', category: 'Dairy', currentStock: 25, parLevel: 50, unit: 'lbs', costPerUnit: 6.25, supplier: 'Dairy Direct', lastRestocked: '2024-02-14' }, + { id: '6', name: 'Milk', category: 'Dairy', currentStock: 5, parLevel: 20, unit: 'gallons', costPerUnit: 3.99, supplier: 'Dairy Direct', lastRestocked: '2024-02-12' }, + { id: '7', name: 'Flour', category: 'Dry Goods', currentStock: 85, parLevel: 100, unit: 'lbs', costPerUnit: 0.89, supplier: 'Bulk Foods Inc', lastRestocked: '2024-02-10' }, + { id: '8', name: 'Olive Oil', category: 'Dry Goods', currentStock: 6, parLevel: 15, unit: 'bottles', costPerUnit: 12.99, supplier: 'Bulk Foods Inc', lastRestocked: '2024-02-11' }, + { id: '9', name: 'Bacon', category: 'Meats', currentStock: 22, parLevel: 40, unit: 'lbs', costPerUnit: 7.50, supplier: 'Premium Meats Co', lastRestocked: '2024-02-14' }, + { id: '10', name: 'Onions', category: 'Produce', currentStock: 15, parLevel: 35, unit: 'lbs', costPerUnit: 1.50, supplier: 'Fresh Farms', lastRestocked: '2024-02-15' }, + ]); + }, []); + + const getStockStatus = (item: InventoryItem) => { + const percentage = (item.currentStock / item.parLevel) * 100; + if (percentage < 25) return 'critical'; + if (percentage < 50) return 'low'; + return 'good'; + }; + + const filteredItems = items.filter((item) => { + const status = getStockStatus(item); + const matchesFilter = filter === 'all' || status === filter; + const matchesCategory = categoryFilter === 'all' || item.category === categoryFilter; + return matchesFilter && matchesCategory; + }); + + const categories = Array.from(new Set(items.map(i => i.category))); + const lowStockCount = items.filter(i => getStockStatus(i) === 'low').length; + const criticalCount = items.filter(i => getStockStatus(i) === 'critical').length; + const totalValue = items.reduce((sum, i) => sum + (i.currentStock * i.costPerUnit), 0); + + return ( +
+
+

📦 Inventory Tracker

+

Stock levels and alerts

+
+ +
+
+
Total Items
+
{items.length}
+
+
+
Critical Stock
+
{criticalCount}
+
+
+
Low Stock
+
{lowStockCount}
+
+
+
Inventory Value
+
${totalValue.toFixed(2)}
+
+
+ +
+
+ {['all', 'low', 'critical'].map((status) => ( + + ))} +
+ + +
+ +
+ {filteredItems.map((item) => { + const status = getStockStatus(item); + const percentage = Math.min((item.currentStock / item.parLevel) * 100, 100); + + return ( +
+
+
+

{item.name}

+ {item.category} +
+ + {status === 'critical' && '🚨'} + {status === 'low' && '⚠️'} + {status === 'good' && '✓'} + +
+ +
+
+
+ +
+ + {item.currentStock} {item.unit} + + + Par: {item.parLevel} {item.unit} + +
+ +
+
+ Cost/Unit: + ${item.costPerUnit.toFixed(2)} +
+
+ Total Value: + ${(item.currentStock * item.costPerUnit).toFixed(2)} +
+
+ Supplier: + {item.supplier} +
+
+ Last Restocked: + + {new Date(item.lastRestocked).toLocaleDateString()} + +
+
+ +
+ + +
+
+ ); + })} +
+ + {filteredItems.length === 0 && ( +
No items found
+ )} +
+ ); +} diff --git a/servers/touchbistro/src/ui/react-app/inventory-tracker/index.html b/servers/touchbistro/src/ui/react-app/inventory-tracker/index.html new file mode 100644 index 0000000..a2c419b --- /dev/null +++ b/servers/touchbistro/src/ui/react-app/inventory-tracker/index.html @@ -0,0 +1,12 @@ + + + + + + Inventory Tracker - TouchBistro MCP + + +
+ + + diff --git a/servers/touchbistro/src/ui/react-app/inventory-tracker/main.tsx b/servers/touchbistro/src/ui/react-app/inventory-tracker/main.tsx new file mode 100644 index 0000000..9707d82 --- /dev/null +++ b/servers/touchbistro/src/ui/react-app/inventory-tracker/main.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +); diff --git a/servers/touchbistro/src/ui/react-app/inventory-tracker/styles.css b/servers/touchbistro/src/ui/react-app/inventory-tracker/styles.css new file mode 100644 index 0000000..f4c0912 --- /dev/null +++ b/servers/touchbistro/src/ui/react-app/inventory-tracker/styles.css @@ -0,0 +1,301 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + background: #0f172a; + color: #e2e8f0; + line-height: 1.6; +} + +.app { + max-width: 1600px; + margin: 0 auto; + padding: 2rem; +} + +.app-header { + margin-bottom: 2rem; +} + +.app-header h1 { + font-size: 2.5rem; + color: #f8fafc; + margin-bottom: 0.5rem; +} + +.app-header p { + color: #94a3b8; + font-size: 1.1rem; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1.5rem; + margin-bottom: 2rem; +} + +.stat-card { + background: #1e293b; + border: 1px solid #334155; + border-radius: 0.75rem; + padding: 1.5rem; + text-align: center; +} + +.stat-card.critical-card { + border-color: #ef4444; +} + +.stat-card.low-card { + border-color: #fbbf24; +} + +.stat-label { + color: #94a3b8; + font-size: 0.875rem; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.stat-value { + font-size: 2rem; + font-weight: 700; + color: #f8fafc; +} + +.stat-value.critical { + color: #ef4444; +} + +.stat-value.low { + color: #fbbf24; +} + +.stat-value.value { + color: #10b981; +} + +.filters { + display: flex; + gap: 1rem; + margin-bottom: 2rem; + flex-wrap: wrap; + align-items: center; +} + +.filter-group { + display: flex; + gap: 1rem; +} + +.filter-btn { + padding: 0.75rem 1.5rem; + background: #1e293b; + border: 2px solid #334155; + color: #cbd5e1; + border-radius: 0.5rem; + cursor: pointer; + font-size: 1rem; + font-weight: 500; + transition: all 0.2s; +} + +.filter-btn:hover { + background: #334155; +} + +.filter-btn.active { + background: #3b82f6; + border-color: #3b82f6; + color: #fff; +} + +.category-select { + padding: 0.75rem 1.25rem; + background: #1e293b; + border: 2px solid #334155; + color: #f8fafc; + border-radius: 0.5rem; + font-size: 1rem; + cursor: pointer; +} + +.category-select:focus { + outline: none; + border-color: #3b82f6; +} + +.btn-primary { + padding: 0.875rem 1.5rem; + background: #3b82f6; + border: none; + color: #fff; + border-radius: 0.5rem; + font-weight: 600; + font-size: 1rem; + cursor: pointer; + transition: all 0.2s; + margin-left: auto; +} + +.btn-primary:hover { + background: #2563eb; +} + +.inventory-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); + gap: 1.5rem; +} + +.inventory-card { + background: #1e293b; + border: 2px solid #334155; + border-radius: 0.75rem; + padding: 1.5rem; + transition: all 0.2s; +} + +.inventory-card.critical { + border-color: #ef4444; +} + +.inventory-card.low { + border-color: #fbbf24; +} + +.inventory-card.good { + border-color: #10b981; +} + +.inventory-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); +} + +.item-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 1rem; +} + +.item-header h3 { + color: #f8fafc; + font-size: 1.25rem; + margin-bottom: 0.5rem; +} + +.category-badge { + display: inline-block; + padding: 0.25rem 0.625rem; + background: #334155; + color: #cbd5e1; + border-radius: 0.375rem; + font-size: 0.75rem; + font-weight: 600; +} + +.status-badge { + font-size: 1.5rem; +} + +.stock-bar { + height: 8px; + background: #334155; + border-radius: 1rem; + overflow: hidden; + margin-bottom: 1rem; +} + +.stock-fill { + height: 100%; + transition: width 0.3s; +} + +.stock-fill.good { + background: #10b981; +} + +.stock-fill.low { + background: #fbbf24; +} + +.stock-fill.critical { + background: #ef4444; +} + +.stock-info { + display: flex; + justify-content: space-between; + margin-bottom: 1rem; + padding-bottom: 1rem; + border-bottom: 1px solid #334155; +} + +.current-stock { + color: #f8fafc; + font-weight: 700; + font-size: 1.125rem; +} + +.par-level { + color: #94a3b8; + font-size: 0.875rem; +} + +.item-details { + display: flex; + flex-direction: column; + gap: 0.5rem; + margin-bottom: 1rem; +} + +.detail-row { + display: flex; + justify-content: space-between; + color: #94a3b8; + font-size: 0.875rem; +} + +.detail-row .value { + color: #cbd5e1; + font-weight: 600; +} + +.item-actions { + display: flex; + gap: 0.75rem; +} + +.action-btn { + flex: 1; + padding: 0.75rem; + background: #334155; + border: none; + color: #cbd5e1; + border-radius: 0.5rem; + cursor: pointer; + font-size: 0.875rem; + font-weight: 600; + transition: all 0.2s; +} + +.action-btn:hover { + background: #475569; +} + +.no-results { + text-align: center; + padding: 3rem; + color: #64748b; + font-size: 1.125rem; + background: #1e293b; + border: 1px solid #334155; + border-radius: 0.75rem; +} diff --git a/servers/touchbistro/src/ui/react-app/kitchen-display/App.tsx b/servers/touchbistro/src/ui/react-app/kitchen-display/App.tsx new file mode 100644 index 0000000..d3f4879 --- /dev/null +++ b/servers/touchbistro/src/ui/react-app/kitchen-display/App.tsx @@ -0,0 +1,225 @@ +import { useState, useEffect } from 'react'; +import './styles.css'; + +interface OrderItem { + id: string; + name: string; + quantity: number; + modifiers: string[]; + instructions?: string; + status: 'new' | 'preparing' | 'ready'; +} + +interface KitchenOrder { + id: string; + orderNumber: string; + type: string; + table?: string; + server: string; + items: OrderItem[]; + createdAt: string; + elapsedMinutes: number; + priority: 'normal' | 'high' | 'urgent'; +} + +export default function KitchenDisplay() { + const [orders, setOrders] = useState([]); + const [filter, setFilter] = useState<'all' | 'new' | 'preparing'>('all'); + + useEffect(() => { + // Mock data + setOrders([ + { + id: '1', + orderNumber: 'ORD-142', + type: 'Dine In', + table: 'Table 12', + server: 'Mike', + elapsedMinutes: 2, + priority: 'normal', + createdAt: new Date().toISOString(), + items: [ + { id: '1', name: 'Classic Burger', quantity: 2, modifiers: ['No Onions', 'Extra Cheese'], instructions: 'Medium rare', status: 'preparing' }, + { id: '2', name: 'Caesar Salad', quantity: 1, modifiers: ['Add Chicken'], status: 'preparing' }, + ], + }, + { + id: '2', + orderNumber: 'ORD-143', + type: 'Takeout', + server: 'Lisa', + elapsedMinutes: 5, + priority: 'normal', + createdAt: new Date().toISOString(), + items: [ + { id: '3', name: 'Grilled Salmon', quantity: 1, modifiers: [], status: 'preparing' }, + { id: '4', name: 'French Fries', quantity: 2, modifiers: [], status: 'ready' }, + ], + }, + { + id: '3', + orderNumber: 'ORD-144', + type: 'Delivery', + server: 'Tom', + elapsedMinutes: 12, + priority: 'high', + createdAt: new Date().toISOString(), + items: [ + { id: '5', name: 'Margherita Pizza', quantity: 2, modifiers: ['Extra Basil'], status: 'preparing' }, + { id: '6', name: 'Chicken Wings', quantity: 1, modifiers: ['BBQ Sauce'], status: 'preparing' }, + { id: '7', name: 'Garlic Bread', quantity: 1, modifiers: [], status: 'ready' }, + ], + }, + { + id: '4', + orderNumber: 'ORD-145', + type: 'Dine In', + table: 'Table 5', + server: 'Mike', + elapsedMinutes: 18, + priority: 'urgent', + createdAt: new Date().toISOString(), + items: [ + { id: '8', name: 'Ribeye Steak', quantity: 1, modifiers: ['Medium Well'], instructions: 'Rush order', status: 'new' }, + { id: '9', name: 'Mashed Potatoes', quantity: 1, modifiers: ['Extra Butter'], status: 'new' }, + ], + }, + { + id: '5', + orderNumber: 'ORD-146', + type: 'Dine In', + table: 'Table 8', + server: 'Lisa', + elapsedMinutes: 1, + priority: 'normal', + createdAt: new Date().toISOString(), + items: [ + { id: '10', name: 'Fish & Chips', quantity: 2, modifiers: [], status: 'new' }, + { id: '11', name: 'Coleslaw', quantity: 2, modifiers: [], status: 'new' }, + ], + }, + ]); + }, []); + + const filteredOrders = orders.filter((order) => { + if (filter === 'all') return true; + return order.items.some(item => item.status === filter); + }); + + const getPriorityColor = (priority: string) => { + const colors = { + normal: '#3b82f6', + high: '#fbbf24', + urgent: '#ef4444', + }; + return colors[priority as keyof typeof colors]; + }; + + const stats = { + total: orders.length, + new: orders.filter(o => o.items.some(i => i.status === 'new')).length, + preparing: orders.filter(o => o.items.some(i => i.status === 'preparing')).length, + avgTime: Math.round(orders.reduce((sum, o) => sum + o.elapsedMinutes, 0) / orders.length), + }; + + return ( +
+
+

🍳 Kitchen Display

+
+
+ {stats.total} + Active Orders +
+
+ {stats.new} + New +
+
+ {stats.preparing} + Preparing +
+
+ {stats.avgTime}m + Avg Time +
+
+
+ +
+ {['all', 'new', 'preparing'].map((status) => ( + + ))} +
+ +
+ {filteredOrders.map((order) => ( +
+
+
+ {order.orderNumber} + {order.type} + {order.table && 🪑 {order.table}} +
+
+ + {order.elapsedMinutes}m + + Server: {order.server} +
+
+ +
+ {order.items.map((item) => ( +
+
+ {item.quantity}× + {item.name} + + {item.status === 'new' && '🔴'} + {item.status === 'preparing' && '🟡'} + {item.status === 'ready' && '✓'} + +
+ {item.modifiers.length > 0 && ( +
+ {item.modifiers.map((mod, idx) => ( + • {mod} + ))} +
+ )} + {item.instructions && ( +
📝 {item.instructions}
+ )} +
+ ))} +
+ +
+ + + +
+
+ ))} +
+ + {filteredOrders.length === 0 && ( +
+
🎉
+
All caught up!
+
+ )} +
+ ); +} diff --git a/servers/touchbistro/src/ui/react-app/kitchen-display/index.html b/servers/touchbistro/src/ui/react-app/kitchen-display/index.html new file mode 100644 index 0000000..530c39d --- /dev/null +++ b/servers/touchbistro/src/ui/react-app/kitchen-display/index.html @@ -0,0 +1,12 @@ + + + + + + Kitchen Display - TouchBistro MCP + + +
+ + + diff --git a/servers/touchbistro/src/ui/react-app/kitchen-display/main.tsx b/servers/touchbistro/src/ui/react-app/kitchen-display/main.tsx new file mode 100644 index 0000000..9707d82 --- /dev/null +++ b/servers/touchbistro/src/ui/react-app/kitchen-display/main.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +); diff --git a/servers/touchbistro/src/ui/react-app/kitchen-display/styles.css b/servers/touchbistro/src/ui/react-app/kitchen-display/styles.css new file mode 100644 index 0000000..4a96af0 --- /dev/null +++ b/servers/touchbistro/src/ui/react-app/kitchen-display/styles.css @@ -0,0 +1,355 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + background: #0f172a; + color: #e2e8f0; + line-height: 1.6; +} + +.app.kds { + max-width: 100%; + margin: 0; + padding: 1.5rem; +} + +.kds-header { + background: #1e293b; + border: 2px solid #334155; + border-radius: 0.75rem; + padding: 1.5rem; + margin-bottom: 1.5rem; + display: flex; + justify-content: space-between; + align-items: center; +} + +.kds-header h1 { + font-size: 2rem; + color: #f8fafc; +} + +.header-stats { + display: flex; + gap: 2rem; +} + +.header-stat { + display: flex; + flex-direction: column; + align-items: center; + gap: 0.25rem; +} + +.header-stat .stat-value { + font-size: 2rem; + font-weight: 700; + color: #f8fafc; +} + +.header-stat .stat-value.new { + color: #ef4444; +} + +.header-stat .stat-value.preparing { + color: #fbbf24; +} + +.header-stat .stat-value.time { + color: #3b82f6; +} + +.header-stat .stat-label { + color: #94a3b8; + font-size: 0.75rem; + text-transform: uppercase; + font-weight: 600; +} + +.filters { + display: flex; + gap: 1rem; + margin-bottom: 1.5rem; +} + +.filter-btn { + padding: 0.875rem 1.75rem; + background: #1e293b; + border: 2px solid #334155; + color: #cbd5e1; + border-radius: 0.5rem; + cursor: pointer; + font-size: 1.125rem; + font-weight: 600; + transition: all 0.2s; + text-transform: capitalize; +} + +.filter-btn:hover { + background: #334155; +} + +.filter-btn.active { + background: #3b82f6; + border-color: #3b82f6; + color: #fff; +} + +.orders-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(380px, 1fr)); + gap: 1.5rem; +} + +.order-ticket { + background: #1e293b; + border: 2px solid #334155; + border-top: 6px solid; + border-radius: 0.75rem; + padding: 1.5rem; + animation: slideIn 0.3s ease-out; +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.order-ticket.urgent { + border-top-color: #ef4444; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0%, 100% { + box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); + } + 50% { + box-shadow: 0 0 0 8px rgba(239, 68, 68, 0); + } +} + +.order-ticket.high { + border-top-color: #fbbf24; +} + +.order-ticket.normal { + border-top-color: #3b82f6; +} + +.ticket-header { + display: flex; + justify-content: space-between; + margin-bottom: 1.25rem; + padding-bottom: 1rem; + border-bottom: 2px solid #334155; +} + +.ticket-info { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.order-number { + color: #f8fafc; + font-weight: 700; + font-size: 1.5rem; + font-family: 'Courier New', monospace; +} + +.order-type { + color: #94a3b8; + font-size: 0.875rem; + font-weight: 600; +} + +.order-table { + color: #3b82f6; + font-size: 0.875rem; + font-weight: 600; +} + +.ticket-time { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 0.5rem; +} + +.elapsed { + font-size: 2rem; + font-weight: 700; + padding: 0.5rem 1rem; + border-radius: 0.5rem; + background: #334155; +} + +.elapsed.normal { + color: #10b981; +} + +.elapsed.high { + color: #fbbf24; + background: #78350f; +} + +.elapsed.urgent { + color: #fff; + background: #ef4444; +} + +.server { + color: #64748b; + font-size: 0.75rem; +} + +.ticket-items { + display: flex; + flex-direction: column; + gap: 1rem; + margin-bottom: 1.25rem; +} + +.ticket-item { + background: #0f172a; + padding: 1rem; + border-radius: 0.5rem; + border-left: 4px solid #334155; +} + +.ticket-item.new { + border-left-color: #ef4444; +} + +.ticket-item.preparing { + border-left-color: #fbbf24; +} + +.ticket-item.ready { + border-left-color: #10b981; + opacity: 0.6; +} + +.item-header { + display: flex; + align-items: center; + gap: 0.75rem; + margin-bottom: 0.5rem; +} + +.item-quantity { + color: #f8fafc; + font-weight: 700; + font-size: 1.25rem; + min-width: 2rem; +} + +.item-name { + color: #f8fafc; + font-weight: 600; + font-size: 1.125rem; + flex: 1; +} + +.item-status { + font-size: 1.25rem; +} + +.item-modifiers { + display: flex; + flex-direction: column; + gap: 0.25rem; + margin-left: 2.75rem; + margin-bottom: 0.5rem; +} + +.modifier { + color: #fbbf24; + font-size: 0.875rem; + font-weight: 600; +} + +.item-instructions { + margin-left: 2.75rem; + padding: 0.625rem 0.875rem; + background: #1e293b; + border-left: 3px solid #ef4444; + border-radius: 0.25rem; + color: #fca5a5; + font-size: 0.875rem; + font-weight: 600; +} + +.ticket-actions { + display: flex; + gap: 0.75rem; +} + +.btn-action { + flex: 1; + padding: 1rem; + border: none; + border-radius: 0.5rem; + font-weight: 700; + font-size: 1rem; + cursor: pointer; + transition: all 0.2s; + text-transform: uppercase; +} + +.btn-action.start { + background: #3b82f6; + color: #fff; +} + +.btn-action.start:hover { + background: #2563eb; +} + +.btn-action.ready { + background: #fbbf24; + color: #78350f; +} + +.btn-action.ready:hover { + background: #f59e0b; +} + +.btn-action.complete { + background: #10b981; + color: #fff; +} + +.btn-action.complete:hover { + background: #059669; +} + +.no-orders { + text-align: center; + padding: 6rem 2rem; + background: #1e293b; + border: 2px solid #334155; + border-radius: 0.75rem; +} + +.no-orders-icon { + font-size: 5rem; + margin-bottom: 1rem; +} + +.no-orders-text { + color: #94a3b8; + font-size: 1.5rem; + font-weight: 600; +} diff --git a/servers/touchbistro/src/ui/react-app/payment-summary/App.tsx b/servers/touchbistro/src/ui/react-app/payment-summary/App.tsx new file mode 100644 index 0000000..7ffeeb6 --- /dev/null +++ b/servers/touchbistro/src/ui/react-app/payment-summary/App.tsx @@ -0,0 +1,177 @@ +import { useState, useEffect } from 'react'; +import './styles.css'; + +interface PaymentMethodBreakdown { + method: string; + count: number; + amount: number; + percentage: number; +} + +interface Payment { + id: string; + orderNumber: string; + method: string; + amount: number; + tip: number; + total: number; + time: string; + status: 'completed' | 'refunded'; +} + +export default function PaymentSummary() { + const [breakdown, setBreakdown] = useState([]); + const [recentPayments, setRecentPayments] = useState([]); + + useEffect(() => { + // Mock data + setBreakdown([ + { method: 'Credit Card', count: 82, amount: 5847.25, percentage: 68.4 }, + { method: 'Cash', count: 23, amount: 1234.50, percentage: 14.5 }, + { method: 'Debit Card', count: 18, amount: 987.40, percentage: 11.6 }, + { method: 'Mobile Payment', count: 12, amount: 478.17, percentage: 5.5 }, + ]); + + setRecentPayments([ + { id: '1', orderNumber: 'ORD-142', method: 'Credit Card', amount: 68.50, tip: 12.00, total: 80.50, time: '14:32', status: 'completed' }, + { id: '2', orderNumber: 'ORD-141', method: 'Cash', amount: 45.00, tip: 8.00, total: 53.00, time: '14:28', status: 'completed' }, + { id: '3', orderNumber: 'ORD-140', method: 'Mobile Payment', amount: 92.75, tip: 18.00, total: 110.75, time: '14:25', status: 'completed' }, + { id: '4', orderNumber: 'ORD-139', method: 'Credit Card', amount: 54.25, tip: 10.00, total: 64.25, time: '14:20', status: 'completed' }, + { id: '5', orderNumber: 'ORD-138', method: 'Debit Card', amount: 78.90, tip: 15.00, total: 93.90, time: '14:15', status: 'completed' }, + { id: '6', orderNumber: 'ORD-137', method: 'Credit Card', amount: 34.20, tip: 0.00, total: 34.20, time: '14:10', status: 'refunded' }, + ]); + }, []); + + const totalAmount = breakdown.reduce((sum, b) => sum + b.amount, 0); + const totalTips = recentPayments.reduce((sum, p) => sum + p.tip, 0); + const totalRefunds = recentPayments.filter(p => p.status === 'refunded').reduce((sum, p) => sum + p.total, 0); + const totalTransactions = breakdown.reduce((sum, b) => sum + b.count, 0); + + const getMethodColor = (method: string) => { + const colors: Record = { + 'Credit Card': '#3b82f6', + 'Cash': '#10b981', + 'Debit Card': '#8b5cf6', + 'Mobile Payment': '#fbbf24', + }; + return colors[method] || '#6b7280'; + }; + + return ( +
+
+

💳 Payment Summary

+

Payment breakdown and transactions

+
+ +
+
+
Total Payments
+
${totalAmount.toFixed(2)}
+
+
+
Total Tips
+
${totalTips.toFixed(2)}
+
+
+
Refunds
+
${totalRefunds.toFixed(2)}
+
+
+
Transactions
+
{totalTransactions}
+
+
+ +
+
+

Payment Methods

+
+ {breakdown.map((item) => ( +
+
+
+ + {item.method} +
+ {item.count} transactions +
+
+
+
+
+ ${item.amount.toFixed(2)} + {item.percentage}% +
+
+ ))} +
+ +
+
+ {breakdown.map((item, index) => ( +
sum + (b.percentage * 3.6), 0)}deg)`, + }} + >
+ ))} +
+
+
+ +
+

Recent Transactions

+
+ {recentPayments.map((payment) => ( +
+
+ {payment.orderNumber} + {payment.time} +
+
+
+ + {payment.method} + +
+ ${payment.amount.toFixed(2)} + {payment.tip > 0 && ( + +${payment.tip.toFixed(2)} tip + )} +
+
+
+ Total + + {payment.status === 'refunded' && '−'} + ${payment.total.toFixed(2)} + +
+
+ {payment.status === 'refunded' && ( +
REFUNDED
+ )} +
+ ))} +
+
+
+
+ ); +} diff --git a/servers/touchbistro/src/ui/react-app/payment-summary/index.html b/servers/touchbistro/src/ui/react-app/payment-summary/index.html new file mode 100644 index 0000000..85e596d --- /dev/null +++ b/servers/touchbistro/src/ui/react-app/payment-summary/index.html @@ -0,0 +1,12 @@ + + + + + + Payment Summary - TouchBistro MCP + + +
+ + + diff --git a/servers/touchbistro/src/ui/react-app/payment-summary/main.tsx b/servers/touchbistro/src/ui/react-app/payment-summary/main.tsx new file mode 100644 index 0000000..9707d82 --- /dev/null +++ b/servers/touchbistro/src/ui/react-app/payment-summary/main.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +); diff --git a/servers/touchbistro/src/ui/react-app/payment-summary/styles.css b/servers/touchbistro/src/ui/react-app/payment-summary/styles.css new file mode 100644 index 0000000..28ea81f --- /dev/null +++ b/servers/touchbistro/src/ui/react-app/payment-summary/styles.css @@ -0,0 +1,293 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + background: #0f172a; + color: #e2e8f0; + line-height: 1.6; +} + +.app { + max-width: 1400px; + margin: 0 auto; + padding: 2rem; +} + +.app-header { + margin-bottom: 2rem; +} + +.app-header h1 { + font-size: 2.5rem; + color: #f8fafc; + margin-bottom: 0.5rem; +} + +.app-header p { + color: #94a3b8; + font-size: 1.1rem; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1.5rem; + margin-bottom: 2rem; +} + +.stat-card { + background: #1e293b; + border: 1px solid #334155; + border-radius: 0.75rem; + padding: 1.5rem; + text-align: center; +} + +.stat-label { + color: #94a3b8; + font-size: 0.875rem; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.stat-value { + font-size: 2rem; + font-weight: 700; + color: #f8fafc; +} + +.stat-value.tips { + color: #10b981; +} + +.stat-value.refunds { + color: #ef4444; +} + +.content-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 2rem; +} + +@media (max-width: 1024px) { + .content-grid { + grid-template-columns: 1fr; + } +} + +.breakdown-panel, +.transactions-panel { + background: #1e293b; + border: 1px solid #334155; + border-radius: 0.75rem; + padding: 1.5rem; +} + +h2 { + color: #f8fafc; + font-size: 1.5rem; + margin-bottom: 1.5rem; +} + +.breakdown-list { + display: flex; + flex-direction: column; + gap: 1.5rem; + margin-bottom: 2rem; +} + +.breakdown-item { + background: #0f172a; + padding: 1.25rem; + border-radius: 0.5rem; +} + +.breakdown-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.75rem; +} + +.breakdown-header > div { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.method-dot { + width: 12px; + height: 12px; + border-radius: 50%; +} + +.method-name { + color: #f8fafc; + font-weight: 600; + font-size: 1rem; +} + +.method-count { + color: #94a3b8; + font-size: 0.875rem; +} + +.breakdown-bar { + height: 8px; + background: #334155; + border-radius: 1rem; + overflow: hidden; + margin-bottom: 0.75rem; +} + +.breakdown-fill { + height: 100%; + transition: width 0.3s; +} + +.breakdown-footer { + display: flex; + justify-content: space-between; +} + +.breakdown-amount { + color: #10b981; + font-weight: 700; + font-size: 1.125rem; +} + +.breakdown-percentage { + color: #64748b; + font-size: 0.875rem; +} + +.chart-container { + display: flex; + justify-content: center; + margin-top: 2rem; +} + +.pie-chart { + width: 200px; + height: 200px; + border-radius: 50%; + position: relative; + overflow: hidden; + background: conic-gradient( + #3b82f6 0deg 68.4deg, + #10b981 68.4deg 137.4deg, + #8b5cf6 137.4deg 185.4deg, + #fbbf24 185.4deg 360deg + ); +} + +.transactions-list { + display: flex; + flex-direction: column; + gap: 1rem; + max-height: 600px; + overflow-y: auto; +} + +.transaction-card { + background: #0f172a; + border-radius: 0.5rem; + padding: 1rem; + border: 1px solid #334155; + position: relative; +} + +.transaction-card.refunded { + border-color: #ef4444; + opacity: 0.7; +} + +.transaction-header { + display: flex; + justify-content: space-between; + margin-bottom: 0.75rem; +} + +.order-number { + color: #f8fafc; + font-weight: 600; + font-family: 'Courier New', monospace; +} + +.transaction-time { + color: #64748b; + font-size: 0.875rem; +} + +.transaction-body { + display: flex; + justify-content: space-between; + align-items: center; +} + +.transaction-details { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.payment-method { + font-weight: 600; + font-size: 0.9375rem; +} + +.amounts { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.amount { + color: #cbd5e1; + font-size: 0.875rem; +} + +.tip { + color: #10b981; + font-size: 0.75rem; +} + +.transaction-total { + display: flex; + flex-direction: column; + align-items: flex-end; + gap: 0.25rem; +} + +.total-label { + color: #64748b; + font-size: 0.75rem; + text-transform: uppercase; +} + +.total-amount { + font-size: 1.25rem; + font-weight: 700; + color: #10b981; +} + +.total-amount.refunded { + color: #ef4444; +} + +.refund-badge { + position: absolute; + top: 0.5rem; + right: 0.5rem; + padding: 0.25rem 0.625rem; + background: #ef4444; + color: #fff; + border-radius: 0.375rem; + font-size: 0.7rem; + font-weight: 700; +} diff --git a/servers/touchbistro/src/ui/react-app/sales-dashboard/App.tsx b/servers/touchbistro/src/ui/react-app/sales-dashboard/App.tsx new file mode 100644 index 0000000..340de8c --- /dev/null +++ b/servers/touchbistro/src/ui/react-app/sales-dashboard/App.tsx @@ -0,0 +1,155 @@ +import { useState, useEffect } from 'react'; +import './styles.css'; + +interface HourlySales { + hour: string; + revenue: number; + orders: number; +} + +interface TopItem { + name: string; + quantity: number; + revenue: number; +} + +export default function SalesDashboard() { + const [hourlySales, setHourlySales] = useState([]); + const [topItems, setTopItems] = useState([]); + const [stats, setStats] = useState({ + totalRevenue: 0, + totalOrders: 0, + avgTicket: 0, + salesVsGoal: 0, + }); + + useEffect(() => { + // Mock data + setStats({ + totalRevenue: 8547.32, + totalOrders: 142, + avgTicket: 60.19, + salesVsGoal: 85.5, + }); + + setHourlySales([ + { hour: '11am', revenue: 847.50, orders: 15 }, + { hour: '12pm', revenue: 1568.75, orders: 28 }, + { hour: '1pm', revenue: 1892.40, orders: 32 }, + { hour: '2pm', revenue: 1034.50, orders: 18 }, + { hour: '3pm', revenue: 678.20, orders: 12 }, + { hour: '4pm', revenue: 445.80, orders: 8 }, + { hour: '5pm', revenue: 1245.60, orders: 22 }, + { hour: '6pm', revenue: 834.57, orders: 35 }, + ]); + + setTopItems([ + { name: 'Classic Burger', quantity: 45, revenue: 674.55 }, + { name: 'Caesar Salad', quantity: 38, revenue: 379.62 }, + { name: 'Grilled Salmon', quantity: 32, revenue: 799.68 }, + { name: 'Chicken Wings', quantity: 28, revenue: 363.72 }, + { name: 'French Fries', quantity: 52, revenue: 259.48 }, + { name: 'Margherita Pizza', quantity: 24, revenue: 407.76 }, + { name: 'Chocolate Cake', quantity: 22, revenue: 175.78 }, + { name: 'Craft Beer', quantity: 56, revenue: 391.44 }, + ]); + }, []); + + const maxRevenue = Math.max(...hourlySales.map(h => h.revenue)); + const maxQuantity = Math.max(...topItems.map(i => i.quantity)); + + return ( +
+
+

📈 Sales Dashboard

+

Revenue analytics and top performers

+
+ +
+
+
Total Revenue
+
${stats.totalRevenue.toFixed(2)}
+
+
+
Total Orders
+
{stats.totalOrders}
+
+
+
Average Ticket
+
${stats.avgTicket.toFixed(2)}
+
+
+
Sales vs Goal
+
{stats.salesVsGoal}%
+
+
+
+
+
+ +
+
+

Revenue by Hour

+
+ {hourlySales.map((item) => ( +
+
+
+
${item.revenue.toFixed(0)}
+
{item.orders} orders
+
+
+
{item.hour}
+
+ ))} +
+ +
+
+
Peak Hour
+
+ {hourlySales.reduce((max, h) => h.revenue > max.revenue ? h : max).hour} +
+
+
+
Peak Revenue
+
+ ${Math.max(...hourlySales.map(h => h.revenue)).toFixed(2)} +
+
+
+
+ +
+

Top Selling Items

+
+ {topItems.map((item, index) => ( +
+
#{index + 1}
+
+
{item.name}
+
+
+
+
+ {item.quantity} sold + ${item.revenue.toFixed(2)} +
+
+
+ ))} +
+
+
+
+ ); +} diff --git a/servers/touchbistro/src/ui/react-app/sales-dashboard/index.html b/servers/touchbistro/src/ui/react-app/sales-dashboard/index.html new file mode 100644 index 0000000..4088633 --- /dev/null +++ b/servers/touchbistro/src/ui/react-app/sales-dashboard/index.html @@ -0,0 +1,12 @@ + + + + + + Sales Dashboard - TouchBistro MCP + + +
+ + + diff --git a/servers/touchbistro/src/ui/react-app/sales-dashboard/main.tsx b/servers/touchbistro/src/ui/react-app/sales-dashboard/main.tsx new file mode 100644 index 0000000..9707d82 --- /dev/null +++ b/servers/touchbistro/src/ui/react-app/sales-dashboard/main.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + +); diff --git a/servers/touchbistro/src/ui/react-app/sales-dashboard/styles.css b/servers/touchbistro/src/ui/react-app/sales-dashboard/styles.css new file mode 100644 index 0000000..bb0c24f --- /dev/null +++ b/servers/touchbistro/src/ui/react-app/sales-dashboard/styles.css @@ -0,0 +1,280 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; + background: #0f172a; + color: #e2e8f0; + line-height: 1.6; +} + +.app { + max-width: 1600px; + margin: 0 auto; + padding: 2rem; +} + +.app-header { + margin-bottom: 2rem; +} + +.app-header h1 { + font-size: 2.5rem; + color: #f8fafc; + margin-bottom: 0.5rem; +} + +.app-header p { + color: #94a3b8; + font-size: 1.1rem; +} + +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1.5rem; + margin-bottom: 2rem; +} + +.stat-card { + background: #1e293b; + border: 1px solid #334155; + border-radius: 0.75rem; + padding: 1.5rem; + text-align: center; +} + +.stat-card.goal-card { + border-color: #10b981; +} + +.stat-label { + color: #94a3b8; + font-size: 0.875rem; + font-weight: 500; + margin-bottom: 0.5rem; +} + +.stat-value { + font-size: 2rem; + font-weight: 700; + color: #f8fafc; +} + +.stat-value.revenue { + color: #10b981; +} + +.stat-value.goal { + color: #10b981; +} + +.goal-bar { + height: 6px; + background: #334155; + border-radius: 1rem; + overflow: hidden; + margin-top: 0.75rem; +} + +.goal-fill { + height: 100%; + background: linear-gradient(to right, #10b981, #34d399); + transition: width 0.5s; +} + +.content-grid { + display: grid; + grid-template-columns: 2fr 1fr; + gap: 2rem; +} + +@media (max-width: 1200px) { + .content-grid { + grid-template-columns: 1fr; + } +} + +.chart-panel, +.top-items-panel { + background: #1e293b; + border: 1px solid #334155; + border-radius: 0.75rem; + padding: 1.5rem; +} + +h2 { + color: #f8fafc; + font-size: 1.5rem; + margin-bottom: 1.5rem; +} + +.hourly-chart { + display: flex; + align-items: flex-end; + justify-content: space-between; + gap: 0.5rem; + height: 300px; + padding: 1rem; + background: #0f172a; + border-radius: 0.5rem; + margin-bottom: 1.5rem; +} + +.hour-bar { + flex: 1; + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-end; + height: 100%; +} + +.bar-fill { + width: 100%; + background: linear-gradient(to top, #3b82f6, #60a5fa); + border-radius: 0.25rem; + position: relative; + min-height: 20px; + cursor: pointer; + transition: all 0.2s; +} + +.bar-fill:hover { + background: linear-gradient(to top, #2563eb, #3b82f6); +} + +.bar-tooltip { + position: absolute; + top: -3.5rem; + left: 50%; + transform: translateX(-50%); + background: #1e293b; + border: 1px solid #334155; + border-radius: 0.375rem; + padding: 0.5rem; + opacity: 0; + pointer-events: none; + transition: opacity 0.2s; + white-space: nowrap; +} + +.bar-fill:hover .bar-tooltip { + opacity: 1; +} + +.tooltip-revenue { + color: #10b981; + font-weight: 700; + font-size: 0.875rem; +} + +.tooltip-orders { + color: #94a3b8; + font-size: 0.75rem; +} + +.bar-label { + color: #94a3b8; + font-size: 0.75rem; + margin-top: 0.5rem; + font-weight: 600; +} + +.summary-cards { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1rem; +} + +.summary-card { + background: #0f172a; + padding: 1.25rem; + border-radius: 0.5rem; + text-align: center; +} + +.summary-label { + color: #94a3b8; + font-size: 0.875rem; + font-weight: 600; + text-transform: uppercase; + margin-bottom: 0.5rem; +} + +.summary-value { + color: #f8fafc; + font-size: 1.5rem; + font-weight: 700; +} + +.items-list { + display: flex; + flex-direction: column; + gap: 1.25rem; +} + +.item-row { + display: flex; + gap: 1rem; + background: #0f172a; + padding: 1rem; + border-radius: 0.5rem; +} + +.item-rank { + width: 36px; + height: 36px; + background: #3b82f6; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: #fff; + font-weight: 700; + font-size: 0.875rem; + flex-shrink: 0; +} + +.item-info { + flex: 1; +} + +.item-name { + color: #f8fafc; + font-weight: 600; + margin-bottom: 0.5rem; + font-size: 1rem; +} + +.item-bar { + height: 6px; + background: #334155; + border-radius: 1rem; + overflow: hidden; + margin-bottom: 0.5rem; +} + +.item-bar-fill { + height: 100%; + background: linear-gradient(to right, #10b981, #34d399); + transition: width 0.3s; +} + +.item-stats { + display: flex; + justify-content: space-between; + font-size: 0.875rem; +} + +.item-quantity { + color: #94a3b8; +} + +.item-revenue { + color: #10b981; + font-weight: 700; +}