576 lines
19 KiB
JavaScript
576 lines
19 KiB
JavaScript
/**
|
|
* Module Permanences Intervenant
|
|
* Gestion du formulaire d'encodage des permanences
|
|
*/
|
|
|
|
import { apiFetch } from './agenda-api.js';
|
|
import toastr from 'toastr';
|
|
|
|
/**
|
|
* Initialise le module permanences intervenant
|
|
*/
|
|
export function initializePermanences() {
|
|
console.log('🚀 Initialisation du module permanences intervenant...');
|
|
|
|
const form = document.getElementById('permanences-form');
|
|
if (!form) {
|
|
console.error('Formulaire permanences-form non trouvé');
|
|
return;
|
|
}
|
|
|
|
// Initialiser le champ mois de début avec le mois actuel
|
|
const moisDebutInput = document.getElementById('mois-debut');
|
|
if (moisDebutInput && !moisDebutInput.value) {
|
|
const today = new Date();
|
|
const year = today.getFullYear();
|
|
const month = String(today.getMonth() + 1).padStart(2, '0');
|
|
moisDebutInput.value = `${year}-${month}`;
|
|
}
|
|
|
|
// Initialiser Select2 pour le champ langues
|
|
initializeSelect2();
|
|
|
|
// Écouter les changements pour mettre à jour l'aperçu
|
|
setupPreviewListeners();
|
|
|
|
// Gérer la soumission du formulaire
|
|
form.addEventListener('submit', handleFormSubmit);
|
|
|
|
// Calculer l'aperçu initial
|
|
updatePreview();
|
|
|
|
// Initialiser le formulaire d'import CSV
|
|
initializeCsvImport();
|
|
}
|
|
|
|
/**
|
|
* Initialise Select2 pour le champ de sélection des langues
|
|
*/
|
|
function initializeSelect2() {
|
|
const languesSelect = document.getElementById('langues-permanences');
|
|
if (languesSelect && typeof jQuery !== 'undefined' && jQuery.fn.select2) {
|
|
jQuery(languesSelect).select2({
|
|
placeholder: 'Sélectionnez une ou plusieurs langues',
|
|
allowClear: true,
|
|
width: '100%',
|
|
language: {
|
|
noResults: function() {
|
|
return "Aucun résultat trouvé";
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Configure les listeners pour mettre à jour l'aperçu en temps réel
|
|
*/
|
|
function setupPreviewListeners() {
|
|
// Écouter les changements de mois de début
|
|
const moisDebut = document.getElementById('mois-debut');
|
|
if (moisDebut) {
|
|
moisDebut.addEventListener('change', updatePreview);
|
|
moisDebut.addEventListener('input', updatePreview);
|
|
}
|
|
|
|
// Écouter les changements de période
|
|
const periodInputs = document.querySelectorAll('input[name="periode"]');
|
|
periodInputs.forEach(input => {
|
|
input.addEventListener('change', updatePreview);
|
|
});
|
|
|
|
// Écouter les changements de jours
|
|
const joursInputs = document.querySelectorAll('input[name="jours[]"]');
|
|
joursInputs.forEach(input => {
|
|
input.addEventListener('change', updatePreview);
|
|
});
|
|
|
|
// Écouter les changements des heures sélectionnées
|
|
const heuresInputs = document.querySelectorAll('input[name="heures[]"]');
|
|
heuresInputs.forEach(input => {
|
|
input.addEventListener('change', updatePreview);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Calcule et affiche l'aperçu des permanences qui seront créées
|
|
*/
|
|
function updatePreview() {
|
|
const periode = parseInt(document.querySelector('input[name="periode"]:checked')?.value || '3');
|
|
const joursChecked = Array.from(document.querySelectorAll('input[name="jours[]"]:checked'))
|
|
.map(input => input.value);
|
|
const heuresChecked = Array.from(document.querySelectorAll('input[name="heures[]"]:checked'))
|
|
.map(input => input.value)
|
|
.sort(); // Trier les heures pour un affichage cohérent
|
|
const moisDebut = document.getElementById('mois-debut')?.value;
|
|
|
|
if (heuresChecked.length === 0) {
|
|
clearPreview();
|
|
return;
|
|
}
|
|
|
|
if (!moisDebut) {
|
|
clearPreview();
|
|
return;
|
|
}
|
|
|
|
if (joursChecked.length === 0) {
|
|
clearPreview();
|
|
return;
|
|
}
|
|
|
|
// Calculer les tranches horaires à partir des heures sélectionnées
|
|
// Chaque heure sélectionnée = 1 tranche d'1 heure
|
|
const tranches = heuresChecked.map(heureDebut => {
|
|
const [h, m] = heureDebut.split(':').map(Number);
|
|
const heureFin = `${String(h + 1).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
|
|
return {
|
|
debut: heureDebut,
|
|
fin: heureFin
|
|
};
|
|
});
|
|
|
|
// Calculer les dates à partir du mois de début sélectionné
|
|
const [year, month] = moisDebut.split('-').map(Number);
|
|
const startDate = new Date(year, month - 1, 1); // Premier jour du mois sélectionné
|
|
const endDate = new Date(year, month - 1 + periode, 0); // Dernier jour du mois de fin
|
|
|
|
// Compter les occurrences pour chaque jour sélectionné
|
|
let totalEvents = 0;
|
|
const joursMapping = {
|
|
'lundi': 1,
|
|
'mardi': 2,
|
|
'mercredi': 3,
|
|
'jeudi': 4,
|
|
'vendredi': 5,
|
|
'samedi': 6,
|
|
'dimanche': 0
|
|
};
|
|
|
|
const currentDate = new Date(startDate);
|
|
while (currentDate <= endDate) {
|
|
const dayOfWeek = currentDate.getDay();
|
|
const jourName = Object.keys(joursMapping).find(k => joursMapping[k] === dayOfWeek);
|
|
|
|
if (joursChecked.includes(jourName)) {
|
|
totalEvents += tranches.length;
|
|
}
|
|
|
|
currentDate.setDate(currentDate.getDate() + 1);
|
|
}
|
|
|
|
// Afficher l'aperçu
|
|
displayPreview({
|
|
periode: periode,
|
|
jours: joursChecked,
|
|
tranches: tranches,
|
|
totalEvents: totalEvents,
|
|
dateDebut: startDate,
|
|
dateFin: endDate
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Calcule les tranches horaires (par tranche de 1 heure)
|
|
*/
|
|
function calculateTranches(debut, fin) {
|
|
const tranches = [];
|
|
|
|
const [debutHour, debutMin] = debut.split(':').map(Number);
|
|
const [finHour, finMin] = fin.split(':').map(Number);
|
|
|
|
let currentHour = debutHour;
|
|
let currentMin = debutMin;
|
|
|
|
while (currentHour < finHour || (currentHour === finHour && currentMin < finMin)) {
|
|
const trancheDebut = `${String(currentHour).padStart(2, '0')}:${String(currentMin).padStart(2, '0')}`;
|
|
|
|
// Ajouter 1 heure
|
|
currentHour++;
|
|
if (currentHour >= 24) {
|
|
break; // Ne pas dépasser minuit
|
|
}
|
|
|
|
const trancheFin = `${String(currentHour).padStart(2, '0')}:${String(currentMin).padStart(2, '0')}`;
|
|
|
|
tranches.push({
|
|
debut: trancheDebut,
|
|
fin: trancheFin
|
|
});
|
|
}
|
|
|
|
return tranches;
|
|
}
|
|
|
|
/**
|
|
* Affiche l'aperçu des permanences
|
|
*/
|
|
function displayPreview(data) {
|
|
const container = document.getElementById('preview-container');
|
|
if (!container) return;
|
|
|
|
const joursLabels = {
|
|
'lundi': 'Lundi',
|
|
'mardi': 'Mardi',
|
|
'mercredi': 'Mercredi',
|
|
'jeudi': 'Jeudi',
|
|
'vendredi': 'Vendredi',
|
|
'samedi': 'Samedi',
|
|
'dimanche': 'Dimanche'
|
|
};
|
|
|
|
const joursDisplay = data.jours.map(j => joursLabels[j] || j).join(', ');
|
|
const dateDebutStr = data.dateDebut.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
|
const dateFinStr = data.dateFin.toLocaleDateString('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
|
|
|
container.innerHTML = `
|
|
<div class="alert alert-info">
|
|
<h5 class="alert-heading">
|
|
<i class="fas fa-info-circle me-2"></i>Aperçu des permanences
|
|
</h5>
|
|
<hr>
|
|
<p class="mb-2">
|
|
<strong>Période :</strong> ${data.periode} mois (du ${dateDebutStr} au ${dateFinStr})
|
|
</p>
|
|
<p class="mb-2">
|
|
<strong>Jours sélectionnés :</strong> ${joursDisplay || 'Aucun'}
|
|
</p>
|
|
<p class="mb-2">
|
|
<strong>Tranches horaires :</strong>
|
|
${data.tranches.length > 0
|
|
? data.tranches.map(t => `${t.debut} - ${t.fin}`).join(', ')
|
|
: 'Aucune tranche valide'}
|
|
</p>
|
|
<p class="mb-0">
|
|
<strong class="text-primary">Nombre total d'événements à créer : ${data.totalEvents}</strong>
|
|
</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Efface l'aperçu
|
|
*/
|
|
function clearPreview() {
|
|
const container = document.getElementById('preview-container');
|
|
if (!container) return;
|
|
|
|
container.innerHTML = `
|
|
<div class="alert alert-warning">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
Veuillez sélectionner au moins un jour et une plage horaire valide pour voir l'aperçu.
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Gère la soumission du formulaire
|
|
*/
|
|
async function handleFormSubmit(event) {
|
|
event.preventDefault();
|
|
|
|
const formElement = document.getElementById('permanences-form');
|
|
|
|
// Récupérer les données
|
|
const periode = parseInt(document.querySelector('input[name="periode"]:checked')?.value || '3');
|
|
const moisDebut = document.getElementById('mois-debut')?.value;
|
|
const jours = Array.from(document.querySelectorAll('input[name="jours[]"]:checked'))
|
|
.map(input => input.value);
|
|
const heures = Array.from(document.querySelectorAll('input[name="heures[]"]:checked'))
|
|
.map(input => input.value);
|
|
const dureePermanence = document.querySelector('input[name="duree_permanence"]:checked')?.value || '1h';
|
|
const nbTranches = dureePermanence === '15min' ? parseInt(document.getElementById('nb-tranches')?.value || '1') : null;
|
|
// Récupérer les langues sélectionnées via Select2
|
|
const languesSelect = document.getElementById('langues-permanences');
|
|
const langues = languesSelect && typeof jQuery !== 'undefined' && jQuery(languesSelect).val()
|
|
? jQuery(languesSelect).val().filter(value => value !== '')
|
|
: [];
|
|
const informationsComplementaires = document.getElementById('informations-complementaires')?.value || '';
|
|
|
|
// Validation
|
|
if (!moisDebut) {
|
|
toastr.warning('Veuillez sélectionner un mois de début', 'Validation');
|
|
return;
|
|
}
|
|
|
|
if (jours.length === 0) {
|
|
toastr.warning('Veuillez sélectionner au moins un jour', 'Validation');
|
|
return;
|
|
}
|
|
|
|
if (heures.length === 0) {
|
|
toastr.warning('Veuillez sélectionner au moins une heure de permanence', 'Validation');
|
|
return;
|
|
}
|
|
|
|
// Calculer les tranches à partir des heures sélectionnées
|
|
const tranches = heures.map(heureDebut => {
|
|
const [h, m] = heureDebut.split(':').map(Number);
|
|
const heureFin = `${String(h + 1).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
|
|
return {
|
|
debut: heureDebut,
|
|
fin: heureFin
|
|
};
|
|
});
|
|
|
|
// Calculer la plage horaire globale (min et max des heures sélectionnées)
|
|
const heuresSorted = heures.map(h => {
|
|
const [hour] = h.split(':').map(Number);
|
|
return hour;
|
|
}).sort((a, b) => a - b);
|
|
|
|
const heureMin = heuresSorted[0];
|
|
const heureMax = heuresSorted[heuresSorted.length - 1];
|
|
const plageDebut = `${String(heureMin).padStart(2, '0')}:00`;
|
|
const plageFin = `${String(heureMax + 1).padStart(2, '0')}:00`;
|
|
|
|
// Calculer les dates à partir du mois de début pour l'estimation
|
|
const [year, month] = moisDebut.split('-').map(Number);
|
|
const startDate = new Date(year, month - 1, 1);
|
|
const endDate = new Date(year, month - 1 + periode, 0);
|
|
|
|
// Demander confirmation si beaucoup d'événements
|
|
const estimatedEvents = estimateTotalEvents(startDate, endDate, jours, tranches);
|
|
if (estimatedEvents > 50) {
|
|
const confirmed = confirm(
|
|
`Vous allez créer environ ${estimatedEvents} événements. Êtes-vous sûr de vouloir continuer ?`
|
|
);
|
|
if (!confirmed) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
try {
|
|
// Désactiver le formulaire
|
|
const submitBtn = event.target.querySelector('button[type="submit"]');
|
|
if (submitBtn) {
|
|
submitBtn.disabled = true;
|
|
submitBtn.innerHTML = '<span class="spinner-border spinner-border-sm me-2"></span>Création en cours...';
|
|
}
|
|
if (window.CRVI_OVERLAY && formElement) { window.CRVI_OVERLAY.show(formElement); }
|
|
|
|
// Préparer les données
|
|
const data = {
|
|
periode: periode,
|
|
mois_debut: moisDebut,
|
|
jours: jours,
|
|
plage_horaire: {
|
|
debut: plageDebut,
|
|
fin: plageFin
|
|
},
|
|
heures: heures, // Envoyer aussi les heures individuelles pour le backend
|
|
duree_permanence: dureePermanence, // '1h' ou '15min'
|
|
nb_tranches: nbTranches, // Nombre de tranches si 15min (1-4), null si 1h
|
|
langues: langues, // Langues sélectionnées (peut être un tableau vide)
|
|
informations_complementaires: informationsComplementaires
|
|
};
|
|
|
|
// Envoyer à l'API
|
|
const result = await apiFetch('intervenant/permanences', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data)
|
|
});
|
|
|
|
toastr.success(
|
|
`${result.events_created || 'Les'} permanence(s) ont été créées avec succès`,
|
|
'Succès',
|
|
{ timeOut: 5000 }
|
|
);
|
|
|
|
// Réinitialiser le formulaire
|
|
event.target.reset();
|
|
clearPreview();
|
|
|
|
// Recalculer l'aperçu avec les valeurs par défaut
|
|
setTimeout(updatePreview, 100);
|
|
|
|
} catch (error) {
|
|
console.error('Erreur lors de la création des permanences:', error);
|
|
toastr.error(
|
|
error.message || 'Erreur lors de la création des permanences',
|
|
'Erreur',
|
|
{ timeOut: 5000 }
|
|
);
|
|
} finally {
|
|
// Réactiver le formulaire
|
|
const submitBtn = event.target.querySelector('button[type="submit"]');
|
|
if (submitBtn) {
|
|
submitBtn.disabled = false;
|
|
submitBtn.innerHTML = '<i class="fas fa-save me-2"></i>Créer les permanences';
|
|
}
|
|
if (window.CRVI_OVERLAY && formElement) { window.CRVI_OVERLAY.hide(formElement); }
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Estime le nombre total d'événements qui seront créés
|
|
*/
|
|
function estimateTotalEvents(startDate, endDate, jours, tranches) {
|
|
const joursMapping = {
|
|
'lundi': 1,
|
|
'mardi': 2,
|
|
'mercredi': 3,
|
|
'jeudi': 4,
|
|
'vendredi': 5,
|
|
'samedi': 6,
|
|
'dimanche': 0
|
|
};
|
|
|
|
let total = 0;
|
|
const currentDate = new Date(startDate);
|
|
|
|
while (currentDate <= endDate) {
|
|
const dayOfWeek = currentDate.getDay();
|
|
const jourName = Object.keys(joursMapping).find(k => joursMapping[k] === dayOfWeek);
|
|
|
|
if (jours.includes(jourName)) {
|
|
total += tranches.length;
|
|
}
|
|
|
|
currentDate.setDate(currentDate.getDate() + 1);
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
/**
|
|
* Initialise le formulaire d'import CSV
|
|
*/
|
|
function initializeCsvImport() {
|
|
const csvForm = document.getElementById('import-csv-form');
|
|
if (!csvForm) {
|
|
console.warn('Formulaire import-csv-form non trouvé');
|
|
return;
|
|
}
|
|
|
|
csvForm.addEventListener('submit', handleCsvImportSubmit);
|
|
}
|
|
|
|
/**
|
|
* Gère la soumission du formulaire d'import CSV
|
|
*/
|
|
async function handleCsvImportSubmit(e) {
|
|
e.preventDefault();
|
|
|
|
const form = e.target;
|
|
const fileInput = document.getElementById('csv-file');
|
|
const submitBtn = document.getElementById('submit-csv-import-btn');
|
|
const resultContainer = document.getElementById('csv-import-result');
|
|
const csvForm = document.getElementById('import-csv-form');
|
|
|
|
if (!fileInput || !fileInput.files || fileInput.files.length === 0) {
|
|
toastr.error('Veuillez sélectionner un fichier CSV', 'Erreur');
|
|
return;
|
|
}
|
|
|
|
const file = fileInput.files[0];
|
|
|
|
// Vérifier que c'est un fichier CSV
|
|
if (!file.name.toLowerCase().endsWith('.csv')) {
|
|
toastr.error('Le fichier doit être au format CSV', 'Erreur');
|
|
return;
|
|
}
|
|
|
|
// Désactiver le bouton
|
|
submitBtn.disabled = true;
|
|
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Import en cours...';
|
|
|
|
// Créer FormData pour l'upload
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
|
|
try {
|
|
// Appel API pour l'import CSV
|
|
const url = '/wp-json/crvi/v1/intervenant/permanences/import-csv';
|
|
if (window.CRVI_OVERLAY && csvForm) { window.CRVI_OVERLAY.show(csvForm); }
|
|
const response = await fetch(url, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-WP-Nonce': window.wpApiSettings?.nonce || ''
|
|
},
|
|
body: formData
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (!response.ok || !data.success) {
|
|
const errorMessage = data.error?.message || data.message || 'Erreur lors de l\'import';
|
|
throw new Error(errorMessage);
|
|
}
|
|
|
|
// Afficher les résultats
|
|
displayCsvImportResult(data.data, resultContainer);
|
|
|
|
toastr.success(
|
|
`Import réussi : ${data.data.created || 0} créés, ${data.data.errors?.length || 0} erreurs`,
|
|
'Succès',
|
|
{ timeOut: 5000 }
|
|
);
|
|
|
|
// Réinitialiser le formulaire
|
|
form.reset();
|
|
|
|
} catch (error) {
|
|
console.error('Erreur lors de l\'import CSV:', error);
|
|
toastr.error(error.message || 'Une erreur est survenue lors de l\'import CSV', 'Erreur');
|
|
|
|
if (resultContainer) {
|
|
resultContainer.innerHTML = `
|
|
<div class="alert alert-danger">
|
|
<i class="fas fa-exclamation-circle me-2"></i>
|
|
<strong>Erreur :</strong> ${error.message || 'Une erreur est survenue lors de l\'import'}
|
|
</div>
|
|
`;
|
|
resultContainer.style.display = 'block';
|
|
}
|
|
} finally {
|
|
// Réactiver le bouton
|
|
submitBtn.disabled = false;
|
|
submitBtn.innerHTML = '<i class="fas fa-upload me-2"></i>Importer le CSV';
|
|
if (window.CRVI_OVERLAY && csvForm) { window.CRVI_OVERLAY.hide(csvForm); }
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Affiche les résultats de l'import CSV
|
|
*/
|
|
function displayCsvImportResult(result, container) {
|
|
if (!container) return;
|
|
|
|
let html = '';
|
|
|
|
if (result.errors && result.errors.length > 0) {
|
|
html += `
|
|
<div class="alert alert-warning">
|
|
<i class="fas fa-exclamation-triangle me-2"></i>
|
|
<strong>Attention :</strong> ${result.errors.length} erreur(s) détectée(s)
|
|
</div>
|
|
`;
|
|
|
|
if (result.errors.length <= 10) {
|
|
html += '<ul class="list-group mt-2">';
|
|
result.errors.forEach((error, index) => {
|
|
html += `
|
|
<li class="list-group-item">
|
|
<strong>Ligne ${error.line || '?'} :</strong> ${error.message || 'Erreur inconnue'}
|
|
</li>
|
|
`;
|
|
});
|
|
html += '</ul>';
|
|
}
|
|
}
|
|
|
|
if (result.created > 0) {
|
|
html += `
|
|
<div class="alert alert-success mt-3">
|
|
<i class="fas fa-check-circle me-2"></i>
|
|
<strong>Succès :</strong> ${result.created} permanence(s) créée(s) avec succès
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
container.innerHTML = html;
|
|
container.style.display = 'block';
|
|
}
|
|
|