import React, { useState, useEffect } from 'react'; import Sidebar from '../components/Sidebar'; import { ChevronLeft, ChevronRight, Plus, X, Menu } from 'lucide-react'; import { useAuth } from '../context/AuthContext'; import NewLeaveRequestModal from '../components/NewLeaveRequestModal'; const Calendar = () => { const { user } = useAuth(); const [sidebarOpen, setSidebarOpen] = useState(false); const [currentDate, setCurrentDate] = useState(new Date()); const [selectedDate, setSelectedDate] = useState(null); const [selectedEndDate, setSelectedEndDate] = useState(null); const [isSelectingRange, setIsSelectingRange] = useState(false); const [showNewRequestModal, setShowNewRequestModal] = useState(false); const [contextMenu, setContextMenu] = useState({ show: false, x: 0, y: 0 }); const [preselectedType, setPreselectedType] = useState(null); const [holidays, setHolidays] = useState([]); const [isLoadingHolidays, setIsLoadingHolidays] = useState(true); const [leaveCounters, setLeaveCounters] = useState({ availableCP: 25, availableRTT: 8, availableABS: 0 }); const [teamLeaves, setTeamLeaves] = useState([]); const monthNames = [ 'Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre' ]; const dayNames = ['Lun', 'Mar', 'Mer', 'Jeu', 'Ven']; // Récupération des jours fériés depuis l'API gouvernementale française const fetchFrenchHolidays = async (year) => { try { setIsLoadingHolidays(true); 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(); // Convertir les dates de l'API en objets Date const holidayDates = Object.keys(data).map(dateStr => { const [year, month, day] = dateStr.split('-').map(Number); return { date: new Date(year, month - 1, day), // month - 1 car les mois JS commencent à 0 name: data[dateStr] }; }); return holidayDates; } catch (error) { console.error('Erreur lors de la récupération des jours fériés:', error); // Fallback avec quelques jours fériés fixes si l'API échoue return [ { date: new Date(year, 0, 1), name: 'Jour de l\'An' }, { date: new Date(year, 5, 1), name: 'Fête du Travail' }, { date: new Date(year, 6, 14), name: 'Fête Nationale' }, { date: new Date(year, 12, 25), name: 'Noël' } ]; } finally { setIsLoadingHolidays(false); } }; // Charger les jours fériés au montage du composant et lors du changement d'année useEffect(() => { const loadHolidays = async () => { const currentYear = currentDate.getFullYear(); const nextYear = currentYear + 1; const prevYear = currentYear - 1; // Charger les jours fériés pour l'année précédente, actuelle et suivante const [prevYearHolidays, currentYearHolidays, nextYearHolidays] = await Promise.all([ fetchFrenchHolidays(prevYear), fetchFrenchHolidays(currentYear), fetchFrenchHolidays(nextYear) ]); // Combiner tous les jours fériés const allHolidays = [...prevYearHolidays, ...currentYearHolidays, ...nextYearHolidays]; setHolidays(allHolidays); }; const loadTeamLeaves = async () => { if (user?.id) { try { const response = await fetch(`http://localhost/GTA/project/public/php/getTeamLeaves.php?user_id=${user.id}`); const data = await response.json(); if (data.success) { setTeamLeaves(data.leaves || []); } } catch (error) { console.error('Erreur récupération congés équipe:', error); } } }; loadHolidays(); loadTeamLeaves(); }, [currentDate.getFullYear()]); // Fermer le menu contextuel quand on clique ailleurs useEffect(() => { const handleClickOutside = () => { setContextMenu({ show: false, x: 0, y: 0 }); }; if (contextMenu.show) { document.addEventListener('click', handleClickOutside); return () => document.removeEventListener('click', handleClickOutside); } }, [contextMenu.show]); const getDaysInMonth = (date) => { const year = date.getFullYear(); const month = date.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; // Adjust for Monday start const days = []; // Add empty cells for days before the first day of the month for (let i = 0; i < startingDayOfWeek; i++) { days.push(null); } // Add days of the month (only weekdays) for (let day = 1; day <= daysInMonth; day++) { const currentDay = new Date(year, month, day); const dayOfWeek = currentDay.getDay(); // Only add weekdays (Monday = 1 to Friday = 5) if (dayOfWeek >= 1 && dayOfWeek <= 5) { days.push(currentDay); } } return days; }; 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; }); }; const isToday = (date) => { if (!date) return false; const today = new Date(); return date.toDateString() === today.toDateString(); }; const isPastDate = (date) => { if (!date) return false; const today = new Date(); today.setHours(0, 0, 0, 0); return date < today; }; const isHoliday = (date) => { if (!date) return false; return holidays.some(holiday => holiday.date.toDateString() === date.toDateString()); }; const getHolidayName = (date) => { if (!date) return null; const holiday = holidays.find(holiday => holiday.date.toDateString() === date.toDateString()); return holiday ? holiday.name : null; }; const isSelected = (date) => { if (!date || !selectedDate) return false; if (selectedEndDate) { return date >= selectedDate && date <= selectedEndDate; } return date.toDateString() === selectedDate.toDateString(); }; 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 handleDateClick = (date) => { if (!date || isPastDate(date) || isHoliday(date)) return; if (!selectedDate) { // Première sélection setSelectedDate(date); setSelectedEndDate(null); setIsSelectingRange(true); } else if (isSelectingRange && !selectedEndDate) { // Deuxième sélection pour la plage if (date >= selectedDate) { setSelectedEndDate(date); setIsSelectingRange(false); // Ouvrir automatiquement le modal avec les dates pré-remplies setTimeout(() => { setShowNewRequestModal(true); }, 100); } else { // Si la date est antérieure, recommencer la sélection setSelectedDate(date); setSelectedEndDate(null); } } else { // Nouvelle sélection (reset) 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) => { setPreselectedType(type); setContextMenu({ show: false, x: 0, y: 0 }); setShowNewRequestModal(true); }; const resetSelection = () => { setSelectedDate(null); setSelectedEndDate(null); setIsSelectingRange(false); setPreselectedType(null); }; const getSelectedDays = () => { if (!selectedDate) return 0; if (selectedEndDate) { return calculateWorkingDays(selectedDate, selectedEndDate); } return 1; }; const getAvailableTypes = () => { const days = getSelectedDays(); const types = []; if (leaveCounters.availableCP >= days) { types.push({ key: 'CP', label: 'Congés payés', color: 'bg-blue-600', available: leaveCounters.availableCP }); } if (days <= 5 && leaveCounters.availableRTT >= days) { types.push({ key: 'RTT', label: 'RTT', color: 'bg-green-600', available: leaveCounters.availableRTT }); } types.push({ key: 'ABS', label: 'Congé maladie', color: 'bg-red-600', available: '∞' }); return types; }; const hasLeave = (date) => { if (!date) return false; // Vérifier les congés de l'équipe return teamLeaves.some(leave => { const startDate = new Date(leave.start_date); const endDate = new Date(leave.end_date); return date >= startDate && date <= endDate; }); }; const days = getDaysInMonth(currentDate); // Export CSV const exportTeamLeavesToGoogleCSV = (teamLeaves) => { const csvRows = [ ['Subject', 'Start Date', 'Start Time', 'End Date', 'End Time', 'All Day Event', 'Description', 'Location', 'Private'], ...teamLeaves.map(leave => { // Conversion date YYYY-MM-DD -> MM/DD/YYYY const formatDate = dateStr => { const [year, month, day] = dateStr.split('-'); return `${month}/${day}/${year}`; }; return [ `Congé - ${leave.employee_name}`, // Subject formatDate(leave.start_date), // Start Date '', // Start Time (vide car congé toute la journée) formatDate(leave.end_date), // End Date '', // End Time 'TRUE', // All Day Event `Type de congé: ${leave.type}`, // Description '', // Location vide 'TRUE' // Privé ]; }) ]; const csvContent = csvRows.map(row => row.join(',')).join('\n'); // Ajouter BOM UTF-8 pour Excel/Google Sheets const BOM = '\uFEFF'; const blob = new Blob([BOM + csvContent], { type: 'text/csv;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.setAttribute('href', url); link.setAttribute('download', 'conges_equipe_googleagenda.csv'); document.body.appendChild(link); link.click(); document.body.removeChild(link); }; // Export ICS const exportTeamLeavesToICS = (teamLeaves) => { const ICS_HEADER = `BEGIN:VCALENDAR VERSION:2.0 PRODID:-//TonEntreprise//CongesEquipe//FR CALSCALE:GREGORIAN`; const ICS_FOOTER = 'END:VCALENDAR'; const events = teamLeaves.map(leave => { // Format date YYYYMMDD const startDate = leave.start_date.replace(/-/g, ''); const endDate = leave.end_date.replace(/-/g, ''); return `BEGIN:VEVENT SUMMARY:Congé - ${leave.employee_name} DTSTART;VALUE=DATE:${startDate} DTEND;VALUE=DATE:${endDate} DESCRIPTION:Type de congé: ${leave.type} STATUS:CONFIRMED END:VEVENT`; }).join('\n'); const icsContent = `${ICS_HEADER}\n${events}\n${ICS_FOOTER}`; // Blob pour téléchargement const blob = new Blob([icsContent], { type: 'text/calendar;charset=utf-8;' }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = 'conges_equipe.ics'; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; return (
Vue d'ensemble de vos congés
{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) )}
Clic droit pour choisir le type de congé
)}{getSelectedDays()} jour{getSelectedDays() > 1 ? 's' : ''} sélectionné{getSelectedDays() > 1 ? 's' : ''}