import React, { useState, useEffect, useCallback } from 'react'; import { useAuth } from '../context/AuthContext'; import Sidebar from '../components/Sidebar'; import { Calendar, Clock, Check, X, Save, Lock, Unlock, Download, Menu, AlertCircle, ChevronLeft, ChevronRight, FileText, TrendingUp, RefreshCw, Info } from 'lucide-react'; const CompteRenduActivites = () => { const { user } = useAuth(); const userId = user?.id || user?.CollaborateurADId || user?.ID; const [sidebarOpen, setSidebarOpen] = useState(false); const [currentDate, setCurrentDate] = useState(new Date()); const [joursActifs, setJoursActifs] = useState([]); const [statsAnnuelles, setStatsAnnuelles] = useState(null); const [mensuelData, setMensuelData] = useState(null); const [congesData, setCongesData] = useState([]); const [holidays, setHolidays] = useState([]); const [isLoading, setIsLoading] = useState(true); const [showSaisieModal, setShowSaisieModal] = useState(false); const [selectedJour, setSelectedJour] = useState(null); const [showSaisieMasse, setShowSaisieMasse] = useState(false); const [isSaving, setIsSaving] = useState(false); const [infoMessage, setInfoMessage] = useState(null); // Vérifier l'accès : forfait jour, directeur campus, ou RH const hasAccess = () => { const userRole = user?.role; const typeContrat = user?.TypeContrat || user?.typeContrat; return ( typeContrat === 'forfait-jour' || typeContrat === 'forfait_jour' || userRole === 'Directeur Campus' || userRole === 'Directrice Campus' || userRole === 'RH' || userRole === 'Admin' ); }; const isRH = user?.role === 'RH' || user?.role === 'Admin'; const isDirecteurCampus = user?.role === 'Directeur Campus' || user?.role === 'Directrice Campus'; const annee = currentDate.getFullYear(); const mois = currentDate.getMonth() + 1; // Charger les jours fériés français 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; }; // Charger les données du mois const loadCompteRendu = useCallback(async () => { if (!userId || !hasAccess()) return; setIsLoading(true); try { const response = await fetch(`/getCompteRenduActivites?user_id=${userId}&annee=${annee}&mois=${mois}`); const data = await response.json(); if (data.success) { setJoursActifs(data.jours || []); setMensuelData(data.mensuel); console.log('📅 Jours chargés:', data.jours?.length || 0, 'jours'); console.log('📊 Détail des jours:', data.jours); } const congesResponse = await fetch(`/getTeamLeaves?user_id=${userId}&role=${user.role}`); const congesData = await congesResponse.json(); if (congesData.success) { setCongesData(congesData.leaves || []); } } catch (error) { console.error('Erreur chargement compte-rendu:', error); } finally { setIsLoading(false); } }, [userId, annee, mois, user?.role]); // Charger les stats annuelles const loadStatsAnnuelles = useCallback(async () => { if (!userId || !hasAccess()) return; try { const response = await fetch(`/getStatsAnnuelles?user_id=${userId}&annee=${annee}`); const data = await response.json(); if (data.success) { setStatsAnnuelles(data.stats); } } catch (error) { console.error('Erreur stats annuelles:', error); } }, [userId, annee]); useEffect(() => { loadCompteRendu(); loadStatsAnnuelles(); }, [loadCompteRendu, loadStatsAnnuelles]); // Vérifier si le mois est autorisé (mois en cours + mois précédent) const isMoisAutorise = () => { const today = new Date(); const currentYear = today.getFullYear(); const currentMonth = today.getMonth() + 1; const selectedYear = currentDate.getFullYear(); const selectedMonth = currentDate.getMonth() + 1; // RH peut tout voir if (isRH) return true; // Mois en cours autorisé if (selectedYear === currentYear && selectedMonth === currentMonth) return true; // Mois précédent autorisé const previousMonth = currentMonth === 1 ? 12 : currentMonth - 1; const previousYear = currentMonth === 1 ? currentYear - 1 : currentYear; return selectedYear === previousYear && selectedMonth === previousMonth; }; // Générer les jours du mois (lundi-samedi) avec décalage correct const getDaysInMonth = () => { 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(); // Jour de la semaine du 1er (0=dimanche, 1=lundi, ..., 6=samedi) let firstDayOfWeek = firstDay.getDay(); // Convertir pour que lundi = 0, mardi = 1, ..., samedi = 5, dimanche = 6 firstDayOfWeek = firstDayOfWeek === 0 ? 6 : firstDayOfWeek - 1; const days = []; // Ajouter des cases vides pour le décalage initial for (let i = 0; i < firstDayOfWeek; i++) { days.push(null); } // Ajouter tous les jours du mois (lundi-samedi uniquement) for (let day = 1; day <= daysInMonth; day++) { const currentDay = new Date(year, month, day); const dayOfWeek = currentDay.getDay(); // Exclure les dimanches (0) if (dayOfWeek !== 0) { days.push(currentDay); } } return days; }; const getJourData = (date) => { const dateStr = formatDateToString(date); const found = joursActifs.find(j => { // Normaliser la date de la BDD (peut être un objet Date ou une string) let jourDateStr = j.JourDate; if (j.JourDate instanceof Date) { jourDateStr = formatDateToString(j.JourDate); } else if (typeof j.JourDate === 'string') { // Si c'est déjà une string, extraire juste la partie date (YYYY-MM-DD) jourDateStr = j.JourDate.split('T')[0]; } const match = jourDateStr === dateStr; console.log('Comparaison:', jourDateStr, 'vs', dateStr, 'match:', match); return match; }); if (found) { console.log('✅ Jour trouvé:', dateStr, found); } return found; }; const isJourEnConge = (date) => { return congesData.some(conge => { const start = new Date(conge.startdate); const end = new Date(conge.enddate); return date >= start && date <= end && conge.statut === 'Valide'; }); }; // Vérifier si le jour est STRICTEMENT dans le passé (pas aujourd'hui) const isPastOnly = (date) => { const today = new Date(); today.setHours(0, 0, 0, 0); const checkDate = new Date(date); checkDate.setHours(0, 0, 0, 0); return checkDate < today; }; // Vérifier si un jour spécifique est verrouillé const isJourVerrouille = (date) => { const jourData = getJourData(date); return jourData?.Verrouille === true || jourData?.Verrouille === 1; }; // Afficher message d'info const showInfo = (message, type = 'info') => { setInfoMessage({ message, type }); setTimeout(() => setInfoMessage(null), 5000); }; // Ouvrir le modal de saisie const handleJourClick = (date) => { if (!date) return; // Ignorer les cases vides if (!isMoisAutorise() && !isRH) { showInfo('Vous ne pouvez saisir que pour le mois en cours ou le mois précédent', 'warning'); return; } if (!isPastOnly(date)) { showInfo('Vous ne pouvez pas saisir le jour actuel. Veuillez attendre demain.', 'warning'); return; } if (isHoliday(date)) { showInfo(`Jour férié : ${getHolidayName(date)} - Saisie impossible`, 'info'); return; } if (isJourEnConge(date)) { showInfo('Vous êtes en congé ce jour - Saisie impossible', 'info'); return; } if (isJourVerrouille(date) && !isRH) { showInfo('Ce jour est déjà verrouillé - Contactez les RH pour modification', 'error'); return; } const jourData = getJourData(date); setSelectedJour({ date: date, dateStr: formatDateToString(date), jourTravaille: jourData?.JourTravaille !== false, reposQuotidien: jourData?.ReposQuotidienRespect !== false, reposHebdo: jourData?.ReposHebdomadaireRespect !== false, commentaire: jourData?.CommentaireRepos || '' }); setShowSaisieModal(true); }; // Sauvegarder un jour const handleSaveJour = async () => { if ((!selectedJour.reposQuotidien || !selectedJour.reposHebdo)) { if (!selectedJour.commentaire || selectedJour.commentaire.trim() === '') { showInfo('Commentaire obligatoire en cas de non-respect des repos', 'warning'); return; } } setIsSaving(true); try { const response = await fetch('/saveCompteRenduJour', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: userId, date: selectedJour.dateStr, jour_travaille: selectedJour.jourTravaille, repos_quotidien: selectedJour.reposQuotidien, repos_hebdo: selectedJour.reposHebdo, commentaire: selectedJour.commentaire, rh_override: isRH }) }); const data = await response.json(); if (data.success) { await loadCompteRendu(); await loadStatsAnnuelles(); setShowSaisieModal(false); showInfo('✅ Jour enregistré', 'success'); console.log('Données rechargées après sauvegarde'); } else { showInfo(data.message || 'Erreur lors de la sauvegarde', 'error'); } } catch (error) { console.error('Erreur sauvegarde jour:', error); showInfo('Erreur serveur', 'error'); } finally { setIsSaving(false); } }; // Saisie en masse const handleSaisieMasse = async (joursTravailles) => { if (!isMoisAutorise() && !isRH) { showInfo('Vous ne pouvez saisir que pour le mois en cours ou le mois précédent', 'warning'); return; } setIsSaving(true); try { const response = await fetch('/saveCompteRenduMasse', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ user_id: userId, annee: annee, mois: mois, jours: joursTravailles, rh_override: isRH }) }); const data = await response.json(); if (data.success) { await loadCompteRendu(); await loadStatsAnnuelles(); setShowSaisieMasse(false); showInfo(data.message || `✅ ${data.count || 0} jours enregistrés`, 'success'); } else { showInfo(data.message || 'Erreur', 'error'); } } catch (error) { console.error('Erreur saisie masse:', error); showInfo('Erreur serveur', 'error'); } finally { setIsSaving(false); } }; 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 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 monthNames = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre']; const days = getDaysInMonth(); const moisAutorise = isMoisAutorise(); // Vérifier l'accès if (!hasAccess()) { return (
Cette fonctionnalité est réservée aux :
Chargement...
{infoMessage.message}
Forfait jour - Suivi des jours travaillés et repos obligatoires
Jours travaillés
{statsAnnuelles.totalJoursTravailles || 0}
Mois non accessible
Vous pouvez saisir uniquement le mois en cours et le mois précédent (mais pas le jour actuel).
Sélectionnez tous les jours travaillés du mois. Le jour actuel, les jours fériés et les congés sont automatiquement exclus. Les repos quotidien et hebdomadaire seront considérés comme respectés.