From fbcd80fb6f6d5e781b36410d8d7b2a28aba10a7b Mon Sep 17 00:00:00 2001 From: Ouijdane IMER Date: Wed, 6 Aug 2025 16:49:31 +0200 Subject: [PATCH] =?UTF-8?q?Affichage=20des=20noms=20des=20collaborateurs?= =?UTF-8?q?=20en=20cong=C3=A9=20dans=20le=20calendrier.=20Ajout=20du=20cha?= =?UTF-8?q?mp=20"Autre"=20dans=20le=20formulaire=20de=20demande=20de=20con?= =?UTF-8?q?g=C3=A9.=20Mise=20en=20place=20de=20l=E2=80=99export=20du=20cal?= =?UTF-8?q?endrier=20aux=20formats=20ICS=20et=20CSV.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/NewLeaveRequestModal.jsx | 85 +++++++++- project/src/pages/Calendar.jsx | 150 ++++++++++++++++-- project/src/pages/Requests.jsx | 10 +- 3 files changed, 230 insertions(+), 15 deletions(-) diff --git a/project/src/components/NewLeaveRequestModal.jsx b/project/src/components/NewLeaveRequestModal.jsx index 22bfaf3..ceeb631 100644 --- a/project/src/components/NewLeaveRequestModal.jsx +++ b/project/src/components/NewLeaveRequestModal.jsx @@ -24,6 +24,8 @@ const NewLeaveRequestModal = ({ const [error, setError] = useState(''); const [calculatedDays, setCalculatedDays] = useState(0); const [isPreselected, setIsPreselected] = useState(false); + const [isOtherChecked, setIsOtherChecked] = useState(false); + const [otherLeaveType, setOtherLeaveType] = useState(''); // Vérifier si des valeurs sont pré-sélectionnées useEffect(() => { @@ -130,8 +132,36 @@ const NewLeaveRequestModal = ({ return { ...prev, types: newTypes }; }); setError(''); + + // Désélectionner le type "Autre" si un autre type est sélectionné + + if (type !== 'Autres') { + setIsOtherChecked(false); + setOtherLeaveType(''); + } }; + const handleOtherCheckboxChange = (e) => { + const isChecked = e.target.checked; + setIsOtherChecked(isChecked); + + // Si la case est cochée, désélectionner les autres types + if (isChecked) { + setFormData(prev => ({ ...prev, types: ['Autres'] })); + } else { + // Si elle est décochée, vider le type et les documents + setOtherLeaveType(''); + setFormData(prev => ({ ...prev, types: [] })); + } + setError(''); + }; + + const handleOtherTypeChange = (e) => { + setOtherLeaveType(e.target.value); + setFormData(prev => ({ ...prev, types: [e.target.value] })); + }; + + const handleDistributionChange = (type, days) => { setTypeDistribution(prev => ({ ...prev, @@ -183,6 +213,12 @@ const NewLeaveRequestModal = ({ setError('Impossible de faire une demande pour une date passée'); return false; } + // Vérification de la sélection d'un type "autre" + + if (isOtherChecked && !otherLeaveType) { + setError('Veuillez sélectionner un type de congé dans la liste "Autre"'); + return false; + } // Vérification de la distribution des jours if (formData.types.length > 1) { @@ -197,6 +233,12 @@ const NewLeaveRequestModal = ({ for (const type of formData.types) { const requiredDays = formData.types.length > 1 ? (typeDistribution[type] || 0) : calculatedDays; + // Les types "autres" n'ont pas de solde à vérifier + const nonSoldeTypes = ['Récup', 'Congés sans solde', 'Congés pour évènement familial', 'Congé maternité', 'Congé paternité', 'Congé parental', 'Congé parental à temps partiel', 'Autres']; + if (nonSoldeTypes.includes(type)) { + continue; + } + if (type === 'CP' && requiredDays > availableLeaveCounters.availableCP) { setError(`Solde CP insuffisant. Vous avez ${availableLeaveCounters.availableCP} jours disponibles`); return false; @@ -223,6 +265,8 @@ const NewLeaveRequestModal = ({ // Créer une demande pour chaque type de congé const requests = formData.types.map(type => { const days = formData.types.length > 1 ? (typeDistribution[type] || 0) : calculatedDays; + // Utiliser le type sélectionné dans la liste déroulante si "Autre" est coché + const finalType = type === 'Autres' ? otherLeaveType : type; return { EmployeeId: userId, TypeConge: type, @@ -325,6 +369,7 @@ const NewLeaveRequestModal = ({ )} {/* Type de congé */} +
))} + {/* Nouvelle case à cocher pour "Autre" */} +
+ +
+ {/* Liste déroulante "Autre" conditionnelle */} + {isOtherChecked && ( +
+ + +
+ )} + {/* Distribution des jours si plusieurs types sélectionnés */} {formData.types.length > 1 && calculatedDays > 0 && ( diff --git a/project/src/pages/Calendar.jsx b/project/src/pages/Calendar.jsx index 1a1a4f7..882fe9c 100644 --- a/project/src/pages/Calendar.jsx +++ b/project/src/pages/Calendar.jsx @@ -57,9 +57,9 @@ const Calendar = () => { // 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, 4, 1), name: 'Fête du Travail' }, + { 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, 11, 25), name: 'Noël' } + { date: new Date(year, 12, 25), name: 'Noël' } ]; } finally { setIsLoadingHolidays(false); @@ -298,6 +298,89 @@ const Calendar = () => { 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 (
setSidebarOpen(!sidebarOpen)} /> @@ -410,8 +493,8 @@ const Calendar = () => {
{ hasLeave(date) ? 'bg-green-100 text-green-800' : 'hover:bg-gray-50' } - ${isSelectingRange && selectedDate && !selectedEndDate && date && date > selectedDate && !isPastDate(date) && !isHoliday(date) ? 'bg-blue-50' : ''} - `} + ${isSelectingRange && selectedDate && !selectedEndDate && date && date > selectedDate && !isPastDate(date) && !isHoliday(date) ? 'bg-blue-50' : ''} + `} onClick={() => handleDateClick(date)} onContextMenu={(e) => handleContextMenu(e, date)} title={isHoliday(date) ? getHolidayName(date) : ''} > {date && ( -
+
+ {/* Date number */} {date.getDate()} + + {isHoliday(date) && getHolidayName(date) && ( - - {getHolidayName(date).length > 8 ? getHolidayName(date).substring(0, 8) + '...' : getHolidayName(date)} - +
+ {getHolidayName(date)} +
)} + + + {/* Leave info: names of employees with leave */} {hasLeave(date) && ( -
+
+ {teamLeaves + .filter(leave => { + const start = new Date(leave.start_date); + const end = new Date(leave.end_date); + return date >= start && date <= end; + }) + .map((leave, i) => ( +
+ {/* Optional colored dot for leave type */} + + + {leave.employee_name} + + +
+ ))} +
)}
)}
))} -
+
{/* Legend */}
@@ -518,6 +631,19 @@ const Calendar = () => {
)} + {/* button csv */} +
+
+ + +
+ + {/* Ton rendu de calendrier ici */} +
{/* Modal nouvelle demande */} {showNewRequestModal && ( diff --git a/project/src/pages/Requests.jsx b/project/src/pages/Requests.jsx index 1520bb2..edcdee7 100644 --- a/project/src/pages/Requests.jsx +++ b/project/src/pages/Requests.jsx @@ -52,7 +52,12 @@ const Requests = () => { // Filtre par type if (typeFilter !== 'all') { - filtered = filtered.filter(request => request.type === typeFilter); + if (typeFilter === 'Autres') { + const otherTypes = ['Récup', 'Congés sans solde', 'Congés pour évènement familial', 'Congé maternité', 'Congé paternité', 'Congé parental', 'Congé parental à temps partiel']; + filtered = filtered.filter(request => otherTypes.includes(request.type)); + } else { + filtered = filtered.filter(request => request.type === typeFilter); + } } setFilteredRequests(filtered); @@ -84,7 +89,7 @@ const Requests = () => { }; const fetchAllRequests = async () => { - console.log('🔍 Requests - Début fetchAllRequests pour user:', user?.id); + console.log('Requests - Début fetchAllRequests pour user:', user?.id); try { const url = `http://localhost/GTA/project/public/getRequests.php?user_id=${user.id}`; @@ -381,6 +386,7 @@ const Requests = () => { + {/* Nouvelle option */}