// 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()); // Gérer les champs multiples (comme langues[]) UNIQUEMENT pour les permanences // Ne pas traiter pour les autres types d'événements pour ne pas casser la mise à jour classique if (data.type === 'permanence') { const langues = formData.getAll('langues[]'); if (langues && langues.length > 0) { // Filtrer les valeurs vides et convertir en tableau de slugs data.langues = langues.filter(value => value !== '' && value !== null); } } // 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'); } }