'use client'; import { useState, useEffect } from 'react'; import { DndContext, DragEndEvent, DragOverlay, DragStartEvent, useDraggable, useDroppable } from '@dnd-kit/core'; import { CSS } from '@dnd-kit/utilities'; import TrajetDetailModal from './TrajetDetailModal'; import { useNotification } from './NotificationProvider'; interface Trajet { id: string; date: string; adresseDepart: string; adresseArrivee: string; commentaire?: string | null; statut: string; adherent: { id: string; nom: string; prenom: string; telephone: string; email: string; }; chauffeur?: { id: string; nom: string; prenom: string; telephone: string; } | null; } interface CalendrierTrajetsProps { refreshTrigger?: number; } // Composant draggable pour un événement de trajet function DraggableTrajetEvent({ trajet, onClick }: { trajet: Trajet; onClick: (e: React.MouseEvent, trajet: Trajet) => void }) { // Empêcher le drag pour les trajets annulés ou validés const canDrag = trajet.statut !== 'Annulé' && trajet.statut !== 'Validé' && trajet.statut !== 'Terminé'; const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({ id: trajet.id, data: { trajet }, disabled: !canDrag, }); const style = { transform: CSS.Translate.toString(transform), opacity: isDragging ? 0.5 : 1, }; const getStatutStyle = (statut: string) => { switch (statut) { case 'Validé': return { bg: 'bg-purple-500', text: 'text-white', border: 'border-purple-600', }; case 'Terminé': return { bg: 'bg-green-500', text: 'text-white', border: 'border-green-600', }; case 'En cours': return { bg: 'bg-blue-500', text: 'text-white', border: 'border-blue-600', }; case 'Annulé': return { bg: 'bg-red-500', text: 'text-white', border: 'border-red-600', }; default: return { bg: 'bg-lblue', text: 'text-white', border: 'border-lblue', }; } }; const formatTime = (dateString: string) => { const date = new Date(dateString); return date.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' }); }; const styleObj = getStatutStyle(trajet.statut); return ( ); } // Composant droppable pour une cellule de jour function DroppableDayCell({ date, children, isToday, isSelected, onDateClick }: { date: Date; children: React.ReactNode; isToday: boolean; isSelected: boolean; onDateClick: (date: Date) => void; }) { // Créer un ID basé sur les composants de date pour éviter les problèmes de fuseau horaire const dayId = `day-${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; const { setNodeRef, isOver } = useDroppable({ id: dayId, data: { date }, }); return ( ); } export default function CalendrierTrajets({ refreshTrigger }: CalendrierTrajetsProps) { const { showNotification } = useNotification(); const [trajets, setTrajets] = useState([]); const [loading, setLoading] = useState(true); const [currentDate, setCurrentDate] = useState(new Date()); const [selectedDate, setSelectedDate] = useState(null); const [selectedTrajets, setSelectedTrajets] = useState([]); const [selectedTrajet, setSelectedTrajet] = useState(null); const [activeId, setActiveId] = useState(null); const [draggedTrajet, setDraggedTrajet] = useState(null); const month = currentDate.getMonth(); const year = currentDate.getFullYear(); const firstDayOfMonth = new Date(year, month, 1); const lastDayOfMonth = new Date(year, month + 1, 0); const daysInMonth = lastDayOfMonth.getDate(); const startingDayOfWeek = firstDayOfMonth.getDay(); // Ajuster pour que lundi soit le premier jour (0 = lundi) const adjustedStartingDay = startingDayOfWeek === 0 ? 6 : startingDayOfWeek - 1; useEffect(() => { fetchTrajets(true); }, [currentDate, refreshTrigger]); const fetchTrajets = async (showLoading = true) => { if (showLoading) { setLoading(true); } try { const startDate = new Date(year, month, 1).toISOString(); const endDate = new Date(year, month + 1, 0, 23, 59, 59).toISOString(); const response = await fetch( `/api/trajets?startDate=${startDate}&endDate=${endDate}` ); if (response.ok) { const data = await response.json(); setTrajets(data); // Mettre à jour la liste des trajets du jour sélectionné après le fetch if (selectedDate) { const trajetsDuJour = data.filter((t: Trajet) => { const trajetDate = new Date(t.date); return ( trajetDate.getDate() === selectedDate.getDate() && trajetDate.getMonth() === selectedDate.getMonth() && trajetDate.getFullYear() === selectedDate.getFullYear() ); }); setSelectedTrajets(trajetsDuJour); } } else { console.error('Erreur lors de la récupération des trajets:', response.statusText); } } catch (error) { console.error('Erreur lors du chargement des trajets:', error); // Ne pas vider les trajets en cas d'erreur } finally { if (showLoading) { setLoading(false); } } }; const getTrajetsForDate = (date: Date): Trajet[] => { // Utiliser les composants de date directement pour éviter les problèmes de fuseau horaire const targetYear = date.getFullYear(); const targetMonth = date.getMonth(); const targetDay = date.getDate(); return trajets.filter((trajet) => { const trajetDate = new Date(trajet.date); return ( trajetDate.getFullYear() === targetYear && trajetDate.getMonth() === targetMonth && trajetDate.getDate() === targetDay ); }); }; const handleDateClick = (date: Date) => { setSelectedDate(date); const trajetsDuJour = getTrajetsForDate(date); setSelectedTrajets(trajetsDuJour); }; const handleTrajetClick = (e: React.MouseEvent, trajet: Trajet) => { e.stopPropagation(); // Empêcher le clic sur la date setSelectedTrajet(trajet); }; const handleDragStart = (event: DragStartEvent) => { setActiveId(event.active.id as string); const trajet = event.active.data.current?.trajet as Trajet; setDraggedTrajet(trajet); }; const handleDragEnd = async (event: DragEndEvent) => { const { active, over } = event; setActiveId(null); setDraggedTrajet(null); if (!over || !active.data.current?.trajet) { return; } const trajet = active.data.current.trajet as Trajet; // Vérifier que le trajet peut être déplacé if (trajet.statut === 'Annulé' || trajet.statut === 'Validé' || trajet.statut === 'Terminé') { return; } const targetDayId = over.id as string; // Extraire la date de la cible (format: "day-2026-01-23") if (typeof targetDayId === 'string' && targetDayId.startsWith('day-')) { const dateStr = targetDayId.replace('day-', ''); const [year, month, day] = dateStr.split('-').map(Number); // Vérifier que les composants de date sont valides if (isNaN(year) || isNaN(month) || isNaN(day)) { return; } // Conserver l'heure du trajet original, changer seulement la date const originalDate = new Date(trajet.date); // Créer la nouvelle date avec les composants locaux pour correspondre exactement au calendrier // Note: month - 1 car les mois sont indexés à partir de 0 en JavaScript // On crée la date directement avec l'heure originale mais la date cible // Pour éviter les problèmes de fuseau horaire, on crée d'abord à midi puis on ajuste const newDate = new Date(year, month - 1, day, 12, 0, 0, 0); newDate.setHours(originalDate.getHours(), originalDate.getMinutes(), originalDate.getSeconds(), originalDate.getMilliseconds()); // Vérifier que la date a changé const originalDateOnly = new Date(originalDate.getFullYear(), originalDate.getMonth(), originalDate.getDate()); const newDateOnly = new Date(newDate.getFullYear(), newDate.getMonth(), newDate.getDate()); if (originalDateOnly.getTime() === newDateOnly.getTime()) { return; // Même jour, pas besoin de mettre à jour } // Mettre à jour le trajet try { const response = await fetch(`/api/trajets/${trajet.id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ date: newDate.toISOString(), }), }); if (response.ok) { const targetDateFormatted = new Date(newDate).toLocaleDateString('fr-FR', { day: 'numeric', month: 'long', year: 'numeric', }); showNotification('success', `Trajet déplacé vers le ${targetDateFormatted}`); // Utiliser fetchTrajets sans loading pour éviter de vider les trajets pendant le chargement await fetchTrajets(false); } else { const error = await response.json(); showNotification('error', error.error || 'Erreur lors du déplacement du trajet'); } } catch (error) { console.error('Erreur lors du déplacement:', error); showNotification('error', 'Erreur lors du déplacement du trajet'); } } }; const goToPreviousMonth = () => { setCurrentDate(new Date(year, month - 1, 1)); }; const goToNextMonth = () => { setCurrentDate(new Date(year, month + 1, 1)); }; const goToToday = () => { setCurrentDate(new Date()); }; const formatDate = (date: Date) => { return date.toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric', }); }; const formatTime = (dateString: string) => { const date = new Date(dateString); return date.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' }); }; const days = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim']; const calendarDays = []; // Ajouter les jours vides du début for (let i = 0; i < adjustedStartingDay; i++) { calendarDays.push(null); } // Ajouter les jours du mois for (let day = 1; day <= daysInMonth; day++) { calendarDays.push(new Date(year, month, day)); } return (
{/* En-tête du calendrier */}

