'use client'; import React, { useState, useEffect, useRef } from 'react'; import { motion } from 'framer-motion'; import { Clock, Users, Bike, Package, UtensilsCrossed, CheckCircle, AlertCircle, Timer, ArrowRight } from 'lucide-react'; import { useLanguage } from '@/lib/language-context'; import { getTranslation } from '@/lib/translations'; import type { KitchenOrder as Order, KitchenOrderItem as OrderItem, OrderStatus, } from '@/domain/kitchen-order/entities'; import { ORDER_STATUS_PIPELINE } from '@/domain/kitchen-order/entities'; export type { Order, OrderItem }; const STATUS_ORDER = ORDER_STATUS_PIPELINE; function getStatusMeta(status: OrderStatus, t: any) { const statuses = t.screen?.statuses || { received: 'Order Received', review: 'In Review', preparing: 'Preparing', packing: 'Packing / Plating', ready: 'Ready', }; const colors: Record = { received: 'bg-blue-100 text-blue-800 border-blue-200', review: 'bg-amber-100 text-amber-800 border-amber-200', preparing: 'bg-orange-100 text-orange-800 border-orange-200', packing: 'bg-purple-100 text-purple-800 border-purple-200', ready: 'bg-emerald-100 text-emerald-800 border-emerald-200', }; const icons: Record = { received: , review: , preparing: , packing: , ready: , }; return { label: statuses[status] || status, color: colors[status], icon: icons[status], progress: STATUS_ORDER.indexOf(status) / (STATUS_ORDER.length - 1), }; } function getSourceMeta(source: Order['source']) { if (source === 'table') return { icon: , label: 'Restaurant', color: '#B38B4D' }; if (source === 'foodora') return { icon: , label: 'Foodora', color: '#E30613' }; if (source === 'uber') return { icon: , label: 'Uber Eats', color: '#000000' }; if (source === 'wolt') return { icon: , label: 'Wolt', color: '#00C2E0' }; if (source === 'pickup') return { icon: , label: 'Pickup', color: '#B38B4D' }; return { icon: , label: 'Delivery', color: '#B38B4D' }; } // Beautiful subtle particles (adapted from ScreenDisplay for order theme - gold "steam" specks) function OrderParticles() { const canvasRef = useRef(null); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d', { alpha: true }); if (!ctx) return; let width = window.innerWidth; let height = window.innerHeight; canvas.width = width; canvas.height = height; const particles = Array.from({ length: 28 }, () => ({ x: Math.random() * width, y: Math.random() * height * 0.85, vx: (Math.random() - 0.5) * 0.12, vy: -0.08 - Math.random() * 0.12, size: 1.0 + Math.random() * 1.6, alpha: 0.08 + Math.random() * 0.16, phase: Math.random() * Math.PI * 2, speed: 0.012 + Math.random() * 0.009, })); const onResize = () => { width = window.innerWidth; height = window.innerHeight; canvas.width = width; canvas.height = height; }; window.addEventListener('resize', onResize); let rafId: number; const draw = () => { ctx.clearRect(0, 0, width, height); for (let i = 0; i < particles.length; i++) { const p = particles[i]; p.x += p.vx + Math.sin(p.phase) * 0.04; p.y += p.vy; p.phase += p.speed; if (p.y < -8) { p.y = height * 0.88 + Math.random() * 30; p.x = Math.random() * width; } if (p.x < 0) p.x = width; if (p.x > width) p.x = 0; ctx.fillStyle = `hsla(38, 68%, 52%, ${p.alpha})`; ctx.beginPath(); ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2); ctx.fill(); } rafId = requestAnimationFrame(draw); }; draw(); return () => { cancelAnimationFrame(rafId); window.removeEventListener('resize', onResize); }; }, []); return ( ); } export default function OrderDisplay() { const { language } = useLanguage(); const t = getTranslation(language); const [orders, setOrders] = useState(() => { const now = new Date(); return [ // Restaurant orders (tables) - 8 orders { id: 'T-05', source: 'table', label: 'Table 5', customer: 'Table 5', items: [{name: 'Butter Chicken', qty: 1}, {name: 'Garlic Naan', qty: 2}], status: 'preparing', receivedAt: new Date(now.getTime() - 1000*60*14), etaMinutes: 6 }, { id: 'T-12', source: 'table', label: 'Table 12', customer: 'Table 12', items: [{name: 'Lamb Rogan Josh', qty: 2}, {name: 'Biryani', qty: 1}], status: 'ready', receivedAt: new Date(now.getTime() - 1000*60*22), etaMinutes: 0 }, { id: 'T-03', source: 'table', label: 'Table 3', customer: 'Table 3', items: [{name: 'Samosa Chat', qty: 2}, {name: 'Chana Chat', qty: 1}], status: 'packing', receivedAt: new Date(now.getTime() - 1000*60*11), etaMinutes: 4 }, { id: 'T-08', source: 'table', label: 'Table 8', customer: 'Table 8', items: [{name: 'Daal Makhani', qty: 1}, {name: 'Rice', qty: 2}], status: 'ready', receivedAt: new Date(now.getTime() - 1000*60*18), etaMinutes: 0 }, { id: 'T-01', source: 'table', label: 'Table 1', customer: 'Table 1', items: [{name: 'Shahi Paneer', qty: 1}, {name: 'Naan', qty: 3}], status: 'preparing', receivedAt: new Date(now.getTime() - 1000*60*8), etaMinutes: 7 }, { id: 'T-04', source: 'table', label: 'Table 4', customer: 'Table 4', items: [{name: 'Chicken Tikka', qty: 2}, {name: 'Rice', qty: 1}], status: 'review', receivedAt: new Date(now.getTime() - 1000*60*5), etaMinutes: 10 }, { id: 'T-06', source: 'table', label: 'Table 6', customer: 'Table 6', items: [{name: 'Lahore Sizzler', qty: 1}, {name: 'Naan', qty: 2}], status: 'packing', receivedAt: new Date(now.getTime() - 1000*60*3), etaMinutes: 5 }, { id: 'T-07', source: 'table', label: 'Table 7', customer: 'Table 7', items: [{name: 'Tikka Boti', qty: 1}, {name: 'Salad', qty: 1}], status: 'received', receivedAt: new Date(now.getTime() - 1000*60*1), etaMinutes: 12 }, // Delivery Partners (online) - 8 orders (mix of platforms) { id: 'FD-3921', source: 'foodora', label: 'Foodora #FD-3921', customer: 'Aisha K.', items: [{name: 'Lahore Pizza', qty: 1}, {name: 'Mango Lassi', qty: 2}], status: 'packing', receivedAt: new Date(now.getTime() - 1000*60*9), etaMinutes: 3 }, { id: 'W-884', source: 'wolt', label: 'Wolt #W-884', customer: 'Erik S.', items: [{name: 'Paneer Roll', qty: 3}], status: 'review', receivedAt: new Date(now.getTime() - 1000*60*4), etaMinutes: 11 }, { id: 'FD-3928', source: 'foodora', label: 'Foodora #FD-3928', customer: 'Maria L.', items: [{name: 'Shahi Burger', qty: 2}, {name: 'Coca-Cola', qty: 2}], status: 'preparing', receivedAt: new Date(now.getTime() - 1000*60*7), etaMinutes: 8 }, { id: 'UB-551', source: 'uber', label: 'Uber Eats #UB-551', customer: 'Johan P.', items: [{name: 'Chicken Tikka', qty: 1}, {name: 'Keema Naan', qty: 1}], status: 'received', receivedAt: new Date(now.getTime() - 1000*60*2), etaMinutes: 14 }, { id: 'FD-3935', source: 'foodora', label: 'Foodora #FD-3935', customer: 'Omar B.', items: [{name: 'Butter Chicken', qty: 1}, {name: 'Naan', qty: 2}], status: 'preparing', receivedAt: new Date(now.getTime() - 1000*60*10), etaMinutes: 4 }, { id: 'W-892', source: 'wolt', label: 'Wolt #W-892', customer: 'Lina T.', items: [{name: 'Veg Pizza', qty: 1}, {name: 'Lassi', qty: 1}], status: 'packing', receivedAt: new Date(now.getTime() - 1000*60*6), etaMinutes: 6 }, { id: 'UB-562', source: 'uber', label: 'Uber Eats #UB-562', customer: 'Peter H.', items: [{name: 'Lamb Rogan Josh', qty: 1}, {name: 'Rice', qty: 2}], status: 'review', receivedAt: new Date(now.getTime() - 1000*60*12), etaMinutes: 9 }, { id: 'FD-3940', source: 'foodora', label: 'Foodora #FD-3940', customer: 'Sara N.', items: [{name: 'Tikka Boti Roll', qty: 2}], status: 'packing', receivedAt: new Date(now.getTime() - 1000*60*15), etaMinutes: 2 }, // Pickup orders (whatsapp/phone) - 8 orders { id: 'WA-4521', source: 'pickup', label: 'Pickup WA-4521', customer: 'Ahmed R.', items: [{name: 'Chicken Biryani', qty: 2}, {name: 'Raita', qty: 1}], status: 'preparing', receivedAt: new Date(now.getTime() - 1000*60*13), etaMinutes: 5 }, { id: 'PH-783', source: 'pickup', label: 'Pickup PH-783', customer: 'Sara M.', items: [{name: 'Veg Pizza', qty: 1}, {name: 'Garlic Naan', qty: 2}], status: 'packing', receivedAt: new Date(now.getTime() - 1000*60*6), etaMinutes: 2 }, { id: 'WA-4530', source: 'pickup', label: 'Pickup WA-4530', customer: 'Omar K.', items: [{name: 'Tikka Boti', qty: 1}], status: 'ready', receivedAt: new Date(now.getTime() - 1000*60*20), etaMinutes: 0 }, { id: 'WA-4535', source: 'pickup', label: 'Pickup WA-4535', customer: 'Fatima L.', items: [{name: 'Shahi Paneer', qty: 1}, {name: 'Naan', qty: 1}], status: 'preparing', receivedAt: new Date(now.getTime() - 1000*60*8), etaMinutes: 7 }, { id: 'PH-791', source: 'pickup', label: 'Pickup PH-791', customer: 'Hassan Q.', items: [{name: 'Daal Makhani', qty: 1}, {name: 'Rice', qty: 2}], status: 'review', receivedAt: new Date(now.getTime() - 1000*60*11), etaMinutes: 8 }, { id: 'WA-4542', source: 'pickup', label: 'Pickup WA-4542', customer: 'Ayesha S.', items: [{name: 'Samosa Keema', qty: 3}], status: 'packing', receivedAt: new Date(now.getTime() - 1000*60*4), etaMinutes: 3 }, { id: 'PH-802', source: 'pickup', label: 'Pickup PH-802', customer: 'Bilal M.', items: [{name: 'Lamm Vindaloo', qty: 1}, {name: 'Naan', qty: 2}], status: 'preparing', receivedAt: new Date(now.getTime() - 1000*60*9), etaMinutes: 6 }, { id: 'WA-4550', source: 'pickup', label: 'Pickup WA-4550', customer: 'Zainab P.', items: [{name: 'Jalebi', qty: 2}, {name: 'Kulfi', qty: 1}], status: 'received', receivedAt: new Date(now.getTime() - 1000*60*1), etaMinutes: 13 }, ]; }); const [lastUpdate, setLastUpdate] = useState(new Date()); const [liveClock, setLiveClock] = useState(new Date()); // Live clock useEffect(() => { const id = setInterval(() => setLiveClock(new Date()), 1000); return () => clearInterval(id); }, []); // Simulate live backend updates (replace with real fetch / WebSocket / polling when ready) useEffect(() => { const interval = setInterval(() => { setOrders(prev => { let updated = [...prev]; const now = new Date(); // Occasionally advance a random non-ready order if (Math.random() < 0.65) { const candidates = updated .map((o, i) => ({o, i})) .filter(({o}) => o.status !== 'ready'); if (candidates.length > 0) { const pick = candidates[Math.floor(Math.random() * candidates.length)]; const currIdx = STATUS_ORDER.indexOf(pick.o.status); if (currIdx < STATUS_ORDER.length - 1) { updated[pick.i] = { ...pick.o, status: STATUS_ORDER[currIdx + 1], }; } } } // Occasionally add a new incoming order (allow up to ~30 total so each category can have 6-10+ for reels) if (Math.random() < 0.28 && updated.length < 30) { const sources: Order['source'][] = ['table', 'foodora', 'wolt', 'uber', 'pickup']; const src = sources[Math.floor(Math.random() * sources.length)]; const isTable = src === 'table'; const isPickup = src === 'pickup'; const num = Math.floor(1000 + Math.random() * 9000); const newOrder: Order = { id: isTable ? `T-${Math.floor(1+Math.random()*14)}` : isPickup ? `WA-${num}` : `${src.toUpperCase().slice(0,2)}-${num}`, source: src, label: isTable ? `Table ${Math.floor(1+Math.random()*14)}` : isPickup ? `Pickup WA-${num}` : `${src.charAt(0).toUpperCase() + src.slice(1)} #${src.slice(0,2).toUpperCase()}-${num}`, customer: isTable ? `Table ${Math.floor(1+Math.random()*14)}` : isPickup ? ['Ahmed R.', 'Sara M.', 'Omar K.', 'Fatima L.'][Math.floor(Math.random()*4)] : ['A. Khan', 'S. Patel', 'L. Berg', 'M. Lind'][Math.floor(Math.random()*4)], items: [ { name: ['Butter Chicken','Lahore Pizza','Lamb Rogan Josh','Shahi Paneer','Tikka Boti Roll'][Math.floor(Math.random()*5)], qty: 1 + Math.floor(Math.random()*2) }, { name: ['Naan','Rice','Lassi','Samosa','Salad'][Math.floor(Math.random()*5)], qty: 1 }, ], status: 'received', receivedAt: now, etaMinutes: 10 + Math.floor(Math.random() * 8), }; updated = [newOrder, ...updated].slice(0, 30); } // Occasionally remove a very old ready order (simulates pickup / serve) if (Math.random() < 0.22) { const readyIdx = updated.findIndex(o => o.status === 'ready' && (now.getTime() - o.receivedAt.getTime()) > 1000*60*3); if (readyIdx !== -1) updated.splice(readyIdx, 1); } return updated; }); setLastUpdate(new Date()); }, 6200); // ~every 6s for mesmerizing live feel return () => clearInterval(interval); }, []); // Group into the three required sections const restaurantOrders = orders.filter(o => o.source === 'table').sort((a,b) => b.receivedAt.getTime() - a.receivedAt.getTime()); const deliveryOrders = orders.filter(o => ['foodora', 'uber', 'wolt', 'other'].includes(o.source)).sort((a,b) => b.receivedAt.getTime() - a.receivedAt.getTime()); const pickupOrders = orders.filter(o => o.source === 'pickup').sort((a,b) => b.receivedAt.getTime() - a.receivedAt.getTime()); const readyOrders = orders.filter(o => o.status === 'ready'); const formatTime = (date: Date) => date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); const getElapsed = (date: Date) => { const mins = Math.floor((Date.now() - date.getTime()) / 60000); return `${mins} ${t.screen?.elapsed || 'min'}`; }; const screenLabel = t.screen?.display3 || 'DISPLAY 3'; // Helper to render a vertical looping reel for a list of orders. // Viewport h-full (fills remaining screen height after headers). // Cards fixed px height => browser zoom changes # visible (fewer on zoom-in, more on zoom-out), like screen2. // Duplicates list for seamless upward CSS loop when count > visible. // Always fills screen top-to-bottom with no whitespace. const renderOrderReel = (ordersList: Order[], sectionLabel: string, sectionIcon: React.ReactNode) => { const count = ordersList.length; const shouldLoop = count > 3; const displayList = shouldLoop ? [...ordersList, ...ordersList] : ordersList; const duration = shouldLoop ? `${count * REEL_TIME_PER_ITEM}s` : undefined; return (
{sectionIcon}
{sectionLabel}
{count} ACTIVE
{displayList.map((order, idx) => { const meta = getStatusMeta(order.status, t); const srcMeta = getSourceMeta(order.source); const elapsed = getElapsed(order.receivedAt); const isReady = order.status === 'ready'; // Card with FIXED height in px. + h-full viewport (fills screen) means zoom in: fewer visible/column, zoom out: more visible. No whitespace, max top-bottom. return (
{order.label}
{order.customer} • {elapsed}
{meta.icon} {meta.label.toUpperCase().slice(0,6)}
{/* Compact items */}
{order.items.slice(0,2).map((it, i) => (
{it.qty}× {it.name}
))} {order.items.length > 2 &&
+{order.items.length-2} more
}
{/* Compact progress */}
{isReady ? 'READY' : `${order.etaMinutes}m`}
{isReady && (
READY
)}
); })}
); }; // Reel settings for screen3 vertical marquees - dynamic with zoom like screen2 // Fixed card height in px so that browser zoom in/out changes how many cards fit vertically in the column (less when zoom in, more when zoom out). // The reel viewport uses 100% of available column height (after section header) to fill the screen with no whitespace. const REEL_CARD_HEIGHT = 120; // px per order card - tune for ~3-5 at 100% on 43" TV. Zoom affects visible count. const REEL_TIME_PER_ITEM = 6; // seconds per order in the animation - slow upward movement return (
{/* Mesmerizing top bar - luxurious, welcoming, eye-catching for customers & riders. Taller, pulsing LIVE, centered tagline, elegant depth for 43" kiosk appeal. */}
{/* Brand with depth and elegance */}
SHAHI KITCHEN
EST. 2016
{/* Elegant status badges */}
{screenLabel}
LIVE
{/* Centered mesmerizing tagline */}
YOUR JOURNEY TO FLAVOR • REAL-TIME MAGIC
{/* Right info - elegant and clear */}
ASKIM
BACKAPLAN
{liveClock.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'})}
{t.screen?.prepared?.toUpperCase() || 'FRESHLY PREPARED'}
{/* Hero header with beautiful typography and live feel */}
{t.screen?.orderBoard || 'LIVE ORDER BOARD'}

{t.screen?.orderSubtitle || 'Delivery • Pickup • Restaurant — live for riders & guests'}

Delivery Partners • Pickup (WhatsApp/Phone) • Restaurant Tables • Real-time kitchen progress

LAST SYNC
{formatTime(lastUpdate)}
{t.screen?.liveUpdates || 'Live updates • Backend integration coming soon'}
{/* Stats bar - beautiful metrics with motion - three sections */}
{restaurantOrders.length}
RESTAURANT ORDERS
{deliveryOrders.length}
DELIVERY PARTNERS
{pickupOrders.length}
PICKUP ORDERS (WA/PHONE)
AVERAGE PREP TIME
12-16 MIN
{/* Main beautiful content area - THREE COLUMNS with vertical reels filling entire remaining screen height */} {/* Reels use h-full after fixed headers; cards fixed px height => zoom in: fewer visible per column, zoom out: more visible. No white space, max top to bottom like screen2 max left to right. */}
{/* 1. DELIVERY PARTNERS - reel column */}
{renderOrderReel(deliveryOrders, 'DELIVERY PARTNERS', )}
{/* 2. PICKUP ORDERS (WHATSAPP/PHONE) - reel column */}
{renderOrderReel(pickupOrders, 'PICKUP ORDERS (WHATSAPP/PHONE)', )}
{/* 3. RESTAURANT ORDERS - reel column */}
{renderOrderReel(restaurantOrders, 'RESTAURANT ORDERS', )}
{/* Bottom elegant bar / marquee for future rider messages */}
SHAHI KITCHEN • GOTHENBURG • FRESH EVERY DAY
INTEGRATING WITH FOODORA • WOLT • UBER EATS
BACKEND API READY SOON — LIVE DATA FEED
); }