From 93d46851bebeac841b8e618ae5c65e247714ca4e Mon Sep 17 00:00:00 2001 From: arsam Date: Fri, 18 Sep 2026 23:10:45 +0330 Subject: [PATCH] Import source: src/components/features/charts Import the existing local project snapshot. This commit organizes existing files; it does not represent newly developed functionality. --- src/components/features/charts.tsx | 227 +++++++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 src/components/features/charts.tsx 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 ( +
+
{label?.slice(11, 16)}
+ {payload.map((entry) => ( +
+ + {entry.name} + + {Math.round(entry.value).toLocaleString()} + +
+ ))} +
+ ); +} + +function ChartFrame({ + children, +}: { + children: (size: { width: number; height: number }) => ReactNode; +}) { + const ref = useRef(null); + const [size, setSize] = useState({ width: 0, height: 0 }); + + useEffect(() => { + const element = ref.current; + if (!element) return; + const target = element; + + function readSize() { + const rect = target.getBoundingClientRect(); + setSize({ + width: Math.max(1, Math.floor(rect.width)), + height: Math.max(1, Math.floor(rect.height)), + }); + } + + readSize(); + const observer = new ResizeObserver(readSize); + observer.observe(element); + return () => observer.disconnect(); + }, []); + + return ( + +
+ {size.width > 1 && size.height > 1 ? ( + children(size) + ) : ( +
+ )} +
+ + ); +} + +export function UsageChart({ + data, + title = "Resource history", +}: { + data: MetricPoint[]; + title?: string; +}) { + const colors = useChartTheme(); + return ( + + + {title} + + + {({ width, height }) => ( + + + + + + + + + + + + + value.slice(11, 16)} + tick={{ fontSize: 11, fill: colors.muted }} + axisLine={false} + tickLine={false} + /> + + } /> + + + + + )} + + + ); +} + +export function TrafficChart({ data }: { data: MetricPoint[] }) { + const colors = useChartTheme(); + return ( + + + Traffic and requests + + + {({ width, height }) => ( + + + value.slice(11, 16)} + tick={{ fontSize: 11, fill: colors.muted }} + axisLine={false} + tickLine={false} + /> + + } cursor={{ fill: colors.grid }} /> + + + + + )} + + + ); +}