modularisation agenda modal

This commit is contained in:
Jean-Philippe Staelen 2026-01-20 13:24:39 +01:00
parent ec0d7da5f9
commit 88367d0530
7 changed files with 1952 additions and 4285 deletions

View File

@ -1,7 +1,8 @@
// Module ES6 pour la création d'entités depuis le modal d'événement // Module ES6 pour la création d'entités depuis le modal d'événement
import { apiFetch } from './agenda-api.js'; import { apiFetch } from './agenda-api.js';
import { notifyError, notifySuccess } from './agenda-notifications.js'; import { notifyError, notifySuccess } from './agenda-notifications.js';
import { populateSelects, preserveModalData } from './agenda-modal.js'; import { openSubModal } from './agenda-modal.js';
import { populateSelects } from './agenda-modal-select.js';
// Configuration des entités // Configuration des entités
const ENTITY_CONFIG = { const ENTITY_CONFIG = {
@ -97,49 +98,18 @@ function openCreateEntityModal(entityType) {
return; return;
} }
const modal = document.getElementById(config.modalId); // Utiliser la fonction générique pour ouvrir la sous-modale
if (!modal) { openSubModal(
console.error(`Modal non trouvé: ${config.modalId}`); config.modalId,
return; // Callback avant ouverture : réinitialiser le formulaire
} (subModal) => {
// Réinitialiser le formulaire
const form = document.getElementById(config.formId); const form = document.getElementById(config.formId);
if (form) { if (form) {
form.reset(); form.reset();
} }
// Les selects sont maintenant initialisés automatiquement par jQuery('.select2').select2() // Les selects sont maintenant initialisés automatiquement par jQuery('.select2').select2()
// Préserver les données de la modale principale avant de la fermer
preserveModalData();
// Fermer le modal principal d'événement s'il est ouvert
const eventModal = document.getElementById('eventModal');
if (eventModal) {
const eventBsModal = bootstrap.Modal.getInstance(eventModal);
if (eventBsModal) {
eventBsModal.hide();
} }
} );
// Attendre un peu que le modal principal se ferme avant d'ouvrir le nouveau
setTimeout(() => {
// Ouvrir le modal de création
if (window.bootstrap && window.bootstrap.Modal) {
const bsModal = new window.bootstrap.Modal(modal);
bsModal.show();
// Ajouter un event listener pour rouvrir le modal principal quand on ferme
modal.addEventListener('hidden.bs.modal', function() {
// Rouvrir le modal principal d'événement avec les données préservées
if (eventModal && window.bootstrap && window.bootstrap.Modal) {
const newEventModal = new window.bootstrap.Modal(eventModal);
newEventModal.show();
}
}, { once: true }); // Une seule fois
}
}, 300); // Délai pour la transition
} }
// Les selects sont maintenant initialisés automatiquement par jQuery('.select2').select2() // Les selects sont maintenant initialisés automatiquement par jQuery('.select2').select2()

View File

