// 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 */ export 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) */ export 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) { // Ordre de priorité : département > type d'intervention > local > défaut // Priorité 1 : Couleur du département const departementId = ev.id_departement ? parseInt(ev.id_departement) : null; // 🔍 DEBUG pour événement 410 if (ev.id === 410 || ev.id === '410') { console.log('🔍 [MAPPER DEBUG 410 - RDV] Début analyse couleur département:', { eventId: ev.id, type: ev.type, id_departement_brut: ev.id_departement, departementId_parsed: departementId, crviACFData_existe: !!window.crviACFData, departements_existe: !!(window.crviACFData && window.crviACFData.departements) }); } if (departementId && !isNaN(departementId) && window.crviACFData && window.crviACFData.departements) { // 🔍 DEBUG pour événement 410 - liste des départements if (ev.id === 410 || ev.id === '410') { console.log('🔍 [MAPPER DEBUG 410 - RDV] Recherche département ID:', departementId); console.log('🔍 [MAPPER DEBUG 410 - RDV] Départements disponibles:', Object.keys(window.crviACFData.departements).map(key => ({ key: key, id: window.crviACFData.departements[key].id, nom: window.crviACFData.departements[key].nom, couleur: window.crviACFData.departements[key].couleur })) ); } // Chercher le département par ID for (const key in window.crviACFData.departements) { const dept = window.crviACFData.departements[key]; // 🔍 DEBUG pour événement 410 - comparaison if (ev.id === 410 || ev.id === '410') { console.log('🔍 [MAPPER DEBUG 410 - RDV] Comparaison:', { key: key, dept_id: dept.id, dept_id_type: typeof dept.id, recherche_id: departementId, recherche_id_type: typeof departementId, sont_egaux: dept.id === departementId, dept_couleur: dept.couleur }); } if (dept.id === departementId && dept.couleur) { backgroundColor = dept.couleur; textColor = getTextColor(backgroundColor); console.log('🎨 [COULEUR] RDV assigné - département:', { eventId: ev.id, type: ev.type, departementId: departementId, departementNom: dept.nom, backgroundColor: backgroundColor, source: 'departement' }); // 🔍 DEBUG pour événement 410 - succès if (ev.id === 410 || ev.id === '410') { console.log('✅ [MAPPER DEBUG 410 - RDV] Couleur département appliquée:', { departementNom: dept.nom, couleurAppliquee: backgroundColor }); } break; } } // 🔍 DEBUG pour événement 410 - échec de recherche if ((ev.id === 410 || ev.id === '410') && !backgroundColor) { console.warn('⚠️ [MAPPER DEBUG 410 - RDV] Aucun département correspondant trouvé!'); } } // Priorité 2 : Type d'intervention (si pas de département) if (!backgroundColor) { 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' }); } } } // Priorité 3 : Couleur du local (type de local) if (!backgroundColor && ev.local) { const localType = ev.local.type || ev.local_type; if (localType && window.crviACFData && window.crviACFData.types_local) { const typeLocalConfig = window.crviACFData.types_local[localType]; if (typeLocalConfig && typeLocalConfig.couleur) { backgroundColor = typeLocalConfig.couleur; textColor = getTextColor(backgroundColor); console.log('🎨 [COULEUR] RDV assigné - type de local:', { eventId: ev.id, type: ev.type, localType: localType, backgroundColor: backgroundColor, source: 'type_local' }); } } } // Fallback : 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 : ordre de priorité - département puis local // Priorité 1 : Couleur du département const departementId = ev.id_departement ? parseInt(ev.id_departement) : null; // 🔍 DEBUG pour événement 410 if (ev.id === 410 || ev.id === '410') { console.log('🔍 [MAPPER DEBUG 410] Début analyse couleur département:', { eventId: ev.id, id_departement_brut: ev.id_departement, departementId_parsed: departementId, crviACFData_existe: !!window.crviACFData, departements_existe: !!(window.crviACFData && window.crviACFData.departements), departements_data: window.crviACFData ? window.crviACFData.departements : 'N/A' }); } if (departementId && !isNaN(departementId) && window.crviACFData && window.crviACFData.departements) { // 🔍 DEBUG pour événement 410 - liste des départements if (ev.id === 410 || ev.id === '410') { console.log('🔍 [MAPPER DEBUG 410] Recherche département ID:', departementId); console.log('🔍 [MAPPER DEBUG 410] Départements disponibles:', Object.keys(window.crviACFData.departements).map(key => ({ key: key, id: window.crviACFData.departements[key].id, nom: window.crviACFData.departements[key].nom, couleur: window.crviACFData.departements[key].couleur })) ); } // Chercher le département par ID for (const key in window.crviACFData.departements) { const dept = window.crviACFData.departements[key]; // 🔍 DEBUG pour événement 410 - comparaison if (ev.id === 410 || ev.id === '410') { console.log('🔍 [MAPPER DEBUG 410] Comparaison:', { key: key, dept_id: dept.id, dept_id_type: typeof dept.id, recherche_id: departementId, recherche_id_type: typeof departementId, sont_egaux: dept.id === departementId, dept_couleur: dept.couleur }); } if (dept.id === departementId && dept.couleur) { backgroundColor = dept.couleur; console.log('🎨 [COULEUR] Permanence assignée - département:', { eventId: ev.id, departementId: departementId, departementNom: dept.nom, backgroundColor: backgroundColor, source: 'departement' }); // 🔍 DEBUG pour événement 410 - succès if (ev.id === 410 || ev.id === '410') { console.log('✅ [MAPPER DEBUG 410] Couleur département appliquée:', { departementNom: dept.nom, couleurAppliquee: backgroundColor }); } break; } } // 🔍 DEBUG pour événement 410 - échec de recherche if ((ev.id === 410 || ev.id === '410') && !backgroundColor) { console.warn('⚠️ [MAPPER DEBUG 410] Aucun département correspondant trouvé!'); } } // Priorité 2 : Couleur du local (type de local) if (!backgroundColor && ev.local) { const localType = ev.local.type || ev.local_type; if (localType && window.crviACFData && window.crviACFData.types_local) { const typeLocalConfig = window.crviACFData.types_local[localType]; if (typeLocalConfig && typeLocalConfig.couleur) { backgroundColor = typeLocalConfig.couleur; console.log('🎨 [COULEUR] Permanence assignée - type de local:', { eventId: ev.id, localType: localType, backgroundColor: backgroundColor, source: 'type_local' }); } } } // Fallback : couleur par défaut des permanences 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' }); } // 🔍 DEBUG FINAL pour événement 410 if (ev.id === 410 || ev.id === '410') { console.log('🔍 [MAPPER DEBUG 410] COULEUR FINALE avant return:', { eventId: ev.id, backgroundColor: backgroundColor, textColor: textColor, type: ev.type, id_departement: ev.id_departement, isPermanenceAssigned: ev.assign === 1 || (ev.id_beneficiaire && ev.id_local), ev_complet: ev }); } // 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 } }; }