46 lines
1.6 KiB
TypeScript
46 lines
1.6 KiB
TypeScript
import { Card } from '@/components/ui';
|
|
import { db } from '@/lib/server/db';
|
|
import { t } from '@/lib/i18n';
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
export default async function AdminOverviewPage() {
|
|
const [totalUsers, activeSubs, recent] = await Promise.all([
|
|
db.user.count(),
|
|
db.subscription.count({ where: { status: 'active' } }),
|
|
db.auditLog.findMany({ orderBy: { createdAt: 'desc' }, take: 5, include: { actor: true } }),
|
|
]);
|
|
|
|
const stats = [
|
|
{ label: t('admin.totalUsers'), value: totalUsers },
|
|
{ label: t('admin.activeSubs'), value: activeSubs },
|
|
];
|
|
|
|
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">
|
|
{stats.map((s) => (
|
|
<div key={s.label} className="card">
|
|
<p className="muted text-sm">{s.label}</p>
|
|
<p className="mt-2 text-3xl font-bold">{s.value}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<Card title={t('admin.audit')}>
|
|
<ul className="divide-y" style={{ borderColor: 'var(--border)' }}>
|
|
{recent.map((entry) => (
|
|
<li key={entry.id} className="flex items-center justify-between 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 text-xs">{entry.actor?.email ?? 'system'}</span>
|
|
</li>
|
|
))}
|
|
</ul>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|