From 04069ef9542903d62a6e4bb059e4fe449494cdcf Mon Sep 17 00:00:00 2001 From: dlawler489 <104159223@student.swin.edu.au> Date: Wed, 6 May 2026 06:34:25 +1000 Subject: [PATCH] fix: resolve revenue trend chart and top profitable products issues - Fix revenue trend chart to display month-year format for multi-year data - Implement proper date sorting (newest first) for monthly revenue data - Enhance chart with gradients, hover tooltips, and better visual design - Fix top profitable products to use actual sales data instead of catalog data - Calculate profits based on real order performance within date range - Add comprehensive debugging and empty state handling - Change default date range to 'All Time' for better initial experience - Improve month-year parsing and data validation --- client/src/pages/Analytics.tsx | 208 ++++++++++++++++++++++++++------- 1 file changed, 164 insertions(+), 44 deletions(-) diff --git a/client/src/pages/Analytics.tsx b/client/src/pages/Analytics.tsx index b4099ef..f029786 100644 --- a/client/src/pages/Analytics.tsx +++ b/client/src/pages/Analytics.tsx @@ -11,9 +11,20 @@ const Analytics = () => { const { customers } = useSelector((state: RootState) => state.customers); const { expenses } = useSelector((state: RootState) => state.expenses); + // Debug: Log the raw data + console.log('=== RAW DATA DEBUG ==='); + console.log('Raw orders:', orders?.length || 0); + console.log('Raw products:', products?.length || 0); + console.log('Raw customers:', customers?.length || 0); + console.log('Raw expenses:', expenses?.length || 0); + if (orders && orders.length > 0) { + console.log('Sample order:', orders[0]); + } + console.log('====================='); + const [dateRange, setDateRange] = useState(() => { - // Default to current year - return new Date().getFullYear().toString(); + // Default to "All Time" to show all data initially + return 'all'; }); const [customStartDate, setCustomStartDate] = useState(''); const [customEndDate, setCustomEndDate] = useState(''); @@ -307,22 +318,48 @@ const Analytics = () => { // Create monthly revenue data from actual orders const monthlyData = useMemo(() => { - if (!filteredOrders.length) return []; + if (!filteredOrders.length) { + console.log('No filtered orders found for monthly data'); + return []; + } const monthlyMap = new Map(); const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; - // Initialize all months with 0 - months.forEach(month => { - monthlyMap.set(month, { month, revenue: 0, expenses: 0, profit: 0 }); + console.log('Building monthly data from', filteredOrders.length, 'orders'); + + // First pass: identify all months that have orders + const monthsWithData = new Set(); + filteredOrders.forEach(order => { + if (order.dateOrdered) { + const date = new Date(order.dateOrdered); + const year = date.getFullYear(); + const monthIndex = date.getMonth(); + const monthKey = `${months[monthIndex]} ${year}`; + monthsWithData.add(monthKey); + } }); + // Initialize months that have data + Array.from(monthsWithData).forEach(monthKey => { + monthlyMap.set(monthKey, { + month: monthKey, + revenue: 0, + expenses: 0, + profit: 0 + }); + }); + + console.log('Months with data:', Array.from(monthsWithData)); + // Aggregate orders by month filteredOrders.forEach(order => { if (order.dateOrdered) { const date = new Date(order.dateOrdered); - const monthName = months[date.getMonth()]; - const current = monthlyMap.get(monthName); + const year = date.getFullYear(); + const monthIndex = date.getMonth(); + const monthKey = `${months[monthIndex]} ${year}`; + const current = monthlyMap.get(monthKey); if (current) { current.revenue += order.total || 0; } @@ -333,8 +370,10 @@ const Analytics = () => { filteredExpenses.forEach(expense => { if (expense.date) { const date = new Date(expense.date); - const monthName = months[date.getMonth()]; - const current = monthlyMap.get(monthName); + const year = date.getFullYear(); + const monthIndex = date.getMonth(); + const monthKey = `${months[monthIndex]} ${year}`; + const current = monthlyMap.get(monthKey); if (current) { // For monthly chart, exclude transaction fees to match profit calculation @@ -356,7 +395,21 @@ const Analytics = () => { data.profit = data.revenue - data.expenses; }); - return Array.from(monthlyMap.values()).filter(data => data.revenue > 0 || data.expenses > 0); + // Sort by date (newest first) and return + const result = Array.from(monthlyMap.values()) + .sort((a, b) => { + // Parse month and year from "Jan 2026" format + const parseMonthYear = (monthStr: string) => { + const [month, year] = monthStr.split(' '); + const monthIndex = months.indexOf(month); + return new Date(parseInt(year), monthIndex); + }; + + return parseMonthYear(b.month).getTime() - parseMonthYear(a.month).getTime(); + }); + + console.log('Final monthly data:', result); + return result; }, [filteredOrders, filteredExpenses]); // Create expense categories data from actual expenses @@ -397,14 +450,46 @@ const Analytics = () => { return categoryData; }, [filteredExpenses, totalExpenses, totalRevenue, totalPrintingCosts, netProfit]); - const topProducts = (products || []) - .filter(product => product && typeof product.price === 'number' && typeof product.costOfGoods === 'number') - .sort((a, b) => { - const aTotalCosts = a.costOfGoods + (a.printingCost || 0); - const bTotalCosts = b.costOfGoods + (b.printingCost || 0); - return (b.price - bTotalCosts) - (a.price - aTotalCosts); - }) - .slice(0, 5); + // Calculate top profitable products based on actual sales performance + const topProducts = useMemo(() => { + // Create a map to track sales and profits for each product + const productSalesMap = new Map(); + + filteredOrders.forEach(order => { + if (order.items && Array.isArray(order.items)) { + order.items.forEach(item => { + const key = item.title || 'Unknown Product'; + const current = productSalesMap.get(key) || { + title: item.title, + totalRevenue: 0, + totalQuantity: 0, + totalPrintingCost: 0, + totalCostOfGoods: 0, + totalProfit: 0 + }; + + const itemRevenue = (item.price || 0) * (item.quantity || 1); + const itemPrintingCost = (item.printingCost || 0) * (item.quantity || 1); + const itemCostOfGoods = (item.costOfGoods || 0) * (item.quantity || 1); + const itemProfit = itemRevenue - itemPrintingCost - itemCostOfGoods; + + current.totalRevenue += itemRevenue; + current.totalQuantity += (item.quantity || 1); + current.totalPrintingCost += itemPrintingCost; + current.totalCostOfGoods += itemCostOfGoods; + current.totalProfit += itemProfit; + + productSalesMap.set(key, current); + }); + } + }); + + // Convert to array and sort by total profit + return Array.from(productSalesMap.values()) + .filter(product => product.totalRevenue > 0) + .sort((a, b) => b.totalProfit - a.totalProfit) + .slice(0, 5); + }, [filteredOrders]); const recentOrders = filteredOrders .filter(order => order && order.dateOrdered) @@ -612,31 +697,66 @@ const Analytics = () => { {/* Revenue Chart */}
No revenue data for selected period
+Import orders to see revenue trends
{product?.title || 'Unknown Product'}
-SKU: {product?.sku || 'N/A'}
+{product.title || 'Unknown Product'}
+Sold: {product.totalQuantity} units
- ${((product?.price || 0) - (product?.costOfGoods || 0) - (product?.printingCost || 0)).toFixed(2)} + ${product.totalProfit.toFixed(2)}
-profit per item
+total profit