// Module principal de gestion de la modale d'événement // Version refactorisée utilisant des modules séparés import { createEvent, updateEvent, deleteEvent, changeEventStatus, getFilters, apiFetch } from './agenda-api.js'; import { notifyError, notifySuccess } from './agenda-notifications.js'; import { initializeEntityCreators } from './agenda-entity-creator.js'; // Modules refactorisés import { clearDomCache, clearFormFields } from './agenda-modal-dom.js'; import { initializeModalButtons } from './agenda-modal-buttons.js'; import { initializeSelect2, populateSelects, filterTraducteursByLangue, getIsUpdatingSelects, clearDisponibilitesCache } from './agenda-modal-select.js'; import { fillFormWithDate, fillFormWithEvent, resetForm, showFormErrors, clearFormErrors, handleEventFormSubmit } from './agenda-modal-forms.js'; import { fillViewBlock, updateModalDisplay, checkIfEventIsPast } from './agenda-modal-display.js'; // Variables globales let currentMode = 'view'; // 'view', 'edit', 'create' let currentEventData = null; let preservedMode = null; let preservedEventData = null; /** * Préserve les données de la modale avant de l'ouvrir une sous-modale */ export function preserveModalData() { preservedMode = currentMode; preservedEventData = currentEventData; } /** * Restaure les données de la modale après fermeture d'une sous-modale * @returns {boolean} - True si des données ont été restaurées */ export function restoreModalData() { if (preservedMode !== null && preservedEventData !== null) { currentMode = preservedMode; currentEventData = preservedEventData; preservedMode = null; preservedEventData = null; return true; } return false; } /** * Ouvre une sous-modale en gérant automatiquement la fermeture/réouverture de la modale principale * @param {string} subModalId - ID de la sous-modale à ouvrir * @param {Function} onBeforeOpen - Callback optionnel appelé avant l'ouverture (pour préparer les données) * @param {Function} onAfterClose - Callback optionnel appelé après la fermeture de la sous-modale * @param {number} delay - Délai avant ouverture de la sous-modale (ms, défaut: 300) */ export function openSubModal(subModalId, onBeforeOpen = null, onAfterClose = null, delay = 300) { const subModal = document.getElementById(subModalId); if (!subModal) { console.error(`Sous-modale non trouvée: ${subModalId}`); return; } // Préserver les données de la modale principale preserveModalData(); // Exécuter le callback de préparation si fourni if (onBeforeOpen && typeof onBeforeOpen === 'function') { try { onBeforeOpen(subModal); } catch (error) { console.error('Erreur dans onBeforeOpen:', error); } } // Fermer la modale principale const eventModal = document.getElementById('eventModal'); if (eventModal) { const eventBsModal = bootstrap.Modal.getInstance(eventModal); if (eventBsModal) { eventBsModal.hide(); } } // Attendre que la modale principale se ferme setTimeout(() => { // Ouvrir la sous-modale if (window.bootstrap && window.bootstrap.Modal) { const bsModal = new window.bootstrap.Modal(subModal); bsModal.show(); // Gérer la réouverture de la modale principale à la fermeture subModal.addEventListener('hidden.bs.modal', function handleSubModalClose() { // Exécuter le callback de fermeture si fourni if (onAfterClose && typeof onAfterClose === 'function') { try { onAfterClose(subModal); } catch (error) { console.error('Erreur dans onAfterClose:', error); } } // Rouvrir la modale principale 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 } }, delay); } /** * Charge les incidents d'un événement * @param {number} eventId - ID de l'événement * @returns {Promise} - Tableau des incidents */ async function loadEventIncidents(eventId) { try { const incidents = await apiFetch(`events/${eventId}/incidents`); return incidents || []; } catch (error) { console.error('Erreur lors du chargement des incidents:', error); return []; } } /** * Ouvre la modal d'incidents en mode affichage * @param {number} eventId - ID de l'événement */ async function openIncidentsViewModal(eventId) { const incidentModal = document.getElementById('declarationIncidentModal'); const modalTitle = document.getElementById('declarationIncidentModalLabel'); const viewSection = document.getElementById('incidentsViewSection'); const formSection = document.getElementById('incidentsFormSection'); const viewFooter = document.getElementById('incidentsViewFooter'); const formFooter = document.getElementById('incidentsFormFooter'); const listContainer = document.getElementById('incidentsListContainer'); if (!incidentModal || !viewSection || !formSection || !listContainer) { notifyError('Modal d\'incidents introuvable'); return; } try { // Charger les incidents const incidents = await loadEventIncidents(eventId); if (incidents.length === 0) { listContainer.innerHTML = '

Aucun incident signalé pour cet événement.