@ -0,0 +1,269 @@
// Module de gestion des boutons de la modale
// Contient les gestionnaires d'événements pour tous les boutons de la modale
import { deleteEvent, changeEventStatus } from './agenda-api.js';
import { notifyError, notifySuccess } from './agenda-notifications.js';
/**
* Helper pour gérer l'overlay de chargement
* @param {Function} asyncAction - Action asynchrone à exécuter
* @returns {Promise<void>}
*/
async function withLoadingOverlay(asyncAction) {
const overlayTarget = document.querySelector('#eventModal .modal-content') || document.getElementById('eventModal');
try {
if (window.CRVI_OVERLAY && overlayTarget) {
window.CRVI_OVERLAY.show(overlayTarget);
}
await asyncAction();
} finally {
if (window.CRVI_OVERLAY && overlayTarget) {
window.CRVI_OVERLAY.hide(overlayTarget);
}
}
}
/**
* Helper pour rafraîchir les calendriers
*/
function refreshCalendars() {
if (window.currentCalendar) {
window.currentCalendar.refetchEvents();
}
if (window.currentColleaguesCalendar) {
window.currentColleaguesCalendar.refetchEvents();
}
}
/**
* Helper pour fermer le modal
* @param {Function} onClosed - Callback après fermeture
*/
function closeModal(onClosed = null) {
const modal = document.getElementById('eventModal');
if (modal) {
const bsModal = bootstrap.Modal.getInstance(modal);
if (bsModal) {
if (onClosed) {
modal.addEventListener('hidden.bs.modal', onClosed, { once: true });
}
bsModal.hide();
}
}
}
/**
* Vérifie les permissions utilisateur
* @param {string} permission - Type de permission ('can_edit', 'can_delete', 'can_create')
* @returns {boolean}
*/
function checkPermission(permission) {
const hasPermission = window.crviPermissions && window.crviPermissions[permission];
if (!hasPermission) {
console.warn(`Utilisateur non autorisé: ${permission}`);
}
return hasPermission;
}
/**
* Initialise le bouton de fermeture
* @param {string} buttonId - ID du bouton
*/
export function initializeCloseButton(buttonId) {
const button = document.getElementById(buttonId);
if (button) {
button.onclick = () => closeModal();
}
}
/**
* Initialise le bouton de suppression
* @param {Function} getCurrentEventData - Fonction pour obtenir les données de l'événement
* @param {Function} onDeleted - Callback après suppression
*/
export function initializeDeleteButton(getCurrentEventData, onDeleted = null) {
const deleteBtn = document.getElementById('deleteEvent');
if (!deleteBtn) return;
deleteBtn.onclick = async function() {
if (!checkPermission('can_delete')) return;
if (!confirm('Êtes-vous sûr de vouloir supprimer cet événement ?')) {
return;
}
const currentEventData = getCurrentEventData();
const eventId = currentEventData ? currentEventData.id : null;
if (!eventId) {
notifyError('ID d\'événement manquant');
return;
}
await withLoadingOverlay(async () => {
try {
await deleteEvent(eventId);
refreshCalendars();
closeModal(onDeleted);
} catch (error) {
console.error('Erreur lors de la suppression:', error);
notifyError('Erreur lors de la suppression de l\'événement');
}
});
};
}
/**
* Initialise le bouton de validation de présence
* @param {Function} getCurrentEventData - Fonction pour obtenir les données de l'événement
* @param {Function} openCheckPresenceModal - Fonction pour ouvrir la modal de validation de présence de groupe
* @param {Function} onStatusChanged - Callback après changement de statut
*/
export function initializeMarkPresentButton(getCurrentEventData, openCheckPresenceModal, onStatusChanged = null) {
const markPresentBtn = document.getElementById('markPresentBtn');
if (!markPresentBtn) return;
markPresentBtn.onclick = async function() {
if (!checkPermission('can_edit')) return;
const currentEventData = getCurrentEventData();
const eventId = currentEventData ? currentEventData.id : null;
if (!eventId) {
notifyError('ID d\'événement manquant');
return;
}
// Vérifier le type d'événement
const eventType = currentEventData?.type || currentEventData?.extendedProps?.type || '';
const isGroupe = eventType === 'groupe';
if (isGroupe) {
// Pour les événements de groupe, ouvrir la modal de validation des présences
openCheckPresenceModal(currentEventData);
} else {
// Pour les événements individuels
if (!confirm('Confirmer la présence à cet événement ?')) {
return;
}
await withLoadingOverlay(async () => {
try {
await changeEventStatus(eventId, 'present');
notifySuccess('Présence validée');
refreshCalendars();
closeModal(onStatusChanged);
} catch (error) {
console.error('Erreur lors du changement de statut:', error);
notifyError('Erreur lors de la validation de présence');
}
});
}
};
}
/**
* Initialise le bouton pour marquer comme absent
* @param {Function} getCurrentEventData - Fonction pour obtenir les données de l'événement
* @param {Function} onStatusChanged - Callback après changement de statut
*/
export function initializeMarkAbsentButton(getCurrentEventData, onStatusChanged = null) {
const markAbsentBtn = document.getElementById('markAbsentBtn');
if (!markAbsentBtn) return;
markAbsentBtn.onclick = async function() {
if (!checkPermission('can_edit')) return;
const currentEventData = getCurrentEventData();
const eventId = currentEventData ? currentEventData.id : null;
if (!eventId) {
notifyError('ID d\'événement manquant');
return;
}
if (!confirm('Marquer cet événement comme absent ?')) {
return;
}
await withLoadingOverlay(async () => {
try {
await changeEventStatus(eventId, 'absence');
notifySuccess('Événement marqué comme absent');
refreshCalendars();
closeModal(onStatusChanged);
} catch (error) {
console.error('Erreur lors du changement de statut:', error);
notifyError('Erreur lors du changement de statut');
}
});
};
}
/**
* Initialise le bouton d'annulation de rendez-vous
* @param {Function} getCurrentEventData - Fonction pour obtenir les données de l'événement
* @param {Function} onCancelled - Callback après annulation
*/
export function initializeCancelAppointmentButton(getCurrentEventData, onCancelled = null) {
const cancelAppointmentBtn = document.getElementById('cancelAppointmentBtn');
if (!cancelAppointmentBtn) return;
cancelAppointmentBtn.onclick = async function() {
if (!checkPermission('can_edit')) return;
const currentEventData = getCurrentEventData();
const eventId = currentEventData ? currentEventData.id : null;
if (!eventId) {
notifyError('ID d\'événement manquant');
return;
}
const motif = prompt('Motif de l\'annulation (optionnel):');
if (motif === null) {
return; // L'utilisateur a cliqué sur Annuler
}
await withLoadingOverlay(async () => {
try {
await changeEventStatus(eventId, 'annule', motif);
notifySuccess('Rendez-vous annulé');
refreshCalendars();
closeModal(onCancelled);
} catch (error) {
console.error('Erreur lors de l\'annulation:', error);
notifyError('Erreur lors de l\'annulation du rendez-vous');
}
});
};
}
/**
* Initialise tous les boutons de la modale
* @param {Object} options - Options de configuration
* @param {Function} options.getCurrentEventData - Fonction pour obtenir les données de l'événement
* @param {Function} options.openCheckPresenceModal - Fonction pour ouvrir la modal de validation de présence
* @param {Function} options.onDeleted - Callback après suppression
* @param {Function} options.onStatusChanged - Callback après changement de statut
*/
export function initializeModalButtons(options = {}) {
const {
getCurrentEventData,
openCheckPresenceModal,
onDeleted,
onStatusChanged
} = options;
// Boutons de fermeture
initializeCloseButton('closeModalBtn');
initializeCloseButton('closeViewBtn');
// Boutons d'action
if (getCurrentEventData) {
initializeDeleteButton(getCurrentEventData, onDeleted);
initializeMarkPresentButton(getCurrentEventData, openCheckPresenceModal, onStatusChanged);
initializeMarkAbsentButton(getCurrentEventData, onStatusChanged);
initializeCancelAppointmentButton(getCurrentEventData, onStatusChanged);
}
}

