spb/tools-spboucher-ai
Public
1import Link from "next/link";2import { adminStats } from "@/lib/admin/tools";3import { fmtDateTime } from "@/lib/utils";4function Stat({ k, v, href, tone }: { k: string; v: React.ReactNode; href?: string; tone?: string }) { const c = <div className={`card p-4 ${tone ?? ""}`}><p className="eyebrow">{k}</p><p className="display mt-1 text-3xl">{v}</p></div>; return href ? <Link href={href}>{c}</Link> : c; }5export default async function Dashboard() {6 const s = await adminStats();7 return (8 <div className="space-y-8">9 <div><h1 className="display h1">Dashboard</h1><p className="mt-1 text-sm text-fg-2">Editorial and operational health at a glance. Last audit entry {fmtDateTime(s.lastAudit)}.</p></div>10 <div className="grid grid-cols-2 gap-3 md:grid-cols-4 xl:grid-cols-6">11 <Stat k="Published" v={s.published} href="/admin/tools?status=published" /><Stat k="In-depth" v={s.deep} href="/admin/tools?depth=deep" /><Stat k="Awaiting review" v={s.reviewed} href="/admin/review" /><Stat k="Drafts" v={s.draft} href="/admin/tools?status=draft" /><Stat k="Private" v={s.private} href="/admin/tools?visibility=private" /><Stat k="Total records" v={s.total} href="/admin/tools" />12 <Stat k="Broken links" v={s.broken} href="/admin/links" tone={s.broken ? "border-bad/40" : ""} /><Stat k="Unchecked" v={s.unchecked} href="/admin/links" /><Stat k="Flags" v={s.flags} href="/admin/review" /><Stat k="Open reports" v={s.reports} href="/admin/review#reports" /><Stat k="Jobs queued" v={`${s.queued}${s.running ? ` +${s.running} running` : ""}`} href="/admin/jobs" /><Stat k="Dead jobs" v={s.dead} href="/admin/jobs?status=dead" tone={s.dead ? "border-warn/40" : ""} />13 </div>14 <div className="grid gap-6 lg:grid-cols-2">15 <section className="card p-5"><h2 className="font-semibold">Coverage by discipline (published)</h2>16 <table className="table mt-3"><thead><tr><th>Discipline</th><th className="text-right">Tools</th><th className="text-right">In-depth</th></tr></thead><tbody>{s.byCat.map((c: any) => <tr key={c.name}><td>{c.name}</td><td className="text-right">{c.n}</td><td className="text-right">{c.deep}</td></tr>)}</tbody></table></section>17 <section className="card p-5"><h2 className="font-semibold">Evidence confidence (published)</h2>18 <ul className="mt-3 space-y-2">{s.byConf.map((c: any) => <li key={c.confidence} className="flex items-center gap-3 text-sm"><span className="w-20 capitalize">{c.confidence}</span><span className="h-2 flex-1 rounded bg-bg-3"><span className="block h-2 rounded bg-accent" style={{ width: `${(100 * c.n) / Math.max(1, s.published)}%` }} /></span><span className="w-10 text-right">{c.n}</span></li>)}</ul>19 <h2 className="mt-6 font-semibold">Owner inventory</h2><p className="mt-2 text-sm text-fg-2">{s.active} active · {s.testing} testing · {s.candidate} candidates — <Link href="/admin/owner" className="text-accent">open dashboard</Link></p>20 <h2 className="mt-6 font-semibold">AI usage this month</h2><p className="mt-2 text-sm text-fg-2">{s.aiUsage ? `${(s.aiUsage as any).calls} calls · ${Number((s.aiUsage as any).usd).toFixed(3)} USD` : "No AI calls (provider not configured or unused)."}</p></section>21 </div>22 </div>23 );24}25