'use client'; import { useEffect } from 'react'; import { useQuery } from '@tanstack/react-query'; import { dashboardApi } from '@/lib/api'; import { Link, useRouter } from '@/i18n/navigation'; import { useTranslations, useLocale } from 'next-intl'; import { ArrowRight, Clock, Package, PackageCheck, PackageX, Plus, TrendingUp, Weight, } from 'lucide-react'; import { Bar, BarChart, CartesianGrid, Cell, Pie, PieChart, ResponsiveContainer, Tooltip, XAxis, YAxis, } from 'recharts'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { PageHeader } from '@/components/ui/PageHeader'; import { EmptyState, Skeleton } from '@/components/ui/states'; import { StatCard } from '@/components/data/stat-card'; import { useSubscription } from '@/lib/context/subscription-context'; import ExportButton from '@/components/ExportButton'; import { CHART_COLORS, CHART_UI, chartAxisProps, chartTooltipStyle } from '@/lib/chart-colors'; export default function DashboardPage() { const router = useRouter(); const { hasFeature, loading: subLoading } = useSubscription(); const t = useTranslations('dashboard.home'); const locale = useLocale(); useEffect(() => { if (!subLoading && !hasFeature('dashboard')) { router.replace('/dashboard/bookings'); } }, [subLoading, hasFeature, router]); const { data: csvKpis, isLoading: csvKpisLoading } = useQuery({ queryKey: ['dashboard', 'csv-booking-kpis'], queryFn: () => dashboardApi.getCsvBookingKPIs(), }); const { data: topCarriers, isLoading: carriersLoading } = useQuery({ queryKey: ['dashboard', 'top-carriers'], queryFn: () => dashboardApi.getTopCarriers(), }); const numberFormat = new Intl.NumberFormat(locale === 'fr' ? 'fr-FR' : 'en-US'); const totalBookings = (csvKpis?.totalAccepted ?? 0) + (csvKpis?.totalRejected ?? 0) + (csvKpis?.totalPending ?? 0); const statusDistribution = csvKpis ? [ { name: t('charts.distribution.accepted'), value: csvKpis.totalAccepted, color: CHART_COLORS.success, }, { name: t('charts.distribution.rejected'), value: csvKpis.totalRejected, color: CHART_COLORS.danger, }, { name: t('charts.distribution.pending'), value: csvKpis.totalPending, color: CHART_COLORS.warning, }, ] : []; const weightDataKey = t('charts.weightByCarrier.weight'); const carrierWeightData = topCarriers ? topCarriers.slice(0, 5).map(c => ({ name: c.carrierName.length > 15 ? c.carrierName.slice(0, 15) + '…' : c.carrierName, [weightDataKey]: Math.round(c.totalWeightKG), })) : []; return ( <> v?.toLocaleString(locale === 'fr' ? 'fr-FR' : 'en-US') || '0', }, { key: 'totalVolumeCBM', label: t('export.totalVolume'), format: v => v?.toFixed(2) || '0', }, { key: 'acceptanceRate', label: t('export.acceptanceRate'), format: v => v?.toFixed(1) || '0', }, { key: 'avgPriceUSD', label: t('export.avgPrice'), format: v => v?.toFixed(2) || '0', }, ]} /> } />
{/* Indicateurs primaires : les quatre chiffres qui repondent a « ou en sommes-nous ». Alignes a gauche pour se comparer en colonne. */}
{/* Indicateurs secondaires : volumetrie et performance, en bandeau compact pour ne pas concurrencer la rangee primaire. */}
{t('charts.distribution.title')} {t('charts.distribution.description')} {csvKpisLoading ? ( ) : totalBookings === 0 ? ( ) : ( `${name} ${((percent || 0) * 100).toFixed(0)}%` } outerRadius={72} innerRadius={44} paddingAngle={2} dataKey="value" > {statusDistribution.map(entry => ( ))} )} {t('charts.weightByCarrier.title')} {t('charts.weightByCarrier.description')} {carriersLoading ? ( ) : carrierWeightData.length === 0 ? ( ) : ( )}
{t('topCarriers.title')} {t('topCarriers.description')}
{carriersLoading ? (
{Array.from({ length: 3 }).map((_, i) => (
))}
) : topCarriers && topCarriers.length > 0 ? (
    {topCarriers.slice(0, 5).map((carrier, index) => (
  • {index + 1}

    {carrier.carrierName}

    {t('topCarriers.bookingsCount', { count: carrier.totalBookings })} {' · '} {numberFormat.format(carrier.totalWeightKG)} kg

    {carrier.acceptedBookings} {carrier.rejectedBookings > 0 && ( {carrier.rejectedBookings} )}

    {carrier.acceptanceRate.toFixed(0)} %

    ${carrier.avgPriceUSD.toFixed(0)}

  • ))}
) : ( {t('topCarriers.empty.cta')} } /> )}
); } /** Indicateur secondaire, en bandeau, sans le poids visuel d'une carte. */ function SecondaryStat({ label, value, icon: Icon, }: { label: string; value: string | null; icon: React.ComponentType<{ className?: string }>; }) { return (
); }