Fix/clean up order range dialog
This commit is contained in:
@@ -105,6 +105,11 @@ const TimeSeriesChart = ({
|
||||
const ChartComponent = type === "line" ? LineChart : BarChart;
|
||||
const DataComponent = type === "line" ? Line : Bar;
|
||||
|
||||
// Handle multiple series
|
||||
const keys = Array.isArray(dataKey) ? dataKey : [dataKey];
|
||||
const names = Array.isArray(name) ? name : [name];
|
||||
const colors = Array.isArray(color) ? color : [color];
|
||||
|
||||
return (
|
||||
<div className="h-[400px] w-full">
|
||||
<ResponsiveContainer width="100%" height={height}>
|
||||
@@ -135,15 +140,18 @@ const TimeSeriesChart = ({
|
||||
}}
|
||||
/>
|
||||
<Legend />
|
||||
<DataComponent
|
||||
type="monotone"
|
||||
dataKey={dataKey}
|
||||
name={name}
|
||||
stroke={color}
|
||||
fill={color}
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
{keys.map((key, index) => (
|
||||
<DataComponent
|
||||
key={key}
|
||||
type="monotone"
|
||||
dataKey={key}
|
||||
name={names[index] || key}
|
||||
stroke={colors[index]}
|
||||
fill={colors[index]}
|
||||
strokeWidth={2}
|
||||
dot={false}
|
||||
/>
|
||||
))}
|
||||
</ChartComponent>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
@@ -732,20 +740,6 @@ const RefundDetails = ({ data }) => {
|
||||
const OrderRangeDetails = ({ data }) => {
|
||||
if (!data?.length) return <div className="text-muted-foreground">No data available for the selected time range.</div>;
|
||||
|
||||
const rangeData = data[0]?.orderValueRange || {
|
||||
largest: 0,
|
||||
smallest: 0,
|
||||
largestOrderId: null,
|
||||
smallestOrderId: null,
|
||||
distribution: {
|
||||
under25: { count: 0, total: 0 },
|
||||
under50: { count: 0, total: 0 },
|
||||
under100: { count: 0, total: 0 },
|
||||
under200: { count: 0, total: 0 },
|
||||
over200: { count: 0, total: 0 }
|
||||
}
|
||||
};
|
||||
|
||||
const timeSeriesData = data.map(day => ({
|
||||
timestamp: day.timestamp,
|
||||
largest: day.orderValueRange?.largest || 0,
|
||||
@@ -753,116 +747,31 @@ const OrderRangeDetails = ({ data }) => {
|
||||
average: day.averageOrderValue || 0
|
||||
}));
|
||||
|
||||
const distributionData = [
|
||||
{ range: 'Under $25', ...rangeData.distribution.under25 },
|
||||
{ range: '$25-$50', ...rangeData.distribution.under50 },
|
||||
{ range: '$50-$100', ...rangeData.distribution.under100 },
|
||||
{ range: '$100-$200', ...rangeData.distribution.under200 },
|
||||
{ range: 'Over $200', ...rangeData.distribution.over200 }
|
||||
];
|
||||
|
||||
const totalOrders = distributionData.reduce((sum, range) => sum + (range.count || 0), 0);
|
||||
const totalRevenue = distributionData.reduce((sum, range) => sum + (range.total || 0), 0);
|
||||
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<StatCard
|
||||
title="Largest Order"
|
||||
value={formatCurrency(rangeData.largest)}
|
||||
description={rangeData.largestOrderId ? `Order #${rangeData.largestOrderId}` : null}
|
||||
colorClass="text-violet-600 dark:text-violet-400"
|
||||
icon={TrendingUp}
|
||||
/>
|
||||
<StatCard
|
||||
title="Smallest Order"
|
||||
value={formatCurrency(rangeData.smallest)}
|
||||
description={rangeData.smallestOrderId ? `Order #${rangeData.smallestOrderId}` : null}
|
||||
colorClass="text-violet-600 dark:text-violet-400"
|
||||
icon={TrendingDown}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-lg font-medium mb-4">Order Value Range Over Time</h3>
|
||||
<h3 className="text-lg font-medium mb-4">Order Value Range</h3>
|
||||
<TimeSeriesChart
|
||||
data={timeSeriesData}
|
||||
dataKey="largest"
|
||||
name="Largest Order"
|
||||
color="hsl(262.1 83.3% 57.8%)"
|
||||
dataKey={["largest", "smallest"]}
|
||||
name={["Largest Order", "Smallest Order"]}
|
||||
color={["hsl(262.1 83.3% 57.8%)", "hsl(221.2 83.2% 53.3%)"]}
|
||||
valueFormatter={(value) => formatCurrency(value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-lg font-medium mb-4">Average Order Value Trend</h3>
|
||||
<h3 className="text-lg font-medium mb-4">Average Order Value</h3>
|
||||
<TimeSeriesChart
|
||||
data={timeSeriesData}
|
||||
dataKey="average"
|
||||
name="Average Order Value"
|
||||
color="hsl(221.2 83.2% 53.3%)"
|
||||
color="hsl(142.1 76.2% 36.3%)"
|
||||
valueFormatter={(value) => formatCurrency(value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-lg font-medium mb-4">Order Value Distribution</h3>
|
||||
<div className="grid grid-cols-2 gap-4 mb-4">
|
||||
<StatCard
|
||||
title="Total Orders"
|
||||
value={totalOrders.toLocaleString()}
|
||||
description="orders analyzed"
|
||||
colorClass="text-violet-600 dark:text-violet-400"
|
||||
icon={ShoppingCart}
|
||||
/>
|
||||
<StatCard
|
||||
title="Total Revenue"
|
||||
value={formatCurrency(totalRevenue)}
|
||||
description="from all orders"
|
||||
colorClass="text-violet-600 dark:text-violet-400"
|
||||
icon={DollarSign}
|
||||
/>
|
||||
</div>
|
||||
<div className="rounded-lg border bg-card">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Range</TableHead>
|
||||
<TableHead className="text-right">Orders</TableHead>
|
||||
<TableHead className="text-right">Total Revenue</TableHead>
|
||||
<TableHead className="text-right">% of Orders</TableHead>
|
||||
<TableHead className="text-right">Avg. Order Value</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{distributionData.map((range, index) => (
|
||||
<TableRow key={index}>
|
||||
<TableCell className="font-medium">{range.range}</TableCell>
|
||||
<TableCell className="text-right">{range.count?.toLocaleString() || 0}</TableCell>
|
||||
<TableCell className="text-right">{formatCurrency(range.total || 0)}</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{((range.count / totalOrders) * 100).toFixed(1)}%
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
{formatCurrency((range.total || 0) / (range.count || 1))}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h3 className="text-lg font-medium mb-4">Distribution Chart</h3>
|
||||
<TimeSeriesChart
|
||||
data={distributionData}
|
||||
dataKey="count"
|
||||
name="Orders"
|
||||
type="bar"
|
||||
color="hsl(262.1 83.3% 57.8%)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1143,20 +1052,14 @@ const StatCards = ({
|
||||
|
||||
// For order range
|
||||
if (metric === 'order_range') {
|
||||
const response = await axios.get('/api/klaviyo/events/stats/details', { params });
|
||||
const data = response.data.stats.map(day => ({
|
||||
...day,
|
||||
orderValueRange: {
|
||||
...day.orderValueRange,
|
||||
distribution: day.orderValueRange?.distribution || {
|
||||
under25: { count: 0, total: 0 },
|
||||
under50: { count: 0, total: 0 },
|
||||
under100: { count: 0, total: 0 },
|
||||
under200: { count: 0, total: 0 },
|
||||
over200: { count: 0, total: 0 }
|
||||
}
|
||||
const response = await axios.get('/api/klaviyo/events/stats/details', {
|
||||
params: {
|
||||
...params,
|
||||
eventType: 'PLACED_ORDER'
|
||||
}
|
||||
}));
|
||||
});
|
||||
const data = response.data.stats;
|
||||
console.log('Fetched order range data:', data);
|
||||
setCacheData(detailTimeRange, metric, data);
|
||||
setDetailData(prev => ({ ...prev, [metric]: data }));
|
||||
setError(null);
|
||||
@@ -1193,8 +1096,9 @@ const StatCards = ({
|
||||
'on_hold'
|
||||
];
|
||||
|
||||
// Await all fetchDetailData calls
|
||||
return Promise.all(metrics.map(metric => fetchDetailData(metric, metric))).catch(error => {
|
||||
return Promise.all(
|
||||
metrics.map(metric => fetchDetailData(metric, metric))
|
||||
).catch(error => {
|
||||
console.error('Error during detail data preload:', error);
|
||||
});
|
||||
}, [fetchDetailData]);
|
||||
|
||||
Reference in New Issue
Block a user