import React, { useState, useEffect } from 'react'; import { X, AlertCircle, Upload, FileText, Image as ImageIcon, Trash2 } from 'lucide-react'; const EditLeaveRequestModal = ({ isOpen, onClose, request, onRequestUpdated, availableLeaveCounters, userId, userEmail, userName, accessToken }) => { // ======================================== // ÉTATS // ======================================== const [selectedTypes, setSelectedTypes] = useState([]); const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); const [reason, setReason] = useState(''); const [businessDays, setBusinessDays] = useState(0); const [saturdayCount, setSaturdayCount] = useState(0); // Répartition manuelle (multi-types) const [repartition, setRepartition] = useState({}); // Période par type (Matin/Après-midi/Journée entière) const [periodeSelection, setPeriodeSelection] = useState({}); // Documents médicaux const [medicalDocuments, setMedicalDocuments] = useState([]); const [isDragging, setIsDragging] = useState(false); // Compteurs const [countersData, setCountersData] = useState(null); const [isLoadingCounters, setIsLoadingCounters] = useState(true); // UI const [isSubmitting, setIsSubmitting] = useState(false); const [submitMessage, setSubmitMessage] = useState({ type: '', text: '' }); const [validationErrors, setValidationErrors] = useState([]); const availableTypes = [ { id: 'CP', label: 'Congé payé', color: '#3b82f6' }, { id: 'RTT', label: 'RTT', color: '#8b5cf6' }, { id: 'Récup', label: 'Récupération', color: '#10b981' }, { id: 'ABS', label: 'Arrêt maladie', color: '#ef4444' }, { id: 'Formation', label: 'Formation', color: '#f59e0b' } ]; // ======================================== // INITIALISATION // ======================================== useEffect(() => { if (isOpen && request) { console.log('📝 Initialisation EditModal avec request:', request); // Dates setStartDate(request.startDate || ''); setEndDate(request.endDate || ''); setReason(request.reason || ''); // Types (mapping inverse) const typeMapping = { 'Congé payé': 'CP', 'RTT': 'RTT', 'Récupération': 'Récup', 'Congé maladie': 'ABS', 'Formation': 'Formation' }; if (request.type) { const types = request.type.split(', ').map(t => typeMapping[t] || t); setSelectedTypes(types); console.log('✅ Types initialisés:', types); } // Calculer jours ouvrés if (request.startDate && request.endDate) { const days = calculateBusinessDays(request.startDate, request.endDate); setBusinessDays(days.businessDays); setSaturdayCount(days.saturdayCount); } } }, [isOpen, request]); // Charger les compteurs useEffect(() => { if (isOpen && userId) { loadCounters(); } }, [isOpen, userId]); // ======================================== // FONCTIONS UTILITAIRES // ======================================== const calculateBusinessDays = (start, end) => { const startD = new Date(start); const endD = new Date(end); let workingDays = 0; let saturdays = 0; const current = new Date(startD); while (current <= endD) { const dayOfWeek = current.getDay(); if (dayOfWeek === 6) { saturdays++; } else if (dayOfWeek !== 0) { workingDays++; } current.setDate(current.getDate() + 1); } return { businessDays: workingDays, saturdayCount: saturdays }; }; const loadCounters = async () => { setIsLoadingCounters(true); try { const response = await fetch(`/api/getDetailedLeaveCounters?user_id=${userId}`); const data = await response.json(); if (data.success) { setCountersData(data); console.log('✅ Compteurs chargés:', data); } } catch (error) { console.error('❌ Erreur chargement compteurs:', error); } finally { setIsLoadingCounters(false); } }; // ======================================== // GESTION DES TYPES // ======================================== const handleTypeToggle = (typeId) => { if (typeId === 'ABS' && selectedTypes.length > 0 && !selectedTypes.includes('ABS')) { alert('⚠️ L\'arrêt maladie ne peut pas être combiné avec d\'autres types'); return; } if (selectedTypes.includes('ABS') && typeId !== 'ABS') { alert('⚠️ L\'arrêt maladie ne peut pas être combiné avec d\'autres types'); return; } setSelectedTypes(prev => { if (prev.includes(typeId)) { const newTypes = prev.filter(t => t !== typeId); const newRep = { ...repartition }; delete newRep[typeId]; setRepartition(newRep); const newPeriodes = { ...periodeSelection }; delete newPeriodes[typeId]; setPeriodeSelection(newPeriodes); return newTypes; } else { return [...prev, typeId]; } }); }; // ======================================== // GESTION RÉPARTITION // ======================================== const handleRepartitionChange = (typeId, value) => { const numValue = parseFloat(value) || 0; const maxValue = businessDays; if (numValue > maxValue) { alert(`Maximum ${maxValue} jours`); return; } setRepartition(prev => ({ ...prev, [typeId]: numValue })); }; const handlePeriodeChange = (typeId, periode) => { setPeriodeSelection(prev => ({ ...prev, [typeId]: periode })); // Calcul automatique si un seul type if (selectedTypes.length === 1 && startDate === endDate) { if (periode === 'Matin' || periode === 'Après-midi') { setRepartition({ [typeId]: 0.5 }); } else { setRepartition({ [typeId]: businessDays }); } } }; // ======================================== // GESTION FICHIERS MÉDICAUX // ======================================== const handleFileSelect = (e) => { const files = Array.from(e.target.files); addFiles(files); }; const handleDrop = (e) => { e.preventDefault(); setIsDragging(false); const files = Array.from(e.dataTransfer.files); addFiles(files); }; const addFiles = (files) => { const validFiles = files.filter(file => { const isValidType = ['application/pdf', 'image/jpeg', 'image/jpg', 'image/png'].includes(file.type); const isValidSize = file.size <= 5 * 1024 * 1024; if (!isValidType) { alert(`❌ ${file.name}: Type non autorisé (PDF, JPG, PNG uniquement)`); return false; } if (!isValidSize) { alert(`❌ ${file.name}: Taille max 5MB`); return false; } return true; }); setMedicalDocuments(prev => [...prev, ...validFiles]); }; const removeFile = (index) => { setMedicalDocuments(prev => prev.filter((_, i) => i !== index)); }; // ======================================== // VALIDATION // ======================================== const validateForm = () => { const errors = []; // Dates if (!startDate || !endDate) { errors.push('Les dates sont obligatoires'); } else if (new Date(startDate) > new Date(endDate)) { errors.push('La date de fin doit être après la date de début'); } // Types if (selectedTypes.length === 0) { errors.push('Sélectionnez au moins un type de congé'); } // Documents pour ABS if (selectedTypes.includes('ABS') && medicalDocuments.length === 0) { errors.push('Un justificatif médical est obligatoire pour un arrêt maladie'); } // Répartition if (selectedTypes.length > 1) { const total = Object.values(repartition).reduce((sum, val) => sum + val, 0); if (Math.abs(total - businessDays) > 0.01) { errors.push(`La répartition (${total.toFixed(1)}j) ne correspond pas au total (${businessDays}j)`); } } // Compteurs (si chargés) if (countersData?.data?.totalDisponible) { const safeCounters = { availableCP: countersData.data.cpN?.solde || 0, availableRTT: countersData.data.rttN?.solde || 0, availableRecup: countersData.data.recupN?.solde || 0 }; selectedTypes.forEach(type => { if (type === 'CP') { const cpDemande = selectedTypes.length === 1 ? businessDays : (repartition[type] || 0); if (cpDemande > safeCounters.availableCP) { errors.push(`Solde CP insuffisant (${safeCounters.availableCP.toFixed(1)}j disponibles)`); } } if (type === 'RTT') { const rttDemande = selectedTypes.length === 1 ? businessDays : (repartition[type] || 0); if (rttDemande > safeCounters.availableRTT) { errors.push(`Solde RTT insuffisant (${safeCounters.availableRTT.toFixed(1)}j disponibles)`); } } if (type === 'Récup') { const recupDemande = selectedTypes.length === 1 ? businessDays : (repartition[type] || 0); if (recupDemande > safeCounters.availableRecup) { errors.push(`Solde Récup insuffisant (${safeCounters.availableRecup.toFixed(1)}j disponibles)`); } } }); } setValidationErrors(errors); return errors.length === 0; }; // ======================================== // SOUMISSION // ======================================== const handleSubmit = async (e) => { e.preventDefault(); if (!validateForm()) { return; } setIsSubmitting(true); setSubmitMessage({ type: '', text: '' }); try { const formDataToSend = new FormData(); // ⭐ CHAMPS REQUIS PAR LE BACKEND formDataToSend.append('requestId', request.id.toString()); formDataToSend.append('userId', userId.toString()); formDataToSend.append('userEmail', userEmail); formDataToSend.append('userName', userName); formDataToSend.append('accessToken', accessToken || ''); // ⭐ DATES formDataToSend.append('DateDebut', startDate); formDataToSend.append('DateFin', endDate); formDataToSend.append('startDate', startDate); formDataToSend.append('endDate', endDate); // ⭐ COMMENTAIRE formDataToSend.append('Commentaire', reason || 'Aucun commentaire'); formDataToSend.append('reason', reason || 'Aucun commentaire'); // ⭐ CALCUL NOMBRE DE JOURS TOTAL let totalJoursToSend = businessDays; if (selectedTypes.length === 1 && startDate === endDate) { const type = selectedTypes[0]; const periode = periodeSelection[type]; if ((type === 'CP' || type === 'RTT' || type === 'Récup') && (periode === 'Matin' || periode === 'Après-midi')) { totalJoursToSend = 0.5; } } formDataToSend.append('NombreJours', totalJoursToSend.toString()); formDataToSend.append('businessDays', totalJoursToSend.toString()); // ⭐ RÉPARTITION (CORRECTION ICI) const repartitionArray = selectedTypes.map(type => { let nombreJours; let periodeJournee = 'Journée entière'; if (selectedTypes.length === 1) { const periode = periodeSelection[type] || 'Journée entière'; if ((type === 'CP' || type === 'RTT' || type === 'Récup') && startDate === endDate && (periode === 'Matin' || periode === 'Après-midi')) { nombreJours = 0.5; periodeJournee = periode; } else { nombreJours = businessDays; } } else { nombreJours = repartition[type] || 0; periodeJournee = periodeSelection[type] || 'Journée entière'; } return { TypeConge: type, NombreJours: nombreJours, PeriodeJournee: ['CP', 'RTT', 'Récup'].includes(type) ? periodeJournee : 'Journée entière' }; }); // ⭐ STRINGIFIER LA RÉPARTITION (CRITIQUE POUR FORMDATA) formDataToSend.append('Repartition', JSON.stringify(repartitionArray)); // ⭐ TYPE DE CONGÉ (pour compatibilité backend) const leaveTypeMapping = { 'CP': 1, 'RTT': 2, 'ABS': 3, 'Formation': 4, 'Récup': 5 }; const leaveTypeId = leaveTypeMapping[selectedTypes[0]] || 1; formDataToSend.append('leaveType', leaveTypeId.toString()); // Documents médicaux EN DERNIER if (medicalDocuments.length > 0) { medicalDocuments.forEach((file) => { formDataToSend.append('medicalDocuments', file); }); } console.log('📤 Envoi modification demande...'); console.log('📊 Répartition envoyée:', JSON.stringify(repartitionArray, null, 2)); for (let pair of formDataToSend.entries()) { if (pair[0] !== 'medicalDocuments') { console.log(pair[0], ':', pair[1]); } } const response = await fetch('/api/updateRequest', { method: 'POST', body: formDataToSend }); const responseText = await response.text(); console.log('📥 Réponse brute:', responseText); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status} - ${responseText}`); } let data; try { data = JSON.parse(responseText); } catch (parseError) { console.error('❌ Erreur parsing JSON:', parseError); throw new Error('Réponse serveur invalide: ' + responseText); } if (data.success) { setSubmitMessage({ type: 'success', text: '✅ Demande modifiée avec succès !' }); setTimeout(() => { onRequestUpdated(); onClose(); }, 1500); } else { setSubmitMessage({ type: 'error', text: `❌ ${data.message || 'Erreur lors de la modification'}` }); } } catch (error) { console.error('❌ Erreur:', error); setSubmitMessage({ type: 'error', text: `❌ ${error.message || 'Une erreur est survenue'}` }); } finally { setIsSubmitting(false); } }; // ======================================== // RECALCUL AUTO JOURS OUVRÉS // ======================================== useEffect(() => { if (startDate && endDate) { const days = calculateBusinessDays(startDate, endDate); setBusinessDays(days.businessDays); setSaturdayCount(days.saturdayCount); // Réinitialiser répartition si changement de dates if (selectedTypes.length === 1) { const type = selectedTypes[0]; setRepartition({ [type]: days.businessDays }); } } }, [startDate, endDate]); // ======================================== // RENDER // ======================================== if (!isOpen) return null; return (