Crvi/assets/js/modules/agenda-modal.js
2026-01-20 21:17:45 +01:00

537 lines
20 KiB
JavaScript

// 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, initializeBeneficiaireListeRougeAlert } 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<Array>} - 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
* Utilise la fonction générique openSubModal pour gérer la fermeture/réouverture de la modale principale
* @param {number} eventId - ID de l'événement
*/
async function openIncidentsViewModal(eventId) {
// Utiliser openSubModal pour gérer automatiquement la fermeture/réouverture
openSubModal(
'declarationIncidentModal',
// Callback avant ouverture : charger et afficher les incidents
async (subModal) => {
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 (!viewSection || !formSection || !listContainer) {
notifyError('Sections de la modal d\'incidents introuvables');
return;
}
try {
// Afficher un spinner de chargement
listContainer.innerHTML = `
<div class="text-center py-4">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Chargement...</span>
</div>
</div>
`;
// Charger les incidents
const incidents = await loadEventIncidents(eventId);
if (incidents.length === 0) {
listContainer.innerHTML = '<p class="text-muted">Aucun incident signalé pour cet événement.</p>';
} else {
// Afficher les incidents
let html = '<div class="list-group">';
incidents.forEach((incident, index) => {
const createdAt = incident.created_at ? new Date(incident.created_at).toLocaleDateString('fr-FR') : 'Date inconnue';
html += `
<div class="list-group-item">
<div class="d-flex w-100 justify-content-between">
<h6 class="mb-1">Incident #${index + 1}</h6>
<small class="text-muted">${createdAt}</small>
</div>
${incident.resume_incident ? `<p class="mb-1"><strong>Résumé :</strong> ${incident.resume_incident}</p>` : ''}
${incident.commentaire_incident ? `<p class="mb-0 text-muted">${incident.commentaire_incident}</p>` : ''}
</div>
`;
});
html += '</div>';
listContainer.innerHTML = html;
}
// Changer le titre de la modal
if (modalTitle) {
modalTitle.innerHTML = '<i class="fas fa-eye me-2"></i>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';
} catch (error) {
console.error('Erreur lors du chargement des incidents:', error);
listContainer.innerHTML = '<p class="text-danger">Erreur lors du chargement des incidents</p>';
notifyError('Erreur lors du chargement des incidents');
}
},
// Callback après fermeture : réinitialiser au mode formulaire
(subModal) => {
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');
// Réinitialiser au mode formulaire
if (viewSection) viewSection.style.display = 'none';
if (formSection) formSection.style.display = 'block';
if (viewFooter) viewFooter.style.display = 'none';
if (formFooter) formFooter.style.display = 'block';
if (modalTitle) {
modalTitle.innerHTML = '<i class="fas fa-exclamation-triangle me-2"></i>Signaler un incident';
}
if (listContainer) {
listContainer.innerHTML = '';
}
}
);
}
/**
* 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 : '<i class="fas fa-eye me-1"></i>';
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();
initializeBeneficiaireListeRougeAlert();
// 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, currentEventData);
// 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 };