98 lines
3.2 KiB
TypeScript
98 lines
3.2 KiB
TypeScript
'use client';
|
|
|
|
import {useId} from 'react';
|
|
import {Area, AreaChart, CartesianGrid, Tooltip, XAxis, YAxis} from 'recharts';
|
|
import {Card} from '@astryxdesign/core/Card';
|
|
import {VStack} from '@astryxdesign/core/Layout';
|
|
import {Text} from '@astryxdesign/core/Text';
|
|
import {ChartFrame, AXIS_TICK} from '@/shared/components/charts/ChartFrame';
|
|
import {CHART} from '@/shared/components/charts/palette';
|
|
import {useChartMotion} from '@/shared/components/charts/useChartMotion';
|
|
import {formatCount, formatInr, formatInrCompact} from '@/shared/utils/format';
|
|
import type {TrendPoint} from '@/features/commerce/services/salesAnalysis';
|
|
|
|
/**
|
|
* Daily sales across the selected period — compact, one area in brand warm
|
|
* (sales are money, and money is warm on this console).
|
|
*
|
|
* Its own small chart rather than AreaChartView because the tooltip needs two
|
|
* units at once — rupees for sales, a plain count for purchases — and the
|
|
* shared tooltip formats every series with the one `yFormat`.
|
|
*/
|
|
export function SalesTrendChart({
|
|
data,
|
|
height = 180,
|
|
}: {
|
|
data: TrendPoint[];
|
|
height?: number;
|
|
}) {
|
|
const uid = useId().replace(/:/g, '');
|
|
const motion = useChartMotion(true);
|
|
|
|
return (
|
|
<ChartFrame height={height}>
|
|
<AreaChart data={data} margin={{top: 8, right: 8, bottom: 0, left: 0}}>
|
|
<defs>
|
|
<linearGradient id={`sales-${uid}`} x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="0%" stopColor={CHART.brand.warm} stopOpacity={0.35} />
|
|
<stop offset="100%" stopColor={CHART.brand.warm} stopOpacity={0.02} />
|
|
</linearGradient>
|
|
</defs>
|
|
<CartesianGrid stroke={CHART.grid} strokeDasharray="3 3" vertical={false} />
|
|
<XAxis
|
|
dataKey="label"
|
|
tick={AXIS_TICK}
|
|
tickLine={false}
|
|
axisLine={false}
|
|
minTickGap={24}
|
|
/>
|
|
<YAxis
|
|
tick={AXIS_TICK}
|
|
tickLine={false}
|
|
axisLine={false}
|
|
width={48}
|
|
tickCount={4}
|
|
tickFormatter={(v: number) => formatInrCompact(v)}
|
|
/>
|
|
<Tooltip
|
|
cursor={{stroke: CHART.axisLine}}
|
|
content={({active, payload}) => {
|
|
const p = active ? (payload?.[0]?.payload as TrendPoint | undefined) : undefined;
|
|
if (!p) return null;
|
|
return (
|
|
<Card elevation="high" padding={3}>
|
|
<VStack gap={1}>
|
|
<Text size="sm" color="secondary">
|
|
{p.label}
|
|
</Text>
|
|
<Text size="sm" weight="semibold">
|
|
Sales {formatInr(p.sales)}
|
|
</Text>
|
|
<Text size="sm">Purchases {formatCount(p.purchases)}</Text>
|
|
</VStack>
|
|
</Card>
|
|
);
|
|
}}
|
|
/>
|
|
<Area
|
|
type="monotone"
|
|
dataKey="sales"
|
|
name="Sales"
|
|
stroke={CHART.brand.warm}
|
|
strokeWidth={2.5}
|
|
fill={`url(#sales-${uid})`}
|
|
fillOpacity={1}
|
|
dot={false}
|
|
activeDot={{
|
|
r: 4,
|
|
stroke: CHART.brand.warm,
|
|
strokeWidth: 2,
|
|
fill: 'var(--color-background-surface)',
|
|
}}
|
|
{...motion}
|
|
/>
|
|
</AreaChart>
|
|
</ChartFrame>
|
|
);
|
|
}
|