import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import { Clock, ShoppingBag as ShoppingBagIcon, ChevronRight as ChevronRightIcon, X, RefreshCcw, AlertCircle } from 'lucide-react'; import { QRCodeCanvas } from 'qrcode.react'; import { motion, AnimatePresence } from 'framer-motion'; import Header from '../components/Header'; import BottomNav from '../components/BottomNav'; import { useAuth } from '../contexts/AuthContext'; import { useFood } from '../contexts/FoodContext'; import { useCart } from '../contexts/CartContext'; import type { FoodItem } from '../types'; import './MyOrdersScreen.css'; interface Order { id: number; orderNumber: string; displayOrderId: string; totalAmount: number; status: string; paymentMethod: string; createdAt: string; isArchived: boolean; items: Array<{ productName: string; quantity: number; price: number; }>; } const MyOrdersScreen: React.FC = () => { const navigate = useNavigate(); const { user } = useAuth(); const { foodItems } = useFood(); const { addToCart, clearCart } = useCart(); const [orders, setOrders] = useState([]); const [loading, setLoading] = useState(true); const [selectedOrder, setSelectedOrder] = useState(null); // State for stock alert const [showStockAlert, setShowStockAlert] = useState(false); const [unavailableItems, setUnavailableItems] = useState([]); const [pendingItems, setPendingItems] = useState([]); const [isCancelling, setIsCancelling] = useState(false); const isOrderExpired = (order: Order) => { if (order.isArchived) return true; const orderDate = new Date(order.createdAt).toDateString(); const today = new Date().toDateString(); return orderDate !== today; }; useEffect(() => { if (user?.id) { fetchOrders(); } }, [user]); // Polling for live updates (Sync when Dashboard regenerates QR or changes status) useEffect(() => { let interval: ReturnType; // Only poll if there are active (non-finalized) orders const hasActiveOrders = orders.some(o => o.status.toUpperCase() !== 'COMPLETED' && o.status.toUpperCase() !== 'CANCELLED' ); if (user?.id && hasActiveOrders) { interval = setInterval(() => { fetchOrders(); }, 10000); // Poll every 10 seconds } return () => { if (interval) clearInterval(interval); }; }, [user?.id, orders]); const fetchOrders = async () => { try { const token = localStorage.getItem('token'); const response = await fetch(`http://${window.location.hostname}:8080/api/orders/user/${user?.id}`, { headers: { ...(token ? { 'Authorization': `Bearer ${token}` } : {}) } }); const data = await response.json(); setOrders(data); } catch (error) { console.error('Error fetching orders:', error); } finally { setLoading(false); } }; const handleRepeatOrder = (order: Order) => { const missing: string[] = []; const available: FoodItem[] = []; order.items.forEach(orderItem => { const currentItem = foodItems.find(fi => fi.name === orderItem.productName); if (currentItem && currentItem.stock !== undefined && currentItem.stock > 0) { // Only add up to available stock const canAdd = Math.min(orderItem.quantity, currentItem.stock); for (let i = 0; i < canAdd; i++) { available.push(currentItem); } if (canAdd < orderItem.quantity) { missing.push(`${orderItem.productName} (only ${canAdd} of ${orderItem.quantity} available)`); } } else { missing.push(orderItem.productName); // Item no longer exists or out of stock } }); if (missing.length > 0) { setUnavailableItems(missing); setPendingItems(available); setShowStockAlert(true); } else { // All items available performRepeat(available); } }; const handleCancelOrder = async (orderId: number) => { if (!window.confirm('Are you sure you want to cancel this order?')) return; setIsCancelling(true); try { const token = localStorage.getItem('token'); const response = await fetch(`http://${window.location.hostname}:8080/api/orders/${orderId}/cancel`, { method: 'POST', headers: { ...(token ? { 'Authorization': `Bearer ${token}` } : {}) } }); const data = await response.json(); if (data.success) { fetchOrders(); if (selectedOrder?.id === orderId) { setSelectedOrder({ ...selectedOrder, status: 'CANCELLED' }); } } else { alert(data.message || 'Failed to cancel order'); } } catch (error) { console.error('Error cancelling order:', error); alert('Error cancelling order'); } finally { setIsCancelling(false); } }; const performRepeat = (items: FoodItem[]) => { clearCart(); items.forEach(item => addToCart(item)); navigate('/cart'); }; const handleProceedPartial = () => { performRepeat(pendingItems); setShowStockAlert(false); }; const latestOrder = orders.length > 0 ? orders[0] : null; const previousOrders = orders.length > 1 ? orders.slice(1) : []; if (loading) { return
Loading your orders...
; } return (
{orders.length === 0 ? (

No orders yet

Hungry? Place your first order now!

) : ( <> {/* Latest Order Section */} {latestOrder && (
Active/Latest Order
setSelectedOrder(latestOrder)}>
Order #{latestOrder.displayOrderId || latestOrder.id} {new Date(latestOrder.createdAt).toLocaleDateString()}
{latestOrder.status}
{latestOrder.status.toUpperCase() === 'COMPLETED' && (
COMPLETED
)} {latestOrder.status.toUpperCase() === 'CANCELLED' && (
CANCELLED
)} {isOrderExpired(latestOrder) && (
EXPIRED
)}
{isOrderExpired(latestOrder) ? 'QR Expired' : (latestOrder.status.toUpperCase() === 'COMPLETED' ? 'Order Fulfilled' : latestOrder.status.toUpperCase() === 'CANCELLED' ? 'Order Cancelled' : 'Tap to enlarge QR')}
{latestOrder.items.map((item, idx) => ( {item.quantity}x {item.productName}{idx < latestOrder.items.length - 1 ? ', ' : ''} ))}
Total Amount R{latestOrder.totalAmount.toFixed(2)}
{latestOrder.status.toUpperCase() !== 'COMPLETED' && latestOrder.status.toUpperCase() !== 'CANCELLED' && !isOrderExpired(latestOrder) && ( )}
)} {/* Previous Orders List */} {previousOrders.length > 0 && (
Previous Orders
{previousOrders.map((order) => (
setSelectedOrder(order)}>
#{order.displayOrderId} R{order.totalAmount.toFixed(0)}
{new Date(order.createdAt).toLocaleDateString()} {order.items.length} items
))}
)} )}
{/* Order Detail Modal / Overlay */} {selectedOrder && ( setSelectedOrder(null)} > e.stopPropagation()} >

