Nexova / src /components /QueryBench.tsx
Nexova
refactor: server actions + SSR, fix backup/restore scripts, responsive
4a70373
"use client";
import { useState } from "react";
import { runBenchmark } from "@/lib/actions";
type BenchResult = { name: string; ms: number; rows?: number };
const badge = (ms: number) =>
ms < 1 ? "text-emerald-400" : ms < 5 ? "text-yellow-400" : ms < 20 ? "text-orange-400" : "text-red-400";
const bar = (ms: number, max: number) =>
Math.max(4, Math.min(100, (ms / max) * 100));
export default function QueryBench() {
const [results, setResults] = useState<BenchResult[]>([]);
const [total, setTotal] = useState(0);
const [loading, setLoading] = useState(false);
const [runs, setRuns] = useState<number[]>([]);
const run = async () => {
setLoading(true);
try {
const r = await runBenchmark();
setResults(r.results);
setTotal(r.totalMs);
setRuns((p) => [...p.slice(-9), r.totalMs]);
} finally {
setLoading(false);
}
};
const max = results.length ? Math.max(...results.map((r) => r.ms), 0.1) : 1;
const avg = runs.length ? +(runs.reduce((a, b) => a + b, 0) / runs.length).toFixed(2) : 0;
return (
<div className="space-y-4">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div>
<h2 className="text-lg font-bold">⚡ Query Benchmark</h2>
<p className="text-xs text-zinc-500">SQLite query performance — direct server-side via Prisma (no API roundtrip)</p>
</div>
<button
onClick={run}
disabled={loading}
className="bg-emerald-600 hover:bg-emerald-500 disabled:bg-zinc-700 px-5 py-2.5 rounded-lg text-sm font-medium transition shrink-0"
>
{loading ? "Running..." : "🚀 Run Benchmark"}
</button>
</div>
{results.length > 0 && (
<>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3">
<Stat label="Total" value={`${total}ms`} />
<Stat label="Queries" value={`${results.length}`} />
<Stat label="Avg/query" value={`${(total / results.length).toFixed(2)}ms`} />
<Stat label={`Avg (${runs.length} runs)`} value={`${avg}ms`} />
</div>
<div className="bg-zinc-900 border border-zinc-800 rounded-xl overflow-hidden overflow-x-auto">
<div className="min-w-[480px]">
<div className="grid grid-cols-[1fr_80px_60px_1fr] gap-2 px-4 py-2.5 border-b border-zinc-800 text-[11px] text-zinc-500 font-medium uppercase tracking-wider">
<span>Query</span>
<span className="text-right">Time</span>
<span className="text-right">Rows</span>
<span>Speed</span>
</div>
{results.map((r, i) => (
<div
key={i}
className="grid grid-cols-[1fr_80px_60px_1fr] gap-2 px-4 py-3 border-b border-zinc-800/50 hover:bg-zinc-800/30 transition text-sm items-center"
>
<span className="font-mono text-xs truncate">{r.name}</span>
<span className={`text-right font-mono text-xs font-bold ${badge(r.ms)}`}>
{r.ms}ms
</span>
<span className="text-right text-xs text-zinc-500">
{r.rows !== undefined ? r.rows : "—"}
</span>
<div className="h-2 bg-zinc-800 rounded-full overflow-hidden">
<div
className={`h-full rounded-full transition-all duration-500 ${r.ms < 1 ? "bg-emerald-500" : r.ms < 5 ? "bg-yellow-500" : r.ms < 20 ? "bg-orange-500" : "bg-red-500"}`}
style={{ width: `${bar(r.ms, max)}%` }}
/>
</div>
</div>
))}
</div>
</div>
<div className="flex flex-wrap gap-3 text-[11px] text-zinc-500">
<span className="flex items-center gap-1.5"><span className="w-2.5 h-2.5 rounded-full bg-emerald-500" />&lt;1ms</span>
<span className="flex items-center gap-1.5"><span className="w-2.5 h-2.5 rounded-full bg-yellow-500" />1-5ms</span>
<span className="flex items-center gap-1.5"><span className="w-2.5 h-2.5 rounded-full bg-orange-500" />5-20ms</span>
<span className="flex items-center gap-1.5"><span className="w-2.5 h-2.5 rounded-full bg-red-500" />&gt;20ms</span>
</div>
{runs.length > 1 && (
<div className="bg-zinc-900 border border-zinc-800 rounded-xl p-4">
<p className="text-xs text-zinc-500 mb-2">Run History (total ms)</p>
<div className="flex items-end gap-1 h-16">
{runs.map((r, i) => {
const h = Math.max(8, (r / Math.max(...runs)) * 100);
return (
<div key={i} className="flex-1 flex flex-col items-center gap-1">
<span className="text-[9px] text-zinc-500">{r.toFixed(0)}</span>
<div
className={`w-full rounded-t ${r < 20 ? "bg-emerald-600" : r < 50 ? "bg-yellow-600" : "bg-red-600"}`}
style={{ height: `${h}%` }}
/>
</div>
);
})}
</div>
</div>
)}
</>
)}
</div>
);
}
function Stat({ label, value }: { label: string; value: string }) {
return (
<div className="bg-zinc-900 border border-zinc-800 rounded-lg p-3 text-center">
<p className="text-lg font-bold font-mono">{value}</p>
<p className="text-[10px] text-zinc-500 mt-0.5">{label}</p>
</div>
);
}