import { useState, useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import { RootState } from '../store'; import { updateOrder, deleteOrder, addOrder, Order, setOrders } from '../store/slices/orderSlice'; import { addProduct } from '../store/slices/productSlice'; import { calculateOrderPrintingCost, calculateOrderProfit, enrichOrderItemsWithCosts } from '../utils/orderCalculations'; import { formatAustralianDate } from '../utils/dateFormatter'; import { Search, Package, Download, ArrowRight, Edit, Trash2, X, Plus } from 'lucide-react'; import toast from 'react-hot-toast'; const Orders = () => { const dispatch = useDispatch(); const { orders } = useSelector((state: RootState) => state.orders); const { products } = useSelector((state: RootState) => state.products); // Debug: Monitor order changes useEffect(() => { // Clean up orders with invalid IDs on component mount const cleanupInvalidOrders = () => { const validOrders = orders?.filter(order => order._id && order._id !== 'undefined') || []; if (validOrders.length !== orders?.length) { console.log(`Removing ${(orders?.length || 0) - validOrders.length} orders with invalid IDs`); dispatch(setOrders(validOrders)); toast.success('Cleaned up orders with invalid IDs'); } }; if (orders && orders.length > 0) { cleanupInvalidOrders(); } }, []); // Only run on mount const [searchTerm, setSearchTerm] = useState(''); const [dateRange, setDateRange] = useState('all'); // Edit modal state const [editingOrder, setEditingOrder] = useState(null); const [showEditModal, setShowEditModal] = useState(false); // Delete confirmation state const [deletingOrderId, setDeletingOrderId] = useState(null); const [showDeleteConfirm, setShowDeleteConfirm] = useState(false); // Add manual order state const [showAddModal, setShowAddModal] = useState(false); const [showNewProductModal, setShowNewProductModal] = useState(false); const [newProductData, setNewProductData] = useState({ title: '', price: 0, printingCost: 0, costOfGoods: 0 }); const [newOrder, setNewOrder] = useState({ orderNumber: '', customer: { name: '', email: '' }, dateOrdered: new Date().toISOString().split('T')[0], total: 0, items: [{ title: '', quantity: 1, price: 0 }] }); // Helper functions to use current product costs const getUpdatedPrintingCost = (order: Order) => { const updatedItems = enrichOrderItemsWithCosts(order.items || [], products || []); return calculateOrderPrintingCost(updatedItems); }; const getUpdatedProfit = (order: Order) => { const updatedItems = enrichOrderItemsWithCosts(order.items || [], products || []); return calculateOrderProfit(updatedItems, order.total || 0); }; // Sort orders by newest first, then apply filters const sortedOrders = [...orders].sort((a, b) => new Date(b.dateOrdered).getTime() - new Date(a.dateOrdered).getTime() ); const filteredOrders = sortedOrders.filter(order => { const matchesSearch = order.orderNumber.toLowerCase().includes(searchTerm.toLowerCase()) || order.customer?.name?.toLowerCase().includes(searchTerm.toLowerCase()) || order.customer?.email?.toLowerCase().includes(searchTerm.toLowerCase()); let matchesDate = true; if (dateRange !== 'all') { const orderDate = new Date(order.dateOrdered); const now = new Date(); // Debug logging console.log('Filtering order:', { orderNumber: order.orderNumber, dateOrdered: order.dateOrdered, parsedDate: orderDate, now: now, dateRange: dateRange }); const timeDiff = now.getTime() - orderDate.getTime(); const daysDiff = timeDiff / (24 * 60 * 60 * 1000); switch (dateRange) { case 'week': matchesDate = daysDiff >= 0 && daysDiff <= 7; break; case 'month': matchesDate = daysDiff >= 0 && daysDiff <= 30; break; case 'quarter': matchesDate = daysDiff >= 0 && daysDiff <= 90; break; case 'year': matchesDate = daysDiff >= 0 && daysDiff <= 365; break; } console.log('Date filter result:', { daysDiff, matchesDate }); } return matchesSearch && matchesDate; }); // Financial calculations based on filtered results const totalPrintingCosts = filteredOrders.reduce((sum, order) => sum + getUpdatedPrintingCost(order), 0); const handleExportCSV = () => { const headers = [ 'Order Number', 'Customer Name', 'Date', 'Revenue', 'Printing Cost', 'Profit', 'Items' ]; const csvData = filteredOrders.map(order => { const printingCost = getUpdatedPrintingCost(order); const profit = getUpdatedProfit(order); return [ order.orderNumber, order.customer?.name || '', formatAustralianDate(order.dateOrdered), `$${order.total.toFixed(2)}`, `$${printingCost.toFixed(2)}`, `$${profit.toFixed(2)}`, order.items?.map(item => `${item.title} (${item.quantity})`).join('; ') || '' ]; }); const csv = [headers, ...csvData].map((row: string[]) => row.map((field: string) => `"${field}"`).join(',') ).join('\n'); const blob = new Blob([csv], { type: 'text/csv' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `etsy-orders-${new Date().toISOString().split('T')[0]}.csv`; a.click(); window.URL.revokeObjectURL(url); toast.success('Financial data exported successfully'); }; // Handler functions for CRUD operations const handleEditOrder = (order: Order) => { setEditingOrder({ ...order }); setShowEditModal(true); }; const handleSaveOrder = (updatedOrder: Order) => { dispatch(updateOrder(updatedOrder)); setShowEditModal(false); setEditingOrder(null); toast.success('Order updated successfully'); }; const handleDeleteClick = (orderId: string) => { setDeletingOrderId(orderId); setShowDeleteConfirm(true); }; const handleConfirmDelete = () => { if (deletingOrderId && deletingOrderId !== 'undefined') { dispatch(deleteOrder(deletingOrderId)); setShowDeleteConfirm(false); setDeletingOrderId(null); toast.success('Order deleted successfully'); } else { toast.error('Cannot delete order: Invalid order ID'); setShowDeleteConfirm(false); setDeletingOrderId(null); } }; const handleCancelDelete = () => { setShowDeleteConfirm(false); setDeletingOrderId(null); }; // New product creation handlers const handleCreateNewProduct = (productTitle: string) => { setNewProductData({ title: productTitle, price: 0, printingCost: 0, costOfGoods: 0 }); setShowNewProductModal(true); }; const handleSaveNewProduct = () => { if (!newProductData.title) { toast.error('Please enter a product title'); return; } const newProduct = { _id: `product-${Date.now()}`, title: newProductData.title, description: '', price: newProductData.price, costOfGoods: newProductData.costOfGoods, printingCost: newProductData.printingCost, sku: '', category: '', tags: [], inventory: { quantity: 0, lowStockAlert: 10 }, isActive: true }; dispatch(addProduct(newProduct)); setShowNewProductModal(false); toast.success('Product created successfully'); }; // Manual order handlers const handleAddOrder = () => { setNewOrder({ orderNumber: '', customer: { name: '', email: '' }, dateOrdered: new Date().toISOString().split('T')[0], total: 0, items: [{ title: '', quantity: 1, price: 0 }] }); setShowAddModal(true); }; const handleSaveNewOrder = () => { if (!newOrder.orderNumber || !newOrder.items[0]?.title) { toast.error('Please fill in order number and at least one item'); return; } const calculatedTotal = newOrder.items.reduce((sum, item) => sum + (item.quantity * item.price), 0); // Enrich items with printing costs from product database const enrichedItems = newOrder.items .filter(item => item.title.trim() !== '') .map(item => { const matchingProduct = products?.find(p => p.title.toLowerCase() === item.title.toLowerCase()); return { ...item, printingCost: matchingProduct?.printingCost || 0, costOfGoods: matchingProduct?.costOfGoods || 0 }; }); const orderToSave: Order = { _id: `fb-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, // More unique ID orderNumber: `FB-${newOrder.orderNumber}`, // Prefix to identify Facebook orders customer: newOrder.customer, dateOrdered: newOrder.dateOrdered, total: calculatedTotal, status: 'delivered' as const, items: enrichedItems }; dispatch(addOrder(orderToSave)); setShowAddModal(false); toast.success('Facebook Marketplace order added successfully'); }; const handleAddItem = () => { setNewOrder(prev => ({ ...prev, items: [...prev.items, { title: '', quantity: 1, price: 0 }] })); }; const handleRemoveItem = (index: number) => { if (newOrder.items.length > 1) { setNewOrder(prev => ({ ...prev, items: prev.items.filter((_, i) => i !== index) })); } }; const updateNewOrderItem = (index: number, field: string, value: any) => { setNewOrder(prev => ({ ...prev, items: prev.items.map((item, i) => i === index ? { ...item, [field]: value } : item ) })); }; return (

Financial Overview

Track revenue, costs, and profit from your Etsy orders

Import Data
{/* Financial Summary Cards */}

Total Printing Costs

${totalPrintingCosts.toFixed(2)}

{/* Import Instructions */}

💡 Need to import order data?

Use the Data Import page to upload: • Etsy CSV statements • Australia Post shipping data • PDF packing slips

{/* Search and Filters */}
setSearchTerm(e.target.value)} className="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" />
{/* Financial Data Table */}
{filteredOrders.length === 0 ? (

No orders found

Import your sales data using the Data Import page.

) : (
{filteredOrders.map((order) => { const printingCost = getUpdatedPrintingCost(order); const profit = getUpdatedProfit(order); const margin = order.total > 0 ? ((profit / order.total) * 100) : 0; // Use order number as fallback key if _id is undefined const orderKey = order._id || `fallback-${order.orderNumber}`; return ( ); })}
Order Details (↓ newest first)
Customer Items Revenue Printing Cost Profit Margin % Actions
#{order.orderNumber}
{formatAustralianDate(order.dateOrdered)}
{order.customer?.name || 'Unknown'}
{order.items && order.items.length > 0 ? ( order.items.map((item, index) => (
{item.title} × {item.quantity}
)) ) : ( No items listed )}
${order.total.toFixed(2)} ${printingCost.toFixed(2)} = 0 ? 'text-green-600' : 'text-red-600'}> ${profit.toFixed(2)} = 20 ? 'text-green-600' : margin >= 10 ? 'text-yellow-600' : 'text-red-600'}`}> {margin.toFixed(1)}%
)}
{/* Edit Order Modal */} {showEditModal && editingOrder && (

Edit Order

setEditingOrder({...editingOrder, orderNumber: e.target.value})} className="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2 text-sm" />
setEditingOrder({ ...editingOrder, customer: { ...editingOrder.customer!, name: e.target.value } })} className="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2 text-sm" />
setEditingOrder({...editingOrder, total: parseFloat(e.target.value) || 0})} className="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2 text-sm" />
setEditingOrder({...editingOrder, dateOrdered: new Date(e.target.value).toISOString()})} className="mt-1 block w-full border border-gray-300 rounded-md px-3 py-2 text-sm" />
)} {/* Delete Confirmation Modal */} {showDeleteConfirm && (

Delete Order

Are you sure you want to delete this order? This action cannot be undone.

)} {/* Manual Order Creation Modal */} {showAddModal && (

Add Facebook Marketplace Order

{/* Order Details */}
setNewOrder({...newOrder, orderNumber: e.target.value})} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" placeholder="FB-12345" />
setNewOrder({...newOrder, dateOrdered: e.target.value})} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" />
setNewOrder({...newOrder, customer: {...newOrder.customer, name: e.target.value}})} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" placeholder="Customer Name" />
setNewOrder({...newOrder, customer: {...newOrder.customer, email: e.target.value}})} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" placeholder="customer@email.com" />
{/* Order Items */}

Order Items

{newOrder.items.map((item, index) => { const matchingProduct = products?.find(p => p.title.toLowerCase() === item.title.toLowerCase()); return (
updateNewOrderItem(index, 'quantity', parseInt(e.target.value) || 1)} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 text-center" min="1" />
updateNewOrderItem(index, 'price', parseFloat(e.target.value) || 0)} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" placeholder="0.00" />
{newOrder.items.length > 1 && ( )}
{/* Show printing cost info if product is selected */} {matchingProduct && (
Printing Cost: ${matchingProduct.printingCost?.toFixed(2) || '0.00'} Profit per item: ${(item.price - (matchingProduct.printingCost || 0)).toFixed(2)}
)}
); })}
{/* Total Display with Printing Cost Breakdown */}
Order Total (Revenue): ${newOrder.items.reduce((sum, item) => sum + (item.quantity * item.price), 0).toFixed(2)}
Total Printing Cost: ${newOrder.items.reduce((sum, item) => { const product = products?.find(p => p.title.toLowerCase() === item.title.toLowerCase()); return sum + (item.quantity * (product?.printingCost || 0)); }, 0).toFixed(2)}
Estimated Profit: ${(newOrder.items.reduce((sum, item) => sum + (item.quantity * item.price), 0) - newOrder.items.reduce((sum, item) => { const product = products?.find(p => p.title.toLowerCase() === item.title.toLowerCase()); return sum + (item.quantity * (product?.printingCost || 0)); }, 0)).toFixed(2)}
{/* Action Buttons */}
)} {/* New Product Creation Modal */} {showNewProductModal && (

Create New Product

setNewProductData({...newProductData, title: e.target.value})} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" placeholder="Enter product name" />
setNewProductData({...newProductData, price: parseFloat(e.target.value) || 0})} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" placeholder="0.00" />
setNewProductData({...newProductData, printingCost: parseFloat(e.target.value) || 0})} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" placeholder="0.00" />
setNewProductData({...newProductData, costOfGoods: parseFloat(e.target.value) || 0})} className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500" placeholder="0.00" />
)}
); }; export default Orders;