'; } else { // Afficher les incidents let html = '
'; incidents.forEach((incident, index) => { const createdAt = incident.created_at ? new Date(incident.created_at).toLocaleDateString('fr-FR') : 'Date inconnue'; html += `
Incident #${index + 1}
${createdAt}
${incident.resume_incident ? `

Résumé : ${incident.resume_incident}

` : ''} ${incident.commentaire_incident ? `

${incident.commentaire_incident}

` : ''}
`; }); html += '
'; listContainer.innerHTML = html; } // Changer le titre de la modal if (modalTitle) { modalTitle.innerHTML = 'Détail des incidents'; } // Afficher la section de vue, masquer le formulaire viewSection.style.display = 'block'; formSection.style.display = 'none'; viewFooter.style.display = 'block'; formFooter.style.display = 'none'; // Ouvrir la modal const bsModal = new bootstrap.Modal(incidentModal); bsModal.show(); // Réinitialiser au mode formulaire quand la modal se ferme incidentModal.addEventListener('hidden.bs.modal', function resetModalMode() { viewSection.style.display = 'none'; formSection.style.display = 'block'; viewFooter.style.display = 'none'; formFooter.style.display = 'block'; if (modalTitle) { modalTitle.innerHTML = 'Signaler un incident'; } // Retirer l'écouteur pour éviter les duplications incidentModal.removeEventListener('hidden.bs.modal', resetModalMode); }); } catch (error) { console.error('Erreur lors de l\'ouverture de la modal d\'incidents:', error); notifyError('Erreur lors du chargement des incidents'); } } /** * Vérifie si l'événement a des incidents et affiche le bouton en conséquence * @param {number} eventId - ID de l'événement */ export async function checkAndDisplayIncidentsButton(eventId) { const viewIncidentsBtn = document.getElementById('viewIncidentsBtn'); if (!viewIncidentsBtn) { return; } try { const incidents = await loadEventIncidents(eventId); if (incidents.length > 0) { viewIncidentsBtn.style.display = 'inline-block'; // Mettre à jour le texte du bouton avec le nombre const icon = viewIncidentsBtn.querySelector('i'); const iconHtml = icon ? icon.outerHTML : ''; viewIncidentsBtn.innerHTML = `${iconHtml}Détail incident(s) (${incidents.length})`; } else { viewIncidentsBtn.style.display = 'none'; } } catch (error) { console.error('Erreur lors de la vérification des incidents:', error); viewIncidentsBtn.style.display = 'none'; } } /** * Ouvre le modal d'événement * @param {string} mode - Mode ('view', 'edit', 'create') * @param {Object} eventData - Données de l'événement */ export function openModal(mode, eventData = null) { // Vérifier s'il y a des données préservées à restaurer if (restoreModalData()) { mode = currentMode; eventData = currentEventData; } else { if (eventData && !mode) { mode = 'view'; } currentMode = mode; currentEventData = eventData; } const modal = document.getElementById('eventModal'); if (!modal) { console.error('❌ Élément modal non trouvé'); return; } // Obtenir ou créer l'instance Bootstrap du modal let bsModal = window.bootstrap && window.bootstrap.Modal ? window.bootstrap.Modal.getInstance(modal) : null; if (!bsModal && window.bootstrap && window.bootstrap.Modal) { bsModal = new window.bootstrap.Modal(modal); } // Initialiser les composants initializeEntityCreators(); initializeSelect2(); initializeTimeComfort(); initializeLangueFilter(); // Initialiser les boutons (une seule fois) initializeModalButtons({ getCurrentEventData: () => currentEventData, getCurrentMode: () => currentMode, setCurrentMode: (mode) => { currentMode = mode; }, openSubModal: openSubModal, openCheckPresenceModal: openCheckPresenceModal, openIncidentsViewModal: openIncidentsViewModal, enableDateSynchronization: enableDateSynchronization, disableDateSynchronization: disableDateSynchronization, onDeleted: () => disableDateSynchronization(), onStatusChanged: () => disableDateSynchronization() }); // Déterminer les permissions const userCanEdit = window.crviPermissions && window.crviPermissions.can_edit; const userCanCreate = window.crviPermissions && window.crviPermissions.can_create; const userCanDelete = window.crviPermissions && window.crviPermissions.can_delete; // Vérifier si l'événement est passé const isEventPast = checkIfEventIsPast(eventData); // Ajuster le mode selon les permissions if (mode === 'edit' && !userCanEdit) { mode = 'view'; currentMode = 'view'; } if (mode === 'edit' && isEventPast) { console.warn('Impossible de modifier un événement passé'); notifyError('Impossible de modifier un événement passé'); mode = 'view'; currentMode = 'view'; } if (mode === 'create' && !userCanCreate) { console.warn('Utilisateur non autorisé à créer des événements'); return; } // Mettre à jour l'affichage updateModalDisplay(currentMode, userCanEdit, userCanDelete); // Remplir les données selon le mode if (mode === 'view') { fillViewBlock(eventData); clearFormErrors(); } else if (mode === 'edit') { fillFormWithEvent(eventData, filterTraducteursByLangue); populateSelects(eventData); clearFormErrors(); } else if (mode === 'create') { if (eventData && eventData.startStr) { fillFormWithDate(eventData); } else { fillFormWithEvent(eventData, filterTraducteursByLangue); } populateSelects(eventData); clearFormErrors(); enableDateSynchronization(); preventPastDates(); } // Nettoyer les champs lors de la fermeture modal.addEventListener('hidden.bs.modal', function() { if (preservedMode === null && preservedEventData === null) { clearModalFields(); clearDomCache(); clearDisponibilitesCache(); currentMode = 'view'; currentEventData = null; } disableDateSynchronization(); }, { once: true }); // Afficher le modal bsModal.show(); } /** * Ferme le modal de force (pour gérer les bugs de Bootstrap) */ function forceCloseModal() { const modal = document.getElementById('eventModal'); if (modal) { const bsModal = bootstrap.Modal.getInstance(modal); if (bsModal) { bsModal.hide(); } } // Nettoyer manuellement le backdrop et les classes setTimeout(() => { const backdrops = document.querySelectorAll('.modal-backdrop'); backdrops.forEach(backdrop => backdrop.remove()); document.body.classList.remove('modal-open'); document.body.style.overflow = ''; document.body.style.paddingRight = ''; }, 300); } /** * Nettoie les champs du modal */ function clearModalFields() { const textFields = [ 'date_rdv', 'heure_rdv', 'date_fin', 'heure_fin', 'commentaire', 'nb_participants', 'nb_hommes', 'nb_femmes' ]; const selectFields = [ 'type', 'langue', 'id_beneficiaire', 'id_intervenant', 'id_traducteur', 'id_local', 'id_departement', 'id_type_intervention' ]; clearFormFields(textFields, selectFields); clearFormErrors(); // Réinitialiser les champs conditionnels const typeField = document.getElementById('type'); if (typeField) { typeField.dispatchEvent(new Event('change')); } } // ========== Fonctions de synchronisation des dates/heures ========== 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; } function syncHeureFin() { const heureRdvInput = document.getElementById('heure_rdv'); const heureFinInput = document.getElementById('heure_fin'); const typeInput = document.getElementById('type'); if (!heureRdvInput || !heureFinInput || !typeInput) return; if (typeInput.value !== 'individuel') return; const heureDebut = heureRdvInput.value; if (!heureDebut) return; const heureFinStr = calculateHeureFin(heureDebut); heureFinInput.value = heureFinStr; } function syncDateFin() { const dateRdvInput = document.getElementById('date_rdv'); const dateFinInput = document.getElementById('date_fin'); if (dateRdvInput && dateFinInput && dateRdvInput.value) { dateFinInput.value = dateRdvInput.value; } } function enableDateSynchronization() { const dateRdvInput = document.getElementById('date_rdv'); const heureRdvInput = document.getElementById('heure_rdv'); const typeInput = document.getElementById('type'); if (dateRdvInput) { dateRdvInput.removeEventListener('change', syncDateFin); dateRdvInput.addEventListener('change', syncDateFin); } if (heureRdvInput && typeInput) { heureRdvInput.removeEventListener('change', syncHeureFin); heureRdvInput.addEventListener('change', syncHeureFin); } // Sélectionner 'individuel' par défaut en création if (typeInput && currentMode === 'create' && !typeInput.value) { typeInput.value = 'individuel'; typeInput.dispatchEvent(new Event('change')); } } function disableDateSynchronization() { const dateRdvInput = document.getElementById('date_rdv'); const heureRdvInput = document.getElementById('heure_rdv'); if (dateRdvInput) { dateRdvInput.removeEventListener('change', syncDateFin); } if (heureRdvInput) { heureRdvInput.removeEventListener('change', syncHeureFin); } } function preventPastDates() { const dateRdvInput = document.getElementById('date_rdv'); const dateFinInput = document.getElementById('date_fin'); if (dateRdvInput) { const today = new Date().toISOString().split('T')[0]; dateRdvInput.setAttribute('min', today); dateRdvInput.addEventListener('change', function() { const selectedDate = this.value; if (dateFinInput && selectedDate) { dateFinInput.setAttribute('min', selectedDate); if (dateFinInput.value && dateFinInput.value < selectedDate) { dateFinInput.value = selectedDate; } } }); } } // ========== Fonctions pour les fonctionnalités spécifiques ========== function initializeTimeComfort() { // Logique de confort pour les sélecteurs d'heure // (inchangée de l'original) } function initializeLangueFilter() { // Initialiser le filtre de langue const langueSelect = document.getElementById('langue'); if (langueSelect) { langueSelect.addEventListener('change', filterTraducteursByLangue); } } /** * Ouvre le modal de validation des présences pour un événement de groupe * @param {Object} eventData - Données de l'événement */ export function openCheckPresenceModal(eventData) { // Logique pour ouvrir le modal de validation de présences // (à implémenter selon besoin) console.log('Ouverture du modal de validation des présences pour:', eventData); } // ========== Initialisation des event listeners globaux ========== // Rafraîchir les selects si date/heure/type change ['date_rdv', 'heure_rdv', 'type'].forEach(id => { const element = document.getElementById(id); if (element) { element.addEventListener('change', () => { if (getIsUpdatingSelects()) return; if (currentMode === 'edit' || currentMode === 'create') { populateSelects(currentEventData); } }); } }); // Réexporter les fonctions nécessaires export { fillFormWithDate, fillFormWithEvent, resetForm, showFormErrors, clearFormErrors, handleEventFormSubmit };