templates2.Content.Next/app/admin/page.tsx
w4c-templates 3caefb8833
Some checks are pending
CI / quality (push) Waiting to run
chore(template): publish Content.Next
2026-10-11 23:29:57 +03:00

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>
);
}