'use client'; import React, { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import Sidebar from '@/app/admin/components/Sidebar/sidebar'; import { apiClient } from '@/app/utils/apiClient'; import { generateTrackingCode } from '@/app/utils/trackingCodeGenerator'; import Cookies from 'js-cookie'; import { Box, Button, Card, Container, Dialog, DialogActions, DialogContent, DialogTitle, Divider, FormControl, Grid, IconButton, InputAdornment, InputLabel, MenuItem, Paper, Select, Table, TableBody, TableCell, TableContainer, TableHead, TablePagination, TableRow, TextField, Typography, Chip, CircularProgress, Alert, Accordion, AccordionSummary, AccordionDetails, Stepper, Step, StepLabel, StepConnector, StepContent, Tooltip, Switch, FormControlLabel, Tab, Tabs } from '@mui/material'; import { Search as SearchIcon, Visibility as VisibilityIcon, FilterList as FilterListIcon, Refresh as RefreshIcon, ExpandMore as ExpandMoreIcon, LocalShipping as LocalShippingIcon, Email as EmailIcon, ContentCopy as ContentCopyIcon, CheckCircle as CheckCircleIcon, Timeline as TimelineIcon, Send as SendIcon, Add as AddIcon, ArrowForward as ArrowForwardIcon, LocationOn as LocationOnIcon, Autorenew as AutorenewIcon, Lock as LockIcon } from '@mui/icons-material'; import { styled } from '@mui/material/styles'; import '../orders.scss'; interface OrderItem { id: number; product_name: string; quantity: number; } interface TrackingUpdate { id?: number; order_id: number; status: string; location: string; description: string; carrier: string; carrier_tracking_number: string; estimated_delivery: string | null; updated_by: number; is_customer_notified: boolean; createdAt: string; updatedAt: string; } interface Order { id: number; order_number: string; user_id: number; status: string; total_amount: number; payment_method: string; payment_status: string; tracking_number?: string; estimated_delivery_date?: string; createdAt: string; updatedAt: string; user: { id: number; name: string; email: string; }; items: OrderItem[]; tracking_history?: TrackingUpdate[]; } const statusColors: Record = { order_placed: '#f59e0b', // amber-500 processing: '#3b82f6', // blue-500 packed: '#8b5cf6', // violet-500 shipped: '#10b981', // emerald-500 out_for_delivery: '#06b6d4', // cyan-500 delivered: '#10b981', // emerald-500 failed_delivery: '#ef4444', // red-500 returned: '#6b7280', // gray-500 cancelled: '#ef4444', // red-500 }; // Styled Components const StyledStepConnector = styled(StepConnector)(({ theme }) => ({ '& .MuiStepConnector-line': { minHeight: 40, borderLeftWidth: 2, }, })); const TrackingManagementPage = () => { const router = useRouter(); const [adminUser, setAdminUser] = useState(null); const [loading, setLoading] = useState(true); const [orders, setOrders] = useState([]); const [pendingOrders, setPendingOrders] = useState([]); const [selectedOrder, setSelectedOrder] = useState(null); const [openTrackingDialog, setOpenTrackingDialog] = useState(false); const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); const [totalItems, setTotalItems] = useState(0); const [searchTerm, setSearchTerm] = useState(''); const [statusFilter, setStatusFilter] = useState('all'); // New tracking update form const [newTrackingStatus, setNewTrackingStatus] = useState('processing'); const [newTrackingLocation, setNewTrackingLocation] = useState(''); const [newTrackingDescription, setNewTrackingDescription] = useState(''); const [newCarrier, setNewCarrier] = useState(''); const [newTrackingNumber, setNewTrackingNumber] = useState(''); const [newEstimatedDelivery, setNewEstimatedDelivery] = useState(''); const [notifyCustomer, setNotifyCustomer] = useState(true); const [formSubmitting, setFormSubmitting] = useState(false); const [formSuccess, setFormSuccess] = useState(false); const [formError, setFormError] = useState(null); const [trackingTabValue, setTrackingTabValue] = useState(0); const [autoGenerateTracking, setAutoGenerateTracking] = useState(true); const [trackingLocked, setTrackingLocked] = useState(false); // Validation const [errors, setErrors] = useState({ status: false, location: false, carrier: false }); // Tracking history states const [trackingHistory, setTrackingHistory] = useState([]); const [loadingHistory, setLoadingHistory] = useState(false); // Auth check and data fetch useEffect(() => { const user = localStorage.getItem('adminUser'); const token = localStorage.getItem('adminToken'); if (!user || !token) { router.push('/admin/login'); return; } const syncCookie = Cookies.get('adminTokenSync'); if (token && !syncCookie) { Cookies.set('adminTokenSync', token, { expires: 1/6, path: '/', sameSite: 'Strict' }); } setAdminUser(JSON.parse(user)); // Fetch both pending and recent orders fetchPendingOrders(); fetchOrders(); }, [router]); const fetchPendingOrders = async () => { try { setLoading(true); const response = await apiClient('/admin/orders?status=processing&limit=10'); if (response.success) { setPendingOrders(response.data.orders); } } catch (error) { console.error('Error fetching pending orders:', error); } finally { setLoading(false); } }; const fetchOrders = async () => { try { setLoading(true); const queryParams = new URLSearchParams({ page: String(page + 1), limit: String(rowsPerPage), sortBy: 'updatedAt', order: 'DESC', }); if (statusFilter !== 'all') { queryParams.append('status', statusFilter); } if (searchTerm) { queryParams.append('search', searchTerm); } const response = await apiClient(`/admin/orders?${queryParams.toString()}`); if (response.success) { setOrders(response.data.orders); setTotalItems(response.data.totalItems); } } catch (error) { console.error('Error fetching orders:', error); } finally { setLoading(false); } }; // Handle search and pagination const handleSearchSubmit = (e: React.FormEvent) => { e.preventDefault(); setPage(0); fetchOrders(); }; const handleChangePage = (event: unknown, newPage: number) => { setPage(newPage); }; const handleChangeRowsPerPage = (event: React.ChangeEvent) => { setRowsPerPage(parseInt(event.target.value, 10)); setPage(0); }; // Order and tracking management const handleOpenTrackingDialog = async (order: Order) => { setSelectedOrder(order); setOpenTrackingDialog(true); // Reset form fields to use the order's current values if (order.tracking_number) { setNewTrackingNumber(order.tracking_number); setAutoGenerateTracking(false); setTrackingLocked(true); } else { setTrackingLocked(false); if (autoGenerateTracking) { // Auto-generate unique tracking number if none exists setNewTrackingNumber(generateTrackingCode(order.id)); } } if (order.estimated_delivery_date) { setNewEstimatedDelivery(order.estimated_delivery_date.split('T')[0]); } // Fetch tracking history for this order fetchOrderTrackingHistory(order.id); }; const handleCloseTrackingDialog = () => { setOpenTrackingDialog(false); setSelectedOrder(null); setFormSuccess(false); setFormError(null); resetTrackingForm(); }; const fetchOrderTrackingHistory = async (orderId: number) => { try { setLoadingHistory(true); const response = await apiClient(`/admin/orders/${orderId}/tracking`); if (response.success) { setTrackingHistory(response.data || []); } else { setTrackingHistory([]); } } catch (error) { console.error('Error fetching tracking history:', error); setTrackingHistory([]); } finally { setLoadingHistory(false); } }; const validateForm = () => { const newErrors = { status: !newTrackingStatus, location: !newTrackingLocation, carrier: false }; // If shipping/delivery status but no carrier, mark as error if (['shipped', 'out_for_delivery', 'delivered'].includes(newTrackingStatus) && !newCarrier) { newErrors.carrier = true; } setErrors(newErrors); return !Object.values(newErrors).some(error => error); }; const handleAddTrackingUpdate = async (e: React.FormEvent) => { e.preventDefault(); if (!selectedOrder) return; if (!validateForm()) return; try { setFormSubmitting(true); setFormError(null); const isShippingUpdate = ['shipped', 'out_for_delivery', 'delivered'].includes(newTrackingStatus); const needsToLockTracking = isShippingUpdate && !trackingLocked && newTrackingNumber; const trackingData = { status: newTrackingStatus, location: newTrackingLocation, description: newTrackingDescription, carrier: newCarrier, carrierTrackingNumber: newTrackingNumber, estimatedDelivery: newEstimatedDelivery || null, notifyCustomer, lockTracking: needsToLockTracking }; const response = await apiClient(`/admin/orders/${selectedOrder.id}/tracking`, { method: 'POST', body: JSON.stringify(trackingData) }); if (response.success) { if (needsToLockTracking) { setTrackingLocked(true); } setFormSuccess(true); // Update the tracking history fetchOrderTrackingHistory(selectedOrder.id); // Refresh pending orders list fetchPendingOrders(); // Refresh all orders fetchOrders(); // Clear form resetTrackingForm(); // Automatically switch to history tab after successful update setTrackingTabValue(1); // Clear success message after 5 seconds setTimeout(() => { setFormSuccess(false); }, 5000); } else { setFormError(response.message || 'Failed to add tracking update'); } } catch (error: any) { setFormError(error.message || 'An error occurred while updating tracking information'); console.error('Error adding tracking update:', error); } finally { setFormSubmitting(false); } }; const handleResendNotification = async (trackingUpdateId: number) => { try { setLoadingHistory(true); const response = await apiClient(`/admin/tracking/${trackingUpdateId}/notify`, { method: 'POST' }); if (response.success) { // Update the tracking history to reflect the notification was sent fetchOrderTrackingHistory(selectedOrder!.id); } else { alert('Failed to resend notification'); } } catch (error) { console.error('Error resending notification:', error); alert('Error resending notification'); } finally { setLoadingHistory(false); } }; const resetTrackingForm = () => { setNewTrackingStatus('processing'); setNewTrackingLocation(''); setNewTrackingDescription(''); setNewCarrier(''); setNewTrackingNumber(''); setNewEstimatedDelivery(''); setNotifyCustomer(true); setErrors({ status: false, location: false, carrier: false }); }; const getFormattedDate = (dateString: string) => { return new Date(dateString).toLocaleString(); }; const getSuggestedMessage = (status: string) => { switch (status) { case 'processing': return 'Your order is now being processed at our facility.'; case 'packed': return 'Your order has been packed and is ready for shipping.'; case 'shipped': return 'Your order has been shipped and is on its way to you.'; case 'out_for_delivery': return 'Your order is out for delivery and will arrive today.'; case 'delivered': return 'Your order has been delivered successfully.'; case 'failed_delivery': return 'Delivery attempt was unsuccessful. We will try again soon.'; default: return ''; } }; const handleTrackingTabChange = (event: React.SyntheticEvent, newValue: number) => { setTrackingTabValue(newValue); }; const copyToClipboard = (text: string) => { navigator.clipboard.writeText(text); }; const regenerateTrackingCode = () => { if (selectedOrder && !trackingLocked) { setNewTrackingNumber(generateTrackingCode(selectedOrder.id)); } }; return (
Order Tracking Management Manage order tracking information and update shipping status Orders Awaiting Fulfillment {loading && pendingOrders.length === 0 ? ( ) : pendingOrders.length === 0 ? ( No orders awaiting fulfillment! All orders have been processed ) : ( pendingOrders.map((order) => ( Order #{order.order_number} Customer: {order.user.name} Date: {new Date(order.createdAt).toLocaleDateString()} Items: {order.items.length} ${Number(order.total_amount).toFixed(2)} )) )} All Orders
setSearchTerm(e.target.value)} fullWidth InputProps={{ startAdornment: ( ), }} />
Status fetchOrders()} color="primary">
Order # Date Customer Status Tracking Actions {loading && orders.length === 0 ? ( ) : orders.length === 0 ? ( No orders found ) : ( orders.map((order) => ( {order.order_number} {new Date(order.createdAt).toLocaleDateString()} {order.user.name} {order.user.email} {order.tracking_number ? ( } label="Tracking set" size="small" color="info" variant="outlined" /> ) : ( )} handleOpenTrackingDialog(order)} > )) )}
{selectedOrder && ( <> Update Tracking: Order #{selectedOrder.order_number} Customer: {selectedOrder.user.name} ({selectedOrder.user.email}) } iconPosition="start" label="Add Tracking Update" /> } iconPosition="start" label="Tracking History" /> {trackingTabValue === 0 && ( {formSuccess && ( Tracking information updated successfully. )} {formError && ( {formError} )}
Status setNewTrackingLocation(e.target.value)} error={errors.location} helperText={errors.location ? "Location is required" : ""} InputProps={{ startAdornment: ( ), }} /> setNewTrackingDescription(e.target.value)} placeholder="Additional details about this tracking update" /> setNewCarrier(e.target.value)} error={errors.carrier} helperText={errors.carrier ? "Required for shipped status" : ""} /> !trackingLocked && setNewTrackingNumber(e.target.value)} placeholder="Carrier tracking number" disabled={trackingLocked} helperText={trackingLocked ? "Tracking number is permanent and cannot be changed" : ""} InputProps={{ endAdornment: ( {trackingLocked ? ( ) : ( )} ), }} /> setNewEstimatedDelivery(e.target.value)} InputLabelProps={{ shrink: true, }} /> setNotifyCustomer(e.target.checked)} color="primary" /> } label="Notify customer via email" /> { if (!trackingLocked) { setAutoGenerateTracking(e.target.checked); if (e.target.checked && selectedOrder) { setNewTrackingNumber(generateTrackingCode(selectedOrder.id)); } } }} color="primary" disabled={trackingLocked} /> } label="Auto-generate tracking code" />
)} {trackingTabValue === 1 && ( {loadingHistory ? ( ) : trackingHistory.length === 0 ? ( No tracking updates available for this order. ) : ( }> {trackingHistory.map((update, index) => ( {update.status.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1) ).join(' ')} {getFormattedDate(update.createdAt)} {update.location} {update.description && ( {update.description} )} {(update.carrier || update.carrier_tracking_number) && ( {update.carrier && ( Carrier: {update.carrier} )} {update.carrier_tracking_number && ( Tracking #: {update.carrier_tracking_number} copyToClipboard(update.carrier_tracking_number)} sx={{ ml: 1 }} > )} {update.estimated_delivery && ( Est. Delivery: {new Date(update.estimated_delivery).toLocaleDateString()} )} )} : undefined} label={update.is_customer_notified ? "Customer notified" : "Not notified"} variant="outlined" size="small" color={update.is_customer_notified ? "success" : "default"} /> {!update.is_customer_notified && ( )} ))} )} )}
)}
); }; export default TrackingManagementPage;