fixed product section of the reports page
This commit is contained in:
parent
3d4d876283
commit
b88fb93637
1 changed files with 116 additions and 44 deletions
|
|
@ -235,6 +235,11 @@ const ReportsPage = () => {
|
||||||
.slice(0, 8); // Top 8 locations
|
.slice(0, 8); // Top 8 locations
|
||||||
}, [filteredOrders]);
|
}, [filteredOrders]);
|
||||||
|
|
||||||
|
// Debug - log filtered orders
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("Filtered orders:", filteredOrders);
|
||||||
|
}, [filteredOrders]);
|
||||||
|
|
||||||
// Prepare time series data for revenue chart
|
// Prepare time series data for revenue chart
|
||||||
const revenueTimeSeriesData = useMemo(() => {
|
const revenueTimeSeriesData = useMemo(() => {
|
||||||
if (!orders) return [];
|
if (!orders) return [];
|
||||||
|
|
@ -293,9 +298,41 @@ const ReportsPage = () => {
|
||||||
return Array.from(dataMap.values());
|
return Array.from(dataMap.values());
|
||||||
}, [filteredOrders, timelineType, fromDate, toDate]);
|
}, [filteredOrders, timelineType, fromDate, toDate]);
|
||||||
|
|
||||||
|
// Fetch order details for product analysis
|
||||||
|
const { data: orderDetails, isLoading: orderDetailsLoading } = useQuery({
|
||||||
|
queryKey: ['admin-order-details', filteredOrders.map(order => order.id)],
|
||||||
|
queryFn: async () => {
|
||||||
|
// Only fetch if we have filtered orders
|
||||||
|
if (!filteredOrders.length) return [];
|
||||||
|
|
||||||
|
// Fetch details for each order in parallel
|
||||||
|
const detailPromises = filteredOrders.map(order =>
|
||||||
|
apiClient.get(`/admin/orders/${order.id}`)
|
||||||
|
.then(response => response.data)
|
||||||
|
.catch(error => {
|
||||||
|
console.error(`Error fetching details for order ${order.id}:`, error);
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const results = await Promise.all(detailPromises);
|
||||||
|
return results.filter(Boolean); // Remove any failed requests
|
||||||
|
},
|
||||||
|
enabled: filteredOrders.length > 0
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Debug - log order details
|
||||||
|
useEffect(() => {
|
||||||
|
if (orderDetails) {
|
||||||
|
console.log("Order details:", orderDetails);
|
||||||
|
}
|
||||||
|
}, [orderDetails]);
|
||||||
|
|
||||||
|
|
||||||
// Prepare product sales data
|
// Prepare product sales data
|
||||||
const productSalesData = useMemo(() => {
|
const productSalesData = useMemo(() => {
|
||||||
if (!orders || !products) return [];
|
if (!products || !orderDetails) return [];
|
||||||
|
|
||||||
const productSales = {};
|
const productSales = {};
|
||||||
|
|
||||||
|
|
@ -310,13 +347,23 @@ const ReportsPage = () => {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Aggregate sales data from orders
|
// Aggregate sales data from order details
|
||||||
filteredOrders.forEach(order => {
|
orderDetails.forEach(order => {
|
||||||
if (order.items && Array.isArray(order.items)) {
|
if (order.items && Array.isArray(order.items)) {
|
||||||
order.items.forEach(item => {
|
order.items.forEach(item => {
|
||||||
if (productSales[item.product_id]) {
|
if (productSales[item.product_id]) {
|
||||||
productSales[item.product_id].quantity += item.quantity;
|
productSales[item.product_id].quantity += parseInt(item.quantity) || 0;
|
||||||
productSales[item.product_id].revenue += parseFloat(item.price_at_purchase) * item.quantity;
|
productSales[item.product_id].revenue += parseFloat(item.price_at_purchase || 0) * (parseInt(item.quantity) || 0);
|
||||||
|
} else {
|
||||||
|
// Handle case where product might exist in orders but not in current products list
|
||||||
|
console.log(`Product ${item.product_id} found in orders but not in products list`);
|
||||||
|
productSales[item.product_id] = {
|
||||||
|
id: item.product_id,
|
||||||
|
name: item.product_name || `Product ${item.product_id.substring(0, 8)}...`,
|
||||||
|
category: item.product_category || 'Unknown',
|
||||||
|
quantity: parseInt(item.quantity) || 0,
|
||||||
|
revenue: parseFloat(item.price_at_purchase || 0) * (parseInt(item.quantity) || 0)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -326,7 +373,7 @@ const ReportsPage = () => {
|
||||||
return Object.values(productSales)
|
return Object.values(productSales)
|
||||||
.filter(product => product.quantity > 0)
|
.filter(product => product.quantity > 0)
|
||||||
.sort((a, b) => b.quantity - a.quantity);
|
.sort((a, b) => b.quantity - a.quantity);
|
||||||
}, [filteredOrders, products]);
|
}, [products, orderDetails]);
|
||||||
|
|
||||||
// Top selling products for the chart
|
// Top selling products for the chart
|
||||||
const topSellingProducts = useMemo(() => {
|
const topSellingProducts = useMemo(() => {
|
||||||
|
|
@ -358,7 +405,7 @@ const ReportsPage = () => {
|
||||||
}, [productSalesData]);
|
}, [productSalesData]);
|
||||||
|
|
||||||
// Loading state
|
// Loading state
|
||||||
const isLoading = ordersLoading || productsLoading;
|
const isLoading = ordersLoading || productsLoading || orderDetailsLoading;
|
||||||
|
|
||||||
// Handle export reports
|
// Handle export reports
|
||||||
const handleExportCSV = () => {
|
const handleExportCSV = () => {
|
||||||
|
|
@ -629,24 +676,32 @@ const ReportsPage = () => {
|
||||||
<Typography variant="h6" gutterBottom>
|
<Typography variant="h6" gutterBottom>
|
||||||
Top Selling Products
|
Top Selling Products
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ height: 400 }}>
|
{topSellingProducts.length > 0 ? (
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<Box sx={{ height: 400 }}>
|
||||||
<BarChart
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
data={topSellingProducts}
|
<BarChart
|
||||||
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
data={topSellingProducts}
|
||||||
>
|
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
>
|
||||||
<XAxis dataKey="name" />
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
<YAxis />
|
<XAxis dataKey="name" />
|
||||||
<Tooltip formatter={(value, name) => {
|
<YAxis />
|
||||||
return name === 'revenue' ? `$${value.toFixed(2)}` : value;
|
<Tooltip formatter={(value, name) => {
|
||||||
}} />
|
return name === 'revenue' ? `${value.toFixed(2)}` : value;
|
||||||
<Legend />
|
}} />
|
||||||
<Bar dataKey="quantity" name="Units Sold" fill="#8884d8" />
|
<Legend />
|
||||||
<Bar dataKey="revenue" name="Revenue ($)" fill="#82ca9d" />
|
<Bar dataKey="quantity" name="Units Sold" fill="#8884d8" />
|
||||||
</BarChart>
|
<Bar dataKey="revenue" name="Revenue ($)" fill="#82ca9d" />
|
||||||
</ResponsiveContainer>
|
</BarChart>
|
||||||
</Box>
|
</ResponsiveContainer>
|
||||||
|
</Box>
|
||||||
|
) : (
|
||||||
|
<Box sx={{ p: 4, textAlign: 'center', bgcolor: 'background.paper', borderRadius: 1 }}>
|
||||||
|
<Typography variant="body1" color="text.secondary">
|
||||||
|
No product sales data available for the selected time period.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
|
|
@ -654,28 +709,45 @@ const ReportsPage = () => {
|
||||||
Product Sales Details
|
Product Sales Details
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ overflowX: 'auto' }}>
|
<Box sx={{ overflowX: 'auto' }}>
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
{productSalesData.length > 0 ? (
|
||||||
<thead>
|
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||||
<tr>
|
<thead>
|
||||||
<th style={{ textAlign: 'left', padding: '8px', borderBottom: '1px solid #ddd' }}>Product</th>
|
<tr>
|
||||||
<th style={{ textAlign: 'left', padding: '8px', borderBottom: '1px solid #ddd' }}>Category</th>
|
<th style={{ textAlign: 'left', padding: '8px', borderBottom: '1px solid #ddd' }}>Product</th>
|
||||||
<th style={{ textAlign: 'right', padding: '8px', borderBottom: '1px solid #ddd' }}>Units Sold</th>
|
<th style={{ textAlign: 'left', padding: '8px', borderBottom: '1px solid #ddd' }}>Category</th>
|
||||||
<th style={{ textAlign: 'right', padding: '8px', borderBottom: '1px solid #ddd' }}>Revenue</th>
|
<th style={{ textAlign: 'right', padding: '8px', borderBottom: '1px solid #ddd' }}>Units Sold</th>
|
||||||
</tr>
|
<th style={{ textAlign: 'right', padding: '8px', borderBottom: '1px solid #ddd' }}>Revenue</th>
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{productSalesData.map((product) => (
|
|
||||||
<tr key={product.id}>
|
|
||||||
<td style={{ padding: '8px', borderBottom: '1px solid #f0f0f0' }}>{product.name}</td>
|
|
||||||
<td style={{ padding: '8px', borderBottom: '1px solid #f0f0f0' }}>{product.category}</td>
|
|
||||||
<td style={{ textAlign: 'right', padding: '8px', borderBottom: '1px solid #f0f0f0' }}>{product.quantity}</td>
|
|
||||||
<td style={{ textAlign: 'right', padding: '8px', borderBottom: '1px solid #f0f0f0' }}>${product.revenue.toFixed(2)}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
</thead>
|
||||||
</tbody>
|
<tbody>
|
||||||
</table>
|
{productSalesData.map((product) => (
|
||||||
|
<tr key={product.id}>
|
||||||
|
<td style={{ padding: '8px', borderBottom: '1px solid #f0f0f0' }}>{product.name}</td>
|
||||||
|
<td style={{ padding: '8px', borderBottom: '1px solid #f0f0f0' }}>{product.category}</td>
|
||||||
|
<td style={{ textAlign: 'right', padding: '8px', borderBottom: '1px solid #f0f0f0' }}>{product.quantity}</td>
|
||||||
|
<td style={{ textAlign: 'right', padding: '8px', borderBottom: '1px solid #f0f0f0' }}>${product.revenue.toFixed(2)}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
) : (
|
||||||
|
<Box sx={{ p: 4, textAlign: 'center', bgcolor: 'background.paper', borderRadius: 1 }}>
|
||||||
|
<Typography variant="body1" color="text.secondary">
|
||||||
|
No product sales data available for the selected time period.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
|
{orderDetailsLoading && (
|
||||||
|
<Grid item xs={12} sx={{ display: 'flex', justifyContent: 'center', py: 2 }}>
|
||||||
|
<CircularProgress size={28} />
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ ml: 2 }}>
|
||||||
|
Loading product data...
|
||||||
|
</Typography>
|
||||||
|
</Grid>
|
||||||
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue