import React, { useState, useEffect, useCallback, useMemo } from 'react'; import Sidebar from '../components/Sidebar'; import { ChevronLeft, ChevronRight, X, Menu, RefreshCw, Search, Filter, Calendar as CalendarIcon } from 'lucide-react'; import { useAuth } from '../context/AuthContext'; import NewLeaveRequestModal from '../components/NewLeaveRequestModal'; import { useMsal } from "@azure/msal-react"; const Calendar = () => { const { user } = useAuth(); const role = user?.role?.toLowerCase(); const userId = user?.id || user?.CollaborateurADId || user?.ID; const [sidebarOpen, setSidebarOpen] = useState(false); const [currentDate, setCurrentDate] = useState(new Date()); const [showNewRequestModal, setShowNewRequestModal] = useState(false); const [hoveredLeave, setHoveredLeave] = useState(null); const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); const [showFilters, setShowFilters] = useState(false); const [selectedDate, setSelectedDate] = useState(null); const [selectedEndDate, setSelectedEndDate] = useState(null); const [isSelectingRange, setIsSelectingRange] = useState(false); const [contextMenu, setContextMenu] = useState({ show: false, x: 0, y: 0 }); const [preselectedDates, setPreselectedDates] = useState(null); const [holidays, setHolidays] = useState([]); const [detailedCounters, setDetailedCounters] = useState(null); const [teamLeaves, setTeamLeaves] = useState([]); const [filters, setFilters] = useState({}); const [employeeFilter, setEmployeeFilter] = useState("all"); const [selectedCampus, setSelectedCampus] = useState("all"); const [selectedSociete, setSelectedSociete] = useState("all"); const [selectedService, setSelectedService] = useState("all"); const [selectedEmployees, setSelectedEmployees] = useState([]); const [showEmployeeList, setShowEmployeeList] = useState(false); const [isLoading, setIsLoading] = useState(true); const [isRefreshing, setIsRefreshing] = useState(false); const [lastRefresh, setLastRefresh] = useState(new Date()); const [graphToken, setGraphToken] = useState(null); const { instance, accounts } = useMsal(); const [isMobile, setIsMobile] = useState(false); const [sseConnected, setSseConnected] = useState(false); const [toasts, setToasts] = useState([]); useEffect(() => { const checkMobile = () => { setIsMobile(window.innerWidth < 1024); }; checkMobile(); window.addEventListener('resize', checkMobile); return () => window.removeEventListener('resize', checkMobile); }, []); const monthNames = [ 'Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre' ]; const dayNames = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam']; const dayNamesMobile = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam']; useEffect(() => { if (accounts.length > 0) { const request = { scopes: ["User.Read", "Mail.Send"], account: accounts[0], }; instance.acquireTokenSilent(request) .then((response) => { setGraphToken(response.accessToken); }) .catch((err) => { console.error("❌ Erreur récupération token Graph:", err); }); } }, [accounts, instance]); const fetchFrenchHolidays = async (year) => { try { const response = await fetch(`https://calendrier.api.gouv.fr/jours-feries/metropole/${year}.json`); if (!response.ok) throw new Error(`Erreur API: ${response.status}`); const data = await response.json(); const holidayDates = Object.keys(data).map(dateStr => { const [year, month, day] = dateStr.split('-').map(Number); return { date: new Date(year, month - 1, day), name: data[dateStr] }; }); return holidayDates; } catch (error) { console.error('Erreur jours fériés:', error); return []; } }; useEffect(() => { const year = currentDate.getFullYear(); fetchFrenchHolidays(year).then(setHolidays); }, [currentDate]); const isHoliday = (date) => { if (!date) return false; return holidays.some(holiday => holiday.date.getDate() === date.getDate() && holiday.date.getMonth() === date.getMonth() && holiday.date.getFullYear() === date.getFullYear() ); }; const getHolidayName = (date) => { const holiday = holidays.find(h => h.date.getDate() === date.getDate() && h.date.getMonth() === date.getMonth() && h.date.getFullYear() === date.getFullYear() ); return holiday?.name || ''; }; const fetchDetailedCounters = async () => { try { const response = await fetch(`http://localhost:3000/getDetailedLeaveCounters?user_id=${userId}`); const data = await response.json(); if (data.success) { setDetailedCounters(data.data); } } catch (error) { console.error('💥 Erreur compteurs:', error); } }; const loadTeamLeaves = async () => { if (userId) { try { let url = `http://localhost:3000/getTeamLeaves?user_id=${userId}&role=${user.role}`; // ⭐ PRESIDENT/ADMIN/RH : Envoyer le campus if (role === 'president' || role === 'rh' || role === 'admin') { if (selectedCampus !== 'all') { url += `&selectedCampus=${encodeURIComponent(selectedCampus)}`; } if (selectedSociete !== 'all') { url += `&selectedSociete=${encodeURIComponent(selectedSociete)}`; } if (selectedService !== 'all') { url += `&selectedService=${encodeURIComponent(selectedService)}`; } } // ⭐ DIRECTEUR DE CAMPUS : Envoyer société et service if (role === 'directeur de campus' || role === 'directrice de campus') { if (selectedSociete !== 'all') { url += `&selectedSociete=${encodeURIComponent(selectedSociete)}`; } if (selectedService !== 'all') { url += `&selectedService=${encodeURIComponent(selectedService)}`; } } console.log("📡 Appel API:", url); const response = await fetch(url); const data = await response.json(); console.log("📡 Réponse API :", data); if (data.success) { setTeamLeaves(data.leaves || []); setFilters(data.filters || {}); } } catch (error) { console.error('Erreur récupération congés équipe:', error); } } }; const refreshAllData = useCallback(async () => { if (!userId) return; if (!isLoading) setIsRefreshing(true); try { await Promise.all([ fetchDetailedCounters(), loadTeamLeaves() ]); setLastRefresh(new Date()); } catch (error) { console.error('❌ Erreur lors du rafraîchissement:', error); } finally { setIsLoading(false); setIsRefreshing(false); } }, [userId]); const showToast = useCallback((message, type = 'info') => { const id = Date.now(); const newToast = { id, message, type }; setToasts(prev => [...prev, newToast]); setTimeout(() => { setToasts(prev => prev.filter(t => t.id !== id)); }, 3000); }, []); useEffect(() => { refreshAllData(); }, [refreshAllData]); // ⭐ Recharger quand les filtres changent useEffect(() => { if ((role === 'president' || role === 'rh' || role === 'admin') && selectedCampus) { loadTeamLeaves(); } }, [selectedCampus, role]); // ⭐ Recharger pour Directeur de Campus quand société OU service changent // Recharger pour Directeur de Campus quand société OU service changent useEffect(() => { if (role === 'directeur de campus' || role === 'directrice de campus') { console.log("🔄 Rechargement Directeur Campus:", { societe: selectedSociete, service: selectedService, role: role }); loadTeamLeaves(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedSociete, selectedService, role]); const getDaysInMonth = () => { const year = currentDate.getFullYear(); const month = currentDate.getMonth(); const lastDay = new Date(year, month + 1, 0); const daysInMonth = lastDay.getDate(); const days = []; for (let day = 1; day <= daysInMonth; day++) { const currentDay = new Date(year, month, day); const dayOfWeek = currentDay.getDay(); // ⭐ MODIFIÉ : Inclure du lundi au samedi (1 à 6) if (dayOfWeek >= 1 && dayOfWeek <= 6) { days.push(currentDay); } } return days; }; const getDaysInMonthMobile = () => { const year = currentDate.getFullYear(); const month = currentDate.getMonth(); const firstDay = new Date(year, month, 1); const lastDay = new Date(year, month + 1, 0); const daysInMonth = lastDay.getDate(); const startingDayOfWeek = (firstDay.getDay() + 6) % 7; const days = []; for (let i = 0; i < startingDayOfWeek; i++) { days.push(null); } for (let day = 1; day <= daysInMonth; day++) { const currentDay = new Date(year, month, day); const dayOfWeek = currentDay.getDay(); if (dayOfWeek >= 1 && dayOfWeek <= 5) { days.push(currentDay); } } return days; }; // ⭐ FILTRAGE DE BASE SELON LE RÔLE ET LES FILTRES CAMPUS/SOCIÉTÉ/SERVICE const getBaseFilteredLeaves = () => { // Pour directeur de campus - le backend a déjà tout filtré if (role === 'directeur de campus' || role === 'directrice de campus') { return teamLeaves; } // Pour collaborateur, validateur, apprenti - pas de filtres if (['collaborateur', 'collaboratrice','validateur', 'apprenti'].includes(role)) { return teamLeaves; } // Pour president, admin, rh - appliquer les filtres frontend return teamLeaves.filter(leave => { if (selectedCampus !== 'all' && leave.campusnom !== selectedCampus) return false; if (selectedSociete !== 'all' && leave.societenom !== selectedSociete) return false; if (selectedService !== 'all' && leave.servicenom !== selectedService) return false; return true; }); }; // ⭐ OBTENIR TOUS LES EMPLOYÉS DISPONIBLES APRÈS FILTRAGE DE BASE // ⭐ OBTENIR TOUS LES EMPLOYÉS DISPONIBLES - déjà filtrés par le backend const getAllEmployees = () => { if (!filters.employees || filters.employees.length === 0) { return []; } // Pour directeur de campus - le backend a déjà tout filtré if (role === 'directeur de campus' || role === 'directrice de campus') { const employeeList = filters.employees.map(emp => ({ name: typeof emp === 'string' ? emp : emp.name, campus: emp.campus || '', societe: emp.societe || '', service: emp.service || '' })); // Dédupliquer par nom const uniqueEmployees = []; const seenNames = new Set(); for (const emp of employeeList) { if (!seenNames.has(emp.name)) { seenNames.add(emp.name); uniqueEmployees.push(emp); } } return uniqueEmployees.sort((a, b) => a.name.localeCompare(b.name)); } // Pour president/admin/rh - appliquer les filtres frontend const filteredEmployees = filters.employees.filter(emp => { if (role === 'president' || role === 'rh' || role === 'admin') { if (selectedCampus !== 'all' && emp.campus !== selectedCampus) return false; if (selectedSociete !== 'all' && emp.societe !== selectedSociete) return false; if (selectedService !== 'all' && emp.service !== selectedService) return false; return true; } // Pour collaborateur/validateur/apprenti - pas de filtre return true; }).map(emp => ({ name: typeof emp === 'string' ? emp : emp.name, campus: emp.campus || '', societe: emp.societe || '', service: emp.service || '' })); // Dédupliquer par nom const uniqueEmployees = []; const seenNames = new Set(); for (const emp of filteredEmployees) { if (!seenNames.has(emp.name)) { seenNames.add(emp.name); uniqueEmployees.push(emp); } } return uniqueEmployees.sort((a, b) => a.name.localeCompare(b.name)); }; // ⭐ OBTENIR LES EMPLOYÉS À AFFICHER DANS LE TABLEAU const getDisplayedEmployees = () => { const allEmployees = getAllEmployees(); let filteredEmployees = allEmployees; if (selectedEmployees.length > 0) { filteredEmployees = allEmployees.filter(emp => selectedEmployees.includes(emp.name)); } // ⭐ TRI AUTOMATIQUE pour President/RH/Admin/Directeur de Campus // quand "Tous les campus" ET "Toutes les sociétés" ET "Tous les services" const shouldAutoSort = ( ['president', 'rh', 'admin'].includes(role) && selectedCampus === 'all' && selectedSociete === 'all' && selectedService === 'all' ) || ( (role === 'directeur de campus' || role === 'directrice de campus') && selectedSociete === 'all' && selectedService === 'all' ); if (shouldAutoSort) { console.log("🔄 Tri automatique par société → service → nom"); return filteredEmployees.sort((a, b) => { // 1. Tri par société const societeCompare = (a.societe || '').localeCompare(b.societe || ''); if (societeCompare !== 0) return societeCompare; // 2. Tri par service const serviceCompare = (a.service || '').localeCompare(b.service || ''); if (serviceCompare !== 0) return serviceCompare; // 3. Tri par nom return a.name.localeCompare(b.name); }); } // ⭐ Sinon, tri simple par nom return filteredEmployees.sort((a, b) => a.name.localeCompare(b.name)); }; // ⭐ OBTENIR LES CONGÉS À AFFICHER const getDisplayedLeaves = () => { const baseFiltered = getBaseFilteredLeaves(); const displayedEmployees = getDisplayedEmployees(); const displayedNames = displayedEmployees.map(emp => emp.name); return baseFiltered.filter(leave => displayedNames.includes(leave.employeename)); }; // ⭐ USEMEMO POUR ÉVITER LES RECALCULS INUTILES const allEmployeesData = useMemo(() => { return getAllEmployees(); }, (role === 'directeur de campus' || role === 'directrice de campus') ? [filters.employees] // Directeur: seulement filters.employees (backend filtre) : [teamLeaves, filters, selectedCampus, selectedSociete, selectedService, role] // Autres: tous les filtres ); // ⭐ NETTOYER LA SÉLECTION SI DES EMPLOYÉS NE SONT PLUS DISPONIBLES useEffect(() => { const availableNames = allEmployeesData.map(emp => emp.name); setSelectedEmployees(prev => { const filtered = prev.filter(name => availableNames.includes(name)); // ⭐ Ne retourner que si vraiment différent pour éviter boucles if (filtered.length !== prev.length) { console.log("🧹 Nettoyage employés:", { avant: prev.length, après: filtered.length }); return filtered; } return prev; }); }, [allEmployeesData]); // ⭐ LOG DE DÉBOGAGE useEffect(() => { console.log("👥 Employés disponibles (après filtres):", allEmployeesData.length); console.log("✅ Employés sélectionnés:", selectedEmployees.length); console.log("📊 Employés affichés dans le tableau:", getDisplayedEmployees().length); }, [allEmployeesData, selectedEmployees]); const toggleEmployee = (employeeName) => { setSelectedEmployees(prev => { if (prev.includes(employeeName)) { return prev.filter(name => name !== employeeName); } else { return [...prev, employeeName]; } }); }; const selectAllEmployees = () => { const allNames = allEmployeesData.map(emp => emp.name); setSelectedEmployees(allNames); }; const deselectAllEmployees = () => { setSelectedEmployees([]); }; const parseLocalDate = (dateStr) => { if (!dateStr) return null; const [year, month, day] = dateStr.split('-').map(Number); return new Date(year, month - 1, day); }; const getLeaveForEmployeeOnDate = (employeeName, date) => { if (!date) return null; const displayedLeaves = getDisplayedLeaves(); return displayedLeaves.find(leave => { if (leave.employeename !== employeeName) return false; const start = parseLocalDate(leave.startdate); const end = parseLocalDate(leave.enddate); return date >= start && date <= end; }); }; const hasLeave = (date) => { if (!date) return false; const displayedLeaves = getDisplayedLeaves(); return displayedLeaves.some(leave => { const start = parseLocalDate(leave.startdate); const end = parseLocalDate(leave.enddate); return date >= start && date <= end; }); }; const getLeaveColor = (leave) => { if (!leave) return ''; const type = leave.type?.toLowerCase(); const status = leave.statut?.toLowerCase(); if (status === 'en attente' || status === 'pending' || status === 'en attente de validation') { return 'bg-orange-400'; } if (type === 'formation' || type === 'training') { return 'bg-blue-400'; } return 'bg-green-400'; }; const handleMouseMove = (e) => { if (!isMobile) { setMousePosition({ x: e.clientX, y: e.clientY }); } }; const navigateMonth = (direction) => { setCurrentDate(prev => { const newDate = new Date(prev); if (direction === 'prev') { newDate.setMonth(prev.getMonth() - 1); } else { newDate.setMonth(prev.getMonth() + 1); } return newDate; }); }; useEffect(() => { const handleClickOutside = () => { if (contextMenu.show) { setContextMenu({ show: false, x: 0, y: 0 }); } }; document.addEventListener('click', handleClickOutside); return () => document.removeEventListener('click', handleClickOutside); }, [contextMenu.show]); const isPastDate = (date) => { if (!date) return false; const today = new Date(); today.setHours(0, 0, 0, 0); return date < today; }; const isSelected = (date) => { if (!date || !selectedDate) return false; if (selectedEndDate) { return date >= selectedDate && date <= selectedEndDate; } return date.toDateString() === selectedDate.toDateString(); }; const isSaturday = (date) => { return date.getDay() === 6; }; const calculateWorkingDays = (start, end) => { if (!start || !end) return 0; let workingDays = 0; const current = new Date(start); while (current <= end) { const dayOfWeek = current.getDay(); if (dayOfWeek >= 1 && dayOfWeek <= 5 && !isHoliday(current)) { workingDays++; } current.setDate(current.getDate() + 1); } return workingDays; }; const checkIfRangeIncludesSaturday = (start, end) => { const current = new Date(start); while (current <= end) { if (current.getDay() === 6) return true; current.setDate(current.getDate() + 1); } return false; }; const handleDateClick = (date) => { if (!date || isPastDate(date) || isHoliday(date)) return; const saturday = isSaturday(date); // ⭐ Si c'est un samedi et qu'on n'a pas encore de sélection en cours if (saturday && !selectedDate) { // Permettre de sélectionner un samedi seul setSelectedDate(date); setSelectedEndDate(null); setIsSelectingRange(true); showToast('📅 Samedi sélectionné - Cliquez sur une autre date ou validez pour une récupération', 'info'); return; } // ⭐ Si on clique sur un samedi alors qu'on a déjà une sélection en cours if (saturday && selectedDate && isSelectingRange) { // Vérifier que la plage contient bien un samedi if (date >= selectedDate) { setSelectedEndDate(date); setIsSelectingRange(false); // Ouvrir directement le modal avec type "Récup" pré-sélectionné setPreselectedDates({ startDate: selectedDate, endDate: date, type: 'Récup' }); setShowNewRequestModal(true); setSelectedDate(null); setSelectedEndDate(null); } else { showToast('⚠️ La date de fin doit être après la date de début', 'error'); } return; } // ⭐ Logique normale pour les jours de semaine if (!selectedDate) { setSelectedDate(date); setSelectedEndDate(null); setIsSelectingRange(true); } else if (isSelectingRange && !selectedEndDate) { if (date >= selectedDate) { setSelectedEndDate(date); setIsSelectingRange(false); const includesSaturday = checkIfRangeIncludesSaturday(selectedDate, date); if (includesSaturday) { // Si la plage inclut un samedi, forcer le type Récup setPreselectedDates({ startDate: selectedDate, endDate: date, type: 'Récup' }); setShowNewRequestModal(true); setSelectedDate(null); setSelectedEndDate(null); } else { // Sinon, afficher le menu contextuel setTimeout(() => { setContextMenu({ show: true, x: window.innerWidth / 2, y: window.innerHeight / 2 }); }, 100); } } else { setSelectedDate(date); setSelectedEndDate(null); } } else { setSelectedDate(date); setSelectedEndDate(null); setIsSelectingRange(true); } }; const handleContextMenu = (e, date) => { e.preventDefault(); if (!date || isPastDate(date) || isHoliday(date) || !isSelected(date)) return; setContextMenu({ show: true, x: e.clientX, y: e.clientY }); }; const handleTypeSelection = (type) => { if (!selectedDate) return; const startDate = selectedDate; const endDate = selectedEndDate || selectedDate; const includesSaturday = checkIfRangeIncludesSaturday(startDate, endDate); if (includesSaturday && type !== 'Récup') { showToast('⚠️ Les samedis ne peuvent être sélectionnés que pour les récupérations', 'error'); setContextMenu({ show: false, x: 0, y: 0 }); return; } setPreselectedDates({ startDate: startDate, endDate: endDate, type: type }); setShowNewRequestModal(true); setContextMenu({ show: false, x: 0, y: 0 }); setSelectedDate(null); setSelectedEndDate(null); setIsSelectingRange(false); }; const cancelSelection = () => { setSelectedDate(null); setSelectedEndDate(null); setIsSelectingRange(false); setContextMenu({ show: false, x: 0, y: 0 }); showToast('❌ Sélection annulée', 'info'); }; const getSelectedDays = () => { if (!selectedDate) return 0; if (selectedEndDate) { return calculateWorkingDays(selectedDate, selectedEndDate); } return 1; }; const formatDateToString = (date) => { if (!date) return null; const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; }; const days = isMobile ? getDaysInMonthMobile() : getDaysInMonth(); const employees = getDisplayedEmployees(); const allEmployees = allEmployeesData; const totalPTO = detailedCounters ? (detailedCounters.cpN?.solde || 0) + (detailedCounters.cpN1?.solde || 0) + (detailedCounters.rttN?.solde || 0) : 0; const canViewAllFilters = ['president', 'rh', 'admin'].includes(role); const canViewCampusFilters = ['president', 'rh', 'admin', 'directeur de campus','directrice de campus'].includes(role); const activeFiltersCount = [ employeeFilter !== "all" ? employeeFilter : null, selectedCampus !== "all" ? selectedCampus : null, selectedSociete !== "all" ? selectedSociete : null, selectedService !== "all" ? selectedService : null, selectedEmployees.length > 0 ? `${selectedEmployees.length} collab` : null, ].filter(Boolean).length; const clearAllFilters = () => { setEmployeeFilter("all"); setSelectedCampus("all"); setSelectedSociete("all"); setSelectedService("all"); setSelectedEmployees([]); }; const renderLeaveCell = (leave) => { if (!leave) return null; const color = getLeaveColor(leave); // 🔹 CORRECTION : Parser correctement les détails let details; try { if (typeof leave.detailsconges === 'string') { details = JSON.parse(leave.detailsconges); } else { details = leave.detailsconges || []; } } catch (e) { console.error('Erreur parsing detailsconges:', e, leave.detailsconges); details = []; } // Vérifier si c'est une demi-journée const isPeriodePartielle = Array.isArray(details) && details.some(d => d.periode === 'Matin' || d.periode === 'Après-midi' ); if (isPeriodePartielle) { const hasMatin = details?.some(d => d.periode === 'Matin'); const hasApresMidi = details?.some(d => d.periode === 'Après-midi'); return (
{hasMatin &&
} {hasApresMidi &&
} {!hasMatin && !hasApresMidi &&
}
); } // Affichage normal pour journée entière return
; }; return (

Calendrier

Calendrier des congés

{sseConnected && (
Connecté
)}
PTO: {totalPTO.toFixed(1)}j
PTO: {totalPTO.toFixed(1)}j
{isSelectingRange && (

Mode sélection activé

{selectedDate ? `Début: ${selectedDate.toLocaleDateString('fr-FR')} - Cliquez sur la date de fin` : 'Cliquez sur une date de début' }

)} {selectedDate && (

{selectedEndDate ? 'Plage sélectionnée' : 'Date sélectionnée'} : {selectedDate.toLocaleDateString('fr-FR')} {selectedEndDate && ` - ${selectedEndDate.toLocaleDateString('fr-FR')}`}

{getSelectedDays()} jour{getSelectedDays() > 1 ? 's' : ''} ouvré{getSelectedDays() > 1 ? 's' : ''} {isSelectingRange && !selectedEndDate && ( (cliquez sur une autre date pour créer une plage) )}

)}
{activeFiltersCount > 0 && ( )}
{showFilters && (
{/* FILTRES POUR PRESIDENT/ADMIN/RH : Campus + Societe + Service */} {canViewAllFilters && ( <> {filters.campus && filters.campus.length > 0 && (
)} {filters.societes && filters.societes.length > 0 && (
)} {filters.services && filters.services.length > 0 && (
)} )} {/* FILTRES POUR DIRECTEUR DE CAMPUS - Société et Service uniquement */} {(role === 'directeur de campus' || role === 'directrice de campus') && filters.societes && filters.societes.length > 0 && (
)} {(role === 'directeur de campus' || role === 'directrice de campus') && filters.services && filters.services.length > 0 && (
)} {/* Section Collaborateurs - pour tous les rôles */}
{selectedEmployees.length > 0 && (
{selectedEmployees.map((empName, idx) => ( {empName} ))}
)} {showEmployeeList && (
{allEmployeesData.length === 0 ? (

Aucun collaborateur trouvé avec les filtres actuels

) : ( <>
{allEmployeesData.map((employee, idx) => ( ))}
)}
)}
)}
{!isMobile && (
{monthNames[currentDate.getMonth()]} {currentDate.getFullYear()}
{days.map((date, index) => { const dayName = dayNames[date.getDay() - 1]; const inRange = isSelected(date); const past = isPastDate(date); const holiday = isHoliday(date); const saturday = isSaturday(date); return ( ); })} {employees.length === 0 ? ( ) : ( employees.map((employee, empIndex) => ( {days.map((date, dayIndex) => { const leave = getLeaveForEmployeeOnDate(employee.name, date); const inRange = isSelected(date); const past = isPastDate(date); const holiday = isHoliday(date); const saturday = isSaturday(date); const isToday = date.toDateString() === new Date().toDateString(); return ( ); })} )) )}
Collaborateurs ({employees.length} {selectedEmployees.length > 0 && ` / ${allEmployees.length}`})
{dayName}
{date.getDate()}
{allEmployees.length === 0 ? 'Aucun collaborateur trouvé' : 'Aucun collaborateur sélectionné - Cliquez sur "Sélectionner" pour choisir'}
{employee.name}
{employee.service} {canViewCampusFilters && ( <> {employee.societe && ` • ${employee.societe}`} {(role === "president" || role === "rh" || role === "admin") && employee.campus && ` • ${employee.campus}`} {(role === "directeur de campus" || role === 'directrice de campus') && employee.societe === "Ensup Solution & Support" && employee.campus !== "N/A" && ` • ${employee.campus}`} )}
{ // ⭐ Permettre le clic sur les samedis aussi (retirer le check !saturday) if (!leave && employee.name === `${user.prenom} ${user.nom}` && !past && !holiday) { handleDateClick(date); } }} onContextMenu={(e) => handleContextMenu(e, date)} onMouseEnter={() => !isMobile && leave && setHoveredLeave({ employee, leave, date })} onMouseLeave={() => setHoveredLeave(null)} > {/* ⭐ REMPLACER CETTE SECTION */} {holiday ? (
) : leave ? ( renderLeaveCell(leave) ) : employee.name === `${user.prenom} ${user.nom}` && !past && !holiday ? (
) : (
)}
Aujourd'hui
Validé
En attente
Formation
Samedi (Récup)
Jours fériés
Sélection
)} {isMobile && (

{monthNames[currentDate.getMonth()]} {currentDate.getFullYear()}

{dayNamesMobile.map(day => (
{day}
))}
{days.map((date, index) => (
handleDateClick(date)} onContextMenu={(e) => handleContextMenu(e, date)} title={isHoliday(date) ? getHolidayName(date) : ''} > {date && (
{date.getDate()} {isHoliday(date) && getHolidayName(date) && (
{getHolidayName(date)}
)} {hasLeave(date) && (
{getDisplayedLeaves() .filter(leave => { const start = parseLocalDate(leave.startdate); const end = parseLocalDate(leave.enddate); return date >= start && date <= end; }) .map((leave, i) => (
{leave.employeename}
))}
)}
)}
))}
Validé
En attente
Formation
Férié
Sélection
)} {contextMenu.show && (

{getSelectedDays()} jour{getSelectedDays() > 1 ? 's' : ''} sélectionné{getSelectedDays() > 1 ? 's' : ''}

)} {hoveredLeave && !isMobile && (
{/* Emoji selon le type de journée */}
{(() => { const details = typeof hoveredLeave.leave.details_conges === 'string' ? JSON.parse(hoveredLeave.leave.details_conges) : hoveredLeave.leave.details_conges; const isPeriodePartielle = details?.some( d => d.periode === 'Matin' || d.periode === 'Après-midi' ); return isPeriodePartielle ? '🌗' : '📅'; })()}
{hoveredLeave.employee.name}
{/* Afficher les types avec leurs périodes */}
{(() => { const details = typeof hoveredLeave.leave.details_conges === 'string' ? JSON.parse(hoveredLeave.leave.details_conges) : hoveredLeave.leave.details_conges; if (details && Array.isArray(details)) { return details.map((detail, idx) => (
{detail.type} {detail.periode !== 'Journée entière' && ( ({detail.periode === 'Matin' ? '🌅 Matin' : '☀️ Après-midi'}) )} - {detail.jours}j
)); } else { // Fallback pour ancien format return (
{hoveredLeave.leave.type || 'Congé'}
); } })()}
{hoveredLeave.leave.statut}
Du {parseLocalDate(hoveredLeave.leave.startdate)?.toLocaleDateString('fr-FR')} au {parseLocalDate(hoveredLeave.leave.enddate)?.toLocaleDateString('fr-FR')}
{hoveredLeave.leave.nombrejoursouvres && (
Durée totale: {hoveredLeave.leave.nombrejoursouvres} jour(s)
)}
)} {hoveredLeave && isMobile && (
setHoveredLeave(null)}>
e.stopPropagation()}>

