/** * Module Profil Intervenant * Gestion du formulaire de profil et mise à jour des informations */ import { apiFetch } from './agenda-api.js'; import toastr from 'toastr'; // Variables globales pour gérer les indisponibilités let indisponibilitesList = []; let indisponibiliteCounter = 0; /** * Initialise le module profil intervenant */ export function initializeProfile() { console.log('🚀 Initialisation du module profil intervenant...'); // Charger les données du profil loadProfile(); // Écouter les événements du formulaire principal const profileForm = document.getElementById('profile-form'); if (profileForm) { profileForm.addEventListener('submit', handleProfileSubmit); } const cancelBtn = document.getElementById('cancel-profile-btn'); if (cancelBtn) { cancelBtn.addEventListener('click', () => { loadProfile(); // Recharger pour annuler les modifications }); } // Écouter les événements du formulaire de disponibilités const disponibilitesForm = document.getElementById('disponibilites-form'); if (disponibilitesForm) { disponibilitesForm.addEventListener('submit', handleDisponibilitesSubmit); } // Gestion de l'ajout d'indisponibilité const addIndispoBtn = document.getElementById('add-indisponibilite-btn'); if (addIndispoBtn) { addIndispoBtn.addEventListener('click', showIndisponibiliteForm); } const saveIndispoBtn = document.getElementById('save-indisponibilite-btn'); if (saveIndispoBtn) { saveIndispoBtn.addEventListener('click', saveIndisponibilite); } const cancelIndispoBtn = document.getElementById('cancel-indisponibilite-btn'); if (cancelIndispoBtn) { cancelIndispoBtn.addEventListener('click', hideIndisponibiliteForm); } } /** * Charge les données du profil depuis l'API */ async function loadProfile() { try { const profileContainer = document.getElementById('intervenant-profile-container') || document.getElementById('profile-form') || document.body; if (window.CRVI_OVERLAY && profileContainer) { window.CRVI_OVERLAY.show(profileContainer); } const profile = await apiFetch('intervenant/profile'); // Remplir les champs non modifiables const nomEl = document.getElementById('profile-nom'); const prenomEl = document.getElementById('profile-prenom'); const emailEl = document.getElementById('profile-email'); if (nomEl) nomEl.value = profile.nom || ''; if (prenomEl) prenomEl.value = profile.prenom || ''; if (emailEl) emailEl.value = profile.email || ''; // Téléphone (modifiable) const telephoneEl = document.getElementById('profile-telephone'); if (telephoneEl) { telephoneEl.value = profile.telephone || ''; } // Jours de disponibilité (checkboxes) - déjà générés en PHP, on met juste à jour les valeurs updateJoursDisponibiliteCheckboxes(profile.jours_de_disponibilite || []); // Heures de permanences (checkboxes) updateHeuresPermanencesCheckboxes(profile.heures_de_permanences || []); // Indisponibilités ponctuelles - déjà générées en PHP, on synchronise juste la liste indisponibilitesList = profile.indisponibilites_ponctuelles || []; // Synchroniser la liste avec celle affichée en PHP (pour les mises à jour après sauvegarde) syncIndisponibilitesList(indisponibilitesList); // Rafraîchir explicitement l'affichage pour éviter tout écart visuel displayIndisponibilites(indisponibilitesList); // Départements (format: [{id, nom}, ...]) displayDepartements(profile.departements || []); // Spécialisations (format: [{id, nom}, ...]) displaySpecialisations(profile.specialisations || []); } catch (error) { console.error('Erreur lors du chargement du profil:', error); toastr.error( error.message || 'Erreur lors du chargement du profil', 'Erreur', { timeOut: 5000 } ); } finally { const profileContainer = document.getElementById('intervenant-profile-container') || document.getElementById('profile-form') || document.body; if (window.CRVI_OVERLAY && profileContainer) { window.CRVI_OVERLAY.hide(profileContainer); } } } /** * Met à jour les checkboxes des jours de disponibilité (déjà générées en PHP) */ function updateJoursDisponibiliteCheckboxes(joursActuels) { const joursValues = ['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche']; joursValues.forEach(jour => { const checkbox = document.getElementById(`jour-${jour}`); if (checkbox) { checkbox.checked = joursActuels.includes(jour); } }); } /** * Met à jour les checkboxes des heures de permanences */ function updateHeuresPermanencesCheckboxes(heuresActuelles) { const heuresValues = ['09:00','10:00','11:00','12:00','13:00','14:00','15:00','16:00']; heuresValues.forEach(h => { const checkbox = document.getElementById(`heure-${h}`); if (checkbox) { checkbox.checked = Array.isArray(heuresActuelles) && heuresActuelles.includes(h); } }); } /** * Synchronise la liste JS des indisponibilités avec les données de l'API * (les indisponibilités sont déjà affichées en PHP, on synchronise juste la liste interne) */ function syncIndisponibilitesList(indisponibilites) { // La liste est déjà affichée en PHP, on synchronise juste la liste JS // pour les opérations d'ajout/suppression indisponibilitesList = indisponibilites || []; // Mettre à jour l'affichage si des indisponibilités ont été ajoutées/supprimées const container = document.getElementById('profile-indisponibilites-list'); const emptyContainer = document.getElementById('profile-indisponibilites-empty'); if (!container) return; // Si la liste est vide, afficher le message if (indisponibilitesList.length === 0) { const existingItems = container.querySelectorAll('.indisponibilite-item'); existingItems.forEach(item => item.remove()); if (emptyContainer) { emptyContainer.style.display = 'block'; } } else { if (emptyContainer) { emptyContainer.style.display = 'none'; } } } /** * Affiche les indisponibilités ponctuelles (utilisée après ajout/suppression) */ function displayIndisponibilites(indisponibilites) { const container = document.getElementById('profile-indisponibilites-list'); const emptyContainer = document.getElementById('profile-indisponibilites-empty'); if (!container) return; if (!indisponibilites || indisponibilites.length === 0) { container.innerHTML = ''; if (emptyContainer) { emptyContainer.style.display = 'block'; } return; } if (emptyContainer) { emptyContainer.style.display = 'none'; } // Types d'indisponibilité const typesLabels = { 'conge': 'Congé', 'absence': 'Absence', 'maladie': 'Maladie' }; container.innerHTML = indisponibilites.map((indisp, index) => { const debut = indisp.debut || ''; const fin = indisp.fin || ''; const type = indisp.type || 'conge'; const typeLabel = typesLabels[type] || type; const commentaire = indisp.commentaire || ''; // Badge classes selon type let badgeClass = 'badge-indispo-unknown'; if (type === 'conge') badgeClass = 'badge-indispo-conge'; else if (type === 'absence') badgeClass = 'badge-indispo-absence'; else if (type === 'maladie') badgeClass = 'badge-indispo-maladie'; // Convertir les dates au format d/m/Y pour l'affichage let debutFormatted = debut; let finFormatted = fin; // Si les dates sont au format YYYY-MM-DD, les convertir if (debut && debut.match(/^\d{4}-\d{2}-\d{2}$/)) { const dateObj = new Date(debut); debutFormatted = dateObj.toLocaleDateString('fr-FR'); } if (fin && fin.match(/^\d{4}-\d{2}-\d{2}$/)) { const dateObj = new Date(fin); finFormatted = dateObj.toLocaleDateString('fr-FR'); } // Afficher la période let periodeText = ''; if (debutFormatted && finFormatted) { if (debutFormatted === finFormatted) { periodeText = `Le ${debutFormatted}`; } else { periodeText = `Du ${debutFormatted} au ${finFormatted}`; } } else if (debutFormatted) { periodeText = `Le ${debutFormatted}`; } return `
${periodeText} ${typeLabel} ${commentaire ? `

${commentaire}

` : ''}
`; }).join(''); } // Fonction globale pour supprimer une indisponibilité window.removeIndisponibilite = function(index) { if (confirm('Êtes-vous sûr de vouloir supprimer cette indisponibilité ?')) { indisponibilitesList.splice(index, 1); displayIndisponibilites(indisponibilitesList); } }; /** * Affiche le formulaire d'ajout d'indisponibilité */ function showIndisponibiliteForm() { const container = document.getElementById('indisponibilite-form-container'); if (container) { container.style.display = 'block'; // Ajouter les attributs required quand le formulaire est visible const debutInput = document.getElementById('indispo-debut'); const finInput = document.getElementById('indispo-fin'); const typeSelect = document.getElementById('indispo-type'); if (debutInput) debutInput.setAttribute('required', 'required'); if (finInput) finInput.setAttribute('required', 'required'); if (typeSelect) typeSelect.setAttribute('required', 'required'); // Réinitialiser les champs if (debutInput) debutInput.value = ''; if (finInput) finInput.value = ''; if (typeSelect) typeSelect.value = ''; const commentaireTextarea = document.getElementById('indispo-commentaire'); if (commentaireTextarea) commentaireTextarea.value = ''; } } /** * Cache le formulaire d'ajout d'indisponibilité */ function hideIndisponibiliteForm() { const container = document.getElementById('indisponibilite-form-container'); if (container) { container.style.display = 'none'; // Retirer les attributs required quand le formulaire est caché const debutInput = document.getElementById('indispo-debut'); const finInput = document.getElementById('indispo-fin'); const typeSelect = document.getElementById('indispo-type'); if (debutInput) debutInput.removeAttribute('required'); if (finInput) finInput.removeAttribute('required'); if (typeSelect) typeSelect.removeAttribute('required'); } } /** * Sauvegarde une nouvelle indisponibilité dans la liste */ function saveIndisponibilite() { const debut = document.getElementById('indispo-debut').value; const fin = document.getElementById('indispo-fin').value; const type = document.getElementById('indispo-type').value; const commentaire = document.getElementById('indispo-commentaire').value.trim(); // Validation if (!debut || !fin || !type) { toastr.warning('Veuillez remplir tous les champs obligatoires', 'Validation'); return; } // Vérifier que la date de fin est après la date de début if (new Date(fin) < new Date(debut)) { toastr.warning('La date de fin doit être supérieure ou égale à la date de début', 'Validation'); return; } // Convertir les dates au format d/m/Y pour ACF const debutFormatted = formatDateToDMY(debut); const finFormatted = formatDateToDMY(fin); // Ajouter à la liste indisponibilitesList.push({ debut: debutFormatted, fin: finFormatted, type: type, commentaire: commentaire }); // Rafraîchir l'affichage displayIndisponibilites(indisponibilitesList); // Cacher le formulaire hideIndisponibiliteForm(); toastr.success('Indisponibilité ajoutée. N\'oubliez pas d\'enregistrer.', 'Succès', { timeOut: 3000 }); } /** * Convertit une date au format YYYY-MM-DD vers d/m/Y */ function formatDateToDMY(dateStr) { if (!dateStr) return ''; const date = new Date(dateStr); const day = String(date.getDate()).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0'); const year = date.getFullYear(); return `${day}/${month}/${year}`; } /** * Convertit une date au format d/m/Y vers YYYY-MM-DD */ function formatDateToYMD(dateStr) { if (!dateStr) return ''; // Format d/m/Y vers YYYY-MM-DD const parts = dateStr.split('/'); if (parts.length === 3) { return `${parts[2]}-${parts[1]}-${parts[0]}`; } return dateStr; } /** * Affiche les départements */ function displayDepartements(departements) { const container = document.getElementById('profile-departements'); if (!container) return; if (!departements || departements.length === 0) { container.innerHTML = 'Aucun département assigné'; return; } // Les départements viennent comme objets {id, nom} container.innerHTML = departements.map(dep => { const nom = dep.nom || dep.post_title || `Département #${dep.id || dep.ID || dep}`; return `${nom}`; }).join(''); } /** * Affiche les spécialisations (types d'intervention) */ function displaySpecialisations(specialisations) { const container = document.getElementById('profile-specialisations'); if (!container) return; if (!specialisations || specialisations.length === 0) { container.innerHTML = 'Aucune spécialisation assignée'; return; } // Les spécialisations viennent comme objets {id, nom} container.innerHTML = specialisations.map(spec => { const nom = spec.nom || spec.post_title || `Spécialisation #${spec.id || spec.ID || spec}`; return `${nom}`; }).join(''); } /** * Gère la soumission du formulaire de profil */ async function handleProfileSubmit(event) { event.preventDefault(); const telephoneEl = document.getElementById('profile-telephone'); if (!telephoneEl) { toastr.error('Champ téléphone non trouvé', 'Erreur'); return; } const telephone = telephoneEl.value.trim(); // Validation basique if (!telephone) { toastr.warning('Le numéro de téléphone est requis', 'Validation'); telephoneEl.focus(); return; } // Format de validation simple (à adapter selon les besoins) const phoneRegex = /^[0-9+\s\-()]{8,}$/; if (!phoneRegex.test(telephone)) { toastr.warning('Format de numéro de téléphone invalide', 'Validation'); telephoneEl.focus(); return; } try { // Désactiver le bouton de soumission const submitBtn = event.target.querySelector('button[type="submit"]'); if (submitBtn) { submitBtn.disabled = true; submitBtn.innerHTML = 'Enregistrement...'; } const profileContainer = document.getElementById('intervenant-profile-container') || document.getElementById('profile-form') || document.body; if (window.CRVI_OVERLAY && profileContainer) { window.CRVI_OVERLAY.show(profileContainer); } await apiFetch('intervenant/profile', { method: 'PUT', body: JSON.stringify({ telephone: telephone }) }); toastr.success( 'Profil mis à jour avec succès', 'Succès', { timeOut: 3000 } ); // Recharger le profil pour avoir les données à jour await loadProfile(); } catch (error) { console.error('Erreur lors de la mise à jour du profil:', error); toastr.error( error.message || 'Erreur lors de la mise à jour du profil', 'Erreur', { timeOut: 5000 } ); } finally { // Réactiver le bouton const submitBtn = event.target.querySelector('button[type="submit"]'); if (submitBtn) { submitBtn.disabled = false; submitBtn.innerHTML = 'Enregistrer les modifications'; } const profileContainer = document.getElementById('intervenant-profile-container') || document.getElementById('profile-form') || document.body; if (window.CRVI_OVERLAY && profileContainer) { window.CRVI_OVERLAY.hide(profileContainer); } } } /** * Gère la soumission du formulaire de disponibilités */ async function handleDisponibilitesSubmit(event) { event.preventDefault(); try { // Récupérer les jours de disponibilité cochés const joursCheckboxes = document.querySelectorAll('input[name="jours_disponibilite[]"]:checked'); const joursDisponibilite = Array.from(joursCheckboxes).map(cb => cb.value); // Récupérer les heures de permanences cochées const heuresCheckboxes = document.querySelectorAll('input[name="heures_permanences[]"]:checked'); const heuresPermanences = Array.from(heuresCheckboxes).map(cb => cb.value); // Désactiver le bouton de soumission const submitBtn = event.target.querySelector('button[type="submit"]'); if (submitBtn) { submitBtn.disabled = true; submitBtn.innerHTML = 'Enregistrement...'; } const profileContainer = document.getElementById('intervenant-profile-container') || document.getElementById('disponibilites-form') || document.body; if (window.CRVI_OVERLAY && profileContainer) { window.CRVI_OVERLAY.show(profileContainer); } await apiFetch('intervenant/profile', { method: 'PUT', body: JSON.stringify({ jours_de_disponibilite: joursDisponibilite, heures_de_permanences: heuresPermanences, indisponibilites_ponctuelles: indisponibilitesList }) }); toastr.success( 'Disponibilités mises à jour avec succès', 'Succès', { timeOut: 3000 } ); // Vérifier s'il y a des conflits d'indisponibilités await checkAndDisplayConflicts(); // Recharger le profil pour avoir les données à jour await loadProfile(); } catch (error) { console.error('Erreur lors de la mise à jour des disponibilités:', error); toastr.error( error.message || 'Erreur lors de la mise à jour des disponibilités', 'Erreur', { timeOut: 5000 } ); } finally { // Réactiver le bouton const submitBtn = event.target.querySelector('button[type="submit"]'); if (submitBtn) { submitBtn.disabled = false; submitBtn.innerHTML = 'Enregistrer les disponibilités'; } const profileContainer = document.getElementById('intervenant-profile-container') || document.getElementById('disponibilites-form') || document.body; if (window.CRVI_OVERLAY && profileContainer) { window.CRVI_OVERLAY.hide(profileContainer); } } } /** * Vérifie et affiche les conflits d'indisponibilités après la sauvegarde */ async function checkAndDisplayConflicts() { try { // Faire une requête à l'API WordPress pour récupérer le message de conflit const response = await apiFetch('intervenant/conflicts-check'); if (response && response.has_conflicts && response.message) { // Afficher le message de conflit en haut de la page displayConflictMessage(response.message); } } catch (error) { console.error('Erreur lors de la vérification des conflits:', error); // Ne pas afficher d'erreur à l'utilisateur pour ne pas perturber l'expérience } } /** * Affiche un message de conflit en haut de la page * @param {string} message - Le message HTML à afficher */ function displayConflictMessage(message) { // Trouver le conteneur où afficher le message (après le header, avant le contenu) const container = document.querySelector('.crvi-intervenant-profile .container-fluid'); if (!container) return; // Supprimer les anciens messages de conflit s'il y en a const oldAlert = container.querySelector('.conflict-alert'); if (oldAlert) { oldAlert.remove(); } // Créer un nouvel élément d'alerte const alertDiv = document.createElement('div'); alertDiv.className = 'conflict-alert alert alert-danger alert-dismissible fade show mt-3'; alertDiv.setAttribute('role', 'alert'); alertDiv.innerHTML = message; // Ajouter un bouton de fermeture const closeButton = document.createElement('button'); closeButton.type = 'button'; closeButton.className = 'btn-close'; closeButton.setAttribute('data-bs-dismiss', 'alert'); closeButton.setAttribute('aria-label', 'Close'); alertDiv.appendChild(closeButton); // Insérer le message après la navigation (row mb-4) const navRow = container.querySelector('.row.mb-4'); if (navRow && navRow.nextSibling) { container.insertBefore(alertDiv, navRow.nextSibling); } else { container.insertBefore(alertDiv, container.firstChild); } // Scroller vers le haut pour voir le message window.scrollTo({ top: 0, behavior: 'smooth' }); }