Files
carmanagement/apps/admin/src/app/dashboard/page.tsx
T
2026-05-17 17:37:13 -04:00

111 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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 daudit', '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>
)
}