templates2.Admin.Next/app/admin/page.tsx
w4c-templates c64f7ffa9e
Some checks failed
CI / quality (push) Has been cancelled
chore(template): publish Admin.Next
2026-10-11 23:25:34 +03:00

76 lines
2.9 KiB
TypeScript

import Link from 'next/link';
import { Card } from '@/components/ui';
import { requireStaff } from '@/lib/server/auth';
import { overview } from '@/lib/server/crm';
import { formatCurrency, formatDate } from '@/lib/utils';
import { t } from '@/lib/i18n';
export const dynamic = 'force-dynamic';
export default async function AdminOverviewPage() {
await requireStaff();
const data = await overview();
const maxStage = Math.max(1, ...data.byStage.map((s) => s.count));
const counters = [
{ label: t('admin.customersTotal'), value: String(data.customers), href: '/admin/customers' },
{ label: t('admin.leads'), value: String(data.leads), href: '/admin/customers?status=lead' },
{ label: t('admin.dealsTotal'), value: String(data.deals), href: '/admin/deals' },
{ label: t('admin.wonValue'), value: formatCurrency(data.wonValue), href: '/admin/deals?stage=won' },
];
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">
{counters.map((c) => (
<Link key={c.label} href={c.href} className="card block">
<p className="muted text-sm">{c.label}</p>
<p className="mt-2 text-3xl font-bold">{c.value}</p>
</Link>
))}
</div>
<Card title={t('admin.dealStages')}>
<ul className="space-y-4">
{data.byStage.map((s) => (
<li key={s.stage}>
<div className="mb-1 flex justify-between text-sm">
<span>{t(`admin.${s.stage}`)}</span>
<span className="muted">{s.count}</span>
</div>
<div className="h-2 w-full overflow-hidden rounded-full" style={{ background: 'var(--border)' }}>
<div
className="h-full rounded-full"
style={{ width: `${Math.round((s.count / maxStage) * 100)}%`, background: 'var(--color-brand-600)' }}
/>
</div>
</li>
))}
</ul>
</Card>
<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} · {formatDate(entry.createdAt)}
</span>
</li>
))}
</ul>
)}
</Card>
</div>
);
}