modularisation agenda modal
This commit is contained in:
parent
ec0d7da5f9
commit
88367d0530
@ -1,7 +1,8 @@
|
||||
// Module ES6 pour la création d'entités depuis le modal d'événement
|
||||
import { apiFetch } from './agenda-api.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
|
||||
const ENTITY_CONFIG = {
|
||||
@ -97,49 +98,18 @@ function openCreateEntityModal(entityType) {
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = document.getElementById(config.modalId);
|
||||
if (!modal) {
|
||||
console.error(`Modal non trouvé: ${config.modalId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Réinitialiser le formulaire
|
||||
const form = document.getElementById(config.formId);
|
||||
if (form) {
|
||||
form.reset();
|
||||
}
|
||||
|
||||
// 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();
|
||||
// Utiliser la fonction générique pour ouvrir la sous-modale
|
||||
openSubModal(
|
||||
config.modalId,
|
||||
// Callback avant ouverture : réinitialiser le formulaire
|
||||
(subModal) => {
|
||||
const form = document.getElementById(config.formId);
|
||||
if (form) {
|
||||
form.reset();
|
||||
}
|
||||
// Les selects sont maintenant initialisés automatiquement par jQuery('.select2').select2()
|
||||
}
|
||||
}
|
||||
|
||||
// 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()
|
||||
|
||||
269
assets/js/modules/agenda-modal-buttons.js
Normal file
269
assets/js/modules/agenda-modal-buttons.js
Normal 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);
|
||||
}
|
||||
}
|
||||
289
assets/js/modules/agenda-modal-display.js
Normal file
289
assets/js/modules/agenda-modal-display.js
Normal 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;
|
||||
}
|
||||
190
assets/js/modules/agenda-modal-dom.js
Normal file
190
assets/js/modules/agenda-modal-dom.js
Normal 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;
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
477
assets/js/modules/agenda-modal-forms.js
Normal file
477
assets/js/modules/agenda-modal-forms.js
Normal 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');
|
||||
}
|
||||
}
|
||||
512
assets/js/modules/agenda-modal-select.js
Normal file
512
assets/js/modules/agenda-modal-select.js
Normal 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
Loading…
Reference in New Issue
Block a user