{currentDate.toLocaleDateString('fr-FR', { month: 'long', year: 'numeric' })}

{loading ? (
Chargement...
) : ( <> {/* Grille du calendrier avec drag and drop */}
{/* En-têtes des jours */} {days.map((day) => (
{day}
))} {/* Jours du calendrier */} {calendarDays.map((date, index) => { if (!date) { return
; } const trajetsDuJour = getTrajetsForDate(date); const isToday = date.getDate() === new Date().getDate() && date.getMonth() === new Date().getMonth() && date.getFullYear() === new Date().getFullYear(); const isSelected = selectedDate && date.getDate() === selectedDate.getDate() && date.getMonth() === selectedDate.getMonth() && date.getFullYear() === selectedDate.getFullYear(); return (
{date.getDate()}
{trajetsDuJour.length > 0 && (
{trajetsDuJour.slice(0, 2).map((trajet) => ( ))} {trajetsDuJour.length > 2 && (
+{trajetsDuJour.length - 2}
)}
)}
); })}
{/* Overlay pour l'élément en cours de drag */} {draggedTrajet ? (
{draggedTrajet.chauffeur ? `${draggedTrajet.chauffeur.prenom.charAt(0)}${draggedTrajet.chauffeur.nom.charAt(0)}` : '?'} {formatTime(draggedTrajet.date)}
) : null}
{/* Détails des trajets du jour sélectionné */} {selectedDate && selectedTrajets.length > 0 && (

Trajets du {formatDate(selectedDate)}

{selectedTrajets.map((trajet) => { const getStatutColor = (statut: string) => { switch (statut) { case 'Validé': return 'bg-purple-100 text-purple-700 border-purple-200'; case 'Terminé': return 'bg-green-100 text-green-700 border-green-200'; case 'En cours': return 'bg-blue-100 text-blue-700 border-blue-200'; case 'Annulé': return 'bg-red-100 text-red-700 border-red-200'; default: return 'bg-gray-100 text-gray-700 border-gray-200'; } }; return ( ); })}
)} {selectedDate && selectedTrajets.length === 0 && (
Aucun trajet prévu pour le {formatDate(selectedDate)}
)} )} {/* Modal de détails du trajet */} {selectedTrajet && ( setSelectedTrajet(null)} onUpdate={() => { fetchTrajets(); setSelectedTrajet(null); }} /> )}
); }