508 lines
27 KiB
TypeScript
508 lines
27 KiB
TypeScript
'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<OrderStatus, string> = {
|
||
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<OrderStatus, React.ReactNode> = {
|
||
received: <AlertCircle className="w-4 h-4" />,
|
||
review: <Clock className="w-4 h-4" />,
|
||
preparing: <UtensilsCrossed className="w-4 h-4" />,
|
||
packing: <Package className="w-4 h-4" />,
|
||
ready: <CheckCircle className="w-4 h-4" />,
|
||
};
|
||
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: <Users className="w-4 h-4" />, label: 'Restaurant', color: '#B38B4D' };
|
||
if (source === 'foodora') return { icon: <Bike className="w-4 h-4" />, label: 'Foodora', color: '#E30613' };
|
||
if (source === 'uber') return { icon: <Bike className="w-4 h-4" />, label: 'Uber Eats', color: '#000000' };
|
||
if (source === 'wolt') return { icon: <Bike className="w-4 h-4" />, label: 'Wolt', color: '#00C2E0' };
|
||
if (source === 'pickup') return { icon: <Package className="w-4 h-4" />, label: 'Pickup', color: '#B38B4D' };
|
||
return { icon: <Bike className="w-4 h-4" />, label: 'Delivery', color: '#B38B4D' };
|
||
}
|
||
|
||
// Beautiful subtle particles (adapted from ScreenDisplay for order theme - gold "steam" specks)
|
||
function OrderParticles() {
|
||
const canvasRef = useRef<HTMLCanvasElement>(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 (
|
||
<canvas
|
||
ref={canvasRef}
|
||
className="fixed inset-0 pointer-events-none z-[1] opacity-60 mix-blend-multiply"
|
||
/>
|
||
);
|
||
}
|
||
|
||
export default function OrderDisplay() {
|
||
const { language } = useLanguage();
|
||
const t = getTranslation(language);
|
||
|
||
const [orders, setOrders] = useState<Order[]>(() => {
|
||
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: 'Chicken Tikka', 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: 'Chicken Tikka', 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 (
|
||
<div className="flex flex-col h-full">
|
||
<div className="flex items-center gap-3 mb-3 px-1 flex-none">
|
||
{sectionIcon}
|
||
<div className="font-medium tracking-[2px] text-sm text-[#B38B4D]">{sectionLabel}</div>
|
||
<div className="flex-1 h-px bg-[#EDE6D9]" />
|
||
<div className="text-xs text-[#6B665F] tabular-nums">{count} ACTIVE</div>
|
||
</div>
|
||
|
||
<div
|
||
className="reel-viewport h-full overflow-hidden"
|
||
>
|
||
<div
|
||
className="order-reel-track"
|
||
style={{
|
||
gap: '8px',
|
||
animationDuration: duration,
|
||
// If not looping, no animation
|
||
animation: shouldLoop ? undefined : 'none'
|
||
}}
|
||
>
|
||
{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 (
|
||
<div
|
||
key={`${order.id}-${idx}`}
|
||
className={`group relative bg-white rounded-2xl border shadow-md overflow-hidden flex flex-col ${isReady ? 'border-emerald-300 ring-1 ring-emerald-200' : 'border-[#EDE6D9]'}`}
|
||
style={{ height: `${REEL_CARD_HEIGHT}px` }}
|
||
>
|
||
<div className="px-3 pt-2 pb-1 flex items-start justify-between gap-2 flex-none">
|
||
<div className="min-w-0">
|
||
<div className="font-mono text-lg tracking-[-0.5px] font-semibold text-[#2C2A26] leading-none truncate">
|
||
{order.label}
|
||
</div>
|
||
<div className="text-[10px] text-[#6B665F] mt-0.5 tracking-[0.3px] truncate">{order.customer} • {elapsed}</div>
|
||
</div>
|
||
|
||
<div className={`px-2 py-0.5 rounded-full text-[10px] font-medium tracking-wider border flex items-center gap-1 whitespace-nowrap flex-none ${meta.color}`}>
|
||
{meta.icon} {meta.label.toUpperCase().slice(0,6)}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Compact items */}
|
||
<div className="px-3 pb-1 text-xs text-[#2C2A26]/90 flex-1 overflow-hidden">
|
||
{order.items.slice(0,2).map((it, i) => (
|
||
<div key={i} className="flex justify-between leading-tight py-px">
|
||
<span className="truncate">{it.qty}× {it.name}</span>
|
||
</div>
|
||
))}
|
||
{order.items.length > 2 && <div className="text-[10px] text-[#6B665F]">+{order.items.length-2} more</div>}
|
||
</div>
|
||
|
||
{/* Compact progress */}
|
||
<div className="px-3 pb-2 pt-0.5 border-t border-[#EDE6D9]/60 bg-[#F8F5F0]/50 flex items-center gap-2 flex-none">
|
||
<div className="flex-1 h-1 bg-[#EDE6D9] rounded overflow-hidden">
|
||
<div
|
||
className="h-full transition-all"
|
||
style={{
|
||
width: `${meta.progress * 100}%`,
|
||
background: srcMeta.color
|
||
}}
|
||
/>
|
||
</div>
|
||
<div className="text-[10px] font-medium whitespace-nowrap tabular-nums" style={{ color: srcMeta.color }}>
|
||
{isReady ? 'READY' : `${order.etaMinutes}m`}
|
||
</div>
|
||
</div>
|
||
|
||
{isReady && (
|
||
<div className="absolute top-1 right-1 px-2 py-0.5 bg-emerald-600 text-white text-[9px] tracking-widest rounded font-semibold">READY</div>
|
||
)}
|
||
</div>
|
||
);
|
||
})}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
// 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 (
|
||
<div className="h-screen bg-[#F8F5F0] text-[#2C2A26] overflow-hidden select-none flex flex-col screen-root">
|
||
<OrderParticles />
|
||
|
||
{/* Mesmerizing top bar - luxurious, welcoming, eye-catching for customers & riders. Taller, pulsing LIVE, centered tagline, elegant depth for 43" kiosk appeal. */}
|
||
<div className="relative z-20 h-20 border-b border-[#EDE6D9]/50 bg-gradient-to-b from-[#F8F5F0] via-[#F8F5F0]/98 to-[#F8F5F0]/95 backdrop-blur-xl flex items-center px-10 text-sm flex-none shadow-[0_2px_8px_rgba(0,0,0,0.03)]">
|
||
<div className="flex items-center gap-5">
|
||
{/* Brand with depth and elegance */}
|
||
<div className="flex items-baseline gap-1.5">
|
||
<div className="font-serif text-[28px] tracking-[-2.2px] text-[#2C2A26] drop-shadow-sm">SHAHI KITCHEN</div>
|
||
<div className="text-[9px] text-[#B38B4D] tracking-[2px] font-medium -mb-1">EST. 2016</div>
|
||
</div>
|
||
|
||
{/* Elegant status badges */}
|
||
<div className="flex items-center gap-2">
|
||
<div className="text-[11px] px-3.5 py-1 rounded-full border border-[#B38B4D]/60 text-[#B38B4D] tracking-[3px] font-medium bg-white/60 backdrop-blur-sm">
|
||
{screenLabel}
|
||
</div>
|
||
<motion.div
|
||
animate={{ scale: [1, 1.08, 1], opacity: [0.9, 1, 0.9] }}
|
||
transition={{ duration: 2.2, repeat: Infinity, ease: "easeInOut" }}
|
||
className="flex items-center gap-1.5 px-3 py-1 rounded-full bg-[#B38B4D] text-white text-[10px] tracking-[2.5px] font-semibold shadow-sm"
|
||
>
|
||
<div className="w-1.5 h-1.5 rounded-full bg-white animate-pulse" />
|
||
LIVE
|
||
</motion.div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex-1" />
|
||
|
||
{/* Centered mesmerizing tagline */}
|
||
<div className="hidden md:block text-center">
|
||
<div className="text-[11px] tracking-[4.5px] text-[#B38B4D]/70 font-medium">YOUR JOURNEY TO FLAVOR • REAL-TIME MAGIC</div>
|
||
</div>
|
||
|
||
<div className="flex-1" />
|
||
|
||
{/* Right info - elegant and clear */}
|
||
<div className="flex items-center gap-7 text-[#6B665F] tracking-[1.5px] text-xs">
|
||
<div className="flex items-center gap-2.5">
|
||
<div>ASKIM</div>
|
||
<div className="w-px h-2.5 bg-[#EDE6D9]" />
|
||
<div>BACKAPLAN</div>
|
||
</div>
|
||
<div className="h-3 w-px bg-[#EDE6D9]/70" />
|
||
<div className="font-mono text-[#B38B4D] text-sm tracking-widest tabular-nums">
|
||
{liveClock.toLocaleTimeString([], {hour:'2-digit', minute:'2-digit'})}
|
||
</div>
|
||
<div className="text-[10px] px-2.5 py-0.5 rounded bg-[#B38B4D]/10 text-[#B38B4D] tracking-[1.5px]">
|
||
{t.screen?.prepared?.toUpperCase() || 'FRESHLY PREPARED'}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Hero header with beautiful typography and live feel */}
|
||
<div className="relative z-10 bg-[#101724] text-white flex-none py-5 px-9 border-b border-white/10">
|
||
<div className="max-w-[1680px] mx-auto flex items-end justify-between">
|
||
<div>
|
||
<div className="inline-flex items-center gap-2 rounded-full bg-white/10 px-4 py-1 text-xs tracking-[3.5px] mb-2 border border-white/20">
|
||
{t.screen?.orderBoard || 'LIVE ORDER BOARD'}
|
||
</div>
|
||
<h1 className="font-serif text-[42px] md:text-[52px] leading-[0.9] tracking-[-2.2px] text-white">
|
||
{t.screen?.orderSubtitle || 'Delivery • Pickup • Restaurant — live for riders & guests'}
|
||
</h1>
|
||
<p className="mt-1.5 text-white/60 tracking-[0.5px] text-sm">
|
||
Delivery Partners • Pickup (WhatsApp/Phone) • Restaurant Tables • Real-time kitchen progress
|
||
</p>
|
||
</div>
|
||
|
||
<div className="text-right text-sm">
|
||
<div className="text-[#B38B4D] tracking-[2px] text-xs mb-0.5">LAST SYNC</div>
|
||
<div className="font-mono text-lg text-white/90">{formatTime(lastUpdate)}</div>
|
||
<div className="text-[10px] text-white/50 mt-0.5">{t.screen?.liveUpdates || 'Live updates • Backend integration coming soon'}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Stats bar - beautiful metrics with motion - three sections */}
|
||
<div className="relative z-10 bg-[#F8F5F0] border-b border-[#EDE6D9]/60 py-3 px-9 flex-none">
|
||
<div className="max-w-[1680px] mx-auto grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
||
<div className="flex items-center gap-3">
|
||
<div className="text-[#B38B4D]"><Users className="w-5 h-5" /></div>
|
||
<div>
|
||
<div className="font-medium text-lg leading-none">{restaurantOrders.length}</div>
|
||
<div className="text-[#6B665F] text-xs tracking-[1px]">RESTAURANT ORDERS</div>
|
||
</div>
|
||
</div>
|
||
<div className="flex items-center gap-3">
|
||
<div className="text-[#B38B4D]"><Bike className="w-5 h-5" /></div>
|
||
<div>
|
||
<div className="font-medium text-lg leading-none">{deliveryOrders.length}</div>
|
||
<div className="text-[#6B665F] text-xs tracking-[1px]">DELIVERY PARTNERS</div>
|
||
</div>
|
||
</div>
|
||
<div className="flex items-center gap-3">
|
||
<div className="text-[#B38B4D]"><Package className="w-5 h-5" /></div>
|
||
<div>
|
||
<div className="font-medium text-lg leading-none">{pickupOrders.length}</div>
|
||
<div className="text-[#6B665F] text-xs tracking-[1px]">PICKUP ORDERS (WA/PHONE)</div>
|
||
</div>
|
||
</div>
|
||
<div className="flex items-center gap-3 text-[#6B665F]">
|
||
<Timer className="w-5 h-5 text-[#B38B4D]" />
|
||
<div className="text-xs tracking-[1.5px] leading-tight">
|
||
AVERAGE PREP TIME<br />
|
||
<span className="text-[#2C2A26] font-medium">12-16 MIN</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* 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. */}
|
||
<div className="flex-1 min-h-0 overflow-hidden px-4 py-2 md:px-6 md:py-3 bg-[#F8F5F0]">
|
||
<div className="max-w-[1680px] mx-auto h-full grid grid-cols-1 lg:grid-cols-3 gap-4" style={{ minHeight: 0 }}>
|
||
{/* 1. DELIVERY PARTNERS - reel column */}
|
||
<div className="flex flex-col h-full">
|
||
{renderOrderReel(deliveryOrders, 'DELIVERY PARTNERS', <Bike className="w-5 h-5 text-[#B38B4D]" />)}
|
||
</div>
|
||
|
||
{/* 2. PICKUP ORDERS (WHATSAPP/PHONE) - reel column */}
|
||
<div className="flex flex-col h-full">
|
||
{renderOrderReel(pickupOrders, 'PICKUP ORDERS (WHATSAPP/PHONE)', <Package className="w-5 h-5 text-[#B38B4D]" />)}
|
||
</div>
|
||
|
||
{/* 3. RESTAURANT ORDERS - reel column */}
|
||
<div className="flex flex-col h-full">
|
||
{renderOrderReel(restaurantOrders, 'RESTAURANT ORDERS', <Users className="w-5 h-5 text-[#B38B4D]" />)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Bottom elegant bar / marquee for future rider messages */}
|
||
<div className="relative z-10 py-2.5 text-center text-[11px] tracking-[3px] text-[#8A8478] border-t border-[#EDE6D9]/60 bg-[#F8F5F0] flex-none flex items-center justify-center gap-4">
|
||
<div>SHAHI KITCHEN • GOTHENBURG • FRESH EVERY DAY</div>
|
||
<div className="h-px w-6 bg-[#EDE6D9]" />
|
||
<div className="text-[#B38B4D] font-medium">INTEGRATING WITH FOODORA • WOLT • UBER EATS</div>
|
||
<div className="h-px w-6 bg-[#EDE6D9]" />
|
||
<div>BACKEND API READY SOON — LIVE DATA FEED</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|