Repeat orders added
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user