import { useState, useEffect } from "react"; import { useQuery } from "@tanstack/react-query"; import { useAuthUnified } from "@/hooks/useAuthUnified"; import { useStore } from "@/components/Layout"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { Calendar, Package, ShoppingCart, TrendingUp, Clock, MapPin, User, AlertTriangle, CheckCircle, Truck, FileText, BarChart3, Megaphone, Shield, XCircle, CheckSquare, Circle, Info, Star, Sparkles, X } from "lucide-react"; import { safeFormat, safeDate } from "@/lib/dateUtils"; import type { PublicityWithRelations, DashboardMessage } from "@shared/schema"; import AnnouncementCard from "@/components/AnnouncementCard"; export default function Dashboard() { const { user } = useAuthUnified(); const { selectedStoreId } = useStore(); const [showAnnouncementModal, setShowAnnouncementModal] = useState(false); const { data: stats } = useQuery({ queryKey: ['/api/stats/monthly', selectedStoreId], queryFn: async () => { const currentDate = new Date(); const params = new URLSearchParams({ year: currentDate.getFullYear().toString(), month: (currentDate.getMonth() + 1).toString(), }); if (selectedStoreId && user?.role === 'admin') { params.append('storeId', selectedStoreId.toString()); } const response = await fetch(`/api/stats/monthly?${params.toString()}`, { credentials: 'include' }); if (!response.ok) { throw new Error('Failed to fetch stats'); } return response.json(); }, }); // Statistiques annuelles pour la carte de délai moyen const { data: yearlyStats } = useQuery({ queryKey: ['/api/stats/yearly', selectedStoreId], queryFn: async () => { const currentDate = new Date(); const params = new URLSearchParams({ year: currentDate.getFullYear().toString(), }); if (selectedStoreId && user?.role === 'admin') { params.append('storeId', selectedStoreId.toString()); } const response = await fetch(`/api/stats/yearly?${params.toString()}`, { credentials: 'include' }); if (!response.ok) { throw new Error('Failed to fetch yearly stats'); } return response.json(); }, }); // Construire les URLs pour récupérer toutes les données (pas de filtrage par date) const ordersUrl = `/api/orders${selectedStoreId && user?.role === 'admin' ? `?storeId=${selectedStoreId}` : ''}`; const deliveriesUrl = `/api/deliveries${selectedStoreId && user?.role === 'admin' ? `?storeId=${selectedStoreId}` : ''}`; const customerOrdersUrl = `/api/customer-orders${selectedStoreId && user?.role === 'admin' ? `?storeId=${selectedStoreId}` : ''}`; // Utiliser les mêmes clés de cache que les autres pages pour assurer la cohérence const { data: allOrders = [] } = useQuery({ queryKey: [ordersUrl, selectedStoreId], }); const { data: allDeliveries = [] } = useQuery({ queryKey: [deliveriesUrl, selectedStoreId], }); const { data: customerOrders = [] } = useQuery({ queryKey: [customerOrdersUrl, selectedStoreId], }); // Récupérer la dernière annonce récente (créée il y a moins de 2 jours) const { data: recentAnnouncement } = useQuery({ queryKey: ['/api/announcements/recent', selectedStoreId], queryFn: async () => { const params = new URLSearchParams(); if (selectedStoreId && user?.role === 'admin') { params.append('storeId', selectedStoreId.toString()); } params.append('recent', 'true'); const response = await fetch(`/api/announcements?${params.toString()}`, { credentials: 'include' }); if (!response.ok) { return null; } const announcements: DashboardMessage[] = await response.json(); // Filtrer les annonces créées il y a moins de 2 jours côté client const twoDaysAgo = new Date(); twoDaysAgo.setDate(twoDaysAgo.getDate() - 2); const recentAnnouncements = announcements.filter(announcement => { if (!announcement.createdAt) return false; const createdAt = safeDate(announcement.createdAt); return createdAt && createdAt >= twoDaysAgo; }); // Retourner la plus récente return recentAnnouncements.length > 0 ? recentAnnouncements.sort((a, b) => { const dateA = safeDate(a.createdAt); const dateB = safeDate(b.createdAt); return (dateB?.getTime() || 0) - (dateA?.getTime() || 0); })[0] : null; }, enabled: !!user, }); // Fonction pour obtenir la configuration des priorités d'annonces const getAnnouncementPriorityConfig = (type: string) => { switch (type) { case 'error': return { label: 'Nouveauté', color: 'bg-purple-100 text-purple-800', icon: Sparkles, iconColor: 'text-purple-600' }; case 'warning': return { label: 'Attention', color: 'bg-orange-100 text-orange-800', icon: AlertTriangle, iconColor: 'text-orange-600' }; case 'success': return { label: 'Important', color: 'bg-red-100 text-red-800', icon: Star, iconColor: 'text-red-600' }; case 'info': default: return { label: 'Information', color: 'bg-blue-100 text-blue-800', icon: Info, iconColor: 'text-blue-600' }; } }; // Effet pour afficher automatiquement le modal si une annonce récente existe useEffect(() => { if (recentAnnouncement && !showAnnouncementModal) { // Vérifier si l'utilisateur a déjà vu cette annonce (localStorage) const dismissedAnnouncements = JSON.parse(localStorage.getItem('dismissedAnnouncements') || '[]'); if (!dismissedAnnouncements.includes(recentAnnouncement.id)) { setShowAnnouncementModal(true); } } }, [recentAnnouncement, showAnnouncementModal]); // Fonction pour fermer le modal et marquer l'annonce comme vue const handleCloseAnnouncementModal = () => { setShowAnnouncementModal(false); if (recentAnnouncement) { const dismissedAnnouncements = JSON.parse(localStorage.getItem('dismissedAnnouncements') || '[]'); if (!dismissedAnnouncements.includes(recentAnnouncement.id)) { dismissedAnnouncements.push(recentAnnouncement.id); localStorage.setItem('dismissedAnnouncements', JSON.stringify(dismissedAnnouncements)); } } }; // Récupérer les publicités à venir (chercher dans 2024 ET 2025) - TOUTES les publicités const { data: upcomingPublicities = [] } = useQuery({ queryKey: ['/api/publicities', 'upcoming'], queryFn: async () => { // Essayer d'abord 2024, puis 2025 pour avoir toutes les publicités const years = [2024, 2025]; let allPublicities: PublicityWithRelations[] = []; for (const year of years) { const params = new URLSearchParams(); params.append('year', year.toString()); // NE PAS filtrer par magasin - on veut toutes les publicités try { const response = await fetch(`/api/publicities?${params}`, { credentials: 'include' }); if (response.ok) { const yearPublicities = await response.json(); allPublicities = [...allPublicities, ...yearPublicities]; } } catch (error) { console.log(`Erreur lors de la récupération des publicités ${year}:`, error); } } // Filtrer les publicités à venir et les trier par date const futurePublicities = allPublicities .filter((publicity: any) => new Date(publicity.startDate) > new Date()) .sort((a: any, b: any) => new Date(a.startDate).getTime() - new Date(b.startDate).getTime()); return futurePublicities; }, }); // Fetch DLC stats const { data: dlcStats = { active: 0, expiringSoon: 0, expired: 0 } } = useQuery({ queryKey: ["/api/dlc-products/stats", selectedStoreId], queryFn: () => { const params = new URLSearchParams(); if (selectedStoreId && user?.role === 'admin') params.append("storeId", selectedStoreId.toString()); return fetch(`/api/dlc-products/stats?${params.toString()}`, { credentials: 'include' }).then(res => res.json()); }, }); // Fetch tasks const { data: tasks = [] } = useQuery({ queryKey: ["/api/tasks", selectedStoreId], queryFn: () => { const params = new URLSearchParams(); if (selectedStoreId && user?.role === 'admin') params.append("storeId", selectedStoreId.toString()); return fetch(`/api/tasks?${params.toString()}`, { credentials: 'include' }).then(res => res.json()); }, }); // Données dérivées pour les sections const recentOrders = Array.isArray(allOrders) ? allOrders .sort((a: any, b: any) => { const dateA = safeDate(a.createdAt); const dateB = safeDate(b.createdAt); return (dateB ? dateB.getTime() : 0) - (dateA ? dateA.getTime() : 0); }) .slice(0, 4) : []; // Afficher les 4 dernières commandes // Toutes les commandes en attente const pendingOrders = Array.isArray(allOrders) ? allOrders .filter((order: any) => order.status === 'pending') .sort((a: any, b: any) => { const dateA = safeDate(a.createdAt); const dateB = safeDate(b.createdAt); return (dateB ? dateB.getTime() : 0) - (dateA ? dateA.getTime() : 0); }) : []; // Afficher toutes les commandes en attente const upcomingDeliveries = Array.isArray(allDeliveries) ? allDeliveries .filter((d: any) => { const isPlanned = d.status === 'planned'; console.log('🚚 Dashboard - Delivery filter:', { id: d.id, status: d.status, isPlanned, scheduledDate: d.scheduledDate }); return isPlanned; }) .sort((a: any, b: any) => new Date(a.scheduledDate).getTime() - new Date(b.scheduledDate).getTime()) .slice(0, 4) : []; // Afficher les 4 prochaines livraisons console.log('🚚 Dashboard - Upcoming deliveries result:', upcomingDeliveries.length, upcomingDeliveries); // Calculs pour les statistiques const pendingOrdersCount = Array.isArray(allOrders) ? allOrders.filter((order: any) => order.status === 'pending').length : 0; const averageDeliveryTime = Math.round(yearlyStats?.averageDeliveryTime || 0); const deliveredThisMonth = Array.isArray(allDeliveries) ? allDeliveries.filter((delivery: any) => { const deliveryDate = safeDate(delivery.deliveredDate || delivery.createdAt); if (!deliveryDate) return false; const now = new Date(); return deliveryDate.getMonth() === now.getMonth() && deliveryDate.getFullYear() === now.getFullYear() && delivery.status === 'delivered'; }).length : 0; // Calculer le total des palettes pour les livraisons 'delivered' du mois en cours const totalPalettes = Array.isArray(allDeliveries) ? allDeliveries.reduce((total: number, delivery: any) => { if (delivery.status === 'delivered' && delivery.unit === 'palettes') { const deliveryDate = safeDate(delivery.deliveredDate || delivery.createdAt); if (!deliveryDate) return total; const now = new Date(); if (deliveryDate.getMonth() === now.getMonth() && deliveryDate.getFullYear() === now.getFullYear()) { return total + (delivery.quantity || 0); } } return total; }, 0) : 0; // Fonction pour obtenir la configuration des priorités (identique au module Tasks) const getPriorityConfig = (priority: string) => { switch (priority) { case 'high': return { color: 'destructive' as const, icon: AlertTriangle, label: 'Élevée' }; case 'medium': return { color: 'default' as const, icon: Clock, label: 'Moyenne' }; case 'low': return { color: 'secondary' as const, icon: Circle, label: 'Faible' }; default: return { color: 'secondary' as const, icon: Circle, label: 'Moyenne' }; } }; console.log('📊 Dashboard Debug - Raw Data:', { allOrders: Array.isArray(allOrders) ? allOrders.length : 'NOT_ARRAY', allDeliveries: Array.isArray(allDeliveries) ? allDeliveries.length : 'NOT_ARRAY', customerOrders: Array.isArray(customerOrders) ? customerOrders.length : 'NOT_ARRAY', upcomingPublicities: Array.isArray(upcomingPublicities) ? upcomingPublicities.length : 'NOT_ARRAY', stats: stats, samples: { order: Array.isArray(allOrders) && allOrders.length > 0 ? allOrders[0] : null, delivery: Array.isArray(allDeliveries) && allDeliveries.length > 0 ? allDeliveries[0] : null, customerOrder: Array.isArray(customerOrders) && customerOrders.length > 0 ? customerOrders[0] : null, publicity: Array.isArray(upcomingPublicities) && upcomingPublicities.length > 0 ? upcomingPublicities[0] : null } }); // Statistiques pour les commandes clients const ordersByStatus = { pending: Array.isArray(allOrders) ? allOrders.filter((o: any) => o.status === 'pending').length : 0, planned: Array.isArray(allOrders) ? allOrders.filter((o: any) => o.status === 'planned').length : 0, delivered: Array.isArray(allOrders) ? allOrders.filter((o: any) => o.status === 'delivered').length : 0, total: Array.isArray(allOrders) ? allOrders.length : 0 }; // Statistiques pour les commandes clients (nouveau module) const customerOrderStats = { waiting: Array.isArray(customerOrders) ? customerOrders.filter((o: any) => o.status === 'En attente de Commande').length : 0, inProgress: Array.isArray(customerOrders) ? customerOrders.filter((o: any) => o.status === 'Commande en Cours').length : 0, available: Array.isArray(customerOrders) ? customerOrders.filter((o: any) => o.status === 'Disponible').length : 0, withdrawn: Array.isArray(customerOrders) ? customerOrders.filter((o: any) => o.status === 'Retiré').length : 0, canceled: Array.isArray(customerOrders) ? customerOrders.filter((o: any) => o.status === 'Annulé').length : 0, total: Array.isArray(customerOrders) ? customerOrders.length : 0 }; return (
{/* Header */}

Tableau de Bord

Vue d'ensemble des performances et statistiques

{/* Alerts */}
{dlcStats.expiringSoon > 0 && (
{dlcStats.expiringSoon} produit(s) DLC expirent dans les 15 prochains jours
)} {dlcStats.expired > 0 && (
{dlcStats.expired} produit(s) DLC sont expirés et nécessitent une action immédiate
)}
{/* Stats Cards */}

Livraisons ce mois

{deliveredThisMonth}

Commandes en attente

{pendingOrdersCount}

Délai commande → livraison

{averageDeliveryTime} jours

Total palettes

{totalPalettes}

{/* Main Content Grid */}
{/* Commandes en Attente */} Commandes en Attente {pendingOrders.length > 0 && ( {pendingOrders.length} )} {pendingOrders.length > 0 ? pendingOrders.map((order: any) => { // Calculer le nombre de jours en attente depuis la date prévue const orderDate = safeDate(order.plannedDate); const daysPending = orderDate ? Math.floor((new Date().getTime() - orderDate.getTime()) / (1000 * 60 * 60 * 24)) : 0; const isOverdue = daysPending > 10; return (

{order.supplier?.name}

{order.group?.name}

{daysPending} jour{daysPending > 1 ? 's' : ''}

{safeFormat(order.plannedDate, "d MMM")}

); }) : (

Aucune commande en attente

)}
{/* Livraisons à Venir */} Livraisons à Venir {Array.isArray(upcomingDeliveries) && upcomingDeliveries.length > 0 ? upcomingDeliveries.map((delivery: any) => (

{delivery.supplier?.name}

{delivery.group?.name}

{delivery.quantity} {delivery.unit}

{safeFormat(delivery.scheduledDate, "d MMM")}

)) : (

Aucune livraison programmée

({Array.isArray(allDeliveries) ? allDeliveries.length : 0} livraisons totales)

)}
{/* Publicités à Venir */} Publicités à Venir {upcomingPublicities .slice(0, 3) .map((publicity: any) => { const participatingStores = publicity.participations || []; const isCurrentStoreParticipating = selectedStoreId && participatingStores.some((p: any) => p.groupId === parseInt(selectedStoreId.toString())); return (

{publicity.pubNumber}

{publicity.designation}

À venir

{safeFormat(publicity.startDate, "d MMM")}

{/* Magasins participants */}
Magasins: {participatingStores.length === 0 ? ( Aucun magasin ) : (
{participatingStores.map((participation: any) => { const isCurrentStore = selectedStoreId && participation.groupId === parseInt(selectedStoreId.toString()); const groupColor = participation.group?.color || '#1976D2'; return ( {participation.group.name} ); })}
)}
); })} {(!Array.isArray(upcomingPublicities) || upcomingPublicities.length === 0) && (

Aucune publicité à venir

(API: {Array.isArray(upcomingPublicities) ? upcomingPublicities.length : 'NOT_ARRAY'})

)}
{/* Section Rapprochement BL */}
{/* Système d'informations - Remplace les commandes clients */} {/* Tâches à faire */} Tâches à faire {Array.isArray(tasks) && tasks.length > 0 ? tasks .filter((task: any) => task.status !== 'completed') .sort((a: any, b: any) => { const dateA = safeDate(a.createdAt); const dateB = safeDate(b.createdAt); return (dateA ? dateA.getTime() : 0) - (dateB ? dateB.getTime() : 0); }) .slice(0, 5) .map((task: any) => { const priorityConfig = getPriorityConfig(task.priority); const PriorityIcon = priorityConfig.icon; return (

{task.title}

{task.assignedUser?.username || task.assignedTo}

{priorityConfig.label}

{safeFormat(task.createdAt, "d MMM")}

); }) : (

Aucune tâche en cours

Toutes les tâches sont terminées

)}
{/* Modal pour les annonces récentes */} Nouvelle Annonce {recentAnnouncement && (
{(() => { const config = getAnnouncementPriorityConfig(recentAnnouncement.type); const IconComponent = config.icon; return ( <> {config.label} ); })()}

{safeFormat(recentAnnouncement.createdAt, "d MMMM yyyy 'à' HH:mm")}

{recentAnnouncement.title}

{recentAnnouncement.content}

{recentAnnouncement.createdBy && (
Publié par {recentAnnouncement.createdBy}
)}
)}
); }