templates2.Booking.Next/app/admin/page.tsx
w4c-templates 6a0c2e88d2
Some checks are pending
CI / quality (push) Waiting to run
chore(template): publish Booking.Next
2026-10-11 23:27:02 +03:00

54 lines
2.2 KiB
TypeScript

import { Card } from '@/components/ui';
import { db } from '@/lib/server/db';
import { siteTimeZone } from '@/lib/server/booking';
import { dayBoundsUtc, todayInTimeZone } from '@/lib/slots';
import { t } from '@/lib/i18n';
export const dynamic = 'force-dynamic';
export default async function AdminOverviewPage() {
const now = new Date();
const today = dayBoundsUtc(todayInTimeZone(siteTimeZone(), now), siteTimeZone());
const [totalUsers, activeSubs, todayBookings, upcoming, recent] = await Promise.all([
db.user.count(),
db.subscription.count({ where: { status: 'active' } }),
today ? db.booking.count({ where: { status: { not: 'cancelled' }, startsAt: { lt: today.end }, endsAt: { gt: today.start } } }) : Promise.resolve(0),
db.booking.count({ where: { status: { in: ['pending', 'confirmed'] }, startsAt: { gte: now } } }),
db.auditLog.findMany({ orderBy: { createdAt: 'desc' }, take: 5, include: { actor: true } }),
]);
const stats = [
{ label: t('admin.todaysBookings'), value: todayBookings },
{ label: t('admin.upcomingBookings'), value: upcoming },
{ 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 lg:grid-cols-4">
{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>
);
}