"use client"; import { useId, useState } from "react"; import { Area, AreaChart, CartesianGrid, Line, ReferenceLine, XAxis, YAxis, } from "recharts"; import { ChartContainer, type ChartConfig, ChartLegend, ChartTooltip, ChartTooltipContent, } from "@/components/ui/chart"; import { cn } from "@/lib/utils"; import { formatCompact } from "@/lib/format"; import type { TimelineSeries } from "@/lib/traction"; const MONTH_NAMES = [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec", ]; const formatTick = (yearMonth: string) => { const parts = yearMonth.split("-"); const idx = Number(parts[1]) - 1; return MONTH_NAMES[idx] ?? yearMonth; }; const formatTooltipLabel = (yearMonth: string) => { const [y, m] = yearMonth.split("-"); const date = new Date(Number(y), Number(m) - 1, 1); return date.toLocaleDateString("en-US", { month: "long", year: "numeric" }); }; export function DownloadsChart({ timeline }: { timeline: TimelineSeries }) { const gradientPrefix = useId(); const [hidden, setHidden] = useState>(new Set()); if (timeline.data.length < 2 || timeline.series.length === 0) { return (
Download history is currently unavailable.
); } const config: ChartConfig = {}; for (const s of timeline.series) { config[s.key] = { label: s.label, color: `var(--chart-${s.chartIndex})`, }; } const toggle = (key: string) => { setHidden((prev) => { const next = new Set(prev); if (next.has(key)) next.delete(key); else next.add(key); return next; }); }; return ( {timeline.series.map((s) => ( ))} formatCompact(v as number)} tickLine={false} axisLine={false} width={42} /> { // hide _proj entries when their raw counterpart has a value (avoids dup rows on the bridging month). const filtered = tooltipProps.payload ?.filter((entry) => { const key = String(entry?.dataKey ?? ""); if (!key.endsWith("_proj")) return true; const baseKey = key.slice(0, -5); const rowPayload = entry.payload as | Record | undefined; return rowPayload?.[baseKey] === undefined; }) .slice() .sort((a, b) => (Number(b.value) || 0) - (Number(a.value) || 0)); return ( )} payload={filtered} labelFormatter={(_, p) => { const ym = p?.[0]?.payload?.date as string | undefined; return ym ? formatTooltipLabel(ym) : ""; }} formatter={(value, name, item) => { const rawName = String(name); const baseKey = rawName.endsWith("_proj") ? rawName.slice(0, -5) : rawName; const series = timeline.series.find((s) => s.key === baseKey); const color = (item as { color?: string } | undefined)?.color ?? (item?.payload as { fill?: string } | undefined)?.fill; return ( <>
{series?.label ?? rawName} {formatCompact(value as number)}
); }} indicator="dot" /> ); }} /> {timeline.series.map((s) => { const isHidden = hidden.has(s.key); return ( ); })} {timeline.projectedMonth ? ( <> {timeline.series.map((s) => { const isHidden = hidden.has(s.key); return ( ); })} ) : null} (
{( payload as | Array<{ dataKey?: string | number; value?: string; color?: string; }> | undefined )?.map((item) => { const key = String(item.dataKey ?? item.value ?? ""); const series = timeline.series.find((s) => s.key === key); if (!series) return null; const isHidden = hidden.has(key); return ( ); })}
)} /> ); }