SPB Git
3.1 KB · 21 lines tsx
1import Link from "next/link";2import { sql } from "@/db";3import { fmtDateTime } from "@/lib/utils";4import { FlagList, ReportList } from "@/components/admin/review-lists";5export default async function Review() {6  const reviewed = await sql`SELECT id, slug, name, quality_score AS "qualityScore", link_state AS "linkState", author, depth, updated_at AS "updatedAt" FROM tools WHERE editorial_status='reviewed' AND duplicate_of IS NULL ORDER BY quality_score DESC, name LIMIT 300`;7  const drafts = await sql`SELECT id, slug, name, quality_score AS "qualityScore", updated_at AS "updatedAt" FROM tools WHERE editorial_status='draft' ORDER BY updated_at DESC LIMIT 100`;8  const flags = await sql`SELECT key, value, updated_at AS "updatedAt" FROM settings WHERE key LIKE 'review:%' OR key LIKE 'stale:%' ORDER BY updated_at DESC`;9  const reports = await sql`SELECT id, tool_slug AS "toolSlug", message, contact, status, created_at AS "createdAt" FROM error_reports ORDER BY (status='open') DESC, created_at DESC LIMIT 100`;10  return (11    <div className="space-y-10">12      <div><h1 className="display h1">Review queue</h1><p className="mt-1 text-sm text-fg-2">Records awaiting a publishing decision, automated flags (source changes, staleness), and public error reports.</p></div>13      <section><h2 className="font-semibold">Awaiting publication ({reviewed.length})</h2>14        <div className="mt-2 overflow-x-auto rounded-2xl border border-line bg-bg-2"><table className="table"><thead><tr><th>Record</th><th>Depth</th><th>Quality</th><th>Links</th><th>Author</th><th>Updated</th></tr></thead><tbody>{reviewed.map((t) => <tr key={t.id}><td><Link href={`/admin/tools/${t.id}`} className="font-medium hover:text-accent">{t.name}</Link> <span className="text-xs text-fg-3">{t.slug}</span></td><td>{t.depth}</td><td className={t.qualityScore < 40 ? "text-bad" : ""}>{t.qualityScore}</td><td>{t.linkState}</td><td className="text-fg-2">{t.author}</td><td className="text-fg-3">{fmtDateTime(t.updatedAt)}</td></tr>)}{!reviewed.length && <tr><td colSpan={6} className="text-fg-3">Nothing waiting.</td></tr>}</tbody></table></div></section>15      {drafts.length > 0 && <section><h2 className="font-semibold">Drafts ({drafts.length})</h2><ul className="mt-2 grid gap-2 sm:grid-cols-2 lg:grid-cols-3">{drafts.map((d) => <li key={d.id} className="card p-3 text-sm"><Link href={`/admin/tools/${d.id}`} className="font-medium hover:text-accent">{d.name}</Link><span className="block text-xs text-fg-3">quality {d.qualityScore} · {fmtDateTime(d.updatedAt)}</span></li>)}</ul></section>}16      <section id="flags"><h2 className="font-semibold">Automated flags ({flags.length})</h2><p className="mt-1 text-xs text-fg-3">`review:` = official page content or canonical URL changed since last scan; `stale:` = freshness policy exceeded (pricing 90 d, review 365 d, sources 180 d).</p><FlagList flags={JSON.parse(JSON.stringify(flags))} /></section>17      <section id="reports"><h2 className="font-semibold">Error reports ({reports.filter((r) => r.status === "open").length} open)</h2><ReportList reports={JSON.parse(JSON.stringify(reports))} /></section>18    </div>19  );20}21
Files · main
Menu

Theme