mirror of
https://github.com/christianvidalwolf-prog/CrazeAnalytix.git
synced 2026-08-03 11:45:23 +02:00
feat: add cumulative YoY sales chart to Dashboard
New line chart shows accumulated sell-out (€) or units month by month, comparing all available years on the same axis for like-for-like year-over-year tracking. Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
b31911a9df
commit
9f9c0c2975
@@ -479,6 +479,22 @@ const Dashboard: React.FC<DashboardProps> = ({
|
||||
}) => {
|
||||
const [seasonalityMetric, setSeasonalityMetric] = useState<'sellOut' | 'units'>('sellOut');
|
||||
const [top10Metric, setTop10Metric] = useState<'sellOut' | 'units'>('sellOut');
|
||||
const [cumulativeMetric, setCumulativeMetric] = useState<'sellOut' | 'units'>('sellOut');
|
||||
|
||||
const cumulativeData = useMemo(() => {
|
||||
const source = cumulativeMetric === 'sellOut' ? data.seasonality : data.seasonalityUnits;
|
||||
const years = data.availableYears;
|
||||
const running: Record<string, number> = {};
|
||||
years.forEach(y => { running[y] = 0; });
|
||||
return source.map(point => {
|
||||
const result: Record<string, number | string> = { name: point.name };
|
||||
years.forEach(y => {
|
||||
running[y] = (running[y] || 0) + ((point[y] as number) || 0);
|
||||
result[y] = running[y];
|
||||
});
|
||||
return result;
|
||||
});
|
||||
}, [data.seasonality, data.seasonalityUnits, data.availableYears, cumulativeMetric]);
|
||||
|
||||
// Calculate Ads KPIs by Year
|
||||
const adsKPIsByYear = useMemo(() => {
|
||||
@@ -804,6 +820,54 @@ const Dashboard: React.FC<DashboardProps> = ({
|
||||
</div>
|
||||
</ExpandableCard>
|
||||
|
||||
{/* Cumulative Sales Chart - YoY comparison month by month */}
|
||||
<ExpandableCard title="Cumulative Sales YoY (Month by Month)" className="h-96">
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="flex justify-end mb-2">
|
||||
<div className="bg-slate-900 p-1 rounded-lg border border-slate-800 flex text-xs">
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); setCumulativeMetric('sellOut'); }}
|
||||
className={`px-3 py-1 rounded-md transition-colors ${cumulativeMetric === 'sellOut' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-400 hover:text-white'}`}
|
||||
>
|
||||
Sell Out (€)
|
||||
</button>
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); setCumulativeMetric('units'); }}
|
||||
className={`px-3 py-1 rounded-md transition-colors ${cumulativeMetric === 'units' ? 'bg-indigo-600 text-white shadow-sm' : 'text-slate-400 hover:text-white'}`}
|
||||
>
|
||||
Units
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex-1 min-h-0">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<LineChart data={cumulativeData}>
|
||||
<CartesianGrid strokeDasharray="3 3" stroke="#1e293b" />
|
||||
<XAxis dataKey="name" stroke="#64748b" />
|
||||
<YAxis
|
||||
stroke="#64748b"
|
||||
tickFormatter={(val) => cumulativeMetric === 'sellOut' ? `€${(val / 1000).toFixed(0)}k` : val.toLocaleString('de-DE')}
|
||||
/>
|
||||
<Tooltip content={(props: any) => <SeasonalityTooltip {...props} metric={cumulativeMetric} />} />
|
||||
<Legend />
|
||||
{data.availableYears.map((year, index) => (
|
||||
<Line
|
||||
key={year}
|
||||
type="monotone"
|
||||
dataKey={year}
|
||||
name={year}
|
||||
stroke={COLORS[index % COLORS.length]}
|
||||
strokeWidth={3}
|
||||
dot={{ r: 4, strokeWidth: 2 }}
|
||||
activeDot={{ r: 6 }}
|
||||
/>
|
||||
))}
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
</ExpandableCard>
|
||||
|
||||
{/* Country Chart - Uses Specific Data 'data' usually, unless we want to broaden it. Kept specific for now. */}
|
||||
<ExpandableCard title="Revenue Distribution by Customer" className="h-80">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
|
||||
Reference in New Issue
Block a user