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