/** * Module Permanences Admin * Gestion du formulaire d'encodage des permanences pour l'admin (avec sélection d'intervenant) */ import { apiFetch } from './agenda-api.js'; import toastr from 'toastr'; /** * Initialise le module permanences admin */ export function initializeAdminPermanences() { console.log('🚀 Initialisation du module permanences admin...'); const form = document.getElementById('admin-permanences-form'); if (!form) { console.error('Formulaire admin-permanences-form non trouvé'); return; } // Préselection des jours et heures selon l'intervenant setupIntervenantDefaults(); // Initialiser le champ mois de début avec le mois actuel const moisDebutInput = document.getElementById('mois-debut'); if (moisDebutInput && !moisDebutInput.value) { const today = new Date(); const year = today.getFullYear(); const month = String(today.getMonth() + 1).padStart(2, '0'); moisDebutInput.value = `${year}-${month}`; } // Initialiser Select2 pour le champ langues initializeSelect2(); // Écouter les changements pour mettre à jour l'aperçu setupPreviewListeners(); // Gérer la soumission du formulaire form.addEventListener('submit', handleFormSubmit); // Calculer l'aperçu initial updatePreview(); // Initialiser le formulaire d'import CSV initializeCsvImport(); } /** * Initialise Select2 pour le champ de sélection des langues */ function initializeSelect2() { const languesSelect = document.getElementById('langues-permanences'); if (languesSelect && typeof jQuery !== 'undefined' && jQuery.fn.select2) { jQuery(languesSelect).select2({ placeholder: 'Sélectionnez une ou plusieurs langues', allowClear: true, width: '100%', language: { noResults: function() { return "Aucun résultat trouvé"; } } }); } } /** * Configure la préselection des inputs jours[] et heures[] * à partir des attributs data-days et data-time-slots de l'option sélectionnée */ function setupIntervenantDefaults() { const select = document.getElementById('intervenant-select'); if (!select) return; // Suivi des changements manuels pour ne pas écraser l'utilisateur let hasUserChanged = false; // Marquer qu'il y a eu modification dès qu'une case est cochée/décochée const markChanged = () => { hasUserChanged = true; }; document.querySelectorAll('input[name="jours[]"]').forEach(input => { input.addEventListener('change', markChanged); }); document.querySelectorAll('input[name="heures[]"]').forEach(input => { input.addEventListener('change', markChanged); }); const applyDefaults = () => { // Si l'utilisateur a déjà modifié les cases, ne pas écraser ses choix if (hasUserChanged) { // Laisser l'aperçu se recalculer d'après l'état courant updatePreview(); return; } const option = select.options[select.selectedIndex]; if (!option) return; const daysStr = option.getAttribute('data-days') || ''; const timesStr = option.getAttribute('data-time-slots') || ''; const days = daysStr .split(',') .map(s => s.trim().toLowerCase()) .filter(Boolean); const times = timesStr .split(',') .map(s => s.trim()) .filter(Boolean); // Réinitialiser toutes les cases document.querySelectorAll('input[name="jours[]"]').forEach(input => { input.checked = false; }); document.querySelectorAll('input[name="heures[]"]').forEach(input => { input.checked = false; }); // Appliquer les jours if (days.length > 0) { document.querySelectorAll('input[name="jours[]"]').forEach(input => { const value = String(input.value || '').toLowerCase(); if (days.includes(value)) { input.checked = true; } }); } // Appliquer les heures if (times.length > 0) { document.querySelectorAll('input[name="heures[]"]').forEach(input => { const value = String(input.value || '').trim(); if (times.includes(value)) { input.checked = true; } }); } // Mettre à jour l'aperçu updatePreview(); }; // Sur changement d'intervenant select.addEventListener('change', applyDefaults); // Initialisation à l'ouverture si une option est déjà sélectionnée applyDefaults(); // Si le formulaire est réinitialisé, réactiver l'application auto des valeurs par défaut const form = document.getElementById('admin-permanences-form'); if (form) { form.addEventListener('reset', () => { // Attendre la remise à zéro effective du DOM setTimeout(() => { hasUserChanged = false; applyDefaults(); }, 0); }); } } /** * Configure les listeners pour mettre à jour l'aperçu en temps réel */ function setupPreviewListeners() { // Écouter les changements de mois de début const moisDebut = document.getElementById('mois-debut'); if (moisDebut) { moisDebut.addEventListener('change', updatePreview); moisDebut.addEventListener('input', updatePreview); } // Écouter les changements de période const periodInputs = document.querySelectorAll('input[name="periode"]'); periodInputs.forEach(input => { input.addEventListener('change', updatePreview); }); // Écouter les changements de jours const joursInputs = document.querySelectorAll('input[name="jours[]"]'); joursInputs.forEach(input => { input.addEventListener('change', updatePreview); }); // Écouter les changements des heures sélectionnées const heuresInputs = document.querySelectorAll('input[name="heures[]"]'); heuresInputs.forEach(input => { input.addEventListener('change', updatePreview); }); // Écouter les changements de durée de permanence (1h ou 15min) const dureeInputs = document.querySelectorAll('input[name="duree_permanence"]'); dureeInputs.forEach(input => { input.addEventListener('change', updatePreview); }); // Écouter les changements du nombre de tranches (si 15min) const nbTranchesSelect = document.getElementById('nb-tranches'); if (nbTranchesSelect) { nbTranchesSelect.addEventListener('change', updatePreview); } } /** * Calcule et affiche l'aperçu des permanences qui seront créées */ function updatePreview() { const periode = parseInt(document.querySelector('input[name="periode"]:checked')?.value || '3'); const joursChecked = Array.from(document.querySelectorAll('input[name="jours[]"]:checked')) .map(input => input.value); const heuresChecked = Array.from(document.querySelectorAll('input[name="heures[]"]:checked')) .map(input => input.value) .sort(); // Trier les heures pour un affichage cohérent const moisDebut = document.getElementById('mois-debut')?.value; const dureePermanence = document.querySelector('input[name="duree_permanence"]:checked')?.value || '1h'; const nbTranches = dureePermanence === '15min' ? parseInt(document.getElementById('nb-tranches')?.value || '1') : 1; if (heuresChecked.length === 0) { clearPreview(); return; } if (!moisDebut) { clearPreview(); return; } if (joursChecked.length === 0) { clearPreview(); return; } // Calculer les tranches horaires à partir des heures sélectionnées let tranches = []; if (dureePermanence === '15min') { // Mode 15 minutes : créer des tranches de 15 minutes heuresChecked.forEach(heureDebut => { const [h, m] = heureDebut.split(':').map(Number); for (let i = 0; i < nbTranches; i++) { const minutesDebut = m + (i * 15); const minutesFin = m + ((i + 1) * 15); const trancheDebut = `${String(h).padStart(2, '0')}:${String(minutesDebut).padStart(2, '0')}`; const trancheFin = `${String(h).padStart(2, '0')}:${String(minutesFin).padStart(2, '0')}`; tranches.push({ debut: trancheDebut, fin: trancheFin }); } }); } else { // Mode 1 heure : chaque heure sélectionnée = 1 tranche d'1 heure tranches = heuresChecked.map(heureDebut => { const [h, m] = heureDebut.split(':').map(Number); const heureFin = `${String(h + 1).padStart(2, '0')}:${String(m).padStart(2, '0')}`; return { debut: heureDebut, fin: heureFin }; }); } // Calculer les dates à partir du mois de début sélectionné const [year, month] = moisDebut.split('-').map(Number); const startDate = new Date(year, month - 1, 1); // Premier jour du mois sélectionné const endDate = new Date(year, month - 1 + periode, 0); // Dernier jour du mois de fin // Compter les occurrences pour chaque jour sélectionné let totalEvents = 0; const joursMapping = { 'lundi': 1, 'mardi': 2, 'mercredi': 3, 'jeudi': 4, 'vendredi': 5, 'samedi': 6, 'dimanche': 0 }; joursChecked.forEach(jour => { const jourNum = joursMapping[jour.toLowerCase()]; if (jourNum !== undefined) { const occurrences = countDayOccurrences(jourNum, startDate, endDate); totalEvents += occurrences * tranches.length; } }); // Afficher les tranches displayTranches(tranches); // Afficher l'estimation displayEstimation(totalEvents); } /** * Calcule les tranches horaires d'1 heure dans une plage */ function calculateTranches(heureDebut, heureFin) { const tranches = []; const [debutH, debutM] = heureDebut.split(':').map(Number); const [finH, finM] = heureFin.split(':').map(Number); let currentH = debutH; let currentM = debutM; while (currentH < finH || (currentH === finH && currentM < finM)) { const trancheDebut = `${String(currentH).padStart(2, '0')}:${String(currentM).padStart(2, '0')}`; // Ajouter 1 heure currentH += 1; const trancheFin = `${String(currentH).padStart(2, '0')}:${String(currentM).padStart(2, '0')}`; // Vérifier que la tranche ne dépasse pas l'heure de fin if (currentH < finH || (currentH === finH && currentM <= finM)) { tranches.push({ debut: trancheDebut, fin: trancheFin }); } else { break; } } return tranches; } /** * Compte le nombre d'occurrences d'un jour de la semaine dans une plage de dates */ function countDayOccurrences(jourSemaine, startDate, endDate) { let count = 0; const current = new Date(startDate); // Ajuster au premier jour de la semaine ciblée const currentDay = current.getDay(); const diff = (jourSemaine - currentDay + 7) % 7; current.setDate(current.getDate() + diff); // Compter les occurrences while (current <= endDate) { count++; current.setDate(current.getDate() + 7); // Semaine suivante } return count; } /** * Affiche les tranches horaires générées */ function displayTranches(tranches) { const previewEl = document.getElementById('tranches-preview'); if (!previewEl) return; if (tranches.length === 0) { previewEl.innerHTML = 'Veuillez sélectionner une plage horaire valide.'; return; } const tranchesList = tranches.map(t => `
  • ${t.debut} → ${t.fin}
  • `).join(''); previewEl.innerHTML = ` `; } /** * Affiche l'estimation du nombre d'Ă©vĂ©nements */ function displayEstimation(count) { const containerEl = document.getElementById('estimation-container'); const countEl = document.getElementById('estimation-count'); if (!containerEl || !countEl) return; if (count > 0) { countEl.textContent = count; containerEl.style.display = 'block'; } else { containerEl.style.display = 'none'; } } /** * Efface l'aperçu */ function clearPreview() { const previewEl = document.getElementById('tranches-preview'); if (previewEl) { previewEl.innerHTML = 'Veuillez sĂ©lectionner une plage horaire pour voir l\'aperçu.'; } const containerEl = document.getElementById('estimation-container'); if (containerEl) { containerEl.style.display = 'none'; } } /** * Gère la soumission du formulaire */ async function handleFormSubmit(e) { e.preventDefault(); const form = e.target; const submitBtn = document.getElementById('submit-permanences-btn'); // Validation if (!validateForm()) { return; } // RĂ©cupĂ©rer les donnĂ©es du formulaire const formData = new FormData(form); const intervenantId = formData.get('intervenant_id'); const periode = parseInt(formData.get('periode')); const moisDebut = formData.get('mois_debut'); const jours = formData.getAll('jours[]'); const heures = formData.getAll('heures[]'); const dureePermanence = document.querySelector('input[name="duree_permanence"]:checked')?.value || '1h'; const nbTranches = dureePermanence === '15min' ? parseInt(document.getElementById('nb-tranches')?.value || '1') : null; // RĂ©cupĂ©rer les langues sĂ©lectionnĂ©es via Select2 const languesSelect = document.getElementById('langues-permanences'); const langues = languesSelect && typeof jQuery !== 'undefined' && jQuery(languesSelect).val() ? jQuery(languesSelect).val().filter(value => value !== '') : []; const informationsComplementaires = formData.get('informations_complementaires') || ''; // Validation supplĂ©mentaire if (!intervenantId) { toastr.error('Veuillez sĂ©lectionner un intervenant', 'Erreur'); return; } if (!moisDebut) { toastr.error('Veuillez sĂ©lectionner un mois de dĂ©but', 'Erreur'); return; } if (jours.length === 0) { toastr.error('Veuillez sĂ©lectionner au moins un jour de la semaine', 'Erreur'); return; } if (heures.length === 0) { toastr.error('Veuillez sĂ©lectionner au moins une heure de permanence', 'Erreur'); return; } // Calculer la plage horaire globale (min et max des heures sĂ©lectionnĂ©es) const heuresSorted = heures.map(h => { const [hour] = h.split(':').map(Number); return hour; }).sort((a, b) => a - b); const heureMin = heuresSorted[0]; const heureMax = heuresSorted[heuresSorted.length - 1]; const plageDebut = `${String(heureMin).padStart(2, '0')}:00`; const plageFin = `${String(heureMax + 1).padStart(2, '0')}:00`; // DĂ©sactiver le bouton submitBtn.disabled = true; submitBtn.innerHTML = 'CrĂ©ation en cours...'; // PrĂ©parer les donnĂ©es pour l'API const data = { intervenant_id: parseInt(intervenantId), periode: periode, mois_debut: moisDebut, jours: jours, plage_horaire: { debut: plageDebut, fin: plageFin }, heures: heures, // Envoyer aussi les heures individuelles pour le backend duree_permanence: dureePermanence, // '1h' ou '15min' nb_tranches: nbTranches, // Nombre de tranches si 15min (1-4), null si 1h langues: langues, // Langues sĂ©lectionnĂ©es (peut ĂŞtre un tableau vide) informations_complementaires: informationsComplementaires }; try { // Appel API (apiFetch retourne directement data, pas response.data) const result = await apiFetch('admin/permanences', { method: 'POST', body: JSON.stringify(data) }); toastr.success( `${result.message} (${result.permanences_crees} permanences créées)`, 'Succès', { timeOut: 5000 } ); // RĂ©initialiser le formulaire form.reset(); clearPreview(); // RĂ©initialiser la pĂ©riode par dĂ©faut document.getElementById('periode-3').checked = true; } catch (error) { console.error('Erreur lors de la crĂ©ation des permanences:', error); toastr.error('Une erreur est survenue lors de la crĂ©ation des permanences', 'Erreur'); } finally { // RĂ©activer le bouton submitBtn.disabled = false; submitBtn.innerHTML = 'Enregistrer les permanences'; } } /** * Valide le formulaire avant soumission */ function validateForm() { const intervenantId = document.getElementById('intervenant-select')?.value; const periode = document.querySelector('input[name="periode"]:checked')?.value; const moisDebut = document.getElementById('mois-debut')?.value; const joursChecked = document.querySelectorAll('input[name="jours[]"]:checked'); const heuresChecked = document.querySelectorAll('input[name="heures[]"]:checked'); if (!intervenantId) { toastr.error('Veuillez sĂ©lectionner un intervenant', 'Validation'); return false; } if (!moisDebut) { toastr.error('Veuillez sĂ©lectionner un mois de dĂ©but', 'Validation'); return false; } if (!periode) { toastr.error('Veuillez sĂ©lectionner une pĂ©riode (3 ou 6 mois)', 'Validation'); return false; } if (joursChecked.length === 0) { toastr.error('Veuillez sĂ©lectionner au moins un jour de la semaine', 'Validation'); return false; } if (heuresChecked.length === 0) { toastr.error('Veuillez sĂ©lectionner au moins une heure de permanence', 'Validation'); return false; } return true; } /** * Initialise le formulaire d'import CSV */ function initializeCsvImport() { const csvForm = document.getElementById('import-csv-form'); if (!csvForm) { console.warn('Formulaire import-csv-form non trouvĂ©'); return; } csvForm.addEventListener('submit', handleCsvImportSubmit); } /** * Gère la soumission du formulaire d'import CSV */ async function handleCsvImportSubmit(e) { e.preventDefault(); const form = e.target; const fileInput = document.getElementById('csv-file'); const submitBtn = document.getElementById('submit-csv-import-btn'); const resultContainer = document.getElementById('csv-import-result'); if (!fileInput || !fileInput.files || fileInput.files.length === 0) { toastr.error('Veuillez sĂ©lectionner un fichier CSV', 'Erreur'); return; } const file = fileInput.files[0]; // VĂ©rifier que c'est un fichier CSV if (!file.name.toLowerCase().endsWith('.csv')) { toastr.error('Le fichier doit ĂŞtre au format CSV', 'Erreur'); return; } // DĂ©sactiver le bouton submitBtn.disabled = true; submitBtn.innerHTML = 'Import en cours...'; // CrĂ©er FormData pour l'upload const formData = new FormData(); formData.append('file', file); try { // Appel API pour l'import CSV const url = '/wp-json/crvi/v1/admin/permanences/import-csv'; const response = await fetch(url, { method: 'POST', headers: { 'X-WP-Nonce': window.wpApiSettings?.nonce || '' }, body: formData }); const data = await response.json(); if (!response.ok || !data.success) { const errorMessage = data.error?.message || data.message || 'Erreur lors de l\'import'; throw new Error(errorMessage); } // Afficher les rĂ©sultats displayCsvImportResult(data.data, resultContainer); toastr.success( `Import rĂ©ussi : ${data.data.created || 0} créés, ${data.data.errors || 0} erreurs`, 'Succès', { timeOut: 5000 } ); // RĂ©initialiser le formulaire form.reset(); } catch (error) { console.error('Erreur lors de l\'import CSV:', error); toastr.error(error.message || 'Une erreur est survenue lors de l\'import CSV', 'Erreur'); if (resultContainer) { resultContainer.innerHTML = `
    Erreur : ${error.message || 'Une erreur est survenue lors de l\'import'}
    `; resultContainer.style.display = 'block'; } } finally { // Réactiver le bouton submitBtn.disabled = false; submitBtn.innerHTML = 'Importer le CSV'; } } /** * Affiche les résultats de l'import CSV */ function displayCsvImportResult(result, container) { if (!container) return; let html = ''; if (result.errors && result.errors.length > 0) { html += `
    Attention : ${result.errors.length} erreur(s) détectée(s)
    `; if (result.errors.length <= 10) { html += ''; } } if (result.created > 0) { html += `
    Succès : ${result.created} permanence(s) créée(s) avec succès
    `; } container.innerHTML = html; container.style.display = 'block'; }