518 lines
20 KiB
JavaScript
518 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) {
|
|
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) {
|
|
setCurrentDateTime();
|
|
}
|
|
} else {
|
|
setCurrentDateTime();
|
|
}
|
|
|
|
// Remplir les autres champs
|
|
const extendedProps = event.extendedProps || {};
|
|
safeSetValue('type', extendedProps.type || event.type || '');
|
|
|
|
const langueValue = extendedProps.langue || event.langue || '';
|
|
safeSetValue('langue', langueValue);
|
|
|
|
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');
|
|
}
|
|
}
|