feat: implement visual analytics and activity charts

This commit is contained in:
syntaxbullet
2026-01-08 21:36:19 +01:00
parent 5d2d4bb0c6
commit 11e07a0068
11 changed files with 433 additions and 13 deletions

View File

@@ -0,0 +1,126 @@
import React from 'react';
import {
AreaChart,
Area,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
ResponsiveContainer,
} from 'recharts';
import type { ActivityData } from '../hooks/use-activity-stats';
interface ActivityChartProps {
data: ActivityData[];
loading?: boolean;
}
const CustomTooltip = ({ active, payload, label }: any) => {
if (active && payload && payload.length) {
const date = new Date(label);
const timeStr = date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
return (
<div className="glass p-3 rounded-lg border border-white/10 text-sm shadow-xl animate-in fade-in zoom-in duration-200">
<p className="font-semibold text-white/90 border-b border-white/10 pb-1 mb-2">
{timeStr}
</p>
<div className="space-y-1">
<p className="flex items-center justify-between gap-4">
<span className="text-blue-400">Commands</span>
<span className="font-mono">{payload[0].value}</span>
</p>
<p className="flex items-center justify-between gap-4">
<span className="text-emerald-400">Transactions</span>
<span className="font-mono">{payload[1].value}</span>
</p>
</div>
</div>
);
}
return null;
};
export const ActivityChart: React.FC<ActivityChartProps> = ({ data, loading }) => {
if (loading) {
return (
<div className="w-full h-[300px] flex items-center justify-center">
<div className="flex flex-col items-center gap-3">
<div className="w-8 h-8 border-4 border-primary/20 border-t-primary rounded-full animate-spin" />
<p className="text-muted-foreground animate-pulse text-sm">Aggregating stats...</p>
</div>
</div>
);
}
// Format hour for XAxis (e.g., "HH:00")
const chartData = data.map(item => ({
...item,
displayTime: new Date(item.hour).getHours() + ':00'
}));
return (
<div className="w-full h-[300px] mt-4">
<ResponsiveContainer width="100%" height="100%">
<AreaChart
data={chartData}
margin={{ top: 10, right: 10, left: -20, bottom: 0 }}
>
<defs>
<linearGradient id="colorCommands" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="var(--color-primary)" stopOpacity={0.3} />
<stop offset="95%" stopColor="var(--color-primary)" stopOpacity={0} />
</linearGradient>
<linearGradient id="colorTransactions" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="oklch(0.7 0.15 160)" stopOpacity={0.3} />
<stop offset="95%" stopColor="oklch(0.7 0.15 160)" stopOpacity={0} />
</linearGradient>
</defs>
<CartesianGrid
strokeDasharray="3 3"
vertical={false}
stroke="rgba(255,255,255,0.05)"
/>
<XAxis
dataKey="hour"
fontSize={10}
tickFormatter={(str) => {
const date = new Date(str);
return date.getHours() % 4 === 0 ? `${date.getHours()}:00` : '';
}}
axisLine={false}
tickLine={false}
stroke="var(--muted-foreground)"
minTickGap={30}
/>
<YAxis
fontSize={10}
axisLine={false}
tickLine={false}
stroke="var(--muted-foreground)"
width={40}
/>
<Tooltip content={<CustomTooltip />} />
<Area
type="monotone"
dataKey="commands"
stroke="var(--color-primary)"
strokeWidth={2}
fillOpacity={1}
fill="url(#colorCommands)"
animationDuration={1500}
/>
<Area
type="monotone"
dataKey="transactions"
stroke="oklch(0.7 0.15 160)"
strokeWidth={2}
fillOpacity={1}
fill="url(#colorTransactions)"
animationDuration={1500}
/>
</AreaChart>
</ResponsiveContainer>
</div>
);
};