/** * CalendarPage Mobile - Calendrier simplifié pour mobile * Affiche les commandes et livraisons comme événements */ import { useMemo, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { useAuthUnified } from "@/hooks/useAuthUnified"; import { useStore } from "@/contexts/StoreContext"; import MobileLayout from "./MobileLayout"; import { Card, CardContent } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Calendar as CalendarIcon, ChevronLeft, ChevronRight, Package, Truck } from "lucide-react"; import { format, startOfMonth, endOfMonth, eachDayOfInterval, isSameMonth, isToday, addMonths, subMonths, startOfWeek, endOfWeek, isSameDay } from "date-fns"; import { fr } from "date-fns/locale"; // Type pour les événements (combinaison de commandes et livraisons) type CalendarEvent = { id: number; title: string; date: string; type: 'order' | 'delivery'; status?: string; supplier?: { name: string }; }; // Références stables (évite de recalculer l'index des événements à chaque rendu) const NO_DATA: any[] = []; const NO_EVENTS: CalendarEvent[] = []; export default function MobileCalendarPage() { const { user } = useAuthUnified(); const { selectedStoreId } = useStore(); const [currentMonth, setCurrentMonth] = useState(new Date()); const [selectedDate, setSelectedDate] = useState(new Date()); // Fetch orders for current month const { data: orders = NO_DATA } = useQuery({ queryKey: ["/api/orders", selectedStoreId, format(currentMonth, 'yyyy-MM')], queryFn: async () => { const start = format(startOfMonth(currentMonth), 'yyyy-MM-dd'); const end = format(endOfMonth(currentMonth), 'yyyy-MM-dd'); const params = new URLSearchParams({ startDate: start, endDate: end }); if (selectedStoreId) params.append('storeId', selectedStoreId.toString()); const response = await fetch(`/api/orders?${params}`, { credentials: 'include' }); if (!response.ok) { return []; } const data = await response.json(); return Array.isArray(data) ? data : []; }, enabled: !!user, }); // Fetch deliveries for current month const { data: deliveries = NO_DATA } = useQuery({ queryKey: ["/api/deliveries", selectedStoreId, format(currentMonth, 'yyyy-MM')], queryFn: async () => { const start = format(startOfMonth(currentMonth), 'yyyy-MM-dd'); const end = format(endOfMonth(currentMonth), 'yyyy-MM-dd'); const params = new URLSearchParams({ startDate: start, endDate: end }); if (selectedStoreId) params.append('storeId', selectedStoreId.toString()); const response = await fetch(`/api/deliveries?${params}`, { credentials: 'include' }); if (!response.ok) { return []; } const data = await response.json(); return Array.isArray(data) ? data : []; }, enabled: !!user, }); // Combine orders and deliveries into events, indexés par jour ('yyyy-MM-dd', // heure locale comme isSameDay) une seule fois par chargement au lieu d'un // filtrage complet pour chaque case du calendrier const { events, eventsByDay } = useMemo(() => { const events: CalendarEvent[] = [ ...orders.map((order: any) => ({ id: order.id, title: `Cmd #${order.orderNumber || order.id}`, date: order.plannedDate, type: 'order' as const, status: order.status, supplier: order.supplier })), ...deliveries.map((delivery: any) => ({ id: delivery.id, title: `Liv #${delivery.deliveryNumber || delivery.id}`, date: delivery.scheduledDate, type: 'delivery' as const, status: delivery.status, supplier: delivery.supplier })) ]; const eventsByDay = new Map(); for (const event of events) { if (!event.date) continue; const eventDate = new Date(event.date); if (isNaN(eventDate.getTime())) continue; const key = format(eventDate, 'yyyy-MM-dd'); const dayEvents = eventsByDay.get(key); if (dayEvents) { dayEvents.push(event); } else { eventsByDay.set(key, [event]); } } return { events, eventsByDay }; }, [orders, deliveries]); const monthStart = startOfMonth(currentMonth); const monthEnd = endOfMonth(currentMonth); // Get days for the calendar grid const calendarStart = startOfWeek(monthStart, { locale: fr }); const calendarEnd = endOfWeek(monthEnd, { locale: fr }); const calendarDays = eachDayOfInterval({ start: calendarStart, end: calendarEnd }); // Check if a day has events const getEventsForDay = (day: Date) => { return eventsByDay.get(format(day, 'yyyy-MM-dd')) || NO_EVENTS; }; // Get events for selected date const selectedDateEvents = selectedDate ? getEventsForDay(selectedDate) : NO_EVENTS; const navigateMonth = (direction: 'prev' | 'next') => { setCurrentMonth(direction === 'prev' ? subMonths(currentMonth, 1) : addMonths(currentMonth, 1) ); }; return (
{/* Header */}

Calendrier

{events.length} événements
{/* Month navigation */}

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

{/* Legend */}
Commandes
Livraisons
{/* Calendar grid */} {/* Weekday headers */}
{['L', 'M', 'M', 'J', 'V', 'S', 'D'].map((day, i) => (
{day}
))}
{/* Days grid */}
{calendarDays.map((day, i) => { const isCurrentMonth = isSameMonth(day, currentMonth); const isTodayDate = isToday(day); const isSelected = selectedDate && isSameDay(day, selectedDate); const dayEvents = getEventsForDay(day); const hasOrders = dayEvents.some(e => e.type === 'order'); const hasDeliveries = dayEvents.some(e => e.type === 'delivery'); return ( ); })}
{/* Events for selected date */} {selectedDate && (

{format(selectedDate, 'EEEE d MMMM', { locale: fr })}

{selectedDateEvents.length === 0 ? (

Aucun événement ce jour

) : ( selectedDateEvents.map((event) => (
{event.type === 'order' ? : }

{event.title}

{event.supplier && (

{event.supplier.name}

)} {event.status && ( {event.status} )}
)) )}
)}
); }