Order Details

Order #{selectedOrder.displayOrderId}

{selectedOrder.status.toUpperCase() === 'COMPLETED' && (
COMPLETED
)} {selectedOrder.status.toUpperCase() === 'CANCELLED' && (
CANCELLED
)} {isOrderExpired(selectedOrder) && (
EXPIRED
)}

{isOrderExpired(selectedOrder) ? 'QR Expired' : selectedOrder.status.toUpperCase() === 'COMPLETED' ? 'This order has been fulfilled' : selectedOrder.status.toUpperCase() === 'CANCELLED' ? 'This order has been cancelled' : 'Show this QR code at the counter'}

Date {new Date(selectedOrder.createdAt).toLocaleString()}
Payment {selectedOrder.paymentMethod}
Status {selectedOrder.status}

Items Ordered

{selectedOrder.items.map((item, idx) => (
{item.quantity} x {item.productName} R{(item.price * item.quantity).toFixed(0)}
))}
Grand Total R{selectedOrder.totalAmount.toFixed(2)}
{selectedOrder.status.toUpperCase() !== 'COMPLETED' && selectedOrder.status.toUpperCase() !== 'CANCELLED' && !isOrderExpired(selectedOrder) && ( )}
)}
{/* Stock Alert Dialog */} {showStockAlert && (

Items Unavailable

Some items from your previous order are currently out of stock or unavailable.

{unavailableItems.map((item, idx) => (
{item}
))}
{pendingItems.length > 0 ? ( ) : (
All items in this order are currently unavailable.
)}
)}
); }; export default MyOrdersScreen;