View File

@ -0,0 +1,289 @@
// Module d'affichage des données de la modale
// Contient les fonctions d'affichage en mode lecture seule
import { setText, toggleElement } from './agenda-modal-dom.js';
/**
* Extrait la date et l'heure d'un événement selon différentes sources
* @param {Object} event - Données de l'événement
* @returns {Object} - {dateFormatted, heureFormatted}
*/
function extractDateTime(event) {
let dateFormatted = '';
let heureFormatted = '';
// Priorité 1: Données directes de l'API
if (event?.date && event?.heure) {
const dateObj = new Date(event.date + 'T' + event.heure);
dateFormatted = dateObj.toLocaleDateString('fr-FR');
heureFormatted = dateObj.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
}
// Priorité 2: Données dans extendedProps
else if (event?.extendedProps?.date && event?.extendedProps?.heure) {
const dateObj = new Date(event.extendedProps.date + 'T' + event.extendedProps.heure);
dateFormatted = dateObj.toLocaleDateString('fr-FR');
heureFormatted = dateObj.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
}
// Priorité 3: Données FullCalendar
else if (event?.start) {
const startDate = new Date(event.start);
dateFormatted = startDate.toLocaleDateString('fr-FR');
heureFormatted = startDate.toLocaleTimeString('fr-FR', { hour: '2-digit', minute: '2-digit' });
}
return { dateFormatted, heureFormatted };
}
/**
* Récupère le nom d'une entité depuis le select ou les données
* @param {string} entityName - Nom de l'entité ('beneficiaire', 'intervenant', etc.)
* @param {Object} event - Données de l'événement
* @param {string} selectId - ID du select
* @param {string} idField - Nom du champ ID
* @returns {string} - Nom formaté
*/
function getEntityName(entityName, event, selectId, idField) {
let name = '';
// Vérifier dans les relations directes
if (event?.[entityName]?.nom) {
name = event[entityName].nom + (event[entityName].prenom ? ' ' + event[entityName].prenom : '');
} else if (event?.extendedProps?.[entityName]?.nom) {
name = event.extendedProps[entityName].nom + (event.extendedProps[entityName].prenom ? ' ' + event.extendedProps[entityName].prenom : '');
} else {
// Chercher par ID dans le select
const entityId = event?.[idField] || event?.extendedProps?.[idField];
if (entityId) {
const select = document.getElementById(selectId);
// Vérifier si c'est un SELECT (pour intervenant côté front c'est un input hidden)
if (select && select.tagName === 'SELECT') {
const option = select.querySelector(`option[value="${entityId}"]`);
if (option) {
name = option.textContent;
}
} else if (select && selectId === 'id_intervenant') {
// Cas spécial pour intervenant côté front (input hidden)
const displayEl = document.getElementById('id_intervenant_display');
if (displayEl && displayEl.textContent) {
name = displayEl.textContent;
}
}
if (!name) {
name = `ID: ${entityId}`;
}
}
}
return name;
}
/**
* Récupère le nom d'un terme taxonomy depuis les données ou le select
* @param {Object} event - Données de l'événement
* @param {string} labelField - Champ de label (_label)
* @param {string} relationField - Champ de relation
* @param {string} idField - Champ ID
* @param {string} selectId - ID du select
* @returns {string} - Nom du terme
*/
function getTermName(event, labelField, relationField, idField, selectId) {
let name = '';
// Priorité 1: Label direct
name = event?.[labelField] || event?.extendedProps?.[labelField] || '';
// Priorité 2: Relation
if (!name && event?.[relationField]?.nom) {
name = event[relationField].nom;
} else if (!name && event?.extendedProps?.[relationField]?.nom) {
name = event.extendedProps[relationField].nom;
}
// Priorité 3: Chercher dans le select
if (!name) {
const termId = event?.[idField] || event?.extendedProps?.[idField];
if (termId && termId !== '0' && termId !== 0) {
const select = document.getElementById(selectId);
if (select) {
const option = select.querySelector(`option[value="${termId}"]`);
if (option) {
name = option.textContent;
}
}
if (!name) {
name = `ID: ${termId}`;
}
}
}
return name;
}
/**
* Remplit le bloc de vue avec les données d'un événement
* @param {Object} event - Données de l'événement
*/
export function fillViewBlock(event) {
// Date et heure
const { dateFormatted, heureFormatted } = extractDateTime(event);
setText('view_date_rdv', dateFormatted);
setText('view_heure_rdv', heureFormatted);
// Type et langue
const type = event?.type || event?.extendedProps?.type || '';
setText('view_type', type);
let langue = event?.langue_label || event?.extendedProps?.langue_label || '';
if (!langue || /^\d+$/.test(langue)) {
const langueId = langue || event?.langue || event?.extendedProps?.langue || '';
if (langueId) {
const langueSelect = document.getElementById('langue');
if (langueSelect) {
const option = langueSelect.querySelector(`option[value="${langueId}"]`);
if (option) {
langue = option.textContent;
} else {
langue = langueId;
}
} else {
langue = langueId;
}
}
}
if (!langue) {
langue = event?.langue || event?.extendedProps?.langue || '';
}
setText('view_langue', langue);
// Entités
setText('view_beneficiaire', getEntityName('beneficiaire', event, 'id_beneficiaire', 'id_beneficiaire'));
setText('view_intervenant', getEntityName('intervenant', event, 'id_intervenant', 'id_intervenant'));
setText('view_local', getEntityName('local', event, 'id_local', 'id_local'));
// Termes taxonomy
setText('view_departement', getTermName(event, 'departement_label', 'departement', 'id_departement', 'id_departement'));
setText('view_type_intervention', getTermName(event, 'type_intervention_label', 'type_intervention', 'id_type_intervention', 'id_type_intervention'));
// Traducteur (logique spéciale)
let traducteurNom = '';
const traducteurId = event?.id_traducteur || event?.extendedProps?.id_traducteur;
const hasValidTraducteurId = traducteurId && parseInt(traducteurId, 10) > 0;
if (hasValidTraducteurId) {
traducteurNom = getEntityName('traducteur', event, 'id_traducteur', 'id_traducteur');
} else {
traducteurNom = event?.nom_traducteur || event?.extendedProps?.nom_traducteur || '';
}
setText('view_traducteur', traducteurNom);
// Données de groupe
const eventType = event?.type || event?.extendedProps?.type || '';
const groupeFields = document.querySelectorAll('.groupe-only-field');
if (eventType === 'groupe') {
groupeFields.forEach(field => field.style.display = '');
setText('view_nb_participants', event?.nb_participants || event?.extendedProps?.nb_participants || '');
setText('view_nb_hommes', event?.nb_hommes || event?.extendedProps?.nb_hommes || '');
setText('view_nb_femmes', event?.nb_femmes || event?.extendedProps?.nb_femmes || '');
} else {
groupeFields.forEach(field => field.style.display = 'none');
setText('view_nb_participants', '');
setText('view_nb_hommes', '');
setText('view_nb_femmes', '');
}
// Commentaire
setText('view_commentaire', event?.commentaire || event?.extendedProps?.commentaire || '');
// Bouton historique bénéficiaire (uniquement pour événements individuels)
const historiqueBtn = document.getElementById('showBeneficiaireHistoriqueBtn');
if (historiqueBtn) {
const beneficiaireId = event?.id_beneficiaire || event?.extendedProps?.id_beneficiaire;
if (eventType === 'individuel' && beneficiaireId) {
historiqueBtn.style.display = 'block';
historiqueBtn.setAttribute('data-benef', beneficiaireId);
} else {
historiqueBtn.style.display = 'none';
historiqueBtn.removeAttribute('data-benef');
}
}
}
/**
* Met à jour l'affichage de la modale selon le mode
* @param {string} mode - Mode actuel ('view', 'edit', 'create')
* @param {boolean} canEdit - Permission d'édition
* @param {boolean} canDelete - Permission de suppression
*/
export function updateModalDisplay(mode, canEdit, canDelete) {
const viewBlock = document.getElementById('eventViewBlock');
const editBlock = document.getElementById('eventEditBlock');
const editBtn = document.getElementById('editEventBtn');
const deleteBtn = document.getElementById('deleteEvent');
const saveBtn = document.getElementById('saveEvent');
const cancelBtn = document.getElementById('cancelEditBtn');
const closeViewBtn = document.getElementById('closeViewBtn');
const markPresentBtn = document.getElementById('markPresentBtn');
const markAbsentBtn = document.getElementById('markAbsentBtn');
const cancelAppointmentBtn = document.getElementById('cancelAppointmentBtn');
const reportIncidentBtn = document.getElementById('reportIncidentBtn');
if (mode === 'view') {
if (viewBlock) viewBlock.style.display = 'block';
if (editBlock) editBlock.style.display = 'none';
if (editBtn) editBtn.style.display = canEdit ? 'inline-block' : 'none';
if (deleteBtn) deleteBtn.style.display = canDelete ? 'inline-block' : 'none';
if (saveBtn) saveBtn.style.display = 'none';
if (cancelBtn) cancelBtn.style.display = 'none';
if (closeViewBtn) closeViewBtn.style.display = 'inline-block';
if (markPresentBtn) markPresentBtn.style.display = canEdit ? 'inline-block' : 'none';
if (markAbsentBtn) markAbsentBtn.style.display = canEdit ? 'inline-block' : 'none';
if (cancelAppointmentBtn) cancelAppointmentBtn.style.display = canEdit ? 'inline-block' : 'none';
if (reportIncidentBtn) reportIncidentBtn.style.display = 'inline-block';
} else if (mode === 'edit' || mode === 'create') {
if (viewBlock) viewBlock.style.display = 'none';
if (editBlock) editBlock.style.display = 'block';
if (editBtn) editBtn.style.display = 'none';
if (deleteBtn) deleteBtn.style.display = 'none';
if (saveBtn) saveBtn.style.display = 'inline-block';
if (cancelBtn) cancelBtn.style.display = 'inline-block';
if (closeViewBtn) closeViewBtn.style.display = 'none';
if (markPresentBtn) markPresentBtn.style.display = 'none';
if (markAbsentBtn) markAbsentBtn.style.display = 'none';
if (cancelAppointmentBtn) cancelAppointmentBtn.style.display = 'none';
if (reportIncidentBtn) reportIncidentBtn.style.display = 'none';
}
}
/**
* Vérifie si un événement est passé
* @param {Object} eventData - Données de l'événement
* @returns {boolean} - True si l'événement est passé
*/
export function checkIfEventIsPast(eventData) {
if (!eventData) return false;
let eventDate = null;
let eventTime = null;
// Essayer différentes sources pour la date
if (eventData.date_rdv) {
eventDate = eventData.date_rdv;
eventTime = eventData.heure_rdv || '00:00';
} else if (eventData.start) {
const startDate = new Date(eventData.start);
eventDate = startDate.toISOString().split('T')[0];
eventTime = startDate.toTimeString().substring(0, 5);
} else if (eventData.extendedProps?.date_rdv) {
eventDate = eventData.extendedProps.date_rdv;
eventTime = eventData.extendedProps.heure_rdv || '00:00';
}
if (!eventDate) return false;
const eventDateTime = new Date(`${eventDate}T${eventTime}`);
const now = new Date();
return eventDateTime <= now;
}

