import { useState, useMemo } from "react"; import { useQuery } from "@tanstack/react-query"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Calendar } from "@/components/ui/calendar"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { useAuthUnified } from "@/hooks/useAuthUnified"; import { useStore } from "@/contexts/StoreContext"; import { CalendarIcon, Download, RefreshCw, TrendingUp, Package, CheckCircle2, Euro, Store, Truck, BarChart3, PieChart, Activity, FileDown } from "lucide-react"; import { addDays, format, startOfMonth, endOfMonth, startOfWeek, endOfWeek, startOfYear, endOfYear } from "date-fns"; import { fr } from "date-fns/locale"; import { LineChart, Line, BarChart, Bar, PieChart as RechartsChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts"; // Couleurs pour les graphiques const COLORS = ['#0088FE', '#00C49F', '#FFBB28', '#FF8042', '#8884D8', '#82CA9D', '#FFC658', '#8DD1E1', '#D084D0', '#FFB3BA']; export default function Analytics() { const { user } = useAuthUnified(); const { selectedStoreId } = useStore(); // États des filtres - élargir la période par défaut pour inclure plus de données const [dateRange, setDateRange] = useState({ from: addDays(new Date(), -365), // Dernière année to: new Date() }); const [selectedSuppliers, setSelectedSuppliers] = useState([]); const [selectedStores, setSelectedStores] = useState([]); const [selectedStatus, setSelectedStatus] = useState([]); const [granularity, setGranularity] = useState<'day' | 'week' | 'month'>('day'); const [isRefreshing, setIsRefreshing] = useState(false); // Périodes rapides const quickPeriods = [ { label: "Aujourd'hui", value: () => ({ from: new Date(), to: new Date() }) }, { label: "Cette semaine", value: () => ({ from: startOfWeek(new Date(), { locale: fr }), to: endOfWeek(new Date(), { locale: fr }) }) }, { label: "Ce mois", value: () => ({ from: startOfMonth(new Date()), to: endOfMonth(new Date()) }) }, { label: "Cette année", value: () => ({ from: startOfYear(new Date()), to: endOfYear(new Date()) }) }, { label: "30 derniers jours", value: () => ({ from: addDays(new Date(), -30), to: new Date() }) }, { label: "90 derniers jours", value: () => ({ from: addDays(new Date(), -90), to: new Date() }) } ]; // Construction des paramètres de requête const queryParams = useMemo(() => { const params = new URLSearchParams(); if (dateRange.from) params.append('startDate', dateRange.from.toISOString()); if (dateRange.to) params.append('endDate', dateRange.to.toISOString()); if (selectedSuppliers.length) params.append('supplierIds', selectedSuppliers.join(',')); if (selectedStores.length) params.append('groupIds', selectedStores.join(',')); if (selectedStatus.length) params.append('status', selectedStatus.join(',')); return params.toString(); }, [dateRange, selectedSuppliers, selectedStores, selectedStatus]); // Récupération des données const { data: summary, isLoading: summaryLoading } = useQuery({ queryKey: ['/api/analytics/summary', queryParams], queryFn: async () => { console.log('📊 [ANALYTICS] Fetching summary with params:', queryParams); const response = await fetch(`/api/analytics/summary?${queryParams}`, { credentials: 'include' }); if (!response.ok) { console.error('❌ [ANALYTICS] Summary fetch failed:', response.status, response.statusText); throw new Error('Failed to fetch summary'); } const data = await response.json(); console.log('✅ [ANALYTICS] Summary data received:', data); return data; }, refetchInterval: isRefreshing ? 30000 : false }); const { data: timeseries } = useQuery({ queryKey: ['/api/analytics/timeseries', queryParams, granularity], queryFn: async () => { const url = `/api/analytics/timeseries?${queryParams}&granularity=${granularity}`; console.log('📊 [ANALYTICS] Fetching timeseries:', url); const response = await fetch(url, { credentials: 'include' }); if (!response.ok) { console.error('❌ [ANALYTICS] Timeseries fetch failed:', response.status, response.statusText); throw new Error('Failed to fetch timeseries'); } const data = await response.json(); console.log('✅ [ANALYTICS] Timeseries data received:', data?.length || 0, 'entries'); return data; } }); const { data: bySupplier } = useQuery({ queryKey: ['/api/analytics/by-supplier', queryParams], queryFn: async () => { const response = await fetch(`/api/analytics/by-supplier?${queryParams}`, { credentials: 'include' }); if (!response.ok) throw new Error('Failed to fetch by supplier'); return response.json(); } }); const { data: byStore } = useQuery({ queryKey: ['/api/analytics/by-store', queryParams], queryFn: async () => { const response = await fetch(`/api/analytics/by-store?${queryParams}`, { credentials: 'include' }); if (!response.ok) throw new Error('Failed to fetch by store'); return response.json(); } }); const { data: suppliers } = useQuery({ queryKey: ['/api/suppliers'], }); const { data: stores } = useQuery({ queryKey: ['/api/groups'], }); // Fonction d'export const handleExport = async (type: string) => { const response = await fetch(`/api/analytics/export?${queryParams}&type=${type}`, { credentials: 'include' }); if (response.ok) { const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `analytics_${type}_${format(new Date(), 'yyyy-MM-dd')}.csv`; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(url); } }; if (summaryLoading) { return (

Chargement des statistiques...

); } return (
{/* Header */}

Tableau de bord Analytics

Analysez vos performances logistiques

{/* Filtres */} Filtres {/* Périodes rapides */}
{quickPeriods.map(period => ( ))}
{/* Sélecteur de dates */}
date && setDateRange(prev => ({ ...prev, from: date }))} initialFocus /> → date && setDateRange(prev => ({ ...prev, to: date }))} initialFocus />
{/* KPIs */}
Commandes totales
{summary?.totalOrders || 0}
Livraisons totales
{summary?.totalDeliveries || 0}
Taux de réconciliation
{summary?.reconciliationRate ? `${summary.reconciliationRate.toFixed(1)}%` : '0%'}
Montant total
{summary?.totalAmount ? `${summary.totalAmount.toFixed(2)}€` : '0€'}
{/* Graphiques */}
{/* Évolution temporelle */} Évolution temporelle Commandes vs Livraisons {Array.isArray(timeseries) && timeseries.length > 0 ? ( ) : (

Aucune donnée temporelle

Vérifiez la période sélectionnée

)}
{/* Répartition par fournisseur */} Répartition par fournisseur Top 5 fournisseurs {Array.isArray(bySupplier) && bySupplier.length > 0 ? ( entry.supplierName} outerRadius={80} fill="#8884d8" dataKey="deliveries" > {bySupplier.slice(0, 5).map((entry: any, index: number) => ( ))} ) : (

Aucune donnée fournisseur

Aucune livraison trouvée

)}
{/* Performance par magasin */} Performance par magasin Commandes et livraisons {Array.isArray(byStore) && byStore.length > 0 ? ( [value, name]} labelFormatter={(label) => `Magasin : ${label}`} /> ) : (

Aucune donnée magasin

Aucune activité détectée

)}
{/* Top fournisseurs */} Top 5 Fournisseurs Par volume et montant
{Array.isArray(summary?.topSuppliers) && summary.topSuppliers.length > 0 ? ( summary.topSuppliers.map((supplier: any, index: number) => (
{index + 1}

{supplier.name}

{supplier.count} livraisons • {supplier.amount.toFixed(2)}€

{supplier.count}
livraisons
)) ) : (

Aucune donnée disponible pour la période sélectionnée

Essayez d'élargir la plage de dates

)}
{/* Actions d'export */} Export des données
); }