519 lines
18 KiB
JavaScript
519 lines
18 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à
|
|
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;
|
|
}
|