View File

@ -0,0 +1,190 @@
// Module de gestion du DOM pour les modales
// Contient les helpers pour accéder et manipuler les éléments DOM de manière optimisée
/**
* Cache pour les éléments DOM fréquemment accédés
*/
const domCache = new Map();
/**
* Obtient un élément du DOM avec mise en cache
* @param {string} elementId - ID de l'élément
* @returns {HTMLElement|null} - L'élément ou null
*/
export function getElement(elementId) {
if (domCache.has(elementId)) {
const cached = domCache.get(elementId);
// Vérifier que l'élément est toujours dans le DOM
if (cached && document.contains(cached)) {
return cached;
}
domCache.delete(elementId);
}
const element = document.getElementById(elementId);
if (element) {
domCache.set(elementId, element);
}
return element;
}
/**
* Vide le cache DOM (à appeler lors de la fermeture du modal)
*/
export function clearDomCache() {
domCache.clear();
}
/**
* Définit la valeur d'un élément de manière sécurisée
* @param {string} elementId - ID de l'élément
* @param {*} value - Valeur à définir
* @returns {boolean} - True si succès
*/
export function safeSetValue(elementId, value) {
const element = getElement(elementId);
if (!element) {
console.warn(`Élément ${elementId} non trouvé dans le DOM`);
return false;
}
// Si c'est un select avec Select2 initialisé
if (element.tagName === 'SELECT' && window.jQuery && jQuery(element).hasClass('select2-hidden-accessible')) {
jQuery(element).val(value).trigger('change');
} else {
element.value = value;
}
return true;
}
/**
* Récupère la valeur d'un élément de manière sécurisée
* @param {string} elementId - ID de l'élément
* @returns {string|null} - Valeur de l'élément ou null
*/
export function safeGetValue(elementId) {
const element = getElement(elementId);
if (!element) {
console.warn(`Élément ${elementId} non trouvé dans le DOM`);
return null;
}
return element.value;
}
/**
* Affiche ou cache un élément
* @param {string} elementId - ID de l'élément
* @param {boolean} show - True pour afficher, false pour cacher
*/
export function toggleElement(elementId, show) {
const element = getElement(elementId);
if (element) {
element.style.display = show ? '' : 'none';
}
}
/**
* Ajoute une classe à un élément
* @param {string} elementId - ID de l'élément
* @param {string} className - Classe à ajouter
*/
export function addClass(elementId, className) {
const element = getElement(elementId);
if (element) {
element.classList.add(className);
}
}
/**
* Retire une classe d'un élément
* @param {string} elementId - ID de l'élément
* @param {string} className - Classe à retirer
*/
export function removeClass(elementId, className) {
const element = getElement(elementId);
if (element) {
element.classList.remove(className);
}
}
/**
* Vérifie si un élément a une classe
* @param {string} elementId - ID de l'élément
* @param {string} className - Classe à vérifier
* @returns {boolean} - True si l'élément a la classe
*/
export function hasClass(elementId, className) {
const element = getElement(elementId);
return element ? element.classList.contains(className) : false;
}
/**
* Définit le texte HTML d'un élément
* @param {string} elementId - ID de l'élément
* @param {string} html - HTML à définir
*/
export function setHTML(elementId, html) {
const element = getElement(elementId);
if (element) {
element.innerHTML = html;
}
}
/**
* Définit le texte d'un élément
* @param {string} elementId - ID de l'élément
* @param {string} text - Texte à définir
*/
export function setText(elementId, text) {
const element = getElement(elementId);
if (element) {
element.textContent = text;
}
}
/**
* Ajoute un event listener à un élément
* @param {string} elementId - ID de l'élément
* @param {string} event - Type d'événement
* @param {Function} handler - Gestionnaire d'événement
*/
export function addListener(elementId, event, handler) {
const element = getElement(elementId);
if (element) {
element.addEventListener(event, handler);
}
}
/**
* Nettoie tous les champs d'un formulaire
* @param {Array<string>} textFieldIds - IDs des champs texte
* @param {Array<string>} selectFieldIds - IDs des selects
*/
export function clearFormFields(textFieldIds, selectFieldIds) {
// Nettoyer les champs de texte
textFieldIds.forEach(fieldId => {
const field = getElement(fieldId);
if (field) {
field.value = '';
}
});
// Nettoyer les selects
selectFieldIds.forEach(selectId => {
const select = getElement(selectId);
if (select) {
select.value = '';
// Si c'est un <select>, gérer Select2 et les options
if (select.tagName === 'SELECT' && select.options) {
if (window.jQuery && jQuery(select).hasClass('select2-hidden-accessible')) {
jQuery(select).val('').trigger('change');
}
Array.from(select.options).forEach(option => {
option.style.display = '';
option.disabled = false;
});
}
}
});
}

