import { useCallback, useState } from "react"; import { useQuery, useQueryClient, keepPreviousData } from "@tanstack/react-query"; import { ChevronLeft, ChevronRight, Plus, RefreshCw, Calendar as CalendarIcon } from "lucide-react"; import { Button } from "@/components/ui/button"; import CalendarGrid from "@/components/CalendarGrid"; import QuickCreateMenu from "@/components/modals/QuickCreateMenu"; import OrderDetailModal from "@/components/modals/OrderDetailModal"; import CreateOrderModal from "@/components/modals/CreateOrderModal"; import CreateDeliveryModal from "@/components/modals/CreateDeliveryModal"; import StatsPanel from "@/components/StatsPanel"; import { useAuthUnified } from "@/hooks/useAuthUnified"; import { useStore } from "@/contexts/StoreContext"; import { apiRequest } from "@/lib/queryClient"; import { useToast } from "@/hooks/use-toast"; import { format, startOfMonth, endOfMonth } from "date-fns"; import { fr } from "date-fns/locale"; import { hasPermission } from "@/lib/permissions"; // Référence stable tant que les données ne sont pas chargées (CalendarGrid est mémorisé) const NO_ITEMS: any[] = []; export default function Calendar() { const { user } = useAuthUnified(); const { selectedStoreId } = useStore(); const queryClient = useQueryClient(); const { toast } = useToast(); const [currentDate, setCurrentDate] = useState(new Date()); // Current date const [selectedDate, setSelectedDate] = useState(null); const [showQuickCreate, setShowQuickCreate] = useState(false); const [showOrderDetail, setShowOrderDetail] = useState(false); const [showCreateOrder, setShowCreateOrder] = useState(false); const [showCreateDelivery, setShowCreateDelivery] = useState(false); const [selectedItem, setSelectedItem] = useState(null); const startDate = format(startOfMonth(currentDate), 'yyyy-MM-dd'); const endDate = format(endOfMonth(currentDate), 'yyyy-MM-dd'); // Fetch orders and deliveries for the current month with store filtering const { data: orders = NO_ITEMS, isLoading: loadingOrders, isPlaceholderData: ordersPlaceholder } = useQuery({ queryKey: ['/api/orders', selectedStoreId, { startDate, endDate }], queryFn: async () => { const params = new URLSearchParams({ startDate, endDate }); // CRITICAL FIX: Appliquer le filtrage par storeId pour TOUS les rôles, pas seulement admin if (selectedStoreId) { params.append('storeId', selectedStoreId.toString()); } const response = await fetch(`/api/orders?${params.toString()}`, { credentials: 'include' }); if (!response.ok) { throw new Error('Failed to fetch orders'); } const data = await response.json(); // Protection contre les données invalides en production if (!Array.isArray(data)) { console.warn('⚠️ Orders data is not an array, returning empty array'); return []; } return data; }, staleTime: 5 * 60 * 1000, // 5 minutes de cache pour éviter la disparition des données gcTime: 10 * 60 * 1000, // 10 minutes en cache // Changement de mois : garder la grille affichée pendant le chargement au lieu // du spinner. Jamais au changement de magasin (données d'un autre magasin). placeholderData: (previousData, previousQuery) => previousQuery?.queryKey[1] === selectedStoreId ? keepPreviousData(previousData) : undefined, }); const { data: deliveries = NO_ITEMS, isLoading: loadingDeliveries, isPlaceholderData: deliveriesPlaceholder } = useQuery({ queryKey: ['/api/deliveries', selectedStoreId, { startDate, endDate }], queryFn: async () => { const params = new URLSearchParams({ startDate, endDate }); // CRITICAL FIX: Appliquer le filtrage par storeId pour TOUS les rôles, pas seulement admin if (selectedStoreId) { params.append('storeId', selectedStoreId.toString()); } const response = await fetch(`/api/deliveries?${params.toString()}`, { credentials: 'include' }); if (!response.ok) { throw new Error('Failed to fetch deliveries'); } const data = await response.json(); // Protection contre les données invalides en production if (!Array.isArray(data)) { console.warn('⚠️ Deliveries data is not an array, returning empty array'); return []; } return data; }, staleTime: 5 * 60 * 1000, // 5 minutes de cache pour éviter la disparition des données gcTime: 10 * 60 * 1000, // 10 minutes en cache // Même règle que pour les commandes : jamais au changement de magasin placeholderData: (previousData, previousQuery) => previousQuery?.queryKey[1] === selectedStoreId ? keepPreviousData(previousData) : undefined, }); // Fetch publicities for the current year const { data: publicities = NO_ITEMS } = useQuery({ queryKey: ['/api/ad-campaigns', currentDate.getFullYear(), selectedStoreId], queryFn: async () => { const params = new URLSearchParams(); params.append('year', currentDate.getFullYear().toString()); if (selectedStoreId && user?.role === 'admin') { params.append('storeId', selectedStoreId.toString()); } const response = await fetch(`/api/ad-campaigns?${params.toString()}`, { credentials: 'include' }); if (!response.ok) { throw new Error('Failed to fetch publicities'); } const data = await response.json(); return Array.isArray(data) ? data : []; }, // Changement d'année : garder les publicités affichées pendant le chargement, // sauf au changement de magasin placeholderData: (previousData, previousQuery) => previousQuery?.queryKey[2] === selectedStoreId ? keepPreviousData(previousData) : undefined, }); const navigateMonth = (direction: 'prev' | 'next') => { const newDate = new Date(currentDate); if (direction === 'prev') { newDate.setMonth(newDate.getMonth() - 1); } else { newDate.setMonth(newDate.getMonth() + 1); } setCurrentDate(newDate); }; // Gestionnaires stables : CalendarGrid (mémorisé) n'est pas re-rendu à // l'ouverture ou à la fermeture des modales const handleDateClick = useCallback((date: Date) => { setSelectedDate(date); setShowQuickCreate(true); }, []); const handleItemClick = useCallback((item: any, type: 'order' | 'delivery') => { // Ne pas invalider le cache à l'ouverture pour éviter la disparition des données setSelectedItem({ ...item, type }); setShowOrderDetail(true); }, []); const handleCreateOrder = () => { setShowQuickCreate(false); setShowCreateOrder(true); }; const handleCreateDelivery = () => { setShowQuickCreate(false); setShowCreateDelivery(true); }; // La grille n'attend pas les publicités : elles s'affichent dès leur arrivée const isLoading = loadingOrders || loadingDeliveries; // Mois précédent encore affiché pendant le chargement du nouveau mois const isMonthLoading = ordersPlaceholder || deliveriesPlaceholder; return (
{/* Header */}

Calendrier des Commandes & Livraisons

{format(currentDate, 'MMMM yyyy', { locale: fr })}

{/* Legend */}
Commandes
Livraisons
Livré
{/* Calendar Navigation */}

{format(currentDate, 'MMMM yyyy', { locale: fr })}

{(hasPermission(user?.role || '', 'orders', 'create') || hasPermission(user?.role || '', 'deliveries', 'create')) && ( )}
{/* Calendar Grid */}
{isLoading ? (
) : (
)}
{/* Modals */} {showQuickCreate && ( setShowQuickCreate(false)} onCreateOrder={handleCreateOrder} onCreateDelivery={handleCreateDelivery} /> )} {showOrderDetail && selectedItem && ( setShowOrderDetail(false)} item={selectedItem} /> )} {showCreateOrder && ( setShowCreateOrder(false)} selectedDate={selectedDate} /> )} {showCreateDelivery && ( setShowCreateDelivery(false)} selectedDate={selectedDate} /> )} {/* Stats Panel */}
); }