Crvi/assets/js/modules/agenda-modal-forms.js
2026-01-21 21:59:24 +01:00

519 lines
20 KiB
JavaScript

// 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 options langue selon langues_disponibles si c'est une permanence
const type = extendedProps.type || event.type || '';
if (type === 'permanence') {
const languesDisponibles = extendedProps.langues_disponibles;
const langueSelect = document.getElementById('langue');
if (langueSelect && languesDisponibles && typeof languesDisponibles === 'string' && languesDisponibles.trim() !== '') {
const languesPermises = languesDisponibles.split(',').map(l => l.trim()).filter(l => l !== '');
if (languesPermises.length > 0) {
const currentValue = langueSelect.value; // Récupérer la valeur actuelle (définie juste avant)
Array.from(langueSelect.options).forEach(option => {
if (option.value === '') {
option.style.display = '';
option.disabled = false;
return;
}
// Garder l'option sélectionnée visible même si elle n'est pas dans langues_disponibles
const isCurrentlySelected = option.value === currentValue;
const optionSlug = option.getAttribute('data-slug');
const isPermise = optionSlug && languesPermises.includes(optionSlug);
if (isPermise || isCurrentlySelected) {
option.style.display = '';
option.disabled = false;
} else {
option.style.display = 'none';
option.disabled = true;
}
});
}
}
}
// 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
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', '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) {
// Gérer 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 || !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');
}
}