130 lines
4.4 KiB
TypeScript
130 lines
4.4 KiB
TypeScript
import { useQuery } from '@tanstack/react-query';
|
|
import { adminApi, api } from '../../api';
|
|
import { Activity, AlertTriangle, Car, CheckCircle2, RefreshCw, Route, Users } from 'lucide-react';
|
|
|
|
export default function AdminStatsCards() {
|
|
const { data, isLoading, isError, refetch } = useQuery({
|
|
queryKey: ['admin-stats'],
|
|
queryFn: async () => {
|
|
try {
|
|
const res = await adminApi.get('/api/admin/stats');
|
|
return res.data;
|
|
} catch {
|
|
const res = await api.get('/api/admin/stats');
|
|
return res.data;
|
|
}
|
|
},
|
|
refetchInterval: 10000,
|
|
});
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4">
|
|
{[1, 2, 3, 4, 5].map((i) => (
|
|
<div key={i} className="h-28 rounded-xl bg-slate-100 dark:bg-slate-800 animate-pulse border border-slate-200 dark:border-slate-800 p-4" />
|
|
))}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
if (isError) {
|
|
return (
|
|
<div className="rounded-xl border border-red-200 bg-red-50 p-4 text-red-800 dark:border-red-900/50 dark:bg-red-950/30 dark:text-red-300 flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<AlertTriangle className="h-5 w-5 text-red-600 dark:text-red-400 shrink-0" />
|
|
<span className="text-sm font-medium">Failed to load platform telemetry stats.</span>
|
|
</div>
|
|
<button
|
|
onClick={() => refetch()}
|
|
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold rounded-lg bg-red-100 hover:bg-red-200 text-red-900 dark:bg-red-900 dark:hover:bg-red-800 dark:text-red-100 transition-colors"
|
|
>
|
|
<RefreshCw className="h-3.5 w-3.5" />
|
|
Retry
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const stats = data?.stats || {
|
|
total_users: 0,
|
|
active_drivers: 0,
|
|
total_rides: 0,
|
|
active_rides: 0,
|
|
completed_rides: 0,
|
|
};
|
|
|
|
const cards = [
|
|
{
|
|
title: 'Total Users',
|
|
value: stats.total_users ?? 0,
|
|
icon: Users,
|
|
color: 'text-slate-900 dark:text-slate-100',
|
|
bg: 'bg-slate-100 dark:bg-slate-800',
|
|
description: 'Registered passengers & drivers',
|
|
},
|
|
{
|
|
title: 'Online Drivers',
|
|
value: stats.active_drivers ?? 0,
|
|
icon: Car,
|
|
color: 'text-emerald-600 dark:text-emerald-400',
|
|
bg: 'bg-emerald-50 dark:bg-emerald-950/40',
|
|
description: 'Available for instant dispatch',
|
|
},
|
|
{
|
|
title: 'Total System Rides',
|
|
value: stats.total_rides ?? 0,
|
|
icon: Route,
|
|
color: 'text-sky-600 dark:text-sky-400',
|
|
bg: 'bg-sky-50 dark:bg-sky-950/40',
|
|
description: 'All-time ride requests',
|
|
},
|
|
{
|
|
title: 'Active Ongoing',
|
|
value: stats.active_rides ?? 0,
|
|
icon: Activity,
|
|
color: 'text-amber-600 dark:text-amber-400',
|
|
bg: 'bg-amber-50 dark:bg-amber-950/40',
|
|
description: 'Requested, accepted, or picked up',
|
|
},
|
|
{
|
|
title: 'Completed Trips',
|
|
value: stats.completed_rides ?? 0,
|
|
icon: CheckCircle2,
|
|
color: 'text-teal-600 dark:text-teal-400',
|
|
bg: 'bg-teal-50 dark:bg-teal-950/40',
|
|
description: 'Successfully finished rides',
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-5 gap-4">
|
|
{cards.map((card) => {
|
|
const Icon = card.icon;
|
|
return (
|
|
<div
|
|
key={card.title}
|
|
className="rounded-xl border border-slate-200 dark:border-slate-800 bg-white dark:bg-slate-900 p-4 shadow-sm flex flex-col justify-between transition-all hover:border-slate-300 dark:hover:border-slate-700"
|
|
>
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-xs font-semibold uppercase tracking-wider text-slate-500 dark:text-slate-400">
|
|
{card.title}
|
|
</span>
|
|
<div className={`p-2 rounded-lg ${card.bg}`}>
|
|
<Icon className={`h-4 w-4 ${card.color}`} />
|
|
</div>
|
|
</div>
|
|
<div className="mt-3">
|
|
<div className="text-2xl font-bold tracking-tight text-slate-900 dark:text-white">
|
|
{(card.value ?? 0).toLocaleString()}
|
|
</div>
|
|
<p className="mt-1 text-xs text-slate-500 dark:text-slate-400 line-clamp-1">
|
|
{card.description}
|
|
</p> // Fixed text formatting
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
);
|
|
}
|