Add analytics page
This commit is contained in:
182
inventory/src/components/analytics/PriceAnalysis.tsx
Normal file
182
inventory/src/components/analytics/PriceAnalysis.tsx
Normal file
@@ -0,0 +1,182 @@
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import { ResponsiveContainer, ScatterChart, Scatter, XAxis, YAxis, Tooltip, ZAxis, LineChart, Line } from 'recharts';
|
||||
import config from '../../config';
|
||||
|
||||
interface PriceData {
|
||||
pricePoints: {
|
||||
price: number;
|
||||
salesVolume: number;
|
||||
revenue: number;
|
||||
category: string;
|
||||
}[];
|
||||
elasticity: {
|
||||
date: string;
|
||||
price: number;
|
||||
demand: number;
|
||||
}[];
|
||||
recommendations: {
|
||||
product: string;
|
||||
currentPrice: number;
|
||||
recommendedPrice: number;
|
||||
potentialRevenue: number;
|
||||
confidence: number;
|
||||
}[];
|
||||
}
|
||||
|
||||
export function PriceAnalysis() {
|
||||
const { data, isLoading } = useQuery<PriceData>({
|
||||
queryKey: ['price-analysis'],
|
||||
queryFn: async () => {
|
||||
const response = await fetch(`${config.apiUrl}/analytics/pricing`);
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch price analysis');
|
||||
}
|
||||
const rawData = await response.json();
|
||||
return {
|
||||
pricePoints: rawData.pricePoints.map((item: any) => ({
|
||||
...item,
|
||||
price: Number(item.price) || 0,
|
||||
salesVolume: Number(item.salesVolume) || 0,
|
||||
revenue: Number(item.revenue) || 0
|
||||
})),
|
||||
elasticity: rawData.elasticity.map((item: any) => ({
|
||||
...item,
|
||||
price: Number(item.price) || 0,
|
||||
demand: Number(item.demand) || 0
|
||||
})),
|
||||
recommendations: rawData.recommendations.map((item: any) => ({
|
||||
...item,
|
||||
currentPrice: Number(item.currentPrice) || 0,
|
||||
recommendedPrice: Number(item.recommendedPrice) || 0,
|
||||
potentialRevenue: Number(item.potentialRevenue) || 0,
|
||||
confidence: Number(item.confidence) || 0
|
||||
}))
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
if (isLoading || !data) {
|
||||
return <div>Loading price analysis...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid gap-4">
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Price Point Analysis</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<ScatterChart>
|
||||
<XAxis
|
||||
dataKey="price"
|
||||
name="Price"
|
||||
tickFormatter={(value) => `$${value}`}
|
||||
/>
|
||||
<YAxis
|
||||
dataKey="salesVolume"
|
||||
name="Sales Volume"
|
||||
/>
|
||||
<ZAxis
|
||||
dataKey="revenue"
|
||||
range={[50, 400]}
|
||||
name="Revenue"
|
||||
/>
|
||||
<Tooltip
|
||||
formatter={(value: number, name: string) => {
|
||||
if (name === 'Price') return [`$${value}`, name];
|
||||
if (name === 'Sales Volume') return [value.toLocaleString(), name];
|
||||
if (name === 'Revenue') return [`$${value.toLocaleString()}`, name];
|
||||
return [value, name];
|
||||
}}
|
||||
/>
|
||||
<Scatter
|
||||
data={data.pricePoints}
|
||||
fill="#a78bfa"
|
||||
name="Products"
|
||||
/>
|
||||
</ScatterChart>
|
||||
</ResponsiveContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Price Elasticity</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<LineChart data={data.elasticity}>
|
||||
<XAxis
|
||||
dataKey="date"
|
||||
tickFormatter={(value) => new Date(value).toLocaleDateString()}
|
||||
/>
|
||||
<YAxis yAxisId="left" orientation="left" stroke="#a78bfa" />
|
||||
<YAxis
|
||||
yAxisId="right"
|
||||
orientation="right"
|
||||
stroke="#4ade80"
|
||||
tickFormatter={(value) => `$${value}`}
|
||||
/>
|
||||
<Tooltip
|
||||
labelFormatter={(label) => new Date(label).toLocaleDateString()}
|
||||
formatter={(value: number, name: string) => {
|
||||
if (name === 'Price') return [`$${value}`, name];
|
||||
return [value.toLocaleString(), name];
|
||||
}}
|
||||
/>
|
||||
<Line
|
||||
yAxisId="left"
|
||||
type="monotone"
|
||||
dataKey="demand"
|
||||
stroke="#a78bfa"
|
||||
name="Demand"
|
||||
/>
|
||||
<Line
|
||||
yAxisId="right"
|
||||
type="monotone"
|
||||
dataKey="price"
|
||||
stroke="#4ade80"
|
||||
name="Price"
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Price Optimization Recommendations</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-4">
|
||||
{data.recommendations.map((item) => (
|
||||
<div key={item.product} className="flex items-center">
|
||||
<div className="flex-1">
|
||||
<p className="text-sm font-medium">{item.product}</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Current Price: ${item.currentPrice.toFixed(2)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="ml-4 text-right space-y-1">
|
||||
<p className="text-sm font-medium">
|
||||
Recommended: ${item.recommendedPrice.toFixed(2)}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Potential Revenue: ${item.potentialRevenue.toLocaleString()}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Confidence: {item.confidence}%
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user