111 lines
4.5 KiB
TypeScript
111 lines
4.5 KiB
TypeScript
'use client'
|
||
|
||
import { useEffect, useState } from 'react'
|
||
import Link from 'next/link'
|
||
import { useAdminI18n } from '@/components/I18nProvider'
|
||
import { ADMIN_API_BASE } from '@/lib/api'
|
||
|
||
interface Metrics {
|
||
totalCompanies: number
|
||
activeCompanies: number
|
||
totalRenters: number
|
||
totalReservations: number
|
||
mrr?: number
|
||
}
|
||
|
||
export default function AdminDashboardPage() {
|
||
const { language, dict } = useAdminI18n()
|
||
const copy = {
|
||
en: {
|
||
kpis: ['Total companies', 'Active companies', 'Total renters', 'Total reservations'],
|
||
platformOverview: 'Platform overview',
|
||
cards: [
|
||
['Companies', 'List, search, suspend, reactivate, and review subscription state.', 'View all →'],
|
||
['Renters', 'Support flows for blocking and unblocking marketplace renters.', 'View all →'],
|
||
['Audit logs', 'Full trace of every admin action taken on the platform.', 'View logs →'],
|
||
],
|
||
},
|
||
fr: {
|
||
kpis: ['Entreprises totales', 'Entreprises actives', 'Locataires totaux', 'Réservations totales'],
|
||
platformOverview: 'Vue plateforme',
|
||
cards: [
|
||
['Entreprises', 'Lister, rechercher, suspendre, réactiver et revoir l’état des abonnements.', 'Voir tout →'],
|
||
['Locataires', 'Flux de support pour bloquer et débloquer les locataires marketplace.', 'Voir tout →'],
|
||
['Journaux d’audit', 'Trace complète de chaque action admin sur la plateforme.', 'Voir les journaux →'],
|
||
],
|
||
},
|
||
ar: {
|
||
kpis: ['إجمالي الشركات', 'الشركات النشطة', 'إجمالي المستأجرين', 'إجمالي الحجوزات'],
|
||
platformOverview: 'نظرة عامة على المنصة',
|
||
cards: [
|
||
['الشركات', 'اعرض وابحث وعلّق وأعد التفعيل وراجع حالة الاشتراك.', 'عرض الكل ←'],
|
||
['المستأجرون', 'مسارات دعم لحظر وفك حظر مستأجري السوق.', 'عرض الكل ←'],
|
||
['سجلات التدقيق', 'تتبع كامل لكل إجراء إداري تم على المنصة.', 'عرض السجلات ←'],
|
||
],
|
||
},
|
||
}[language]
|
||
const [metrics, setMetrics] = useState<Metrics | null>(null)
|
||
const [loading, setLoading] = useState(true)
|
||
|
||
useEffect(() => {
|
||
const token = localStorage.getItem('admin_token') ?? ''
|
||
fetch(`${ADMIN_API_BASE}/admin/metrics`, {
|
||
headers: { Authorization: `Bearer ${token}` },
|
||
cache: 'no-store',
|
||
})
|
||
.then((r) => r.json())
|
||
.then((json) => setMetrics(json.data))
|
||
.catch(() => null)
|
||
.finally(() => setLoading(false))
|
||
}, [])
|
||
|
||
const kpis = metrics
|
||
? [
|
||
{ label: 'Total companies', value: metrics.totalCompanies },
|
||
{ label: copy.kpis[1], value: metrics.activeCompanies },
|
||
{ label: copy.kpis[2], value: metrics.totalRenters },
|
||
{ label: copy.kpis[3], value: metrics.totalReservations },
|
||
]
|
||
: []
|
||
if (kpis.length > 0) kpis[0].label = copy.kpis[0]
|
||
|
||
return (
|
||
<div className="shell py-8 space-y-8">
|
||
<div>
|
||
<p className="text-xs uppercase tracking-[0.2em] text-emerald-400">{dict.admin}</p>
|
||
<h1 className="mt-1 text-3xl font-black">{copy.platformOverview}</h1>
|
||
</div>
|
||
|
||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||
{loading
|
||
? Array.from({ length: 4 }).map((_, i) => (
|
||
<div key={i} className="panel p-6 animate-pulse">
|
||
<div className="h-3 w-24 rounded bg-zinc-800" />
|
||
<div className="mt-3 h-8 w-16 rounded bg-zinc-800" />
|
||
</div>
|
||
))
|
||
: kpis.map((kpi) => (
|
||
<div key={kpi.label} className="panel p-6">
|
||
<p className="text-xs text-zinc-500">{kpi.label}</p>
|
||
<p className="mt-1 text-3xl font-black">{kpi.value?.toLocaleString() ?? '—'}</p>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<div className="grid gap-6 md:grid-cols-3">
|
||
{[
|
||
['/dashboard/companies', ...copy.cards[0]],
|
||
['/dashboard/renters', ...copy.cards[1]],
|
||
['/dashboard/audit-logs', ...copy.cards[2]],
|
||
].map(([href, title, body, cta]) => (
|
||
<Link key={href} href={href} className="panel p-6 hover:border-zinc-700 transition-colors block">
|
||
<p className="text-sm font-semibold text-zinc-200">{title}</p>
|
||
<p className="mt-2 text-sm text-zinc-500">{body}</p>
|
||
<p className="mt-4 text-xs text-emerald-400 font-medium">{cta}</p>
|
||
</Link>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|