Swept the whole housekeeping panel (34 pages) so it's a consistent, improved adaptation of the old Filament admin rather than bare tables: - Live status tiles (StatusCard) summarising each page's data — counts and stateful metrics (users online, active/permanent bans, pending applications, settings, radio shouts, etc.). - Status pills (admin-badge ok/danger) replacing ad-hoc text: online/ offline, account/ip ban types, permanent vs timed, command OK/FAIL, alert severity & read/new, enabled/disabled, open/closed. - Sections wrapped in .admin-section with h2 headers; redundant inline table styling removed (the global .admin layer handles it). - alerts/logs/emulator hand-built as the reference pattern; the rest via parallel agents that read the schema and preserved every server action, query and field name. Behaviour unchanged (same actions, queries, field names). Verified in a real authed admin session (prod, amx_test): pages render with live data (Users tiles show 7 real accounts + online/offline pills), no errors. tsc 0, vitest 49/49, next build 0.
73 lines
2.3 KiB
TypeScript
73 lines
2.3 KiB
TypeScript
import Link from "next/link";
|
|
import { StatusCard } from "@/components/admin/dashboard";
|
|
import { prisma } from "@/lib/prisma";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
export default async function AdminArticles() {
|
|
// .catch(() => []) keeps the precise selected-row type while falling back to
|
|
// an empty list when the DB is unavailable (instead of a 500).
|
|
const articles = await prisma.websiteArticles
|
|
.findMany({
|
|
orderBy: { createdAt: "desc" },
|
|
select: { id: true, title: true, slug: true, createdAt: true },
|
|
take: 50,
|
|
})
|
|
.catch(() => []);
|
|
|
|
const latest = articles[0]?.createdAt
|
|
? articles[0].createdAt.toISOString().slice(0, 10)
|
|
: "—";
|
|
|
|
return (
|
|
<main>
|
|
<div style={{ display: "flex", alignItems: "center", gap: "1rem" }}>
|
|
<h1 style={{ flex: 1 }}>Articles</h1>
|
|
<Link href="/admin/articles/new" className="btn btn-primary">
|
|
+ New article
|
|
</Link>
|
|
</div>
|
|
<p className="muted" style={{ marginTop: "-0.4rem" }}>
|
|
News articles (<code>website_articles</code>) shown on the public site. 50 most recent.
|
|
</p>
|
|
|
|
<div className="admin-stats">
|
|
<StatusCard label="Articles shown" value={articles.length} icon="📰" />
|
|
<StatusCard label="Latest" value={latest} hint="most recent post" icon="🕒" />
|
|
</div>
|
|
|
|
<section className="admin-section">
|
|
<h2>Recent articles</h2>
|
|
{articles.length === 0 ? (
|
|
<p className="muted">No articles yet.</p>
|
|
) : (
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Title</th>
|
|
<th>Slug</th>
|
|
<th>Created</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{articles.map((a) => (
|
|
<tr key={String(a.id)}>
|
|
<td>
|
|
<Link href={`/admin/articles/${a.id}`}>{a.title}</Link>
|
|
</td>
|
|
<td className="muted">
|
|
<code>{a.slug}</code>
|
|
</td>
|
|
<td className="muted" style={{ whiteSpace: "nowrap" }}>
|
|
{a.createdAt ? a.createdAt.toISOString().slice(0, 10) : "—"}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
)}
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|