- 백엔드 GET /v1/statistics/summary: 회사/내견적 스코프, 최근 6개월 파생 집계(저장 X, DDL 0) - 지표: 총절감/절감률/낙찰률/앵커도달률/마감수/재견적라운드 + 월별추이·마감결과분해·참여율·유형별(협상/견적)·카테고리·카드빈도 - 프론트 recharts+shadcn Chart, 사이드바 통계 nav, /statistics Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
42 lines
2.0 KiB
TypeScript
42 lines
2.0 KiB
TypeScript
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from 'recharts';
|
|
import { ChartContainer, ChartTooltip, type ChartConfig } from '@/components/ui/chart';
|
|
import { Typography } from '@/components/ui/typography';
|
|
import { themeOf } from '../palette';
|
|
import { won, wonCompact, pct, monthLabel } from '../fmt';
|
|
import type { MonthPoint } from '../types';
|
|
|
|
// 월별 목표가 대비 절감액(단일 시리즈=크기). 한 축 원칙 — 절감률은 이중축 대신 툴팁에 얹는다.
|
|
const config = {
|
|
savings: { label: '절감액', theme: themeOf('emerald') },
|
|
} satisfies ChartConfig;
|
|
|
|
export function SavingsTrendChart({ data }: { data: MonthPoint[] }) {
|
|
return (
|
|
<ChartContainer config={config} className="aspect-auto h-56 w-full">
|
|
<BarChart data={data} margin={{ top: 8, right: 8, left: 4, bottom: 0 }}>
|
|
<CartesianGrid vertical={false} strokeDasharray="3 3" />
|
|
<XAxis dataKey="month" tickLine={false} axisLine={false} tickMargin={8} tickFormatter={monthLabel} />
|
|
<YAxis tickLine={false} axisLine={false} width={44} tickFormatter={(v) => wonCompact(Number(v))} />
|
|
<ChartTooltip cursor={false} content={<TrendTooltip />} />
|
|
<Bar dataKey="savings" fill="var(--color-savings)" radius={[4, 4, 0, 0]} maxBarSize={48} />
|
|
</BarChart>
|
|
</ChartContainer>
|
|
);
|
|
}
|
|
|
|
// 절감액(₩) + 절감률을 한 툴팁에. payload[0].payload = MonthPoint.
|
|
function TrendTooltip({ active, payload }: { active?: boolean; payload?: { payload: MonthPoint }[] }) {
|
|
if (!active || !payload?.length) return null;
|
|
const p = payload[0].payload;
|
|
return (
|
|
<div className="rounded-lg border border-border/50 bg-background px-2.5 py-1.5 text-xs shadow-xl">
|
|
<Typography as="p" variant="caption" className="mb-0.5 font-medium text-foreground">
|
|
{monthLabel(p.month)}
|
|
</Typography>
|
|
<Typography as="p" variant="caption" className="font-mono text-foreground">
|
|
{won(p.savings)} · {pct(p.rate)}
|
|
</Typography>
|
|
</div>
|
|
);
|
|
}
|