"use client"; import { useState, useEffect, useCallback, useTransition } from "react"; import NoteCard from "./NoteCard"; import NoteForm from "./NoteForm"; import QueryBench from "./QueryBench"; import DbInfo from "./DbInfo"; import { fetchNotes, fetchStats, createNote, updateNote, deleteNote, type Note, } from "@/lib/actions"; type Stats = { total: number; pinned: number; latest: Date | null; colors: { color: string; count: number }[]; }; type Tab = "notes" | "bench" | "db"; export default function NotesClient({ initialNotes, initialQueryMs, initialStats, initialStatsMs, }: { initialNotes: Note[]; initialQueryMs: number; initialStats: Stats; initialStatsMs: number; }) { const [notes, setNotes] = useState(initialNotes); const [stats, setStats] = useState(initialStats); const [queryMs, setQueryMs] = useState(initialQueryMs); const [statsMs, setStatsMs] = useState(initialStatsMs); const [search, setSearch] = useState(""); const [editing, setEditing] = useState(null); const [showForm, setShowForm] = useState(false); const [tab, setTab] = useState("notes"); const [isPending, startTransition] = useTransition(); const [lastAction, setLastAction] = useState<{ name: string; ms: number } | null>(null); const reload = useCallback(() => { startTransition(async () => { const [nr, sr] = await Promise.all([ fetchNotes(search || undefined), fetchStats(), ]); setNotes(nr.data); setQueryMs(nr.ms); setStats(sr.data); setStatsMs(sr.ms); }); }, [search]); useEffect(() => { reload(); }, [reload]); const onSave = async (data: { title: string; content: string; color: string; pinned: boolean }) => { const r = editing ? await updateNote(editing.id, data) : await createNote(data); setLastAction({ name: editing ? "UPDATE" : "CREATE", ms: r.ms }); setEditing(null); setShowForm(false); reload(); }; const onDel = async (id: string) => { const r = await deleteNote(id); setLastAction({ name: "DELETE", ms: r.ms }); reload(); }; const onPin = async (note: Note) => { const r = await updateNote(note.id, { pinned: !note.pinned }); setLastAction({ name: "PIN", ms: r.ms }); reload(); }; const tabs: { key: Tab; label: string; icon: string }[] = [ { key: "notes", label: "Notes", icon: "📝" }, { key: "bench", label: "Benchmark", icon: "⚡" }, { key: "db", label: "Database", icon: "💾" }, ]; return (

Nexova Server Actions

{tabs.map((t) => ( ))}
{tab === "notes" && ( <>
📊 {stats.total} notes 📌 {stats.pinned} pinned {stats.latest && ( 🕐 {new Date(stats.latest).toLocaleString()} )}
{stats.colors.map((c) => ( ))}
🔍 Query: {queryMs}ms 📊 Stats: {statsMs}ms {lastAction && ( ✅ {lastAction.name}: {lastAction.ms}ms )} {isPending && ⏳ loading...} server-side via Prisma
setSearch(e.target.value)} className="flex-1 min-w-0 bg-zinc-900 border border-zinc-800 rounded-lg px-3 sm:px-4 py-2.5 text-sm focus:outline-none focus:border-emerald-500 transition" />
{showForm && ( { setShowForm(false); setEditing(null); }} /> )} {notes.length === 0 ? (

📝

No notes yet. Create one!

) : (
{notes.map((n) => ( { setEditing(n); setShowForm(true); }} onDelete={() => onDel(n.id)} onPin={() => onPin(n)} /> ))}
)} )} {tab === "bench" && } {tab === "db" && }
); } const msColor = (ms: number) => ms < 1 ? "text-emerald-400" : ms < 5 ? "text-yellow-400" : ms < 20 ? "text-orange-400" : "text-red-400";