Crvi/assets/js/modules/agenda-stats-table.js
2026-01-20 22:00:20 +01:00

403 lines
13 KiB
JavaScript

// Module ES6 pour le tableau de statistiques Agenda
import { apiFetch } from './agenda-api.js';
let currentPage = 1;
let currentFilters = {};
/**
* Initialise le module de tableau de stats
*/
export function initStatsTable() {
// Initialiser Select2 sur les filtres
if (typeof jQuery !== 'undefined' && jQuery.fn.select2) {
jQuery('.stats-filters .select2').select2({
width: '100%'
});
}
// Écouter le bouton de filtre
const filterBtn = document.getElementById('stats_filterBtn');
if (filterBtn) {
filterBtn.addEventListener('click', handleFilter);
}
// Écouter le bouton de réinitialisation
const resetBtn = document.getElementById('stats_resetFiltersBtn');
if (resetBtn) {
resetBtn.addEventListener('click', handleReset);
}
// Charger les événements au démarrage
loadEvents();
}
/**
* Collecte les filtres du formulaire
*/
function collectFilters() {
const filters = {};
// Date (convertir en date pour recherche exacte sur une journée)
const dateInput = document.getElementById('stats_date');
if (dateInput && dateInput.value) {
filters.date = dateInput.value;
}
// Local
const localSelect = document.getElementById('stats_local');
if (localSelect && localSelect.value) {
filters.local = localSelect.value;
}
// Personne (intervenant)
const personneSelect = document.getElementById('stats_personne');
if (personneSelect && personneSelect.value) {
filters.intervenant = personneSelect.value;
}
// Type d'intervention
const typeInterventionSelect = document.getElementById('stats_type_intervention');
if (typeInterventionSelect && typeInterventionSelect.value) {
filters.type_intervention = typeInterventionSelect.value;
}
// Bénéficiaire
const beneficiaireSelect = document.getElementById('stats_beneficiaire');
if (beneficiaireSelect && beneficiaireSelect.value) {
filters.beneficiaire = beneficiaireSelect.value;
}
// Langue
const langueSelect = document.getElementById('stats_langue');
if (langueSelect && langueSelect.value) {
filters.langue = langueSelect.value;
}
// Intervenant externe (traducteur)
const intervenantExterneSelect = document.getElementById('stats_intervenant_externe');
if (intervenantExterneSelect && intervenantExterneSelect.value) {
filters.traducteur = intervenantExterneSelect.value;
}
// Année
const anneeInput = document.getElementById('stats_annee');
if (anneeInput && anneeInput.value) {
filters.annee = parseInt(anneeInput.value, 10);
}
// Statut
const statutSelect = document.getElementById('stats_statut');
if (statutSelect && statutSelect.value) {
filters.statut = statutSelect.value;
}
// Filtre permanence
const permanenceCheckbox = document.getElementById('stats_filtre_permanence');
if (permanenceCheckbox && permanenceCheckbox.checked) {
filters.type = 'permanence';
}
return filters;
}
/**
* Gère le clic sur le bouton Filtrer
*/
function handleFilter() {
currentPage = 1;
loadEvents();
}
/**
* Gère le clic sur le bouton Réinitialiser
*/
function handleReset() {
// Réinitialiser tous les champs
const form = document.querySelector('.stats-filters');
if (form) {
form.reset();
// Réinitialiser Select2
if (typeof jQuery !== 'undefined' && jQuery.fn.select2) {
jQuery('.stats-filters .select2').val(null).trigger('change');
}
}
currentPage = 1;
currentFilters = {};
loadEvents();
}
/**
* Charge les événements depuis l'API
*/
async function loadEvents() {
const loadingIndicator = document.getElementById('stats-loading-indicator');
const table = document.getElementById('stats-events-table');
const tableBody = document.getElementById('stats-events-table-body');
const paginationContainer = document.getElementById('stats-pagination-container');
const tableWrapper = document.getElementById('stats-table-wrapper');
// Afficher le loading et masquer le tableau
if (loadingIndicator) {
loadingIndicator.style.display = 'flex';
}
if (tableWrapper) {
tableWrapper.style.display = 'none';
}
if (table) {
table.style.display = 'none';
}
if (paginationContainer) {
paginationContainer.style.display = 'none';
}
try {
// Collecter les filtres
currentFilters = collectFilters();
// Ajouter la pagination
const params = {
...currentFilters,
page: currentPage,
per_page: 20
};
// Appel API
const result = await apiFetch(`events/table?${new URLSearchParams(params).toString()}`);
// Afficher les résultats
displayEvents(result.events || []);
// Afficher le nombre d'événements filtrés comme total, et le nombre d'événements sur la page courante comme affichés
updateCounters(result.filtered || 0, (result.events || []).length);
displayPagination(result.page || 1, result.total_pages || 0);
// IMPORTANT: Masquer le loader EN PREMIER, puis afficher le tableau
if (loadingIndicator) {
loadingIndicator.style.display = 'none';
}
// Afficher le conteneur du tableau
if (tableWrapper) {
tableWrapper.style.display = 'block';
}
// Afficher le tableau
if (table) {
table.style.display = 'table';
}
// Afficher la pagination si nécessaire (seulement s'il y a plus d'une page)
if (paginationContainer && result.total_pages > 1) {
paginationContainer.style.display = 'block';
}
} catch (error) {
console.error('Erreur lors du chargement des événements:', error);
// En cas d'erreur, masquer le loader et afficher le tableau avec le message d'erreur
if (loadingIndicator) {
loadingIndicator.style.display = 'none';
}
if (tableWrapper) {
tableWrapper.style.display = 'block';
}
if (table) {
table.style.display = 'table';
}
if (tableBody) {
tableBody.innerHTML = `<tr><td colspan="8" style="text-align: center; color: red; padding: 20px;">Erreur lors du chargement des données</td></tr>`;
}
}
}
/**
* Affiche les événements dans le tableau
*/
function displayEvents(events) {
const tableBody = document.getElementById('stats-events-table-body');
if (!tableBody) return;
if (events.length === 0) {
tableBody.innerHTML = '<tr><td colspan="8" style="text-align: center;">Aucun événement trouvé</td></tr>';
return;
}
let html = '';
events.forEach(event => {
const dateHeure = event.date_rdv && event.heure_rdv
? `${formatDate(event.date_rdv)} ${event.heure_rdv}`
: '-';
const statutLabel = formatStatutLabel(event.statut);
const statutClass = getStatutBadgeClass(event.statut);
html += `
<tr>
<td style="font-weight: 500; color: #495057;">${event.id || '-'}</td>
<td>${dateHeure}</td>
<td>${event.intervenant_nom || '-'}</td>
<td>${event.beneficiaire_nom || '-'}</td>
<td>${event.traducteur_nom || '-'}</td>
<td>${event.langue || '-'}</td>
<td><span class="badge badge-${statutClass}">${statutLabel}</span></td>
<td style="max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="${escapeHtml(event.commentaire || '')}">${event.commentaire || '-'}</td>
</tr>
`;
});
tableBody.innerHTML = html;
}
/**
* Formate une date au format français
*/
function formatDate(dateString) {
if (!dateString) return '-';
const date = new Date(dateString + 'T00:00:00');
return date.toLocaleDateString('fr-FR', {
year: 'numeric',
month: '2-digit',
day: '2-digit'
});
}
/**
* Formate le label du statut pour l'affichage
*/
function formatStatutLabel(statut) {
if (!statut) return '-';
const statutMap = {
'prevu': 'Prévu',
'annule': 'Annulé',
'non_tenu': 'Non tenu',
'cloture': 'Clôturé',
'absence': 'Absence'
};
return statutMap[statut.toLowerCase()] || statut;
}
/**
* Retourne la classe Bootstrap pour le badge de statut
*/
function getStatutBadgeClass(statut) {
if (!statut) return 'secondary';
const statutMap = {
'prevu': 'success',
'annule': 'danger',
'non_tenu': 'warning',
'cloture': 'secondary',
'absence': 'warning'
};
return statutMap[statut.toLowerCase()] || 'secondary';
}
/**
* Échappe les caractères HTML pour éviter les injections XSS
*/
function escapeHtml(text) {
if (!text) return '';
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
/**
* Met à jour les compteurs
*/
function updateCounters(total, filtered) {
const totalCount = document.getElementById('total-count');
const filteredCount = document.getElementById('filtered-count');
if (totalCount) {
totalCount.textContent = total;
}
if (filteredCount) {
filteredCount.textContent = filtered;
}
}
/**
* Affiche la pagination (uniquement les numéros de page)
*/
function displayPagination(currentPageNum, totalPages) {
const paginationContainer = document.getElementById('stats-pagination-container');
if (!paginationContainer || totalPages <= 1) {
if (paginationContainer) {
paginationContainer.style.display = 'none';
}
return;
}
// Afficher le conteneur
paginationContainer.style.display = 'block';
let html = '<nav aria-label="Pagination" style="display: flex; justify-content: center; align-items: center;"><ul class="pagination" style="margin: 0; display: flex; list-style: none; gap: 5px; flex-wrap: wrap;">';
// Afficher toutes les pages si peu nombreuses, sinon un sous-ensemble
const maxPagesToShow = 10;
if (totalPages <= maxPagesToShow) {
// Afficher toutes les pages
for (let i = 1; i <= totalPages; i++) {
if (i === currentPageNum) {
html += `<li class="page-item active"><span class="page-link" style="padding: 8px 12px; border: 1px solid #007bff; border-radius: 4px; background: #007bff; color: #fff; font-weight: 600;">${i}</span></li>`;
} else {
html += `<li class="page-item"><a class="page-link" href="#" data-page="${i}" style="padding: 8px 12px; border: 1px solid #dee2e6; border-radius: 4px; text-decoration: none; color: #007bff; background: #fff; cursor: pointer;">${i}</a></li>`;
}
}
} else {
// Afficher un sous-ensemble de pages avec ellipses
let startPage = Math.max(1, currentPageNum - Math.floor(maxPagesToShow / 2));
let endPage = Math.min(totalPages, startPage + maxPagesToShow - 1);
if (endPage - startPage < maxPagesToShow - 1) {
startPage = Math.max(1, endPage - maxPagesToShow + 1);
}
// Première page
if (startPage > 1) {
html += `<li class="page-item"><a class="page-link" href="#" data-page="1" style="padding: 8px 12px; border: 1px solid #dee2e6; border-radius: 4px; text-decoration: none; color: #007bff; background: #fff; cursor: pointer;">1</a></li>`;
if (startPage > 2) {
html += `<li class="page-item disabled"><span class="page-link" style="padding: 8px 12px; color: #6c757d;">...</span></li>`;
}
}
// Pages du milieu
for (let i = startPage; i <= endPage; i++) {
if (i === currentPageNum) {
html += `<li class="page-item active"><span class="page-link" style="padding: 8px 12px; border: 1px solid #007bff; border-radius: 4px; background: #007bff; color: #fff; font-weight: 600;">${i}</span></li>`;
} else {
html += `<li class="page-item"><a class="page-link" href="#" data-page="${i}" style="padding: 8px 12px; border: 1px solid #dee2e6; border-radius: 4px; text-decoration: none; color: #007bff; background: #fff; cursor: pointer;">${i}</a></li>`;
}
}
// Dernière page
if (endPage < totalPages) {
if (endPage < totalPages - 1) {
html += `<li class="page-item disabled"><span class="page-link" style="padding: 8px 12px; color: #6c757d;">...</span></li>`;
}
html += `<li class="page-item"><a class="page-link" href="#" data-page="${totalPages}" style="padding: 8px 12px; border: 1px solid #dee2e6; border-radius: 4px; text-decoration: none; color: #007bff; background: #fff; cursor: pointer;">${totalPages}</a></li>`;
}
}
html += '</ul></nav>';
paginationContainer.innerHTML = html;
// Ajouter les écouteurs d'événements
const pageLinks = paginationContainer.querySelectorAll('a[data-page]');
pageLinks.forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const page = parseInt(link.getAttribute('data-page'), 10);
if (page && page !== currentPage) {
currentPage = page;
loadEvents();
}
});
});
}