54 lines
2.1 KiB
TypeScript
54 lines
2.1 KiB
TypeScript
import Link from 'next/link';
|
|
import { Card } from '@/components/ui';
|
|
import { requireContentStaff } from '@/lib/server/auth';
|
|
import { contentOverview } from '@/lib/server/content';
|
|
import { formatDate } from '@/lib/utils';
|
|
import { t } from '@/lib/i18n';
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
export default async function AdminOverviewPage() {
|
|
await requireContentStaff();
|
|
const data = await contentOverview();
|
|
const stats = [
|
|
{ label: t('admin.publishedPosts'), value: data.published, href: '/admin/posts?status=published' },
|
|
{ label: t('admin.drafts'), value: data.drafts, href: '/admin/posts?status=draft' },
|
|
{ label: t('admin.docsTotal'), value: data.docs, href: '/admin/docs' },
|
|
{ label: t('admin.tagsTotal'), value: data.tags, href: '/tags' },
|
|
];
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<h1 className="text-2xl font-bold">{t('admin.overview')}</h1>
|
|
<div className="grid gap-5 sm:grid-cols-2 lg:grid-cols-4">
|
|
{stats.map((s) => (
|
|
<Link key={s.label} href={s.href} className="card block">
|
|
<p className="muted text-sm">{s.label}</p>
|
|
<p className="mt-2 text-3xl font-bold">{s.value}</p>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
|
|
<Card title={t('admin.recentActivity')} actions={<Link href="/admin/audit" className="btn-secondary">{t('admin.audit')}</Link>}>
|
|
{data.recent.length === 0 ? (
|
|
<p className="muted text-sm">{t('common.empty')}</p>
|
|
) : (
|
|
<ul className="divide-y" style={{ borderColor: 'var(--border)' }}>
|
|
{data.recent.map((entry) => (
|
|
<li key={entry.id} className="flex items-center justify-between gap-4 py-2 text-sm">
|
|
<span>
|
|
<span className="font-medium">{entry.action}</span>
|
|
{entry.target ? <span className="muted"> → {entry.target}</span> : null}
|
|
</span>
|
|
<span className="muted shrink-0 text-xs">
|
|
{entry.actor?.email ?? 'system'} · {formatDate(entry.createdAt)}
|
|
</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
)}
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|