View File

@ -0,0 +1,477 @@
// Module de gestion des formulaires de la modale
// Contient la logique de remplissage, validation et soumission des formulaires
import { createEvent, updateEvent } from './agenda-api.js';
import { notifyError, notifySuccess } from './agenda-notifications.js';
import { safeSetValue, safeGetValue } from './agenda-modal-dom.js';
/**
* Calcule l'heure de fin (+1h par rapport au début)
* @param {string} heureDebut - Heure de début au format HH:MM
* @returns {string} - Heure de fin au format HH:MM
*/
function calculateHeureFin(heureDebut) {
const [h, m] = heureDebut.split(':').map(Number);
const heureFin = ((h + 1) % 24).toString().padStart(2, '0') + ':' + m.toString().padStart(2, '0');
return heureFin;
}
/**
* Remplit le formulaire avec une date (pour création)
* @param {Object} data - Données avec startStr et endStr
*/
export function fillFormWithDate(data) {
const dateRdv = data?.startStr?.split('T')[0] || '';
safeSetValue('date_rdv', dateRdv);
const heureDebut = data?.startStr?.split('T')[1]?.substring(0, 5) || '09:00';
safeSetValue('heure_rdv', heureDebut);
safeSetValue('date_fin', dateRdv);
const heureFin = data?.endStr?.split('T')[1]?.substring(0, 5) || '09:15';
safeSetValue('heure_fin', heureFin);
}
/**
* Remplit le formulaire avec les données d'un événement (pour édition)
* @param {Object} event - Données de l'événement
* @param {Function} filterTraducteursByLangue - Fonction pour filtrer les traducteurs
*/
export function fillFormWithEvent(event, filterTraducteursByLangue = null) {
if (!event) {
console.warn('fillFormWithEvent: event est undefined');
return;
}
// Gérer les dates/heures selon les sources disponibles
if (event.date && event.heure) {
// Données directes de l'API
safeSetValue('date_rdv', event.date);
const heureDebut = event.heure.substring(0, 5);
safeSetValue('heure_rdv', heureDebut);
const dateFin = event.date_fin && event.date_fin >= event.date ? event.date_fin : event.date;
safeSetValue('date_fin', dateFin);
const heureFin = event.heure_fin ? event.heure_fin.substring(0, 5) : calculateHeureFin(heureDebut);
safeSetValue('heure_fin', heureFin);
} else if (event.extendedProps?.date && event.extendedProps?.heure) {
// Données dans extendedProps
safeSetValue('date_rdv', event.extendedProps.date);
const heureDebut = event.extendedProps.heure.substring(0, 5);
safeSetValue('heure_rdv', heureDebut);
const dateFin = event.extendedProps.date_fin && event.extendedProps.date_fin >= event.extendedProps.date ? event.extendedProps.date_fin : event.extendedProps.date;
safeSetValue('date_fin', dateFin);
const heureFin = event.extendedProps.heure_fin ? event.extendedProps.heure_fin.substring(0, 5) : calculateHeureFin(heureDebut);
safeSetValue('heure_fin', heureFin);
} else if (event.start && event.end) {
// Données FullCalendar
try {
const startDate = new Date(event.start);
const endDate = new Date(event.end);
if (!isNaN(startDate.getTime()) && !isNaN(endDate.getTime())) {
const startDateStr = formatDate(startDate);
const endDateStr = formatDate(endDate);
const heureDebut = formatTime(startDate);
const heureFin = formatTime(endDate);
safeSetValue('date_rdv', startDateStr);
safeSetValue('heure_rdv', heureDebut);
safeSetValue('date_fin', endDateStr >= startDateStr ? endDateStr : startDateStr);
safeSetValue('heure_fin', heureFin);
} else {
setCurrentDateTime();
}
} catch (error) {
console.error('Erreur lors du parsing des dates FullCalendar', error);
setCurrentDateTime();
}
} else {
console.warn('Aucune donnée de date/heure trouvée, utilisation de la date actuelle');
setCurrentDateTime();
}
// Remplir les autres champs
const extendedProps = event.extendedProps || {};
safeSetValue('type', extendedProps.type || event.type || '');
safeSetValue('langue', extendedProps.langue || event.langue || '');
const beneficiaireId = extendedProps.id_beneficiaire || event.id_beneficiaire || '';
safeSetValue('id_beneficiaire', beneficiaireId);
safeSetValue('id_intervenant', extendedProps.id_intervenant || event.id_intervenant || '');
safeSetValue('id_traducteur', extendedProps.id_traducteur || event.id_traducteur || '');
safeSetValue('nom_traducteur', extendedProps.nom_traducteur || event.nom_traducteur || '');
safeSetValue('id_local', extendedProps.id_local || event.id_local || '');
safeSetValue('id_departement', extendedProps.id_departement || event.id_departement || '');
safeSetValue('id_type_intervention', extendedProps.id_type_intervention || event.id_type_intervention || '');
safeSetValue('commentaire', extendedProps.commentaire || event.commentaire || '');
// Mettre à jour les selects Select2
updateSelect2Fields();
// Filtrer les traducteurs selon la langue
if (filterTraducteursByLangue) {
setTimeout(() => filterTraducteursByLangue(), 50);
}
// Gérer la case "traducteur existant"
handleTraducteurExistingCheckbox(extendedProps.id_traducteur || event.id_traducteur);
// Gérer les champs conditionnels selon le type
const type = extendedProps.type || event.type || '';
handleTypeConditionalFields(type, event, extendedProps);
// Charger le statut liste rouge si bénéficiaire sélectionné
if (beneficiaireId) {
setTimeout(() => {
const beneficiaireSelect = document.getElementById('id_beneficiaire');
if (beneficiaireSelect) {
beneficiaireSelect.dispatchEvent(new Event('change', { bubbles: true }));
}
}, 100);
}
}
/**
* Formate une date en YYYY-MM-DD
* @param {Date} date - Date à formater
* @returns {string}
*/
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
/**
* Formate une heure en HH:MM
* @param {Date} date - Date à formater
* @returns {string}
*/
function formatTime(date) {
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${hours}:${minutes}`;
}
/**
* Définit la date et l'heure actuelles
*/
function setCurrentDateTime() {
const now = new Date();
const nowStr = formatDate(now);
const heureDebut = formatTime(now);
safeSetValue('date_rdv', nowStr);
safeSetValue('heure_rdv', heureDebut);
safeSetValue('date_fin', nowStr);
safeSetValue('heure_fin', calculateHeureFin(heureDebut));
}
/**
* Met à jour les champs Select2
*/
function updateSelect2Fields() {
const select2Fields = ['type', 'langue', 'id_beneficiaire', 'id_intervenant', 'id_traducteur', 'id_local', 'id_departement', 'id_type_intervention'];
select2Fields.forEach(fieldId => {
const element = document.getElementById(fieldId);
if (element && element.tagName === 'SELECT' && window.jQuery && jQuery(element).hasClass('select2-hidden-accessible')) {
const currentValue = element.value;
if (currentValue) {
jQuery(element).val(currentValue).trigger('change');
}
}
});
}
/**
* Gère la case à cocher "traducteur existant"
* @param {number|string} traducteurId - ID du traducteur
*/
function handleTraducteurExistingCheckbox(traducteurId) {
const tradSelect = document.getElementById('id_traducteur');
const useExistingCheckbox = document.getElementById('use_existing_traducteur');
const tradContainer = document.getElementById('traducteur-select-container');
if (useExistingCheckbox && tradContainer && tradSelect) {
const hasTraducteur = traducteurId && parseInt(traducteurId, 10) > 0;
useExistingCheckbox.checked = hasTraducteur;
if (hasTraducteur) {
tradContainer.classList.remove('d-none');
tradSelect.setAttribute('required', 'required');
} else {
tradContainer.classList.add('d-none');
tradSelect.removeAttribute('required');
safeSetValue('id_traducteur', '');
}
}
}
/**
* Gère les champs conditionnels selon le type d'événement
* @param {string} type - Type d'événement
* @param {Object} event - Données de l'événement
* @param {Object} extendedProps - Propriétés étendues
*/
function handleTypeConditionalFields(type, event, extendedProps) {
const groupeFields = document.getElementById('groupeFields');
const nbParticipantsField = document.getElementById('nb_participants');
const beneficiaireContainer = document.getElementById('id_beneficiaire')?.closest('.col-md-6');
const beneficiaireField = document.getElementById('id_beneficiaire');
if (!groupeFields || !nbParticipantsField || !beneficiaireContainer || !beneficiaireField) return;
if (type === 'groupe') {
// Afficher les champs de groupe
groupeFields.style.display = '';
nbParticipantsField.required = true;
beneficiaireContainer.style.display = 'none';
beneficiaireField.required = false;
// Remplir les champs de groupe
if (extendedProps.nb_participants || event.nb_participants) {
safeSetValue('nb_participants', extendedProps.nb_participants || event.nb_participants);
}
if (extendedProps.nb_hommes || event.nb_hommes) {
safeSetValue('nb_hommes', extendedProps.nb_hommes || event.nb_hommes);
}
if (extendedProps.nb_femmes || event.nb_femmes) {
safeSetValue('nb_femmes', extendedProps.nb_femmes || event.nb_femmes);
}
} else {
// Masquer les champs de groupe
groupeFields.style.display = 'none';
nbParticipantsField.required = false;
beneficiaireContainer.style.display = '';
beneficiaireField.required = true;
}
}
/**
* Réinitialise le formulaire
*/
export function resetForm() {
const form = document.getElementById('eventForm');
if (form) {
form.reset();
}
clearFormErrors();
// Réinitialiser les champs conditionnels
const groupeFields = document.getElementById('groupeFields');
const nbParticipantsField = document.getElementById('nb_participants');
const beneficiaireContainer = document.getElementById('id_beneficiaire')?.closest('.col-md-6');
const beneficiaireField = document.getElementById('id_beneficiaire');
if (groupeFields) groupeFields.style.display = 'none';
if (nbParticipantsField) nbParticipantsField.required = false;
if (beneficiaireContainer) beneficiaireContainer.style.display = '';
if (beneficiaireField) beneficiaireField.required = true;
// Réinitialiser le bloc traducteur
const tradContainer = document.getElementById('traducteur-select-container');
const tradSelect = document.getElementById('id_traducteur');
const useExistingCheckbox = document.getElementById('use_existing_traducteur');
if (tradContainer) tradContainer.classList.add('d-none');
if (tradSelect) {
tradSelect.removeAttribute('required');
tradSelect.value = '';
if (window.jQuery && jQuery(tradSelect).hasClass('select2-hidden-accessible')) {
jQuery(tradSelect).val('').trigger('change');
}
}
if (useExistingCheckbox) useExistingCheckbox.checked = false;
}
/**
* Affiche les erreurs de formulaire
* @param {Object} errors - Objet des erreurs {field: message}
*/
export function showFormErrors(errors) {
clearFormErrors();
let hasGeneral = false;
for (const [field, message] of Object.entries(errors)) {
const input = document.getElementById(field);
if (input) {
input.classList.add('is-invalid');
let feedback = input.parentNode.querySelector('.invalid-feedback');
if (!feedback) {
feedback = document.createElement('div');
feedback.className = 'invalid-feedback';
input.parentNode.appendChild(feedback);
}
feedback.textContent = message;
} else {
hasGeneral = true;
}
}
// Message général si erreur non liée à un champ
if (hasGeneral) {
const errorBox = document.getElementById('eventFormErrors');
if (errorBox) {
errorBox.textContent = errors._general || 'Erreur lors de la validation du formulaire';
errorBox.classList.remove('d-none');
}
}
}
/**
* Efface les erreurs de formulaire
*/
export function clearFormErrors() {
const form = document.getElementById('eventForm');
if (!form) return;
form.querySelectorAll('.is-invalid').forEach(el => el.classList.remove('is-invalid'));
form.querySelectorAll('.invalid-feedback').forEach(el => el.remove());
const errorBox = document.getElementById('eventFormErrors');
if (errorBox) {
errorBox.textContent = '';
errorBox.classList.add('d-none');
}
}
/**
* Validation du formulaire
* @param {Object} data - Données du formulaire
* @param {string} mode - Mode ('create' ou 'edit')
* @returns {Object} - Objet des erreurs ou {}
*/
function validateForm(data, mode) {
const errors = {};
// Champs requis de base
const requiredFields = ['date_rdv', 'heure_rdv', 'type', 'langue', 'id_intervenant', 'id_local'];
// Ajouter le bénéficiaire seulement si ce n'est pas un RDV de groupe
if (data.type !== 'groupe') {
requiredFields.push('id_beneficiaire');
}
requiredFields.forEach(field => {
const value = data[field];
if (!value || value === '' || value === '0' || value === 'null') {
errors[field] = 'Le champ est requis';
}
});
// Champs conditionnels pour groupe
if (data.type === 'groupe') {
if (!data.nb_participants || parseInt(data.nb_participants) < 1) {
errors['nb_participants'] = 'Nombre de participants requis (>0)';
}
}
// Validation: empêcher la création d'événements à une date passée
if (mode === 'create' && data.date_rdv) {
const today = new Date();
today.setHours(0, 0, 0, 0);
const eventDate = new Date(data.date_rdv + 'T00:00:00');
eventDate.setHours(0, 0, 0, 0);
if (eventDate < today) {
errors['date_rdv'] = 'La date de rendez-vous ne peut pas être dans le passé';
}
}
// Cohérence des dates/heures
if (data.date_fin && data.date_rdv) {
if (data.date_fin < data.date_rdv) {
errors['date_fin'] = 'La date de fin doit être après ou égale à la date de début';
} else if (data.date_fin === data.date_rdv && data.heure_fin && data.heure_rdv) {
if (data.heure_fin <= data.heure_rdv) {
errors['heure_fin'] = 'L\'heure de fin doit être après l\'heure de début';
}
}
}
return errors;
}
/**
* Prépare les données du formulaire
* @param {Object} data - Données brutes du formulaire
* @returns {Object} - Données préparées
*/
function prepareFormData(data) {
// Gérer la logique "traducteur existant"
const useExisting = data.use_existing_traducteur === '1' || data.use_existing_traducteur === 'on' || data.use_existing_traducteur === 'true';
const traducteurId = parseInt(data.id_traducteur, 10);
const hasValidTraducteurId = !isNaN(traducteurId) && traducteurId > 0;
if (useExisting && hasValidTraducteurId) {
// Utiliser id_traducteur et ignorer nom_traducteur
delete data.nom_traducteur;
data.id_traducteur = traducteurId.toString();
} else {
// Utiliser nom_traducteur et mettre id_traducteur à null
data.id_traducteur = null;
if (!data.nom_traducteur || data.nom_traducteur.trim() === '') {
delete data.nom_traducteur;
}
}
return data;
}
/**
* Gestion de la soumission du formulaire
* @param {string} mode - Mode ('create' ou 'edit')
* @param {number|null} eventId - ID de l'événement (si édition)
* @param {Function} onSuccess - Callback de succès
*/
export async function handleEventFormSubmit(mode, eventId = null, onSuccess = null) {
clearFormErrors();
const form = document.getElementById('eventForm');
if (!form) {
notifyError('Formulaire non trouvé');
return;
}
const formData = new FormData(form);
let data = Object.fromEntries(formData.entries());
// Validation côté client
const errors = validateForm(data, mode);
if (Object.keys(errors).length > 0) {
showFormErrors(errors);
notifyError('Veuillez corriger les erreurs du formulaire');
return;
}
// Préparer les données
data = prepareFormData(data);
// Appel API
try {
if (mode === 'create') {
await createEvent(data);
notifySuccess('Événement créé avec succès');
} else if (mode === 'edit' && eventId) {
await updateEvent(eventId, data);
notifySuccess('Événement modifié avec succès');
}
if (onSuccess) onSuccess();
} catch (e) {
// Gestion des erreurs serveur
const serverErrors = {};
if (e && e.message) {
if (e.details && e.details.field) {
serverErrors[e.details.field] = e.details.message;
} else {
serverErrors._general = e.message;
}
} else {
serverErrors._general = 'Erreur serveur inconnue';
}
showFormErrors(serverErrors);
notifyError(e.message || 'Erreur lors de la sauvegarde');
}
}

View File

@ -0,0 +1,512 @@
// 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 {
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;
}

File diff suppressed because it is too large Load Diff