/** * 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 // Utiliser un petit délai pour s'assurer que le DOM est complètement prêt setTimeout(() => { initializeSelect2(); }, 100); // Réinitialiser Select2 quand l'onglet devient visible (problème avec éléments cachés) setupSelect2TabListener(); // É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(); } // Variable de garde pour éviter les initialisations multiples simultanées let isInitializingSelect2 = false; let select2Initialized = false; // Garde globale pour éviter toute réinitialisation /** * Initialise Select2 pour le champ de sélection des langues */ function initializeSelect2() { console.log('🔍 initializeSelect2() appelée - Stack:', new Error().stack); // Éviter les appels multiples simultanés if (isInitializingSelect2) { console.log('⏳ Initialisation Select2 déjà en cours, attente...'); return; } // Si Select2 est déjà initialisé et fonctionne, ne pas réinitialiser const languesSelect = document.getElementById('langues-permanences'); if (languesSelect && select2Initialized) { const $select = jQuery(languesSelect); if ($select.data('select2') && $select.hasClass('select2-hidden-accessible')) { console.log('✅ Select2 déjà initialisé (flag=true), pas de réinitialisation'); return; } } if (!languesSelect) { console.warn('Select langues-permanences non trouvé'); return; } // Vérifier que jQuery et Select2 sont disponibles if (typeof jQuery === 'undefined' || !jQuery.fn.select2) { console.warn('jQuery ou Select2 non disponible, réessai dans 100ms...'); setTimeout(initializeSelect2, 100); return; } const $select = jQuery(languesSelect); // DESTRUCTION AGRESSIVE : Détruire tout Select2 au début, sans pitié ! try { // Détruire l'instance Select2 si elle existe if ($select.data('select2')) { $select.select2('destroy'); } // Supprimer TOUS les conteneurs Select2 dans le parent const parentContainer = languesSelect.closest('.card-body') || languesSelect.parentElement; if (parentContainer) { // Supprimer tous les conteneurs Select2 const select2Containers = parentContainer.querySelectorAll('span.select2-container'); select2Containers.forEach(container => container.remove()); } // Nettoyer tous les attributs Select2 du select $select.removeClass('select2-hidden-accessible'); $select.removeAttr('data-select2-id'); $select.removeAttr('aria-hidden'); $select.removeAttr('tabindex'); console.log('💥 Select2 complètement détruit (méchamment)'); } catch (e) { console.warn('Erreur lors de la destruction agressive:', e); } // Marquer qu'on est en train d'initialiser isInitializingSelect2 = true; select2Initialized = false; // Réinitialiser le flag // Vérifier que le select a des options const options = languesSelect.querySelectorAll('option'); if (options.length === 0) { console.warn('Le select langues-permanences n\'a pas d\'options'); isInitializingSelect2 = false; return; } // Initialiser Select2 try { // Vérifier que les options sont valides const validOptions = Array.from(options).filter(opt => { return opt.value && opt.value.trim() !== '' && opt.textContent && opt.textContent.trim() !== ''; }); console.log('📋 Options valides trouvées:', validOptions.length, 'sur', options.length); if (validOptions.length === 0) { console.error('Aucune option valide trouvée dans le select'); return; } // S'assurer que le select est visible avant l'initialisation const isVisible = languesSelect.offsetParent !== null; if (!isVisible) { console.warn('Le select langues-permanences n\'est pas visible, réessai dans 200ms...'); setTimeout(initializeSelect2, 200); return; } // Initialiser Select2 - laisser Select2 lire automatiquement depuis le select natif // Ne pas passer data pour éviter les conflits avec le select natif $select.select2({ placeholder: 'Sélectionnez une ou plusieurs langues', allowClear: true, width: '100%', language: { noResults: function() { return "Aucun résultat trouvé"; } } }); // Vérifier que Select2 a bien chargé les options setTimeout(() => { const select2Instance = $select.data('select2'); if (select2Instance) { // Forcer Select2 à recharger les options depuis le select natif $select.trigger('change.select2'); console.log('✅ Select2 initialisé pour langues-permanences avec', validOptions.length, 'options'); // Marquer comme initialisé select2Initialized = true; } else { console.warn('⚠️ Select2 n\'a pas été correctement initialisé'); select2Initialized = false; } // Libérer la garde isInitializingSelect2 = false; }, 50); } catch (e) { console.error('Erreur lors de l\'initialisation de Select2:', e); isInitializingSelect2 = false; select2Initialized = false; } } /** * Configure un listener pour réinitialiser Select2 quand l'onglet devient visible * (uniquement si Select2 n'est pas déjà correctement initialisé) */ function setupSelect2TabListener() { const tabButtons = document.querySelectorAll('#permanences-tabs button[data-bs-toggle="tab"]'); tabButtons.forEach(button => { button.addEventListener('shown.bs.tab', function(e) { // Vérifier si l'onglet qui devient visible contient le select langues-permanences const targetTabId = e.target.getAttribute('data-bs-target'); const targetTab = document.querySelector(targetTabId); if (targetTab && targetTab.querySelector('#langues-permanences')) { // Ne PAS réinitialiser si Select2 est déjà initialisé // La garde globale select2Initialized empêche toute réinitialisation if (!select2Initialized) { console.log('🔄 Onglet changé, vérification Select2...'); setTimeout(() => { initializeSelect2(); }, 100); } else { console.log('✅ Select2 déjà initialisé, pas de réinitialisation'); } } }); }); } /** * 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 => `