import { format, startOfMonth, endOfMonth, eachDayOfInterval, isSameDay, isSameMonth, isToday } from "date-fns"; import { fr } from "date-fns/locale"; import { safeDate } from "@/lib/dateUtils"; import { Plus, Check, MoreHorizontal } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { useState } from "react"; import type { OrderWithRelations, DeliveryWithRelations } from "@shared/schema"; interface CalendarGridProps { currentDate: Date; orders: OrderWithRelations[]; deliveries: DeliveryWithRelations[]; publicities: any[]; selectedStoreId: number | null; userGroups: any[]; onDateClick: (date: Date) => void; onItemClick: (item: any, type: 'order' | 'delivery') => void; } // Composant pour afficher un élément (commande ou livraison) function CalendarItem({ item, type, onItemClick }: { item: any, type: 'order' | 'delivery', onItemClick: (item: any, type: 'order' | 'delivery') => void }) { const formatQuantity = (quantity: number, unit: string) => { return `${quantity}${unit === 'palettes' ? 'P' : 'C'}`; }; if (type === 'order') { const getOrderStyle = () => { switch (item.status) { case 'delivered': return 'bg-gray-400 text-white shadow-md border-l-4 border-gray-500'; case 'planned': return 'bg-yellow-300 text-gray-800 shadow-md border-l-4 border-yellow-500 font-medium'; default: return 'bg-blue-300 text-gray-800 shadow-md border-l-4 border-blue-500'; } }; return (
{ e.stopPropagation(); onItemClick(item, 'order'); }} > {item.supplier.name}
{item.status === 'planned' && (
)} {item.status === 'delivered' && ( )}
); } // Livraison const getDeliveryStyle = () => { switch (item.status) { case 'delivered': return 'bg-gray-400 text-white shadow-md border-l-4 border-gray-500'; case 'pending': return 'bg-green-300 text-gray-800 shadow-md border-l-4 border-green-500'; default: return 'bg-green-300 text-gray-800 shadow-md border-l-4 border-green-500'; } }; return (
{ e.stopPropagation(); onItemClick(item, 'delivery'); }} > {item.supplier.name} - {formatQuantity(item.quantity, item.unit)}
{item.status === 'pending' && (
)} {item.status === 'delivered' && ( )}
); } // Composant pour gérer l'overflow avec popover function DayItemsContainer({ dayOrders, dayDeliveries, onItemClick }: { dayOrders: any[], dayDeliveries: any[], onItemClick: (item: any, type: 'order' | 'delivery') => void }) { const [isOpen, setIsOpen] = useState(false); const MAX_VISIBLE_ITEMS = 4; const totalItems = dayOrders.length + dayDeliveries.length; if (totalItems === 0) return null; // Combiner tous les éléments avec leur type const allItems = [ ...dayOrders.map(order => ({ ...order, itemType: 'order' as const })), ...dayDeliveries.map(delivery => ({ ...delivery, itemType: 'delivery' as const })) ]; const visibleItems = allItems.slice(0, MAX_VISIBLE_ITEMS); const hiddenCount = Math.max(0, totalItems - MAX_VISIBLE_ITEMS); return (
{/* Éléments visibles */} {visibleItems.map((item, index) => ( ))} {/* Badge "+X autres" avec popover moderne */} {hiddenCount > 0 && ( e.stopPropagation()} >

Tous les éléments ({totalItems})

{allItems.map((item) => ( { onItemClick(clickedItem, type); setIsOpen(false); }} /> ))}
)}
); } export default function CalendarGrid({ currentDate, orders, deliveries, publicities, selectedStoreId, userGroups, onDateClick, onItemClick, }: CalendarGridProps) { if (import.meta.env.DEV) { console.log('🗓️ CalendarGrid rendered with:', { currentDate: currentDate?.toISOString(), ordersCount: orders?.length || 0, deliveriesCount: deliveries?.length || 0, publicitiesCount: publicities?.length || 0, selectedStoreId, userGroupsCount: userGroups?.length || 0 }); } const monthStart = startOfMonth(currentDate); const monthEnd = endOfMonth(currentDate); // Helper function to filter publicities based on user's assigned stores const getPublicitiesForDate = (date: Date) => { const dateStr = format(date, 'yyyy-MM-dd'); // Protection contre les données invalides qui causent des plantages en production if (!publicities || !Array.isArray(publicities)) { console.warn('⚠️ Invalid publicities data:', publicities); return []; } if (import.meta.env.DEV) { console.log('🔍 CalendarGrid getPublicitiesForDate:', { date: dateStr, totalPublicities: publicities.length, selectedStoreId, userGroups: userGroups?.length || 0 }); } try { return publicities.filter(pub => { // Vérifications de sécurité pour éviter les plantages if (!pub || typeof pub !== 'object') { console.warn('⚠️ Invalid publicity object:', pub); return false; } // Check if the date is within the publicity period const pubStart = pub.startDate; const pubEnd = pub.endDate; if (!pubStart || !pubEnd) { console.warn('⚠️ Publicity missing dates:', { pubNumber: pub.pubNumber, startDate: pubStart, endDate: pubEnd }); return false; } if (dateStr < pubStart || dateStr > pubEnd) { return false; } // If no store is selected and user has no assigned groups, show only publicities with participations if (!selectedStoreId && (!userGroups || userGroups.length === 0)) { const hasParticipations = pub.participations && Array.isArray(pub.participations) && pub.participations.length > 0; if (hasParticipations && import.meta.env.DEV) { console.log('📋 Publicity has participations (no store selected):', { pubNumber: pub.pubNumber, participationCount: pub.participations.length }); } return hasParticipations; } // If a specific store is selected, check if that store participates if (selectedStoreId) { const matches = pub.participations && Array.isArray(pub.participations) && pub.participations.some((pg: any) => pg?.groupId === selectedStoreId); if (matches && import.meta.env.DEV) { console.log('🎯 Publicity matches selected store:', { pubNumber: pub.pubNumber, selectedStoreId }); } return matches; } // If no specific store selected but user has assigned stores, // show publicities where any of user's stores participate if (userGroups && Array.isArray(userGroups) && userGroups.length > 0) { const userGroupIds = userGroups.map((ug: any) => ug?.groupId).filter(id => id !== undefined); const matches = pub.participations && Array.isArray(pub.participations) && pub.participations.some((pg: any) => pg?.groupId && userGroupIds.includes(pg.groupId)); if (matches) { console.log('👥 Publicity matches user groups:', { pubNumber: pub.pubNumber, userGroupIds }); } return matches; } // Default case: only show publicities with participations const hasParticipations = pub.participations && Array.isArray(pub.participations) && pub.participations.length > 0; if (hasParticipations) { console.log('📋 Publicity has participations (default case):', { pubNumber: pub.pubNumber, participationCount: pub.participations.length }); } return hasParticipations; }); } catch (error) { console.error('❌ Error filtering publicities:', error); return []; } }; // Get all days in the month const monthDays = eachDayOfInterval({ start: monthStart, end: monthEnd, }); // Pad the calendar to start on Monday const firstDayOfWeek = monthStart.getDay(); const startPadding = firstDayOfWeek === 0 ? 6 : firstDayOfWeek - 1; const paddedDays = []; // Add padding days from previous month for (let i = startPadding; i > 0; i--) { const paddingDate = new Date(monthStart); paddingDate.setDate(paddingDate.getDate() - i); paddedDays.push(paddingDate); } // Add current month days paddedDays.push(...monthDays); // Add padding days from next month to complete the grid const remainingCells = 42 - paddedDays.length; // 6 weeks * 7 days for (let i = 1; i <= remainingCells; i++) { const paddingDate = new Date(monthEnd); paddingDate.setDate(paddingDate.getDate() + i); paddedDays.push(paddingDate); } const weekDays = ["Lun", "Mar", "Mer", "Jeu", "Ven", "Sam", "Dim"]; const getItemsForDate = (date: Date) => { // Debug: Log des commandes reçues (seulement une fois) if (orders.length > 0 && date.getDate() === 1) { console.log('📅 CalendarGrid Debug - Orders received:', orders.length); console.log('📅 First order structure:', orders[0]); console.log('📅 All orders dates:', orders.map(o => ({ id: o.id, plannedDate: o.plannedDate, supplier: o.supplier?.name }))); } // Debug: Log des livraisons reçues (seulement une fois) if (deliveries.length > 0 && date.getDate() === 1) { console.log('🚛 CalendarGrid Debug - Deliveries received:', deliveries.length); console.log('🚛 First delivery structure:', deliveries[0]); console.log('🚛 All deliveries dates:', deliveries.map(d => ({ id: d.id, scheduledDate: d.scheduledDate, supplier: d.supplier?.name }))); } const dayOrders = orders.filter(order => { // Protection contre undefined/null if (!order || !order.supplier) { console.warn('📅 Invalid order found:', order); return false; } // Essayer plusieurs champs de date possibles const orderDate = safeDate(order.plannedDate || order.createdAt); const matches = orderDate && isSameDay(orderDate, date); if (matches) { console.log('📅 Order matches date:', { orderId: order.id, supplier: order.supplier?.name, plannedDate: order.plannedDate, matchingDate: format(date, 'yyyy-MM-dd') }); } return matches; }); const dayDeliveries = deliveries.filter(delivery => { // Protection contre undefined/null if (!delivery || !delivery.supplier) { console.warn('🚛 Invalid delivery found:', delivery); return false; } // Essayer plusieurs champs de date possibles const deliveryDate = safeDate(delivery.scheduledDate || delivery.deliveredDate || delivery.createdAt); const matches = deliveryDate && isSameDay(deliveryDate, date); if (matches) { console.log('🚛 Delivery matches date:', { deliveryId: delivery.id, supplier: delivery.supplier?.name, scheduledDate: delivery.scheduledDate, matchingDate: format(date, 'yyyy-MM-dd') }); } return matches; }); return { orders: dayOrders, deliveries: dayDeliveries }; }; return (
{/* Calendar Header - Design moderne épuré */}
{weekDays.map((day, index) => (
= 5 ? 'text-slate-700' : 'text-slate-800' } uppercase border-r border-gray-300 last:border-r-0`} > {day}
))}
{/* Calendar Days - Grid moderne sans arrondi */}
{paddedDays.map((date, index) => { const isCurrentMonth = isSameMonth(date, currentDate); const isTodayDate = isToday(date); const isWeekend = date.getDay() === 0 || date.getDay() === 6; const { orders: dayOrders, deliveries: dayDeliveries } = getItemsForDate(date); const dayPublicities = getPublicitiesForDate(date); return (
onDateClick(date)} >
{/* Numéro du jour avec design moderne */}
{format(date, 'd')} {/* Indicateur weekend moderne */} {isWeekend && isCurrentMonth && (
)}
{/* Orders and Deliveries avec design moderne */}
{/* Publicities avec design épuré */} {dayPublicities.length > 0 && (
{dayPublicities.slice(0, 2).map((pub, idx) => (
pg.group?.name).join(', ')}` : ''}`} > {pub.pubNumber}
))} {dayPublicities.length > 2 && (
+{dayPublicities.length - 2}
)}
)} {/* Quick Create Button épuré */} {isCurrentMonth && (
)}
); })}
); }