xpeditis2.0/apps/frontend/app/carrier/dashboard/page.tsx
2025-12-05 13:55:40 +01:00

215 lines
6.7 KiB
TypeScript

'use client';
import { useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import {
FileText,
CheckCircle,
XCircle,
Clock,
TrendingUp,
DollarSign,
Euro,
Activity,
} from 'lucide-react';
interface DashboardStats {
totalBookings: number;
pendingBookings: number;
acceptedBookings: number;
rejectedBookings: number;
acceptanceRate: number;
totalRevenue: {
usd: number;
eur: number;
};
recentActivities: any[];
}
export default function CarrierDashboardPage() {
const router = useRouter();
const [stats, setStats] = useState<DashboardStats | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchStats();
}, []);
const fetchStats = async () => {
try {
const token = localStorage.getItem('carrier_access_token');
const response = await fetch('http://localhost:4000/api/v1/carrier-dashboard/stats', {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (!response.ok) throw new Error('Failed to fetch stats');
const data = await response.json();
setStats(data);
} catch (error) {
console.error('Error fetching stats:', error);
} finally {
setLoading(false);
}
};
if (loading) {
return (
<div className="flex items-center justify-center h-96">
<div className="text-center">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
<p className="text-gray-600">Chargement...</p>
</div>
</div>
);
}
if (!stats) {
return <div>Erreur de chargement des statistiques</div>;
}
const statCards = [
{
title: 'Total Réservations',
value: stats.totalBookings,
icon: FileText,
color: 'blue',
},
{
title: 'En attente',
value: stats.pendingBookings,
icon: Clock,
color: 'yellow',
},
{
title: 'Acceptées',
value: stats.acceptedBookings,
icon: CheckCircle,
color: 'green',
},
{
title: 'Refusées',
value: stats.rejectedBookings,
icon: XCircle,
color: 'red',
},
];
return (
<div className="space-y-6">
{/* Header */}
<div>
<h1 className="text-3xl font-bold text-gray-900">Tableau de bord</h1>
<p className="text-gray-600 mt-1">Vue d'ensemble de votre activité</p>
</div>
{/* Stats Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{statCards.map((card) => {
const Icon = card.icon;
return (
<div key={card.title} className="bg-white p-6 rounded-lg shadow-sm border">
<div className="flex items-center justify-between mb-4">
<Icon className={`w-8 h-8 text-${card.color}-600`} />
</div>
<h3 className="text-gray-600 text-sm font-medium">{card.title}</h3>
<p className="text-3xl font-bold text-gray-900 mt-2">{card.value}</p>
</div>
);
})}
</div>
{/* Revenue & Acceptance Rate */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Revenue */}
<div className="bg-white p-6 rounded-lg shadow-sm border">
<h2 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
<TrendingUp className="w-5 h-5 mr-2 text-green-600" />
Revenus totaux
</h2>
<div className="space-y-4">
<div className="flex items-center justify-between">
<div className="flex items-center">
<DollarSign className="w-5 h-5 text-green-600 mr-2" />
<span className="text-gray-700">USD</span>
</div>
<span className="text-2xl font-bold text-gray-900">
${stats.totalRevenue.usd.toLocaleString()}
</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center">
<Euro className="w-5 h-5 text-blue-600 mr-2" />
<span className="text-gray-700">EUR</span>
</div>
<span className="text-2xl font-bold text-gray-900">
€{stats.totalRevenue.eur.toLocaleString()}
</span>
</div>
</div>
</div>
{/* Acceptance Rate */}
<div className="bg-white p-6 rounded-lg shadow-sm border">
<h2 className="text-lg font-semibold text-gray-900 mb-4 flex items-center">
<Activity className="w-5 h-5 mr-2 text-blue-600" />
Taux d'acceptation
</h2>
<div className="flex items-center justify-center h-32">
<div className="text-center">
<div className="text-5xl font-bold text-blue-600">
{stats.acceptanceRate.toFixed(1)}%
</div>
<p className="text-gray-600 mt-2">
{stats.acceptedBookings} acceptées / {stats.totalBookings} total
</p>
</div>
</div>
</div>
</div>
{/* Recent Activities */}
<div className="bg-white p-6 rounded-lg shadow-sm border">
<h2 className="text-lg font-semibold text-gray-900 mb-4">Activité récente</h2>
{stats.recentActivities.length > 0 ? (
<div className="space-y-3">
{stats.recentActivities.map((activity) => (
<div
key={activity.id}
className="flex items-center justify-between p-3 bg-gray-50 rounded-lg"
>
<div>
<p className="text-gray-900 font-medium">{activity.description}</p>
<p className="text-gray-600 text-sm">
{new Date(activity.createdAt).toLocaleDateString('fr-FR', {
day: 'numeric',
month: 'long',
hour: '2-digit',
minute: '2-digit',
})}
</p>
</div>
<span
className={`px-3 py-1 rounded-full text-xs font-medium ${
activity.type === 'BOOKING_ACCEPTED'
? 'bg-green-100 text-green-800'
: activity.type === 'BOOKING_REJECTED'
? 'bg-red-100 text-red-800'
: 'bg-blue-100 text-blue-800'
}`}
>
{activity.type}
</span>
</div>
))}
</div>
) : (
<p className="text-gray-600 text-center py-8">Aucune activité récente</p>
)}
</div>
</div>
);
}