"use client"; import { formatCurrency } from "@/lib/analytics-utils"; import type { RevenueDataPoint } from "@/lib/types"; import { useState } from "react"; interface BarChartProps { current: RevenueDataPoint[]; previous: RevenueDataPoint[]; height?: number; } /** * Pure-SVG grouped bar chart comparing current vs previous period revenue. * Hover bars show tooltip with label, revenue, and order count. * Tooltip auto-flips above/below bar top to stay inside the viewBox. */ export function BarChart({ current, previous, height = 200 }: BarChartProps) { const [hovered, setHovered] = useState<{ set: "cur" | "prev"; idx: number } | null>(null); const W = 800; const H = height; const padL = 56, padR = 16, padT = 16, padB = 40; const chartW = W - padL - padR; const chartH = H - padT - padB; const n = current.length; const maxVal = Math.max(...current.map((d) => d.revenue), ...previous.map((d) => d.revenue)) || 1; const groupW = chartW / n; const barW = groupW * 0.35; const gap = groupW * 0.05; const yTicks = 5; const gridLines = Array.from({ length: yTicks + 1 }, (_, i) => ({ val: (maxVal / yTicks) * (yTicks - i), y: padT + (i / yTicks) * chartH, })); const step = Math.ceil(n / 8); return (
setHovered(null)} > {gridLines.map((g, i) => ( {formatCurrency(g.val)} ))} {current.map((d, i) => { const groupX = padL + i * groupW; const curH = (d.revenue / maxVal) * chartH; const prevH = ((previous[i]?.revenue ?? 0) / maxVal) * chartH; const curX = groupX + gap; const prevX = curX + barW + gap; const isHovCur = hovered?.set === "cur" && hovered.idx === i; const isHovPrev = hovered?.set === "prev" && hovered.idx === i; return ( setHovered({ set: "prev", idx: i })} /> setHovered({ set: "cur", idx: i })} /> {i % step === 0 && ( {d.label} )} ); })} {hovered !== null && (() => { const d = hovered.set === "cur" ? current[hovered.idx] : previous[hovered.idx]; if (!d) return null; const groupX = padL + hovered.idx * groupW; const tipW = 130, tipH = 50; const tipX = Math.min(Math.max(groupX - tipW / 2, padL), W - padR - tipW); const barH = (d.revenue / maxVal) * chartH; const barTopY = padT + chartH - barH; const aboveY = barTopY - tipH - 8; const tipY = Math.min(Math.max(aboveY >= padT ? aboveY : barTopY + 8, padT), padT + chartH - tipH); return ( {d.label} ({hovered.set === "cur" ? "Hiện tại" : "Trước"}) {formatCurrency(d.revenue)} {d.orders} đơn hàng ); })()} {/* Legend */} Hiện tại Kỳ trước
); }