Spaces:
Running
Running
| import React, { useState, useEffect, useRef } from 'react'; | |
| import { db } from '../firebase/config'; | |
| import { ref, onValue, update } from 'firebase/database'; | |
| import { useAuth } from '../context/AuthContext'; | |
| import { useNavigate } from 'react-router-dom'; | |
| import { CheckCircle, Clock, UtensilsCrossed, LogOut, ExternalLink, PlayCircle, Flame } from 'lucide-react'; | |
| export default function KitchenView() { | |
| const { logout } = useAuth(); | |
| const navigate = useNavigate(); | |
| const [orders, setOrders] = useState([]); | |
| const [currentTime, setCurrentTime] = useState(Date.now()); | |
| const lastOrderCount = useRef(0); | |
| useEffect(() => { | |
| const ordersRef = ref(db, 'orders'); | |
| onValue(ordersRef, (snapshot) => { | |
| const data = snapshot.val(); | |
| if (data) { | |
| const orderList = Object.keys(data).map(k => ({ id: k, ...data[k] })) | |
| .filter(o => o.status !== 'completed') | |
| .sort((a, b) => b.timestamp - a.timestamp); | |
| // Alerta de sonido si hay nuevas órdenes | |
| if (orderList.length > lastOrderCount.current) { | |
| playNotificationSound(); | |
| } | |
| lastOrderCount.current = orderList.length; | |
| setOrders(orderList); | |
| } else { | |
| setOrders([]); | |
| } | |
| }); | |
| const timer = setInterval(() => setCurrentTime(Date.now()), 60000); | |
| return () => clearInterval(timer); | |
| }, []); | |
| const playNotificationSound = () => { | |
| const audio = new Audio('https://assets.mixkit.co/active_storage/sfx/2869/2869-preview.mp3'); | |
| audio.play().catch(e => console.log("User interaction required for audio")); | |
| }; | |
| const updateStatus = async (id, status) => { | |
| await update(ref(db, `orders/${id}`), { status }); | |
| }; | |
| const handleLogout = async () => { | |
| await logout(); | |
| navigate('/login'); | |
| }; | |
| return ( | |
| <div className="app-container" style={{ flexDirection: 'column', background: 'var(--bg-base)', height: '100vh', overflow: 'hidden' }}> | |
| <header className="glass-panel" style={{ padding: '1rem 2.5rem', display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderRadius: '0', borderBottom: '1px solid var(--border-subtle)' }}> | |
| <h1 className="text-gradient" style={{ fontSize: '1.5rem', fontWeight: '800', display: 'flex', alignItems: 'center', gap: '0.75rem' }}> | |
| <UtensilsCrossed size={24} /> KDS - Centro de Cocina | |
| </h1> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: '1.5rem' }}> | |
| <div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', color: 'var(--success)', fontSize: '0.9rem' }}> | |
| <div style={{ width: '8px', height: '8px', background: 'var(--success)', borderRadius: '50%', boxShadow: '0 0 10px var(--success)' }}></div> | |
| En Vivo | |
| </div> | |
| <button className="btn-glass" onClick={handleLogout} style={{ padding: '0.5rem 1rem', color: 'var(--primary)' }}> | |
| <LogOut size={16} /> Salir | |
| </button> | |
| </div> | |
| </header> | |
| <div className="main-content" style={{ padding: '2rem', display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(320px, 1fr))', gap: '1.5rem', overflowY: 'auto' }}> | |
| {orders.length === 0 ? ( | |
| <div style={{ gridColumn: '1 / -1', textAlign: 'center', paddingTop: '10vh', color: 'var(--text-muted)' }}> | |
| <UtensilsCrossed size={60} style={{ opacity: 0.1, marginBottom: '1rem' }} /> | |
| <h2 style={{ fontSize: '1.5rem' }}>Cocina Despejada</h2> | |
| <p>No hay pedidos en espera.</p> | |
| </div> | |
| ) : ( | |
| orders.map((order) => ( | |
| <div key={order.id} className="glass-card animate-fade-in" style={{ | |
| padding: '1.5rem', | |
| borderLeft: order.status === 'preparing' ? '4px solid var(--warning)' : '4px solid var(--primary)', | |
| background: order.status === 'preparing' ? 'rgba(255,200,0,0.02)' : 'var(--bg-card)' | |
| }}> | |
| <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: '1.25rem', alignItems: 'center' }}> | |
| <span style={{ fontSize: '1.2rem', fontWeight: '800', color: order.status === 'preparing' ? 'var(--warning)' : 'var(--primary)' }}> | |
| {order.table} {order.type === 'Llevar' && '🛍️'} {order.type === 'Delivery' && '🚀'} | |
| </span> | |
| <span style={{ fontSize: '0.8rem', color: (currentTime - order.timestamp) > 900000 ? 'var(--primary)' : 'var(--text-muted)', fontWeight: '700' }}> | |
| <Clock size={14} /> {Math.floor((currentTime - order.timestamp) / 60000)} min | |
| </span> | |
| </div> | |
| <div style={{ marginBottom: '1.5rem', minHeight: '80px' }}> | |
| {order.items.map((item, idx) => ( | |
| <div key={idx} style={{ display: 'flex', justifyContent: 'space-between', padding: '0.5rem 0', borderBottom: '1px solid rgba(255,255,255,0.05)' }}> | |
| <div style={{ display: 'flex', flexDirection: 'column' }}> | |
| <span style={{ fontWeight: '600' }}><span style={{ color: 'var(--primary)', fontWeight: '800' }}>{item.qty}</span> {item.name}</span> | |
| {item.note && <span style={{ fontSize: '0.75rem', color: 'var(--warning)', fontStyle: 'italic' }}>* {item.note}</span>} | |
| </div> | |
| </div> | |
| ))} | |
| </div> | |
| <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '0.75rem' }}> | |
| {order.status !== 'preparing' ? ( | |
| <button | |
| className="btn-glass" | |
| onClick={() => updateStatus(order.id, 'preparing')} | |
| style={{ background: 'rgba(255,160,0,0.1)', color: '#FFB000' }} | |
| > | |
| <Flame size={18} /> Preparar | |
| </button> | |
| ) : ( | |
| <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: '0.8rem', color: 'var(--warning)', fontWeight: '700' }}> | |
| ENCENDIDO... | |
| </div> | |
| )} | |
| <button | |
| className="btn-primary" | |
| onClick={() => updateStatus(order.id, 'completed')} | |
| style={{ background: 'var(--success)', color: '#fff' }} | |
| > | |
| <CheckCircle size={18} /> Listo | |
| </button> | |
| </div> | |
| </div> | |
| )) | |
| )} | |
| </div> | |
| </div> | |
| ); | |
| } | |