{hoveredLeave.employee.name}

{hoveredLeave.leave.type || 'Congé'}

)}
{toasts.map(toast => (
{toast.message}
))}
{showNewRequestModal && detailedCounters && ( { setShowNewRequestModal(false); setPreselectedDates(null); }} availableLeaveCounters={{ availableCP_N: detailedCounters.cpN?.solde || 0, totalCP_N: detailedCounters.cpN?.acquis || 0, availableCP_N1: detailedCounters.cpN1?.solde || 0, availableRTT_N: detailedCounters.rttN?.solde || 0, totalRTT_N: detailedCounters.rttN?.acquis || 0, availableRTT_N1: 0, availableABS: 0, availableCP: (detailedCounters.cpN1?.solde || 0) + (detailedCounters.cpN?.solde || 0), availableRTT: detailedCounters.rttN?.solde || 0 }} accessToken={graphToken} userId={userId} userRole={user.role} userEmail={user.email} userName={`${user.prenom} ${user.nom}`} onRequestSubmitted={refreshAllData} preselectedStartDate={preselectedDates ? formatDateToString(preselectedDates.startDate) : null} preselectedEndDate={preselectedDates ? formatDateToString(preselectedDates.endDate) : null} preselectedType={preselectedDates?.type} /> )}
); }; export default Calendar;