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
|
||||
}, [filteredOrders]);
|
||||
|
||||
// Debug - log filtered orders
|
||||
useEffect(() => {
|
||||
console.log("Filtered orders:", filteredOrders);
|
||||
}, [filteredOrders]);
|
||||
|
||||
// Prepare time series data for revenue chart
|
||||
const revenueTimeSeriesData = useMemo(() => {
|
||||
if (!orders) return [];
|
||||
|
|
@ -293,9 +298,41 @@ const ReportsPage = () => {
|
|||
return Array.from(dataMap.values());
|
||||
}, [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
|
||||
const productSalesData = useMemo(() => {
|
||||
if (!orders || !products) return [];
|
||||
if (!products || !orderDetails) return [];
|
||||
|
||||
const productSales = {};
|
||||
|
||||
|
|
@ -310,13 +347,23 @@ const ReportsPage = () => {
|
|||
};
|
||||
});
|
||||
|
||||
// Aggregate sales data from orders
|
||||
filteredOrders.forEach(order => {
|
||||
// Aggregate sales data from order details
|
||||
orderDetails.forEach(order => {
|
||||
if (order.items && Array.isArray(order.items)) {
|
||||
order.items.forEach(item => {
|
||||
if (productSales[item.product_id]) {
|
||||
productSales[item.product_id].quantity += item.quantity;
|
||||
productSales[item.product_id].revenue += parseFloat(item.price_at_purchase) * item.quantity;
|
||||
productSales[item.product_id].quantity += parseInt(item.quantity) || 0;
|
||||
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)
|
||||
.filter(product => product.quantity > 0)
|
||||
.sort((a, b) => b.quantity - a.quantity);
|
||||
}, [filteredOrders, products]);
|
||||
}, [products, orderDetails]);
|
||||
|
||||
// Top selling products for the chart
|
||||
const topSellingProducts = useMemo(() => {
|
||||
|
|
@ -358,7 +405,7 @@ const ReportsPage = () => {
|
|||
}, [productSalesData]);
|
||||
|
||||
// Loading state
|
||||
const isLoading = ordersLoading || productsLoading;
|
||||
const isLoading = ordersLoading || productsLoading || orderDetailsLoading;
|
||||
|
||||
// Handle export reports
|
||||
const handleExportCSV = () => {
|
||||
|
|
@ -629,24 +676,32 @@ const ReportsPage = () => {
|
|||
<Typography variant="h6" gutterBottom>
|
||||
Top Selling Products
|
||||
</Typography>
|
||||
<Box sx={{ height: 400 }}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart
|
||||
data={topSellingProducts}
|
||||
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip formatter={(value, name) => {
|
||||
return name === 'revenue' ? `$${value.toFixed(2)}` : value;
|
||||
}} />
|
||||
<Legend />
|
||||
<Bar dataKey="quantity" name="Units Sold" fill="#8884d8" />
|
||||
<Bar dataKey="revenue" name="Revenue ($)" fill="#82ca9d" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</Box>
|
||||
{topSellingProducts.length > 0 ? (
|
||||
<Box sx={{ height: 400 }}>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart
|
||||
data={topSellingProducts}
|
||||
margin={{ top: 5, right: 30, left: 20, bottom: 5 }}
|
||||
>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip formatter={(value, name) => {
|
||||
return name === 'revenue' ? `${value.toFixed(2)}` : value;
|
||||
}} />
|
||||
<Legend />
|
||||
<Bar dataKey="quantity" name="Units Sold" fill="#8884d8" />
|
||||
<Bar dataKey="revenue" name="Revenue ($)" fill="#82ca9d" />
|
||||
</BarChart>
|
||||
</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 item xs={12}>
|
||||
|
|
@ -654,28 +709,45 @@ const ReportsPage = () => {
|
|||
Product Sales Details
|
||||
</Typography>
|
||||
<Box sx={{ overflowX: 'auto' }}>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ textAlign: 'left', padding: '8px', borderBottom: '1px solid #ddd' }}>Product</th>
|
||||
<th style={{ textAlign: 'left', padding: '8px', borderBottom: '1px solid #ddd' }}>Category</th>
|
||||
<th style={{ textAlign: 'right', padding: '8px', borderBottom: '1px solid #ddd' }}>Units Sold</th>
|
||||
<th style={{ textAlign: 'right', padding: '8px', borderBottom: '1px solid #ddd' }}>Revenue</th>
|
||||
</tr>
|
||||
</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>
|
||||
{productSalesData.length > 0 ? (
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ textAlign: 'left', padding: '8px', borderBottom: '1px solid #ddd' }}>Product</th>
|
||||
<th style={{ textAlign: 'left', padding: '8px', borderBottom: '1px solid #ddd' }}>Category</th>
|
||||
<th style={{ textAlign: 'right', padding: '8px', borderBottom: '1px solid #ddd' }}>Units Sold</th>
|
||||
<th style={{ textAlign: 'right', padding: '8px', borderBottom: '1px solid #ddd' }}>Revenue</th>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</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>
|
||||
))}
|
||||
</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>
|
||||
</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>
|
||||
)}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue