Repeat orders added

This commit is contained in:
Sidharth Prabhu
2026-04-17 11:19:18 +05:30
parent 2d24b4d71b
commit acfe9598cb
8 changed files with 686 additions and 83 deletions

View File

@@ -1,9 +1,14 @@
import React, { useState, useEffect } from 'react';
import { Clock, ShoppingBag as ShoppingBagIcon, ChevronRight as ChevronRightIcon, X } from 'lucide-react';
import { useNavigate } from 'react-router-dom';
import { Clock, ShoppingBag as ShoppingBagIcon, ChevronRight as ChevronRightIcon, X, RefreshCcw, AlertCircle, ShoppingCart } 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 {
@@ -23,10 +28,19 @@ interface Order {
}
const MyOrdersScreen: React.FC = () => {
const navigate = useNavigate();
const { user } = useAuth();
const { foodItems } = useFood();
const { addToCart, clearCart } = useCart();
const [orders, setOrders] = useState<Order[]>([]);
const [loading, setLoading] = useState(true);
const [selectedOrder, setSelectedOrder] = useState<Order | null>(null);
// State for stock alert
const [showStockAlert, setShowStockAlert] = useState(false);
const [unavailableItems, setUnavailableItems] = useState<string[]>([]);
const [pendingItems, setPendingItems] = useState<FoodItem[]>([]);
useEffect(() => {
if (user?.id) {
@@ -46,6 +60,50 @@ const MyOrdersScreen: React.FC = () => {
}
};
const handleRepeatOrder = (order: Order) => {
const missing: string[] = [];
const available: FoodItem[] = [];
order.items.forEach(orderItem => {
// Find the current food item by name (since orders store names)
// Robustness: matching by name is fallback if IDs aren't directly available in OrderItem DTO
const currentItem = foodItems.find(fi => fi.name === orderItem.productName);
if (currentItem) {
if (currentItem.stock && currentItem.stock > 0) {
// Add multiple times based on original quantity
for (let i = 0; i < orderItem.quantity; i++) {
available.push(currentItem);
}
} else {
missing.push(orderItem.productName);
}
} else {
missing.push(orderItem.productName); // Item no longer exists in catalog
}
});
if (missing.length > 0) {
setUnavailableItems(missing);
setPendingItems(available);
setShowStockAlert(true);
} else {
// All items available
performRepeat(available);
}
};
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) : [];
@@ -108,6 +166,16 @@ const MyOrdersScreen: React.FC = () => {
<span>Total Amount</span>
<span className="amount-text">{latestOrder.totalAmount.toFixed(2)}</span>
</div>
<button
className="repeat-order-btn mini"
onClick={(e) => {
e.stopPropagation();
handleRepeatOrder(latestOrder);
}}
>
<RefreshCcw size={16} /> Repeat Order
</button>
</div>
</section>
)}
@@ -143,74 +211,141 @@ const MyOrdersScreen: React.FC = () => {
</main>
{/* Order Detail Modal / Overlay */}
{selectedOrder && (
<div className="order-detail-overlay" onClick={() => setSelectedOrder(null)}>
<div className="order-detail-modal" onClick={(e) => e.stopPropagation()}>
<button className="close-details" onClick={() => setSelectedOrder(null)}>
<X size={24} />
</button>
<div className="modal-header">
<h3>Order Details</h3>
<p className="modal-order-id">Order #{selectedOrder.displayOrderId}</p>
</div>
<div className={`modal-qr-section ${selectedOrder.status.toUpperCase() === 'COMPLETED' ? 'qr-completed' : ''} ${selectedOrder.archived ? 'qr-expired' : ''}`}>
<div className="qr-container">
<QRCodeCanvas value={selectedOrder.orderNumber} size={200} includeMargin={true} />
{selectedOrder.status.toUpperCase() === 'COMPLETED' && (
<div className="qr-overlay">
<span className="overlay-text">COMPLETED</span>
</div>
)}
{selectedOrder.archived && (
<div className="qr-overlay">
<span className="overlay-text expired">EXPIRED</span>
</div>
)}
<AnimatePresence>
{selectedOrder && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="order-detail-overlay"
onClick={() => setSelectedOrder(null)}
>
<motion.div
initial={{ y: '100%' }}
animate={{ y: 0 }}
exit={{ y: '100%' }}
transition={{ type: 'spring', damping: 25, stiffness: 200 }}
className="order-detail-modal"
onClick={(e) => e.stopPropagation()}
>
<button className="close-details" onClick={() => setSelectedOrder(null)}>
<X size={24} />
</button>
<div className="modal-header">
<h3>Order Details</h3>
<p className="modal-order-id">Order #{selectedOrder.displayOrderId}</p>
</div>
<p className="modal-qr-hint">
{selectedOrder.archived
? 'This order has expired'
: selectedOrder.status.toUpperCase() === 'COMPLETED'
? 'This order has been fulfilled'
: 'Show this QR code at the counter'}
</p>
</div>
<div className="modal-info-list">
<div className="info-item">
<span className="label">Date</span>
<span className="value">{new Date(selectedOrder.createdAt).toLocaleString()}</span>
</div>
<div className="info-item">
<span className="label">Payment</span>
<span className="value">{selectedOrder.paymentMethod}</span>
</div>
<div className="info-item">
<span className="label">Status</span>
<span className={`value status-badge ${selectedOrder.status.toLowerCase()}`}>
{selectedOrder.status}
</span>
</div>
</div>
<div className="modal-items-section">
<h4>Items Ordered</h4>
{selectedOrder.items.map((item, idx) => (
<div key={idx} className="modal-item-row">
<span>{item.quantity} x {item.productName}</span>
<span>{(item.price * item.quantity).toFixed(0)}</span>
<div className={`modal-qr-section ${selectedOrder.status.toUpperCase() === 'COMPLETED' ? 'qr-completed' : ''} ${selectedOrder.archived ? 'qr-expired' : ''}`}>
<div className="qr-container">
<QRCodeCanvas value={selectedOrder.orderNumber} size={200} includeMargin={true} />
{selectedOrder.status.toUpperCase() === 'COMPLETED' && (
<div className="qr-overlay">
<span className="overlay-text">COMPLETED</span>
</div>
)}
{selectedOrder.archived && (
<div className="qr-overlay">
<span className="overlay-text expired">EXPIRED</span>
</div>
)}
</div>
))}
<div className="modal-total-row">
<span>Grand Total</span>
<span>{selectedOrder.totalAmount.toFixed(2)}</span>
<p className="modal-qr-hint">
{selectedOrder.archived
? 'This order has expired'
: selectedOrder.status.toUpperCase() === 'COMPLETED'
? 'This order has been fulfilled'
: 'Show this QR code at the counter'}
</p>
</div>
</div>
</div>
</div>
)}
<div className="modal-info-list">
<div className="info-item">
<span className="label">Date</span>
<span className="value">{new Date(selectedOrder.createdAt).toLocaleString()}</span>
</div>
<div className="info-item">
<span className="label">Payment</span>
<span className="value">{selectedOrder.paymentMethod}</span>
</div>
<div className="info-item">
<span className="label">Status</span>
<span className={`value status-badge ${selectedOrder.status.toLowerCase()}`}>
{selectedOrder.status}
</span>
</div>
</div>
<div className="modal-items-section">
<h4>Items Ordered</h4>
{selectedOrder.items.map((item, idx) => (
<div key={idx} className="modal-item-row">
<span>{item.quantity} x {item.productName}</span>
<span>{(item.price * item.quantity).toFixed(0)}</span>
</div>
))}
<div className="modal-total-row">
<span>Grand Total</span>
<span>{selectedOrder.totalAmount.toFixed(2)}</span>
</div>
</div>
<button className="repeat-order-btn" onClick={() => handleRepeatOrder(selectedOrder)}>
<RefreshCcw size={18} /> Repeat this order
</button>
</motion.div>
</motion.div>
)}
</AnimatePresence>
{/* Stock Alert Dialog */}
<AnimatePresence>
{showStockAlert && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
className="stock-alert-overlay"
>
<motion.div
initial={{ scale: 0.9, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.9, opacity: 0 }}
className="stock-alert-modal"
>
<div className="alert-icon-wrapper">
<AlertCircle size={32} />
</div>
<h3>Items Unavailable</h3>
<p>Some items from your previous order are currently out of stock or unavailable.</p>
<div className="unavailable-items-list">
{unavailableItems.map((item, idx) => (
<div key={idx} className="unavailable-item-chip">
<X size={14} /> {item}
</div>
))}
</div>
<div className="alert-actions">
{pendingItems.length > 0 ? (
<button className="btn-proceed" onClick={handleProceedPartial}>
Proceed without these items
</button>
) : (
<div style={{ marginBottom: '12px', fontSize: '13px', color: '#e11d48', fontWeight: '600' }}>
All items in this order are currently unavailable.
</div>
)}
<button className="btn-discard" onClick={() => setShowStockAlert(false)}>
Discard
</button>
</div>
</motion.div>
</motion.div>
)}
</AnimatePresence>
<BottomNav />
</div>