"use client"; import { formatCurrency } from "@/lib/analytics-utils"; import type { RevenueDataPoint } from "@/lib/types"; import { useState } from "react"; interface LineChartProps { data: RevenueDataPoint[]; height?: number; } /** * Pure-SVG interactive line chart for revenue over time. * Hover dots show tooltip with label, revenue, and order count. * Tooltip auto-flips above/below the dot to stay inside the viewBox. */ export function LineChart({ data, height = 200 }: LineChartProps) { const [hovered, setHovered] = useState(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 maxRev = Math.max(...data.map((d) => d.revenue)); const range = maxRev || 1; const points = data.map((d, i) => ({ x: padL + (i / (data.length - 1)) * chartW, y: padT + chartH - (d.revenue / range) * chartH, data: d, index: i, })); const pathD = points .map((p, i) => `${i === 0 ? "M" : "L"} ${p.x.toFixed(1)} ${p.y.toFixed(1)}`) .join(" "); const areaD = pathD + ` L ${points[points.length - 1].x.toFixed(1)} ${(padT + chartH).toFixed(1)}` + ` L ${points[0].x.toFixed(1)} ${(padT + chartH).toFixed(1)} Z`; const yTicks = 5; const gridLines = Array.from({ length: yTicks + 1 }, (_, i) => ({ val: (range / yTicks) * (yTicks - i), y: padT + (i / yTicks) * chartH, })); const step = Math.ceil(data.length / 10); return (
setHovered(null)} > {gridLines.map((g, i) => ( {formatCurrency(g.val)} ))} {points.map((p, i) => i % step === 0 ? ( {p.data.label} ) : null, )} {points.map((p) => ( setHovered(p.index)} /> ))} {hovered !== null && (() => { const p = points[hovered]; const tipW = 120, tipH = 48; const tipX = Math.min(Math.max(p.x - tipW / 2, padL), W - padR - tipW); const aboveY = p.y - tipH - 10; const tipY = Math.min(Math.max(aboveY >= padT ? aboveY : p.y + 10, padT), padT + chartH - tipH); return ( {p.data.label} {formatCurrency(p.data.revenue)} {p.data.orders} đơn ); })()}
); }