import { useMemo } from 'react' import { VChart } from '@visactor/react-vchart' import { useTranslation } from 'react-i18next' import { useChartTheme } from '@/lib/use-chart-theme' import { cn } from '@/lib/utils' import { VCHART_OPTION } from '@/lib/vchart' import type { LatencyTimePoint, UptimeDayPoint } from '../lib/mock-stats' function formatHourLabel(iso: string): string { const date = new Date(iso) const hours = date.getHours() return `${String(hours).padStart(2, '0')}:00` } function formatDayLabel(date: string): string { const parsed = new Date(date) if (date.includes('T')) { return parsed.toLocaleString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', }) } return parsed.toLocaleDateString(undefined, { month: 'short', day: 'numeric', }) } // --------------------------------------------------------------------------- // Latency trend chart (24h, multi-group point-line chart) // --------------------------------------------------------------------------- export function LatencyTrendChart(props: { series: LatencyTimePoint[] className?: string }) { const { t } = useTranslation() const { resolvedTheme, themeReady } = useChartTheme() const spec = useMemo(() => { if (props.series.length === 0) return null const data = props.series.map((point) => ({ time: formatHourLabel(point.timestamp), group: point.group, ttft: point.ttft_ms, })) return { type: 'line' as const, data: [{ id: 'latency', values: data }], xField: 'time', yField: 'ttft', seriesField: 'group', smooth: true, point: { visible: true, style: { size: 5, stroke: '#ffffff', lineWidth: 1.5 }, }, line: { style: { lineWidth: 2 }, }, legends: { visible: false }, tooltip: { mark: { title: { value: (d: { time: string }) => d.time }, content: [ { key: t('Average TTFT'), value: (d: { ttft: number }) => `${Math.round(d.ttft)} ms`, }, ], }, }, axes: [ { orient: 'bottom', label: { style: { fill: 'currentColor', fontSize: 10 }, }, tick: { visible: false }, }, { orient: 'left', label: { formatMethod: (val: number | string) => `${val} ms`, style: { fill: 'currentColor', fontSize: 10 }, }, grid: { visible: true, style: { lineDash: [3, 3] } }, }, ], } }, [props.series, t]) if (props.series.length === 0) { return (
{t('No latency data available')}
) } return (
{themeReady && spec && ( )}
) } // --------------------------------------------------------------------------- // Uptime trend chart (24h, point-line chart) // --------------------------------------------------------------------------- export function UptimeTrendChart(props: { series: UptimeDayPoint[] className?: string }) { const { t } = useTranslation() const { resolvedTheme, themeReady } = useChartTheme() const spec = useMemo(() => { if (props.series.length === 0) return null const data = props.series.map((point) => ({ date: formatDayLabel(point.date), uptime: point.uptime_pct, incidents: point.incidents, outage: point.outage_minutes, })) return { type: 'line' as const, data: [{ id: 'uptime', values: data }], xField: 'date', yField: 'uptime', smooth: true, line: { style: { stroke: '#10b981', lineWidth: 2 }, }, point: { visible: true, style: { size: 5, stroke: '#ffffff', lineWidth: 1.5, fill: (datum: { uptime: number }) => { if (datum.uptime >= 99.9) return '#10b981' if (datum.uptime >= 99.0) return '#f59e0b' return '#ef4444' }, }, }, tooltip: { mark: { title: { value: (d: { date: string }) => d.date }, content: [ { key: t('Uptime'), value: (d: { uptime: number }) => `${d.uptime.toFixed(2)}%`, }, { key: t('Incidents'), value: (d: { incidents: number }) => `${d.incidents}`, }, { key: t('Outage'), value: (d: { outage: number }) => `${d.outage} ${t('minutes')}`, }, ], }, }, axes: [ { orient: 'bottom', label: { style: { fill: 'currentColor', fontSize: 10 }, autoLimit: true, }, tick: { visible: false }, }, { orient: 'left', min: 95, max: 100, label: { formatMethod: (val: number | string) => `${val}%`, style: { fill: 'currentColor', fontSize: 10 }, }, grid: { visible: true, style: { lineDash: [3, 3] } }, }, ], } }, [props.series, t]) if (props.series.length === 0) { return (
{t('No uptime data available')}
) } return (
{themeReady && spec && ( )}
) } // --------------------------------------------------------------------------- // Throughput by group (horizontal bar) // --------------------------------------------------------------------------- export function ThroughputBarChart(props: { rows: { group: string; throughput_tps: number }[] className?: string }) { const { t } = useTranslation() const { resolvedTheme, themeReady } = useChartTheme() const filtered = useMemo( () => props.rows.filter((r) => r.throughput_tps > 0), [props.rows] ) const spec = useMemo(() => { if (filtered.length === 0) return null return { type: 'bar' as const, direction: 'horizontal' as const, data: [{ id: 'tput', values: filtered.map((r) => ({ ...r })) }], xField: 'throughput_tps', yField: 'group', bar: { style: { fill: '#6366f1', cornerRadius: 2 }, }, label: { visible: true, position: 'right', style: { fontSize: 11, fill: 'currentColor' }, formatMethod: (text: string) => `${text} t/s`, }, axes: [ { orient: 'left', label: { style: { fill: 'currentColor', fontSize: 10 } }, tick: { visible: false }, }, { orient: 'bottom', label: { style: { fill: 'currentColor', fontSize: 10 } }, grid: { visible: true, style: { lineDash: [3, 3] } }, }, ], tooltip: { mark: { title: { value: (d: { group: string }) => d.group }, content: [ { key: t('Throughput'), value: (d: { throughput_tps: number }) => `${d.throughput_tps.toFixed(1)} t/s`, }, ], }, }, } }, [filtered, t]) if (filtered.length === 0) { return null } return (
{themeReady && spec && ( )}
) }