/* * SPDX-FileCopyrightText: 2020 Stalwart Labs LLC * SPDX-FileCopyrightText: 2026 Coffey Labs * * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL * * Modified by Coffey Labs in 2026 for INBUXA. */ import { useMemo, useRef, useState, useEffect } from 'react'; import { LineChart, Line, AreaChart, Area, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, } from 'recharts'; function ChartSizedContainer({ height, children, }: { height: number; children: (width: number, height: number) => React.ReactNode; }) { const ref = useRef(null); const [width, setWidth] = useState(0); useEffect(() => { const el = ref.current; if (!el) return; const ro = new ResizeObserver((entries) => { for (const entry of entries) { const w = entry.contentRect.width; if (w > 0) setWidth(w); } }); ro.observe(el); const w = el.getBoundingClientRect().width; if (w > 0) setWidth(w); return () => ro.disconnect(); }, []); return (
{width > 0 && children(width, height)}
); } import { Info } from 'lucide-react'; import { GoLink } from './GoLink'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Tooltip as UiTooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'; import { getChartColor } from '@/components/ui/chart'; import { ChartTooltipContent } from '@/components/ui/chart'; import type { Chart as ChartSchema } from '../types/schema'; import type { Metric, Period } from '../types/metrics'; import { bucketize, bucketTimestamps, getBucketCount, seriesBucketValue, formatTimeTick, formatValue, } from '../helpers'; interface DashboardChartProps { chart: ChartSchema; historySamples: Metric[]; historyWindow: { from: Date; to: Date }; period: Period; } export function DashboardChart({ chart, historySamples, historyWindow, period }: DashboardChartProps) { const { from, to } = historyWindow; const bucketCount = getBucketCount(period); const valueFormat = chart.valueFormat ?? 'number'; const data = useMemo(() => { const buckets = bucketize(historySamples, from, to, bucketCount); const timestamps = bucketTimestamps(from, to, bucketCount); const points = timestamps.map((ts, i) => { const point: Record = { time: ts.getTime(), timeLabel: formatTimeTick(ts, period), }; for (const series of chart.series) { point[series.label] = seriesBucketValue(series, buckets[i]); } return point; }); if (chart.stacked) { const lastSeen: Record = {}; for (const point of points) { for (const series of chart.series) { const v = point[series.label]; if (typeof v === 'number') { lastSeen[series.label] = v; } else { point[series.label] = lastSeen[series.label] ?? 0; } } } } return points; }, [historySamples, from, to, bucketCount, chart.series, chart.stacked, period]); const tickFormatter = (value: number) => formatValue(value, valueFormat); const tooltipFormatter = (value: number) => formatValue(value, valueFormat); const renderChart = (chartWidth: number, chartHeight: number) => { const commonProps = { data, width: chartWidth, height: chartHeight, margin: { top: 5, right: 10, left: 10, bottom: 5 }, }; const seriesElements = chart.series.map((s, i) => { const color = getChartColor(i); const key = s.label; switch (chart.kind) { case 'line': return ( ); case 'area': return ( ); case 'bar': return ( ); } }); const axes = ( <> ( )} /> ); switch (chart.kind) { case 'line': return ( {axes} {seriesElements} ); case 'area': return ( {axes} {seriesElements} ); case 'bar': return ( {axes} {seriesElements} ); } }; return (
{chart.title} {chart.description && (

{chart.description}

)} x.metrics)} />
{(width, height) => renderChart(width, height)}
); }