// Module de gestion des selects pour les modales // Contient la logique de filtrage, Select2 et population des selects import { getFilters } from './agenda-api.js'; import { notifyError } from './agenda-notifications.js'; // Configuration const DEBUG_SELECTS = false; // Activer pour déboguer // Flags pour éviter les boucles infinies let isUpdatingSelects = false; let isFilteringTraducteurs = false; // Cache pour les disponibilités const disponibilitesCache = new Map(); let lastPopulateCall = null; let isPopulateSelectsRunning = false; /** * Initialise Select2 sur tous les selects de la modale */ export function initializeSelect2() { if (!window.jQuery || !window.jQuery.fn.select2) { console.warn('Select2 non disponible, réessai dans 100ms...'); setTimeout(initializeSelect2, 100); return; } jQuery('#eventModal select:not(.skip-select2)').each(function() { const $select = jQuery(this); // Si Select2 est déjà initialisé, le détruire pour réappliquer if ($select.hasClass('select2-hidden-accessible') || $select.data('select2')) { try { $select.select2('destroy'); } catch (e) {} } $select.select2({ width: '100%', placeholder: 'Sélectionner...', allowClear: true, dropdownParent: jQuery('#eventModal') }); }); } /** * Fusionne les données de l'événement avec les disponibilités * @param {Object} availabilityData - Données de disponibilité de l'API * @param {Object} eventData - Données de l'événement * @returns {Object} - Données fusionnées */ function mergeEventDataWithAvailability(availabilityData, eventData) { if (DEBUG_SELECTS) { console.log('🔍 [MERGE] Fusion des données', { availabilityData, eventData }); } const merged = { ...availabilityData }; if (!eventData) return merged; const extendedProps = eventData.extendedProps || {}; // Helper pour fusionner un tableau d'entités function mergeEntities(availabilityEntities, eventEntityId, eventEntityData) { if (!eventEntityId || !eventEntityData) { return availabilityEntities; } // Vérifier si l'entité existe déjà const exists = availabilityEntities.some(entity => entity.id == eventEntityId); if (!exists) { return [...availabilityEntities, eventEntityData]; } return availabilityEntities; } // Fusionner chaque type d'entité if (extendedProps.id_beneficiaire && eventData.beneficiaire) { merged.beneficiaires = mergeEntities( merged.beneficiaires || [], extendedProps.id_beneficiaire, { id: extendedProps.id_beneficiaire, nom: eventData.beneficiaire.nom + ' ' + (eventData.beneficiaire.prenom || ''), prenom: eventData.beneficiaire.prenom } ); } if (extendedProps.id_intervenant && eventData.intervenant) { merged.intervenants = mergeEntities( merged.intervenants || [], extendedProps.id_intervenant, { id: extendedProps.id_intervenant, nom: eventData.intervenant.nom + ' ' + (eventData.intervenant.prenom || ''), prenom: eventData.intervenant.prenom } ); } if (extendedProps.id_traducteur && eventData.traducteur) { merged.traducteurs = mergeEntities( merged.traducteurs || [], extendedProps.id_traducteur, { id: extendedProps.id_traducteur, nom: eventData.traducteur.nom + ' ' + (eventData.traducteur.prenom || ''), prenom: eventData.traducteur.prenom } ); } if (extendedProps.id_local && eventData.local) { merged.locaux = mergeEntities( merged.locaux || [], extendedProps.id_local, { id: extendedProps.id_local, nom: eventData.local.nom } ); } if (extendedProps.langue || eventData.langue) { const langueId = extendedProps.langue || eventData.langue; let langueNom = 'Langue'; let langueSlug = null; if (typeof eventData.langue === 'object' && eventData.langue) { langueNom = eventData.langue.nom || 'Langue'; langueSlug = eventData.langue.slug || eventData.langue.id || langueId; } else { // Vérifier si langue_label existe et n'est pas égal à l'ID if (eventData.langue_label && eventData.langue_label !== langueId) { langueNom = eventData.langue_label; } else if (extendedProps.langue_label && extendedProps.langue_label !== langueId) { langueNom = extendedProps.langue_label; } langueSlug = langueId; } const langueData = { id: langueId, nom: langueNom }; if (langueSlug) langueData.slug = langueSlug; merged.langues = mergeEntities( merged.langues || [], langueId, langueData ); } if (extendedProps.id_departement || eventData.id_departement) { const departementId = extendedProps.id_departement || eventData.id_departement; const departementNom = eventData.departement?.nom || eventData.departement || 'Département'; merged.departements = mergeEntities( merged.departements || [], departementId, { id: departementId, nom: departementNom } ); } if (extendedProps.id_type_intervention || eventData.id_type_intervention) { const typeInterventionId = extendedProps.id_type_intervention || eventData.id_type_intervention; const typeInterventionNom = eventData.type_intervention?.nom || eventData.type_intervention || 'Type d\'intervention'; merged.types_intervention = mergeEntities( merged.types_intervention || [], typeInterventionId, { id: typeInterventionId, nom: typeInterventionNom } ); } return merged; } /** * Filtre un select selon les items disponibles * @param {string} selectId - ID du select * @param {Array} availableItems - Items disponibles */ function filterSelect(selectId, availableItems) { const select = document.getElementById(selectId); if (!select || select.tagName !== 'SELECT') return; if (!Array.isArray(availableItems)) { if (DEBUG_SELECTS) console.warn(`Items invalides pour ${selectId}:`, availableItems); return; } // Créer un ensemble des IDs disponibles const availableIds = new Set( availableItems .filter(item => item && item.id != null) .map(item => item.id.toString()) ); // Pour les langues, créer aussi un set des slugs const availableSlugs = selectId === 'langue' ? new Set( availableItems .filter(item => item != null) .flatMap(item => { const slugs = []; if (item.slug != null) slugs.push(item.slug.toString()); if (item.id != null && isNaN(item.id)) slugs.push(item.id.toString()); return slugs; }) ) : new Set(); const currentValue = select.value; // Parcourir les options du select if (!select.options) return; Array.from(select.options).forEach(option => { if (option.value === '') { option.style.display = ''; return; } const isCurrentlySelected = option.value === currentValue; let isAvailable = availableIds.has(option.value); // Pour les langues, vérifier aussi le slug if (!isAvailable && selectId === 'langue') { const optionSlug = option.getAttribute('data-slug'); if (optionSlug && availableSlugs.has(optionSlug)) { isAvailable = true; } else if (availableSlugs.has(option.value)) { isAvailable = true; } else if (availableSlugs.size > 0) { const optionText = option.text.toLowerCase().trim(); if (availableSlugs.has(optionText)) { isAvailable = true; } else { const normalizedText = optionText.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); if (availableSlugs.has(normalizedText)) { isAvailable = true; } } } } // Afficher ou cacher l'option if (isAvailable || isCurrentlySelected) { option.style.display = ''; option.disabled = false; } else { option.style.display = 'none'; option.disabled = true; } }); // Mettre à jour Select2 si initialisé if (window.jQuery && jQuery(select).hasClass('select2-hidden-accessible')) { jQuery(select).trigger('change.select2'); } } /** * Filtre les options des selects selon les disponibilités * @param {Object} data - Données de disponibilité * @param {Object} currentEventData - Données de l'événement actuel */ function filterSelectOptions(data, currentEventData = null) { if (DEBUG_SELECTS) { console.log('🎯 [FILTER] Filtrage des selects', { data }); } // Filtrer chaque type de select filterSelect('id_beneficiaire', data.beneficiaires || []); filterSelect('id_intervenant', data.intervenants || []); filterSelect('id_traducteur', data.traducteurs || []); filterSelect('id_local', data.locaux || []); filterSelect('id_departement', data.departements || []); filterSelect('id_type_intervention', data.types_intervention || []); // Filtrer les langues selon langues_disponibles si c'est une permanence const extendedProps = currentEventData?.extendedProps || {}; const isPermanence = extendedProps.type === 'permanence'; const languesDisponibles = extendedProps.langues_disponibles; if (isPermanence && languesDisponibles && languesDisponibles.trim() !== '') { const languesPermises = languesDisponibles.split(',').map(l => l.trim()).filter(l => l !== ''); const languesFiltrees = (data.langues || []).filter(langue => { return languesPermises.includes(langue.id || langue.slug || langue); }); filterSelect('langue', languesFiltrees); } else { filterSelect('langue', data.langues || []); } } /** * Présélectionne les valeurs dans les selects * @param {Object} eventData - Données de l'événement */ function preselectValues(eventData) { if (!eventData) return; const extendedProps = eventData.extendedProps || {}; // Mapper les champs à présélectionner const fieldsToPreselect = { 'id_beneficiaire': extendedProps.id_beneficiaire, 'id_intervenant': extendedProps.id_intervenant, 'id_traducteur': extendedProps.id_traducteur, 'id_local': extendedProps.id_local, 'id_departement': extendedProps.id_departement, 'id_type_intervention': extendedProps.id_type_intervention, 'langue': extendedProps.langue }; Object.entries(fieldsToPreselect).forEach(([fieldId, value]) => { if (value) { const element = document.getElementById(fieldId); if (element && element.tagName === 'SELECT') { if (window.jQuery && jQuery(element).hasClass('select2-hidden-accessible')) { jQuery(element).val(value).trigger('change.select2'); } else { element.value = value; } } } }); } /** * Population des selects selon les disponibilités * @param {Object} eventData - Données de l'événement */ export async function populateSelects(eventData = null) { // Vérifier si une exécution est déjà en cours if (isPopulateSelectsRunning) { if (DEBUG_SELECTS) console.warn('⚠️ populateSelects déjà en cours'); return; } isPopulateSelectsRunning = true; isUpdatingSelects = true; try { const dateInput = document.getElementById('date_rdv'); const heureInput = document.getElementById('heure_rdv'); if (!dateInput || !heureInput) { console.warn('Champs date/heure non trouvés'); return; } const date = dateInput.value; const heure = heureInput.value; if (!date || !heure) { console.warn('Date et heure requises pour filtrer les disponibilités'); return; } // Créer une clé de cache const cacheKey = `${date}|${heure}|${eventData ? eventData.id || 'new' : 'new'}`; // Vérifier le cache if (disponibilitesCache.has(cacheKey)) { const cachedData = disponibilitesCache.get(cacheKey); const mergedData = mergeEventDataWithAvailability(cachedData, eventData); filterSelectOptions(mergedData, eventData); preselectValues(eventData); return; } // Éviter les appels simultanés const currentCall = `${date}|${heure}`; if (lastPopulateCall === currentCall) return; lastPopulateCall = currentCall; // Appel API const params = { date, heure }; const data = await getFilters('disponibilites', params); // Mettre en cache (expire après 5 minutes) disponibilitesCache.set(cacheKey, data); setTimeout(() => disponibilitesCache.delete(cacheKey), 5 * 60 * 1000); // Fusionner et filtrer const mergedData = mergeEventDataWithAvailability(data, eventData); filterSelectOptions(mergedData, eventData); preselectValues(eventData); } catch (error) { console.error('Erreur lors de la récupération des disponibilités:', error); notifyError('Erreur lors de la récupération des disponibilités'); } finally { isPopulateSelectsRunning = false; isUpdatingSelects = false; } } /** * Filtre les traducteurs selon la langue sélectionnée */ export function filterTraducteursByLangue() { if (isUpdatingSelects || isFilteringTraducteurs) return; isFilteringTraducteurs = true; try { const langueSelect = document.getElementById('langue'); const traducteurSelect = document.getElementById('id_traducteur'); if (!langueSelect || !traducteurSelect || traducteurSelect.tagName !== 'SELECT') { return; } const selectedLangue = langueSelect.value; const currentTraducteurValue = traducteurSelect.value; // Si aucune langue sélectionnée, afficher tous les traducteurs if (!selectedLangue) { if (traducteurSelect.options) { Array.from(traducteurSelect.options).forEach(option => { option.style.display = ''; option.disabled = false; }); if (window.jQuery && jQuery(traducteurSelect).hasClass('select2-hidden-accessible')) { jQuery(traducteurSelect).trigger('change.select2'); } } return; } // Récupérer le slug de la langue const langueOption = langueSelect.options[langueSelect.selectedIndex]; let langueSlug = null; if (langueOption && langueOption.dataset.slug) { langueSlug = langueOption.dataset.slug; } else { console.warn('Aucun slug disponible pour la langue sélectionnée'); return; } // Filtrer les traducteurs if (traducteurSelect.options) { Array.from(traducteurSelect.options).forEach(option => { if (option.value === '') { option.style.display = ''; return; } const traducteurLangues = option.getAttribute('data-langue'); const isCurrentlySelected = option.value === currentTraducteurValue; if (!traducteurLangues) { option.style.display = isCurrentlySelected ? '' : 'none'; option.disabled = !isCurrentlySelected; } else { const languesArray = traducteurLangues.split(','); const langueMatches = languesArray.some(lang => { return lang.trim() === langueSlug || lang.trim() === selectedLangue; }); if (langueMatches || isCurrentlySelected) { option.style.display = ''; option.disabled = false; } else { option.style.display = 'none'; option.disabled = true; } } }); // Si le traducteur actuel ne correspond plus, le vider if (currentTraducteurValue && selectedLangue) { const currentOption = traducteurSelect.options[traducteurSelect.selectedIndex]; if (currentOption && currentOption.value !== '' && currentOption.style.display === 'none') { traducteurSelect.value = ''; if (window.jQuery && jQuery(traducteurSelect).hasClass('select2-hidden-accessible')) { jQuery(traducteurSelect).val('').trigger('change.select2'); } } } // Mettre à jour Select2 if (window.jQuery && jQuery(traducteurSelect).hasClass('select2-hidden-accessible')) { jQuery(traducteurSelect).trigger('change.select2'); } } } finally { isFilteringTraducteurs = false; } } /** * Obtient l'état du flag isUpdatingSelects * @returns {boolean} */ export function getIsUpdatingSelects() { return isUpdatingSelects; } /** * Vide le cache des disponibilités */ export function clearDisponibilitesCache() { disponibilitesCache.clear(); lastPopulateCall = null; }