diff --git a/assets/js/modules/agenda-event-mapper.js b/assets/js/modules/agenda-event-mapper.js new file mode 100644 index 0000000..0ff69c6 --- /dev/null +++ b/assets/js/modules/agenda-event-mapper.js @@ -0,0 +1,441 @@ +// Module de mapping des événements pour FullCalendar +// Contient toute la logique de transformation des données API vers le format FullCalendar + +/** + * Calcule la luminosité d'une couleur hexadécimale + * @param {string} hexColor - Couleur au format #RRGGBB + * @returns {number} - Luminosité entre 0 et 1 + */ +function getLuminance(hexColor) { + // Vérifier que hexColor n'est pas null ou undefined + if (!hexColor || typeof hexColor !== 'string') { + console.warn('⚠️ [getLuminance] Valeur hexColor invalide:', hexColor); + return 0.5; // Retourner une valeur moyenne par défaut + } + + // Convertir hex en RGB + const hex = hexColor.replace('#', ''); + const r = parseInt(hex.substr(0, 2), 16); + const g = parseInt(hex.substr(2, 2), 16); + const b = parseInt(hex.substr(4, 2), 16); + + // Calculer la luminosité relative selon WCAG + return (0.299 * r + 0.587 * g + 0.114 * b) / 255; +} + +/** + * Détermine la couleur de texte optimale selon le contraste + * @param {string} backgroundColor - Couleur de fond au format #RRGGBB + * @returns {string} - Couleur de texte (#000000 ou #ffffff) + */ +function getTextColor(backgroundColor) { + // Vérifier que backgroundColor n'est pas null ou undefined + if (!backgroundColor || typeof backgroundColor !== 'string') { + console.warn('⚠️ [getTextColor] Valeur backgroundColor invalide:', backgroundColor); + return '#000000'; // Retourner noir par défaut + } + + const luminance = getLuminance(backgroundColor); + return luminance > 0.5 ? '#000000' : '#ffffff'; +} + +/** + * Mappe un événement API vers le format FullCalendar + * @param {Object} ev - Événement depuis l'API + * @returns {Object} - Événement au format FullCalendar + */ +export function mapEventToFullCalendar(ev) { + // Hiérarchie de détermination des couleurs : + // 1) Pour RDV assignés (individuel/groupe) avec type d'intervention : couleur du type d'intervention + // 2) Pour RDV assignés (individuel/groupe) sans type d'intervention : orange (par défaut) + // 3) Pour permanences : couleur selon le type (assignée, non attribuée, non disponible) + + let backgroundColor = null; + let textColor = null; + + // Vérifier si l'événement est assigné (a un intervenant et un local/beneficiaire) + const isEventAssigned = ev.id_intervenant && (ev.id_local || ev.id_beneficiaire); + + // Pour les RDV (individuel/groupe) assignés + if (ev.type && ev.type !== 'permanence' && isEventAssigned) { + // Vérifier si l'événement a un type d'intervention défini + let typeInterventionId = null; + if (ev.id_type_intervention) { + typeInterventionId = parseInt(ev.id_type_intervention); + } else if (ev.type_intervention && ev.type_intervention.id) { + typeInterventionId = parseInt(ev.type_intervention.id); + } + + // Si l'événement a un type d'intervention, utiliser sa couleur depuis crviAjax + if (typeInterventionId && !isNaN(typeInterventionId) && window.crviAjax && window.crviAjax.couleurs_types_intervention) { + const couleurTypeIntervention = window.crviAjax.couleurs_types_intervention[typeInterventionId]; + if (couleurTypeIntervention) { + backgroundColor = couleurTypeIntervention; + textColor = getTextColor(couleurTypeIntervention); + console.log('🎨 [COULEUR] RDV assigné avec type d\'intervention:', { + eventId: ev.id, + type: ev.type, + typeInterventionId: typeInterventionId, + backgroundColor: backgroundColor, + textColor: textColor, + source: 'type_intervention' + }); + } + } + + // Si pas de couleur définie (pas de type d'intervention), garder orange par défaut + if (!backgroundColor) { + backgroundColor = '#ff9800'; // Orange pour les événements assignés sans type d'intervention + textColor = getTextColor(backgroundColor); + console.log('🎨 [COULEUR] RDV assigné sans type d\'intervention (défaut orange):', { + eventId: ev.id, + type: ev.type, + backgroundColor: backgroundColor, + textColor: textColor, + source: 'defaut_assigné' + }); + } + } + + // Pour les RDV non assignés : couleur selon type_de_local (bureau ou salle) + if (!backgroundColor && ev.type && ev.type !== 'permanence' && !isEventAssigned) { + // Vérifier si l'événement a un local avec un type_de_local + if (ev.local && ev.local.type_de_local) { + const typeLocal = ev.local.type_de_local.toLowerCase(); + if (window.crviACFData && window.crviACFData.couleurs_rdv) { + const couleurRdv = window.crviACFData.couleurs_rdv[typeLocal]; + if (couleurRdv) { + backgroundColor = couleurRdv; + textColor = getTextColor(couleurRdv); + console.log('🎨 [COULEUR] RDV non assigné selon type de local:', { + eventId: ev.id, + type: ev.type, + typeLocal: typeLocal, + backgroundColor: backgroundColor, + textColor: textColor, + source: 'type_local' + }); + } + } + } + } + + // Pour permanences (événements de type 'permanence') : logique simplifiée + // 1) Non attribuée : dispo ? couleur dispo : couleur indispo + // 2) Attribuée : a type intervention ? couleur type : couleur défaut + let isPermanenceDisabled = false; + if (!backgroundColor && ev.type === 'permanence') { + // Une permanence est assignée si elle a un bénéficiaire ET un local (assign = 1) + const isPermanenceAssigned = ev.assign === 1 || (ev.id_beneficiaire && ev.id_local); + + // Une permanence est non attribuée si elle n'est pas assignée (assign = 0) + const isPermanenceNonAttribuee = ev.assign === 0 && !ev.id_beneficiaire && !ev.id_local; + + // Vérifier si le bénéficiaire est en congé (indisponibilitee_ponctuelle) + let isPermanenceNonDisponible = false; + if (ev.beneficiaire && ev.beneficiaire.indisponibilitee_ponctuelle && Array.isArray(ev.beneficiaire.indisponibilitee_ponctuelle)) { + const eventDate = ev.date_rdv; // Format YYYY-MM-DD + const eventDateObj = new Date(eventDate + 'T00:00:00'); + + // Vérifier si la date de l'événement est dans une période d'indisponibilité + for (const indispo of ev.beneficiaire.indisponibilitee_ponctuelle) { + if (indispo.debut && indispo.fin) { + let debutDate, finDate; + + // Gérer le format d/m/Y (format ACF) + if (typeof indispo.debut === 'string' && indispo.debut.includes('/')) { + const debutParts = indispo.debut.split('/'); + const finParts = indispo.fin.split('/'); + + if (debutParts.length === 3 && finParts.length === 3) { + // Format d/m/Y + debutDate = new Date(parseInt(debutParts[2]), parseInt(debutParts[1]) - 1, parseInt(debutParts[0])); + finDate = new Date(parseInt(finParts[2]), parseInt(finParts[1]) - 1, parseInt(finParts[0])); + } + } else { + // Format YYYY-MM-DD ou timestamp + debutDate = new Date(indispo.debut); + finDate = new Date(indispo.fin); + } + + if (debutDate && finDate && !isNaN(debutDate.getTime()) && !isNaN(finDate.getTime())) { + // Ajuster pour inclure toute la journée + debutDate.setHours(0, 0, 0, 0); + finDate.setHours(23, 59, 59, 999); + + if (eventDateObj >= debutDate && eventDateObj <= finDate) { + isPermanenceNonDisponible = true; + isPermanenceDisabled = true; + break; + } + } + } + } + } + + // Vérifier si l'intervenant est indisponible (congés ou jour non disponible) + if (!isPermanenceNonDisponible && ev.id_intervenant && window.crviACFData && window.crviACFData.indisponibilites_intervenants) { + const intervenantId = parseInt(ev.id_intervenant); + const intervenantDispo = window.crviACFData.indisponibilites_intervenants[intervenantId]; + + if (intervenantDispo) { + const eventDate = ev.date_rdv; // Format YYYY-MM-DD + const eventDateObj = new Date(eventDate + 'T00:00:00'); + + // Vérifier les jours de disponibilité (0 = dimanche, 1 = lundi, etc.) + const dayOfWeek = eventDateObj.getDay(); + const dayNames = ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi']; + const dayName = dayNames[dayOfWeek]; + + // Si l'intervenant a des jours de disponibilité définis et que ce jour n'en fait pas partie + if (intervenantDispo.jours_dispo && Array.isArray(intervenantDispo.jours_dispo) && + intervenantDispo.jours_dispo.length > 0 && + !intervenantDispo.jours_dispo.includes(dayName)) { + console.log('🚫 [PERMANENCE] Intervenant non disponible ce jour:', { + intervenantId, + date: eventDate, + dayName, + joursDisponibles: intervenantDispo.jours_dispo + }); + isPermanenceNonDisponible = true; + isPermanenceDisabled = true; + } + + // Vérifier les congés/indisponibilités ponctuelles + if (!isPermanenceNonDisponible && intervenantDispo.conges && Array.isArray(intervenantDispo.conges)) { + for (const conge of intervenantDispo.conges) { + if (conge.debut && conge.fin) { + let debutDate, finDate; + + // Gérer le format d/m/Y (format ACF) + if (typeof conge.debut === 'string' && conge.debut.includes('/')) { + const debutParts = conge.debut.split('/'); + const finParts = conge.fin.split('/'); + + if (debutParts.length === 3 && finParts.length === 3) { + debutDate = new Date(parseInt(debutParts[2]), parseInt(debutParts[1]) - 1, parseInt(debutParts[0])); + finDate = new Date(parseInt(finParts[2]), parseInt(finParts[1]) - 1, parseInt(finParts[0])); + } + } else { + // Format YYYY-MM-DD ou timestamp + debutDate = new Date(conge.debut); + finDate = new Date(conge.fin); + } + + if (debutDate && finDate && !isNaN(debutDate.getTime()) && !isNaN(finDate.getTime())) { + debutDate.setHours(0, 0, 0, 0); + finDate.setHours(23, 59, 59, 999); + + if (eventDateObj >= debutDate && eventDateObj <= finDate) { + console.log('🚫 [PERMANENCE] Intervenant en congé:', { + intervenantId, + date: eventDate, + congeDebut: conge.debut, + congeFin: conge.fin, + congeType: conge.type + }); + isPermanenceNonDisponible = true; + isPermanenceDisabled = true; + break; + } + } + } + } + } + } + } + + // Vérifier aussi le statut si pas déjà déterminé + if (!isPermanenceNonDisponible && ev.statut && ['annule', 'non_tenu', 'absence'].includes(ev.statut)) { + isPermanenceNonDisponible = true; + } + + // LOGIQUE SIMPLIFIÉE + if (isPermanenceNonAttribuee) { + // 1) Non attribuée : dispo ? couleur dispo : couleur indispo + if (isPermanenceNonDisponible && window.crviAjax && window.crviAjax.couleur_permanence_non_disponible) { + backgroundColor = window.crviAjax.couleur_permanence_non_disponible; + console.log('🎨 [COULEUR] Permanence non attribuée - indisponible:', { + eventId: ev.id, + backgroundColor: backgroundColor, + source: 'permanence_non_attribuee_indispo' + }); + } else if (window.crviAjax && window.crviAjax.couleur_permanence_non_attribuee) { + backgroundColor = window.crviAjax.couleur_permanence_non_attribuee; + console.log('🎨 [COULEUR] Permanence non attribuée - disponible:', { + eventId: ev.id, + backgroundColor: backgroundColor, + source: 'permanence_non_attribuee_dispo' + }); + } + } else if (isPermanenceAssigned) { + // 2) Attribuée : a type intervention ? couleur type : couleur défaut + let typeInterventionId = null; + if (ev.id_type_intervention) { + typeInterventionId = parseInt(ev.id_type_intervention); + } else if (ev.type_intervention && ev.type_intervention.id) { + typeInterventionId = parseInt(ev.type_intervention.id); + } + + if (typeInterventionId && !isNaN(typeInterventionId) && window.crviAjax && window.crviAjax.couleurs_types_intervention) { + const couleurTypeIntervention = window.crviAjax.couleurs_types_intervention[typeInterventionId]; + if (couleurTypeIntervention) { + backgroundColor = couleurTypeIntervention; + console.log('🎨 [COULEUR] Permanence assignée - avec type intervention:', { + eventId: ev.id, + typeInterventionId: typeInterventionId, + backgroundColor: backgroundColor, + source: 'permanence_assignee_type' + }); + } + } + + // Si pas de type d'intervention, utiliser couleur défaut + if (!backgroundColor) { + backgroundColor = (window.crviACFData && window.crviACFData.couleurs_permanence && window.crviACFData.couleurs_permanence.permanence) + ? window.crviACFData.couleurs_permanence.permanence + : '#9e9e9e'; + console.log('🎨 [COULEUR] Permanence assignée - défaut:', { + eventId: ev.id, + backgroundColor: backgroundColor, + source: 'permanence_assignee_defaut' + }); + } + } + + // S'assurer que backgroundColor n'est pas null avant d'appeler getTextColor + if (backgroundColor) { + textColor = getTextColor(backgroundColor); + } else { + textColor = '#000000'; // Par défaut + } + } + + // Couleur par défaut si aucune condition n'est remplie + if (!backgroundColor) { + backgroundColor = '#6c757d'; // Gris par défaut + textColor = getTextColor(backgroundColor); + console.log('🎨 [COULEUR] Couleur par défaut (gris):', { + eventId: ev.id, + type: ev.type, + backgroundColor: backgroundColor, + textColor: textColor, + source: 'defaut_general' + }); + } + + // Log final de la couleur appliquée + console.log('🎨 [COULEUR FINALE] Événement:', { + eventId: ev.id, + title: ev.type || 'Sans type', + backgroundColor: backgroundColor, + borderColor: backgroundColor, + textColor: textColor, + isAssigned: isEventAssigned, + type: ev.type + }); + + // Générer un titre plus explicite + let title = ev.type || 'Sans type'; + + // Gestion spéciale pour les permanences + if (ev.type === 'permanence') { + if (ev.intervenant) { + const intervenant = ev.intervenant; + const nomComplet = `${intervenant.prenom || ''} ${intervenant.nom || ''}`.trim(); + title = 'p. ' + (nomComplet || 'Intervenant'); + } else { + title = 'p. Intervenant'; + } + } else { + // Utiliser le nom de l'intervenant comme titre principal pour les autres événements + if (ev.intervenant) { + const intervenant = ev.intervenant; + const nomComplet = `${intervenant.prenom || ''} ${intervenant.nom || ''}`.trim(); + if (nomComplet) { + title = nomComplet; + } + } + + // Ajouter le type d'intervention principal si disponible + if (ev.intervenant && ev.intervenant.types_intervention_noms && ev.intervenant.types_intervention_noms.length > 0) { + const primaryType = ev.intervenant.types_intervention_noms[0]; // Premier type d'intervention + title += ` - ${primaryType}`; + } + + // Ajouter le commentaire si disponible + if (ev.commentaire) { + title += ' - ' + ev.commentaire; + } + } + + // S'assurer que les couleurs sont toujours définies + if (!backgroundColor || typeof backgroundColor !== 'string') { + console.warn('⚠️ [Mapping] backgroundColor invalide pour l\'événement:', ev.id, backgroundColor); + backgroundColor = '#6c757d'; // Gris par défaut + } + if (!textColor || typeof textColor !== 'string') { + textColor = getTextColor(backgroundColor); + } + + // Vérifier si l'événement est passé + const eventStartDate = new Date(ev.date_rdv + 'T' + ev.heure_rdv); + const now = new Date(); + const isEventPast = eventStartDate < now; + + // Un événement est éditable seulement si : + // 1. Il n'est pas désactivé (permanence) + // 2. Il n'est pas passé + const isEditable = !isPermanenceDisabled && !isEventPast; + + return { + id: ev.id, + title: title, + start: ev.date_rdv + 'T' + ev.heure_rdv, + end: ev.date_fin + 'T' + ev.heure_fin, + backgroundColor: backgroundColor, + borderColor: backgroundColor, + textColor: textColor, + editable: isEditable, // Désactiver l'édition si la permanence est désactivée OU si l'événement est passé + durationEditable: isEditable, // Désactiver le redimensionnement si la permanence est désactivée OU si l'événement est passé + startEditable: isEditable, // Désactiver le déplacement si la permanence est désactivée OU si l'événement est passé + classNames: isPermanenceDisabled ? ['permanence-disabled'] : (isEventPast ? ['event-past'] : []), + extendedProps: { + // Données de base + type: ev.type, + commentaire: ev.commentaire, + date_rdv: ev.date_rdv, + heure_rdv: ev.heure_rdv, + date_fin: ev.date_fin, + heure_fin: ev.heure_fin, + + // Relations avec les entités + id_beneficiaire: ev.id_beneficiaire, + id_intervenant: ev.id_intervenant, + id_traducteur: ev.id_traducteur, + id_local: ev.id_local, + id_departement: ev.id_departement, + id_type_intervention: ev.id_type_intervention || (ev.type_intervention && ev.type_intervention.id ? ev.type_intervention.id : null), + langue: ev.langue, + langues_disponibles: ev.langues_disponibles || null, + + // Données des entités (si disponibles) + beneficiaire: ev.beneficiaire, + intervenant: ev.intervenant, + traducteur: ev.traducteur, + local: ev.local, + + // Données spécifiques aux groupes + nb_participants: ev.nb_participants, + nb_hommes: ev.nb_hommes, + nb_femmes: ev.nb_femmes, + + // Autres données + statut: ev.statut, + assign: ev.assign || 0, + isDisabled: isPermanenceDisabled, + created_at: ev.created_at, + updated_at: ev.updated_at + } + }; +} diff --git a/assets/js/modules/agenda-filters.js b/assets/js/modules/agenda-filters.js index 881e2a6..7296801 100644 --- a/assets/js/modules/agenda-filters.js +++ b/assets/js/modules/agenda-filters.js @@ -1,46 +1,10 @@ // Module de gestion des filtres dynamiques pour l'agenda import { getEvents } from './agenda-api.js'; +import { mapEventToFullCalendar } from './agenda-event-mapper.js'; let currentFilters = {}; let calendarInstance = null; -/** - * Génère le titre d'un événement en utilisant les données enrichies - * @param {Object} event - Données de l'événement - * @returns {string} Titre de l'événement - */ -function getEventTitle(event) { - let title = ''; - - // Utiliser le nom du bénéficiaire si disponible - if (event.beneficiaire && event.beneficiaire.nom_complet) { - title = event.beneficiaire.nom_complet; - } else if (event.type === 'groupe') { - title = 'Groupe'; - } else { - title = event.type || 'Sans type'; - } - - // Ajouter le type d'intervention principal si disponible - if (event.intervenant && event.intervenant.types_intervention_noms && event.intervenant.types_intervention_noms.length > 0) { - const primaryType = event.intervenant.types_intervention_noms[0]; // Premier type d'intervention - title += ` - ${primaryType}`; - } - - // Ajouter le département si disponible (optionnel - décommenter si souhaité) - // if (event.intervenant && event.intervenant.departements_noms && event.intervenant.departements_noms.length > 0) { - // const departements = event.intervenant.departements_noms.join(', '); - // title += ` [${departements}]`; - // } - - // Ajouter le commentaire si disponible - if (event.commentaire) { - title += ' - ' + event.commentaire; - } - - return title; -} - /** * Initialise les filtres dynamiques * @param {Object} calendar - Instance FullCalendar @@ -67,37 +31,7 @@ export function initializeFilters(calendar) { } const apiEvents = await getEvents(params); - const events = apiEvents.map(ev => ({ - id: ev.id, - title: getEventTitle(ev), - start: ev.date_rdv + 'T' + ev.heure_rdv, - end: ev.date_fin + 'T' + ev.heure_fin, - extendedProps: { - type: ev.type, - commentaire: ev.commentaire, - date_rdv: ev.date_rdv, - heure_rdv: ev.heure_rdv, - date_fin: ev.date_fin, - heure_fin: ev.heure_fin, - // Garder les IDs pour compatibilité - id_beneficiaire: ev.id_beneficiaire, - id_intervenant: ev.id_intervenant, - id_traducteur: ev.id_traducteur, - id_local: ev.id_local, - langue: ev.langue, - // Nouvelles données enrichies - beneficiaire: ev.beneficiaire, - intervenant: ev.intervenant, - traducteur: ev.traducteur, - local: ev.local, - nb_participants: ev.nb_participants, - nb_hommes: ev.nb_hommes, - nb_femmes: ev.nb_femmes, - statut: ev.statut, - created_at: ev.created_at, - updated_at: ev.updated_at - } - })); + const events = apiEvents.map(ev => mapEventToFullCalendar(ev)); successCallback(events); } catch (e) { console.error('Erreur lors du chargement des événements:', e); diff --git a/assets/js/modules/agenda-fullcalendar.js b/assets/js/modules/agenda-fullcalendar.js index e149208..9913af2 100644 --- a/assets/js/modules/agenda-fullcalendar.js +++ b/assets/js/modules/agenda-fullcalendar.js @@ -3,6 +3,7 @@ import { openModal } from './agenda-modal.js'; import { getEvents, updateEvent, getEvent } from './agenda-api.js'; import { notifyError } from './agenda-notifications.js'; import { initializeFilters } from './agenda-filters.js'; +import { mapEventToFullCalendar } from './agenda-event-mapper.js'; import toastr from 'toastr'; import { Calendar } from '@fullcalendar/core'; import dayGridPlugin from '@fullcalendar/daygrid'; @@ -12,66 +13,6 @@ import interactionPlugin from '@fullcalendar/interaction'; import { apiFetch } from './agenda-api.js'; // Ajoutez d'autres plugins si besoin -/** - * Calcule la luminosité d'une couleur hexadécimale - * @param {string} hexColor - Couleur au format #RRGGBB - * @returns {number} - Luminosité entre 0 et 1 - */ -function getLuminance(hexColor) { - // Vérifier que hexColor n'est pas null ou undefined - if (!hexColor || typeof hexColor !== 'string') { - console.warn('⚠️ [getLuminance] Valeur hexColor invalide:', hexColor); - return 0.5; // Retourner une valeur moyenne par défaut - } - - // Convertir hex en RGB - const hex = hexColor.replace('#', ''); - const r = parseInt(hex.substr(0, 2), 16); - const g = parseInt(hex.substr(2, 2), 16); - const b = parseInt(hex.substr(4, 2), 16); - - // Calculer la luminosité relative selon WCAG - return (0.299 * r + 0.587 * g + 0.114 * b) / 255; -} - -/** - * Détermine la couleur de texte optimale selon le contraste - * @param {string} backgroundColor - Couleur de fond au format #RRGGBB - * @returns {string} - Couleur de texte (#000000 ou #ffffff) - */ -function getTextColor(backgroundColor) { - // Vérifier que backgroundColor n'est pas null ou undefined - if (!backgroundColor || typeof backgroundColor !== 'string') { - console.warn('⚠️ [getTextColor] Valeur backgroundColor invalide:', backgroundColor); - return '#000000'; // Retourner noir par défaut - } - - const luminance = getLuminance(backgroundColor); - return luminance > 0.5 ? '#000000' : '#ffffff'; -} - -/** - * Convertit une couleur hexadécimale en RGBA avec opacité - * @param {string} hex - Couleur au format #RRGGBB - * @param {number} alpha - Opacité entre 0 et 1 - * @returns {string} - Couleur au format rgba(r, g, b, a) - */ -function hexToRgba(hex, alpha) { - // Vérifier que hex n'est pas null ou undefined - if (!hex || typeof hex !== 'string') { - console.warn('⚠️ [hexToRgba] Valeur hex invalide:', hex); - return `rgba(108, 117, 125, ${alpha || 1})`; // Retourner gris par défaut - } - - const hexClean = hex.replace('#', ''); - const r = parseInt(hexClean.substr(0, 2), 16); - const g = parseInt(hexClean.substr(2, 2), 16); - const b = parseInt(hexClean.substr(4, 2), 16); - const rgbaValue = `rgba(${r}, ${g}, ${b}, ${alpha || 1})`; - console.log('🔧 [hexToRgba] Conversion:', { hex, alpha, result: rgbaValue }); - return rgbaValue; -} - export function initializeCalendar() { console.log('🚀 Initialisation de FullCalendar...'); const calendarEl = document.getElementById('agenda-calendar'); @@ -139,451 +80,7 @@ export function initializeCalendar() { }; const apiEvents = await getEvents(params); // Mapping des objets API vers le format FullCalendar - const events = apiEvents.map(ev => { - // Fonction utilitaire pour trouver la configuration d'un intervenant - function findIntervenantConfig(intervenant) { - if (!intervenant || !crviACFData || !crviACFData.intervenants) { - // console.log('❌ Données manquantes - intervenant:', !!intervenant, 'crviACFData:', !!crviACFData, 'intervenants:', !!crviACFData?.intervenants); - return null; - } - - // console.log('Intervenants disponibles:', crviACFData.intervenants); - // console.log('Intervenant recherché:', intervenant); - - // Vérifier que l'intervenant a un ID - if (!intervenant.id) { - // console.log('❌ ID d\'intervenant manquant:', intervenant); - return null; - } - - const intervenantId = parseInt(intervenant.id); - if (isNaN(intervenantId)) { - // console.log('❌ ID d\'intervenant invalide:', intervenant.id); - return null; - } - - // Chercher directement par ID - let intervenantConfig = null; - for (const key in crviACFData.intervenants) { - const config = crviACFData.intervenants[key]; - - // Vérifier que la config a un ID - if (!config || !config.id) { - // console.log('⚠️ Configuration invalide pour la clé:', key, 'config:', config); - continue; - } - - // console.log('Comparaison - ID recherché:', intervenantId, 'ID config:', config.id, 'Type config.id:', typeof config.id); - - if (config.id == intervenantId) { - intervenantConfig = config; - // console.log('✅ Configuration trouvée pour ID:', intervenantId); - break; - } - } - - if (!intervenantConfig) { - // console.log('❌ Aucune configuration trouvée pour l\'ID:', intervenantId); - // console.log('IDs disponibles:', Object.values(crviACFData.intervenants).map(c => c.id)); - } - - return intervenantConfig; - } - - // Hiérarchie de détermination des couleurs : - // 1) Pour RDV assignés (individuel/groupe) avec type d'intervention : couleur du type d'intervention - // 2) Pour RDV assignés (individuel/groupe) sans type d'intervention : orange (par défaut) - // 3) Pour permanences : couleur selon le type (assignée, non attribuée, non disponible) - - let backgroundColor = null; - let textColor = null; - - // Vérifier si l'événement est assigné (a un intervenant et un local/beneficiaire) - const isEventAssigned = ev.id_intervenant && (ev.id_local || ev.id_beneficiaire); - - // Pour les RDV (individuel/groupe) assignés - if (ev.type && ev.type !== 'permanence' && isEventAssigned) { - // Vérifier si l'événement a un type d'intervention défini - let typeInterventionId = null; - if (ev.id_type_intervention) { - typeInterventionId = parseInt(ev.id_type_intervention); - } else if (ev.type_intervention && ev.type_intervention.id) { - typeInterventionId = parseInt(ev.type_intervention.id); - } - - // Si l'événement a un type d'intervention, utiliser sa couleur depuis crviAjax - if (typeInterventionId && !isNaN(typeInterventionId) && window.crviAjax && window.crviAjax.couleurs_types_intervention) { - const couleurTypeIntervention = window.crviAjax.couleurs_types_intervention[typeInterventionId]; - if (couleurTypeIntervention) { - backgroundColor = couleurTypeIntervention; - textColor = getTextColor(couleurTypeIntervention); - console.log('🎨 [COULEUR] RDV assigné avec type d\'intervention:', { - eventId: ev.id, - type: ev.type, - typeInterventionId: typeInterventionId, - backgroundColor: backgroundColor, - textColor: textColor, - source: 'type_intervention' - }); - } - } - - // Si pas de couleur définie (pas de type d'intervention), garder orange par défaut - if (!backgroundColor) { - backgroundColor = '#ff9800'; // Orange pour les événements assignés sans type d'intervention - textColor = getTextColor(backgroundColor); - console.log('🎨 [COULEUR] RDV assigné sans type d\'intervention (défaut orange):', { - eventId: ev.id, - type: ev.type, - backgroundColor: backgroundColor, - textColor: textColor, - source: 'defaut_assigné' - }); - } - } - - // Pour les RDV non assignés : couleur selon type_de_local (bureau ou salle) - if (!backgroundColor && ev.type && ev.type !== 'permanence' && !isEventAssigned) { - // Vérifier si l'événement a un local avec un type_de_local - if (ev.local && ev.local.type_de_local) { - const typeLocal = ev.local.type_de_local.toLowerCase(); - if (crviACFData && crviACFData.couleurs_rdv) { - const couleurRdv = crviACFData.couleurs_rdv[typeLocal]; - if (couleurRdv) { - backgroundColor = couleurRdv; - textColor = getTextColor(couleurRdv); - console.log('🎨 [COULEUR] RDV non assigné selon type de local:', { - eventId: ev.id, - type: ev.type, - typeLocal: typeLocal, - backgroundColor: backgroundColor, - textColor: textColor, - source: 'type_local' - }); - } - } - } - } - - // Pour permanences (événements de type 'permanence') : logique simplifiée - // 1) Non attribuée : dispo ? couleur dispo : couleur indispo - // 2) Attribuée : a type intervention ? couleur type : couleur défaut - let isPermanenceDisabled = false; - if (!backgroundColor && ev.type === 'permanence') { - // Une permanence est assignée si elle a un bénéficiaire ET un local (assign = 1) - const isPermanenceAssigned = ev.assign === 1 || (ev.id_beneficiaire && ev.id_local); - - // Une permanence est non attribuée si elle n'est pas assignée (assign = 0) - const isPermanenceNonAttribuee = ev.assign === 0 && !ev.id_beneficiaire && !ev.id_local; - - // Vérifier si le bénéficiaire est en congé (indisponibilitee_ponctuelle) - let isPermanenceNonDisponible = false; - if (ev.beneficiaire && ev.beneficiaire.indisponibilitee_ponctuelle && Array.isArray(ev.beneficiaire.indisponibilitee_ponctuelle)) { - const eventDate = ev.date_rdv; // Format YYYY-MM-DD - const eventDateObj = new Date(eventDate + 'T00:00:00'); - - // Vérifier si la date de l'événement est dans une période d'indisponibilité - for (const indispo of ev.beneficiaire.indisponibilitee_ponctuelle) { - if (indispo.debut && indispo.fin) { - let debutDate, finDate; - - // Gérer le format d/m/Y (format ACF) - if (typeof indispo.debut === 'string' && indispo.debut.includes('/')) { - const debutParts = indispo.debut.split('/'); - const finParts = indispo.fin.split('/'); - - if (debutParts.length === 3 && finParts.length === 3) { - // Format d/m/Y - debutDate = new Date(parseInt(debutParts[2]), parseInt(debutParts[1]) - 1, parseInt(debutParts[0])); - finDate = new Date(parseInt(finParts[2]), parseInt(finParts[1]) - 1, parseInt(finParts[0])); - } - } else { - // Format YYYY-MM-DD ou timestamp - debutDate = new Date(indispo.debut); - finDate = new Date(indispo.fin); - } - - if (debutDate && finDate && !isNaN(debutDate.getTime()) && !isNaN(finDate.getTime())) { - // Ajuster pour inclure toute la journée - debutDate.setHours(0, 0, 0, 0); - finDate.setHours(23, 59, 59, 999); - - if (eventDateObj >= debutDate && eventDateObj <= finDate) { - isPermanenceNonDisponible = true; - isPermanenceDisabled = true; - break; - } - } - } - } - } - - // Vérifier si l'intervenant est indisponible (congés ou jour non disponible) - if (!isPermanenceNonDisponible && ev.id_intervenant && window.crviACFData && window.crviACFData.indisponibilites_intervenants) { - const intervenantId = parseInt(ev.id_intervenant); - const intervenantDispo = window.crviACFData.indisponibilites_intervenants[intervenantId]; - - if (intervenantDispo) { - const eventDate = ev.date_rdv; // Format YYYY-MM-DD - const eventDateObj = new Date(eventDate + 'T00:00:00'); - - // Vérifier les jours de disponibilité (0 = dimanche, 1 = lundi, etc.) - const dayOfWeek = eventDateObj.getDay(); - const dayNames = ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi']; - const dayName = dayNames[dayOfWeek]; - - // Si l'intervenant a des jours de disponibilité définis et que ce jour n'en fait pas partie - if (intervenantDispo.jours_dispo && Array.isArray(intervenantDispo.jours_dispo) && - intervenantDispo.jours_dispo.length > 0 && - !intervenantDispo.jours_dispo.includes(dayName)) { - console.log('🚫 [PERMANENCE] Intervenant non disponible ce jour:', { - intervenantId, - date: eventDate, - dayName, - joursDisponibles: intervenantDispo.jours_dispo - }); - isPermanenceNonDisponible = true; - isPermanenceDisabled = true; - } - - // Vérifier les congés/indisponibilités ponctuelles - if (!isPermanenceNonDisponible && intervenantDispo.conges && Array.isArray(intervenantDispo.conges)) { - for (const conge of intervenantDispo.conges) { - if (conge.debut && conge.fin) { - let debutDate, finDate; - - // Gérer le format d/m/Y (format ACF) - if (typeof conge.debut === 'string' && conge.debut.includes('/')) { - const debutParts = conge.debut.split('/'); - const finParts = conge.fin.split('/'); - - if (debutParts.length === 3 && finParts.length === 3) { - debutDate = new Date(parseInt(debutParts[2]), parseInt(debutParts[1]) - 1, parseInt(debutParts[0])); - finDate = new Date(parseInt(finParts[2]), parseInt(finParts[1]) - 1, parseInt(finParts[0])); - } - } else { - // Format YYYY-MM-DD ou timestamp - debutDate = new Date(conge.debut); - finDate = new Date(conge.fin); - } - - if (debutDate && finDate && !isNaN(debutDate.getTime()) && !isNaN(finDate.getTime())) { - debutDate.setHours(0, 0, 0, 0); - finDate.setHours(23, 59, 59, 999); - - if (eventDateObj >= debutDate && eventDateObj <= finDate) { - console.log('🚫 [PERMANENCE] Intervenant en congé:', { - intervenantId, - date: eventDate, - congeDebut: conge.debut, - congeFin: conge.fin, - congeType: conge.type - }); - isPermanenceNonDisponible = true; - isPermanenceDisabled = true; - break; - } - } - } - } - } - } - } - - // Vérifier aussi le statut si pas déjà déterminé - if (!isPermanenceNonDisponible && ev.statut && ['annule', 'non_tenu', 'absence'].includes(ev.statut)) { - isPermanenceNonDisponible = true; - } - - // LOGIQUE SIMPLIFIÉE - if (isPermanenceNonAttribuee) { - // 1) Non attribuée : dispo ? couleur dispo : couleur indispo - if (isPermanenceNonDisponible && window.crviAjax && window.crviAjax.couleur_permanence_non_disponible) { - backgroundColor = window.crviAjax.couleur_permanence_non_disponible; - console.log('🎨 [COULEUR] Permanence non attribuée - indisponible:', { - eventId: ev.id, - backgroundColor: backgroundColor, - source: 'permanence_non_attribuee_indispo' - }); - } else if (window.crviAjax && window.crviAjax.couleur_permanence_non_attribuee) { - backgroundColor = window.crviAjax.couleur_permanence_non_attribuee; - console.log('🎨 [COULEUR] Permanence non attribuée - disponible:', { - eventId: ev.id, - backgroundColor: backgroundColor, - source: 'permanence_non_attribuee_dispo' - }); - } - } else if (isPermanenceAssigned) { - // 2) Attribuée : a type intervention ? couleur type : couleur défaut - let typeInterventionId = null; - if (ev.id_type_intervention) { - typeInterventionId = parseInt(ev.id_type_intervention); - } else if (ev.type_intervention && ev.type_intervention.id) { - typeInterventionId = parseInt(ev.type_intervention.id); - } - - if (typeInterventionId && !isNaN(typeInterventionId) && window.crviAjax && window.crviAjax.couleurs_types_intervention) { - const couleurTypeIntervention = window.crviAjax.couleurs_types_intervention[typeInterventionId]; - if (couleurTypeIntervention) { - backgroundColor = couleurTypeIntervention; - console.log('🎨 [COULEUR] Permanence assignée - avec type intervention:', { - eventId: ev.id, - typeInterventionId: typeInterventionId, - backgroundColor: backgroundColor, - source: 'permanence_assignee_type' - }); - } - } - - // Si pas de type d'intervention, utiliser couleur défaut - if (!backgroundColor) { - backgroundColor = (crviACFData && crviACFData.couleurs_permanence && crviACFData.couleurs_permanence.permanence) - ? crviACFData.couleurs_permanence.permanence - : '#9e9e9e'; - console.log('🎨 [COULEUR] Permanence assignée - défaut:', { - eventId: ev.id, - backgroundColor: backgroundColor, - source: 'permanence_assignee_defaut' - }); - } - } - - // S'assurer que backgroundColor n'est pas null avant d'appeler getTextColor - if (backgroundColor) { - textColor = getTextColor(backgroundColor); - } else { - textColor = '#000000'; // Par défaut - } - } - - // Couleur par défaut si aucune condition n'est remplie - if (!backgroundColor) { - backgroundColor = '#6c757d'; // Gris par défaut - textColor = getTextColor(backgroundColor); - console.log('🎨 [COULEUR] Couleur par défaut (gris):', { - eventId: ev.id, - type: ev.type, - backgroundColor: backgroundColor, - textColor: textColor, - source: 'defaut_general' - }); - } - - // Log final de la couleur appliquée - console.log('🎨 [COULEUR FINALE] Événement:', { - eventId: ev.id, - title: ev.type || 'Sans type', - backgroundColor: backgroundColor, - borderColor: backgroundColor, - textColor: textColor, - isAssigned: isEventAssigned, - type: ev.type - }); - - // Générer un titre plus explicite - let title = ev.type || 'Sans type'; - - // Gestion spéciale pour les permanences - if (ev.type === 'permanence') { - if (ev.intervenant) { - const intervenant = ev.intervenant; - const nomComplet = `${intervenant.prenom || ''} ${intervenant.nom || ''}`.trim(); - title = 'p. ' + (nomComplet || 'Intervenant'); - } else { - title = 'p. Intervenant'; - } - } else { - // Utiliser le nom de l'intervenant comme titre principal pour les autres événements - if (ev.intervenant) { - const intervenant = ev.intervenant; - const nomComplet = `${intervenant.prenom || ''} ${intervenant.nom || ''}`.trim(); - if (nomComplet) { - title = nomComplet; - } - } - - // Ajouter le type d'intervention principal si disponible - if (ev.intervenant && ev.intervenant.types_intervention_noms && ev.intervenant.types_intervention_noms.length > 0) { - const primaryType = ev.intervenant.types_intervention_noms[0]; // Premier type d'intervention - title += ` - ${primaryType}`; - } - - // Ajouter le commentaire si disponible - if (ev.commentaire) { - title += ' - ' + ev.commentaire; - } - } - - // S'assurer que les couleurs sont toujours définies - if (!backgroundColor || typeof backgroundColor !== 'string') { - console.warn('⚠️ [Mapping] backgroundColor invalide pour l\'événement:', ev.id, backgroundColor); - backgroundColor = '#6c757d'; // Gris par défaut - } - if (!textColor || typeof textColor !== 'string') { - textColor = getTextColor(backgroundColor); - } - - // Vérifier si l'événement est passé - const eventStartDate = new Date(ev.date_rdv + 'T' + ev.heure_rdv); - const now = new Date(); - const isEventPast = eventStartDate < now; - - // Un événement est éditable seulement si : - // 1. Il n'est pas désactivé (permanence) - // 2. Il n'est pas passé - const isEditable = !isPermanenceDisabled && !isEventPast; - - return { - id: ev.id, - title: title, - start: ev.date_rdv + 'T' + ev.heure_rdv, - end: ev.date_fin + 'T' + ev.heure_fin, - backgroundColor: backgroundColor, - borderColor: backgroundColor, - textColor: textColor, - editable: isEditable, // Désactiver l'édition si la permanence est désactivée OU si l'événement est passé - durationEditable: isEditable, // Désactiver le redimensionnement si la permanence est désactivée OU si l'événement est passé - startEditable: isEditable, // Désactiver le déplacement si la permanence est désactivée OU si l'événement est passé - classNames: isPermanenceDisabled ? ['permanence-disabled'] : (isEventPast ? ['event-past'] : []), - extendedProps: { - // Données de base - type: ev.type, - commentaire: ev.commentaire, - date_rdv: ev.date_rdv, - heure_rdv: ev.heure_rdv, - date_fin: ev.date_fin, - heure_fin: ev.heure_fin, - - // Relations avec les entités - id_beneficiaire: ev.id_beneficiaire, - id_intervenant: ev.id_intervenant, - id_traducteur: ev.id_traducteur, - id_local: ev.id_local, - id_departement: ev.id_departement, - id_type_intervention: ev.id_type_intervention || (ev.type_intervention && ev.type_intervention.id ? ev.type_intervention.id : null), - langue: ev.langue, - langues_disponibles: ev.langues_disponibles || null, - - // Données des entités (si disponibles) - beneficiaire: ev.beneficiaire, - intervenant: ev.intervenant, - traducteur: ev.traducteur, - local: ev.local, - - // Données spécifiques aux groupes - nb_participants: ev.nb_participants, - nb_hommes: ev.nb_hommes, - nb_femmes: ev.nb_femmes, - - // Autres données - statut: ev.statut, - assign: ev.assign || 0, - isDisabled: isPermanenceDisabled, - created_at: ev.created_at, - updated_at: ev.updated_at - } - }; - }); + const events = apiEvents.map(ev => mapEventToFullCalendar(ev)); successCallback(events); } catch (e) { console.error('Erreur lors du chargement des événements:', e);