Crvi/assets/js/modules/agenda-modal-select.js
2026-01-21 21:33:37 +01:00

580 lines
21 KiB
JavaScript

// 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à (conversion en string pour comparaison)
const eventEntityIdStr = eventEntityId != null ? eventEntityId.toString() : null;
const exists = availabilityEntities.some(entity =>
entity.id != null && entity.id.toString() === eventEntityIdStr
);
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;
// Si c'est une permanence ET qu'il y a des langues_disponibles non vides, filtrer
// Sinon (pas de permanence OU pas de langues_disponibles OU vide), afficher toutes les langues
if (isPermanence && languesDisponibles && typeof languesDisponibles === 'string' && languesDisponibles.trim() !== '') {
const languesPermises = languesDisponibles.split(',').map(l => l.trim()).filter(l => l !== '');
const languesFiltrees = (data.langues || []).filter(langue => {
// Convertir en string pour comparaison (éviter problème string vs number)
const langueIdStr = langue.id != null ? langue.id.toString() : null;
const langueSlugStr = langue.slug != null ? langue.slug.toString() : null;
const langueStr = typeof langue === 'string' ? langue : null;
return languesPermises.includes(langueIdStr) ||
languesPermises.includes(langueSlugStr) ||
languesPermises.includes(langueStr);
});
filterSelect('langue', languesFiltrees);
} else {
// Afficher toutes les langues (pas de permanence OU pas de langues_disponibles OU vide)
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) return;
const element = document.getElementById(fieldId);
if (!element || element.tagName !== 'SELECT') return;
// Si Select2 est actif
if (window.jQuery && jQuery(element).hasClass('select2-hidden-accessible')) {
// Définir la valeur et déclencher l'événement change.select2
jQuery(element).val(value).trigger('change.select2');
} else {
// Select natif
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);
// S'assurer que Select2 est initialisé avant de présélectionner
initializeSelect2();
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);
// S'assurer que Select2 est initialisé avant de présélectionner
initializeSelect2();
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 ? langueSelect.value.toString() : '';
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;
}
/**
* Initialise le listener pour afficher l'alerte "Personne en liste rouge"
*/
export function initializeBeneficiaireListeRougeAlert() {
const beneficiaireSelect = document.getElementById('id_beneficiaire');
const alertElement = document.getElementById('beneficiaire-liste-rouge-alert');
if (!beneficiaireSelect || !alertElement) {
return;
}
// Fonction pour vérifier et afficher l'alerte
const checkListeRouge = () => {
const selectedBeneficiaireId = beneficiaireSelect.value;
// Vérifier si le bénéficiaire est en liste rouge via crviACFData
if (selectedBeneficiaireId && window.crviACFData && window.crviACFData.beneficiaires) {
const beneficiaireData = window.crviACFData.beneficiaires[selectedBeneficiaireId];
if (beneficiaireData && beneficiaireData.personne_en_liste_rouge) {
alertElement.style.display = 'block';
} else {
alertElement.style.display = 'none';
}
} else {
alertElement.style.display = 'none';
}
};
// Écouter les changements du select
beneficiaireSelect.addEventListener('change', checkListeRouge);
// Écouter aussi les événements Select2 si présent
if (window.jQuery) {
jQuery(beneficiaireSelect).on('select2:select', checkListeRouge);
jQuery(beneficiaireSelect).on('select2:clear', checkListeRouge);
}
}