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"; import GlobalTutorial from '../components/GlobalTutorial'; const Calendar = () => { const { user } = useAuth(); const role = user?.role?.toLowerCase(); const userId = user?.id || user?.CollaborateurADId || user?.ID; const [isFirstLoad, setIsFirstLoad] = useState(true); 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([]); const [initialFiltersSet, setInitialFiltersSet] = useState(false); 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', 'Dim']; const dayNamesMobile = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam', 'Dim']; 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(`/api/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 = `/api/getTeamLeaves?user_id=${userId}&role=${user.role}`; console.log("📡 AVANT construction URL:", { selectedSociete, selectedCampus, selectedService }); if (role === 'president' || role === 'rh' || role === 'admin' || role === 'directeur de campus' || role === 'directrice de campus' || role === 'collaborateur' || role === 'collaboratrice' || role === 'apprenti') { // ⭐ TOUJOURS envoyer les paramètres url += `&selectedSociete=${encodeURIComponent(selectedSociete || 'all')}`; url += `&selectedCampus=${encodeURIComponent(selectedCampus || 'all')}`; url += `&selectedService=${encodeURIComponent(selectedService || 'all')}`; } console.log("📡 URL finale:", url); const response = await fetch(url); const data = await response.json(); console.log("📡 Réponse API:", { employees: data.filters?.employees?.length, leaves: data.leaves?.length }); 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, selectedSociete, selectedCampus, selectedService]); 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); }, []); const handleSSEEvent = useCallback((eventData) => { console.log('📅 Événement SSE reçu:', eventData.type); if (eventData.type === 'demande-validated') { showToast( `✅ Demande validée: ${eventData.typeConge}`, 'success' ); console.log(`🎨 Demande ${eventData.demandeId} validée - Type: ${eventData.typeConge}`); refreshAllData(); } }, [showToast, refreshAllData]); useEffect(() => { if (!userId) { console.log('❌ userId manquant, SSE non démarré'); setSseConnected(false); return; } console.log('🔌 Initialisation SSE avec userId:', userId); const eventSource = new EventSource( `/api/sse?user_id=${userId}` ); const handleValidated = (event) => { try { const eventData = JSON.parse(event.data); console.log('📨 SSE demande-validated reçu:', eventData); setSseConnected(true); handleSSEEvent(eventData); } catch (error) { console.error('❌ Erreur parsing SSE:', error); } }; const handleHeartbeat = () => { console.log('💓 SSE Heartbeat'); setSseConnected(true); }; const handleError = (error) => { console.error('❌ Erreur SSE:', error); setSseConnected(false); eventSource.close(); }; eventSource.addEventListener('demande-validated', handleValidated); eventSource.addEventListener('ping', handleHeartbeat); eventSource.onerror = handleError; return () => { console.log('🔌 Fermeture SSE'); eventSource.removeEventListener('demande-validated', handleValidated); eventSource.removeEventListener('ping', handleHeartbeat); eventSource.close(); setSseConnected(false); }; }, [userId, handleSSEEvent]); useEffect(() => { refreshAllData(); }, [refreshAllData]); useEffect(() => { if (isFirstLoad && filters.defaultCampus && (role === 'directeur de campus' || role === 'directrice de campus')) { console.log('🏢 Initialisation campus par défaut:', filters.defaultCampus); setSelectedCampus(filters.defaultCampus); setIsFirstLoad(false); } }, [filters.defaultCampus, isFirstLoad, role]); // ⭐ Initialisation des filtres par défaut pour collaborateur/apprenti (UNE SEULE FOIS) useEffect(() => { if (!initialFiltersSet && filters.defaultCampus && (role === 'collaborateur' || role === 'collaboratrice' || role === 'apprenti')) { console.log('🎯 Initialisation des filtres par défaut pour collaborateur'); console.log('📍 Valeurs reçues du backend:', { defaultSociete: filters.defaultSociete, defaultCampus: filters.defaultCampus, defaultService: filters.defaultService }); setSelectedSociete(filters.defaultSociete || 'all'); setSelectedCampus(filters.defaultCampus || 'all'); setSelectedService(filters.defaultService || 'all'); setInitialFiltersSet(true); } }, [filters.defaultCampus, filters.defaultService, filters.defaultSociete, initialFiltersSet, role]); // ⭐ Rechargement quand les filtres changent (TOUJOURS) useEffect(() => { if (role === 'president' || role === 'rh' || role === 'admin' || role === 'directeur de campus' || role === 'directrice de campus' || role === 'collaborateur' || role === 'collaboratrice' || role === 'apprenti') { console.log("🔄 Rechargement données:", { societe: selectedSociete, campus: selectedCampus, service: selectedService, role: role }); loadTeamLeaves(); } }, [selectedSociete, selectedCampus, 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(); if ((dayOfWeek >= 1 && dayOfWeek <= 6) || dayOfWeek === 0) { days.push(currentDay); } } return days; }; const isSunday = (date) => { return date.getDay() === 0; }; 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; }; // ⭐ SIMPLIFIÉ : Le backend filtre déjà const getBaseFilteredLeaves = () => { return teamLeaves; }; // ⭐ SIMPLIFIÉ : Pas de cas spécial pour collaborateur const getAllEmployees = () => { if (!filters.employees || filters.employees.length === 0) { return []; } const employeeList = filters.employees.map(emp => ({ name: typeof emp === 'string' ? emp : emp.name, campus: emp.campus || '', societe: emp.societe || '', service: emp.service || '' })); // Dédoublonner 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; }; const getDisplayedEmployees = () => { const allEmployees = getAllEmployees(); let filteredEmployees = allEmployees; if (selectedEmployees.length > 0) { filteredEmployees = allEmployees.filter(emp => selectedEmployees.includes(emp.name)); } const shouldAutoSort = ( ['president', 'rh', 'admin', 'directeur de campus', 'directrice de campus'].includes(role) && selectedCampus === 'all' && selectedSociete === 'all' && selectedService === 'all' ); const shouldSortByService = ( ['collaborateur', 'collaboratrice', 'apprenti'].includes(role) && selectedService === 'all' ); if (shouldAutoSort) { console.log("🔄 Tri automatique par société → campus → service → nom"); return filteredEmployees.sort((a, b) => { const isAMultiCampus = a.societe === 'Ensup Solution & Support' || a.societe === 'Ensup Solution et Support'; const isBMultiCampus = b.societe === 'Ensup Solution & Support' || b.societe === 'Ensup Solution et Support'; if (isAMultiCampus && !isBMultiCampus) return -1; if (!isAMultiCampus && isBMultiCampus) return 1; if (isAMultiCampus && isBMultiCampus) { const campusOrder = { 'CGY': 1, 'Cergy': 1, 'SQY': 2, 'Saint-Quentin': 2, 'Marseille': 3, 'Nantes': 4, 'Multi-campus': 5, 'N/A': 6 }; const orderA = campusOrder[a.campus] || 999; const orderB = campusOrder[b.campus] || 999; if (orderA !== orderB) return orderA - orderB; const serviceCompare = (a.service || '').localeCompare(b.service || ''); if (serviceCompare !== 0) return serviceCompare; return a.name.localeCompare(b.name); } const campusOrder = { 'CGY': 1, 'Cergy': 1, 'SQY': 2, 'Saint-Quentin': 2, 'Marseille': 3, 'Nantes': 4 }; const orderA = campusOrder[a.campus] || 999; const orderB = campusOrder[b.campus] || 999; if (orderA !== orderB) return orderA - orderB; const societeCompare = (a.societe || '').localeCompare(b.societe || ''); if (societeCompare !== 0) return societeCompare; const serviceCompare = (a.service || '').localeCompare(b.service || ''); if (serviceCompare !== 0) return serviceCompare; return a.name.localeCompare(b.name); }); } if (shouldSortByService) { console.log("🔄 Tri par service pour collaborateur"); return filteredEmployees.sort((a, b) => { const serviceCompare = (a.service || '').localeCompare(b.service || ''); if (serviceCompare !== 0) return serviceCompare; return a.name.localeCompare(b.name); }); } return filteredEmployees.sort((a, b) => a.name.localeCompare(b.name)); }; const getDisplayedLeaves = () => { const baseFiltered = getBaseFilteredLeaves(); const displayedEmployees = getDisplayedEmployees(); const displayedNames = displayedEmployees.map(emp => emp.name); return baseFiltered.filter(leave => displayedNames.includes(leave.employeename)); }; // ⭐ useMemo simplifié const allEmployeesData = useMemo(() => { return getAllEmployees(); }, [filters.employees]); useEffect(() => { const availableNames = allEmployeesData.map(emp => emp.name); setSelectedEmployees(prev => { const filtered = prev.filter(name => availableNames.includes(name)); if (filtered.length !== prev.length) { console.log("🧹 Nettoyage employés:", { avant: prev.length, après: filtered.length }); return filtered; } return prev; }); }, [allEmployeesData]); 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 { tailwindClass: '', hexColor: '' }; const status = leave.statut?.toLowerCase(); const type = leave.type?.toLowerCase(); if (status === 'en attente' || status === 'pending' || status === 'en attente de validation') { return { tailwindClass: 'bg-orange-400', hexColor: '#fb923c' }; } if (type) { if (type.toLowerCase().includes('récupération') || type.toLowerCase().includes('recuperation') || type.toLowerCase().includes('recup') || type.toLowerCase().includes('récup')) { return { tailwindClass: '', hexColor: '#d946ef' }; } if (type.includes('formation')) { return { tailwindClass: 'bg-blue-400', hexColor: '#60a5fa' }; } if (type.includes('cp') || type.includes('congé') || type.includes('conge') || type.includes('payé') || type.includes('paye')) { return { tailwindClass: 'bg-green-400', hexColor: '#4ade80' }; } if (type.includes('rtt')) { return { tailwindClass: 'bg-blue-300', hexColor: '#93c5fd' }; } } return { tailwindClass: 'bg-green-400', hexColor: '#4ade80' }; }; 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; if (!selectedDate) { setSelectedDate(date); setSelectedEndDate(null); setIsSelectingRange(true); } else if (isSelectingRange && !selectedEndDate) { if (date >= selectedDate) { setSelectedEndDate(date); setIsSelectingRange(false); 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); 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', 'directeur de campus', 'directrice de campus', 'collaborateur', 'collaboratrice', 'apprenti'].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 resetToDefaultFilters = () => { if (['collaborateur', 'collaboratrice', 'apprenti'].includes(role)) { const userEmployee = filters.employees?.find(emp => emp.name === `${user.prenom} ${user.nom}` ); if (userEmployee) { setSelectedSociete(userEmployee.societe || 'all'); setSelectedCampus(userEmployee.campus || 'all'); setSelectedService(userEmployee.service || 'all'); } } else { setSelectedSociete('all'); setSelectedCampus('all'); setSelectedService('all'); } setSelectedEmployees([]); }; const renderLeaveCell = (leave) => { if (!leave) return null; const colorObj = getLeaveColor(leave); const bgColor = colorObj.hexColor || '#4ade80'; 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 = []; } 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 &&
}
); } return (
); }; return (
{/* Mobile header */}

Calendrier

{/* Main content */}
{/* Header */}

Calendrier des congés

{sseConnected && (
Connecté
)}
{/* Selection mode banner */} {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' }

)} {/* Selected dates info */} {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) )}

)} {/* Filters section */}
{activeFiltersCount > 0 && ( )}
{showFilters && (
{/* Société filter */} {canViewAllFilters && filters.societes && filters.societes.length > 0 && (
)} {/* Campus filter */} {filters.campus && filters.campus.length > 0 && (
)} {/* Service filter */} {filters.services && filters.services.length > 0 && (
)} {/* Employee selection */}
{selectedEmployees.length > 0 && (
{selectedEmployees.map((empName, idx) => ( {empName} ))}
)} {showEmployeeList && (
{allEmployeesData.length === 0 ? (

Aucun collaborateur trouvé avec les filtres actuels

) : ( <>
{allEmployeesData.map((employee, idx) => ( ))}
)}
)}
)}
{/* Calendar */} {!isMobile && (
{/* Month navigation */}
{monthNames[currentDate.getMonth()]} {currentDate.getFullYear()}
{/* Calendar table */} {days.map((date, index) => { const dayName = date.getDay() === 0 ? 'Dim' : dayNames[date.getDay() - 1]; const inRange = isSelected(date); const past = isPastDate(date); const holiday = isHoliday(date); const saturday = isSaturday(date); const sunday = isSunday(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 sunday = isSunday(date); const isToday = date.toDateString() === new Date().toDateString(); const weekendAlreadyHasLeave = (saturday || sunday) && leave; let details, hasMatin = false, hasApresMidi = false; if (weekendAlreadyHasLeave) { try { if (typeof leave.detailsconges === 'string') { details = JSON.parse(leave.detailsconges); } else if (Array.isArray(leave.detailsconges)) { details = leave.detailsconges; } hasMatin = details.some(d => d.periode === 'Matin'); hasApresMidi = details.some(d => d.periode === 'Après-midi'); } catch (e) { console.error('Erreur parsing:', e); } } 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} )}
{ if (!leave && employee.name === `${user.prenom} ${user.nom}` && !past && !holiday && !sunday && !saturday) { handleDateClick(date); } }} onContextMenu={(e) => handleContextMenu(e, date)} onMouseEnter={() => !isMobile && leave && setHoveredLeave({ employee, leave, date })} onMouseLeave={() => setHoveredLeave(null)} title={ weekendAlreadyHasLeave ? `${saturday ? 'Samedi' : 'Dimanche'} saisi : ${leave?.type}${hasMatin ? ' - Matin' : ''}${hasApresMidi ? ' - Après-midi' : ''} - Non modifiable` : isHoliday(date) ? getHolidayName(date) : sunday ? 'Dimanche - Non sélectionnable' : saturday ? 'Samedi - Non sélectionnable' : '' } > {holiday ? (
) : sunday ? (
{weekendAlreadyHasLeave ? ( <> {hasMatin &&
} {hasApresMidi &&
} {!hasMatin && !hasApresMidi &&
} ) : (
)}
) : saturday ? (
{weekendAlreadyHasLeave ? ( <> {hasMatin &&
} {hasApresMidi &&
} {!hasMatin && !hasApresMidi &&
} ) : (
)}
) : leave ? ( renderLeaveCell(leave) ) : employee.name === `${user.prenom} ${user.nom}` && !past && !holiday ? (
) : (
)}
{/* Legend */}
Aujourd'hui
Validé
En attente
Formation
Samedi/Dimanche
Jours fériés
JPO/SF
)} {/* Mobile calendar - TO BE IMPLEMENTED IF NEEDED */}
{/* Context menu */} {contextMenu.show && (

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

)} {/* Hover tooltip */} {hoveredLeave && !isMobile && (
{/* Tooltip content - TO BE COMPLETED */}
{hoveredLeave.employee.name}
{hoveredLeave.leave.type}
)} {/* Toasts */}
{toasts.map((toast) => (
{toast.message}
))}
{/* Leave request modal */} {showNewRequestModal && detailedCounters && ( { setShowNewRequestModal(false); setPreselectedDates(null); }} availableLeaveCounters={{ availableCPN: detailedCounters.cpN?.solde || 0, totalCPN: detailedCounters.cpN?.acquis || 0, availableCPN1: detailedCounters.cpN1?.solde || 0, availableRTTN: detailedCounters.rttN?.solde || 0, totalRTTN: detailedCounters.rttN?.acquis || 0, availableRTTN1: 0, availableABS: 0, availableCP: (detailedCounters.cpN1?.solde || 0) + (detailedCounters.cpN?.solde || 0), availableRTT: detailedCounters.rttN?.solde || 0, availableRecup: detailedCounters?.recupN?.solde || 0, availableRecupN: detailedCounters?.recupN?.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;