diff --git a/src/components/features/charts.tsx b/src/components/features/charts.tsx new file mode 100644 index 0000000..c2d8aa0 --- /dev/null +++ b/src/components/features/charts.tsx @@ -0,0 +1,227 @@ +"use client"; + +import type { ReactNode } from "react"; +import { useEffect, useRef, useState } from "react"; +import { + Area, + AreaChart, + Bar, + BarChart, + CartesianGrid, + Legend, + Tooltip, + XAxis, + YAxis, +} from "recharts"; +import type { MetricPoint } from "@/api/contracts/models"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { useUiStore } from "@/stores/ui-store"; + +/** + * Recharts needs concrete color strings, so resolve the active CSS variables + * whenever the accent or theme preference changes. + */ +function useChartTheme() { + const accent = useUiStore((state) => state.accent); + const theme = useUiStore((state) => state.theme); + const [colors, setColors] = useState({ + primary: "hsl(224 88% 53%)", + accent: "hsl(30 100% 48%)", + muted: "hsl(226 14% 44%)", + grid: "hsl(222 24% 86% / 0.5)", + }); + + useEffect(() => { + // Defer one frame so the Providers effect has applied the new + // data-accent / dark-class attributes before the variables are read. + const frame = requestAnimationFrame(() => { + const styles = getComputedStyle(document.documentElement); + const read = (name: string) => `hsl(${styles.getPropertyValue(name).trim()})`; + setColors({ + primary: read("--primary"), + accent: read("--accent"), + muted: read("--muted-foreground"), + grid: `hsl(${styles.getPropertyValue("--border").trim()} / 0.5)`, + }); + }); + return () => cancelAnimationFrame(frame); + }, [accent, theme]); + + return colors; +} + +function GlassTooltip({ + active, + payload, + label, +}: { + active?: boolean; + payload?: Array<{ name: string; value: number; color: string }>; + label?: string; +}) { + if (!active || !payload?.length) return null; + return ( +