|
|
@@ -30,9 +30,117 @@ echo core::filAriane(array(
|
|
|
));
|
|
|
|
|
|
?>
|
|
|
+
|
|
|
+<!-- Panel de filtres avancés avec checkboxes multi-sélection -->
|
|
|
+<div class="card mb-3" id="advancedFiltersPanel">
|
|
|
+ <div class="card-header d-flex justify-content-between align-items-center" style="cursor: pointer;" onclick="toggleAdvancedFilters()">
|
|
|
+ <span><i class="bi bi-funnel"></i> Filtres avancés (sélection multiple)</span>
|
|
|
+ <i class="bi bi-chevron-up" id="advancedFiltersIcon"></i>
|
|
|
+ </div>
|
|
|
+ <div class="card-body" id="advancedFiltersBody">
|
|
|
+ <div class="row">
|
|
|
+ <div class="col-lg-2 col-md-4 mb-2">
|
|
|
+ <label class="form-label fw-bold">Année</label>
|
|
|
+ <input type="text" class="form-control form-control-sm mb-1 filter-search-input" id="search-exe_ref" placeholder="Rechercher..." oninput="filterCheckboxList('exe_ref')">
|
|
|
+ <div class="filter-checkbox-container" id="filter-exe_ref"><small class="text-muted">Chargement...</small></div>
|
|
|
+ </div>
|
|
|
+ <div class="col-lg-2 col-md-4 mb-2">
|
|
|
+ <label class="form-label fw-bold">Catégorie</label>
|
|
|
+ <input type="text" class="form-control form-control-sm mb-1 filter-search-input" id="search-catprest_lib" placeholder="Rechercher..." oninput="filterCheckboxList('catprest_lib')">
|
|
|
+ <div class="filter-checkbox-container" id="filter-catprest_lib"><small class="text-muted">Chargement...</small></div>
|
|
|
+ </div>
|
|
|
+ <div class="col-lg-2 col-md-4 mb-2">
|
|
|
+ <label class="form-label fw-bold">Prestation</label>
|
|
|
+ <input type="text" class="form-control form-control-sm mb-1 filter-search-input" id="search-prest_lib" placeholder="Rechercher..." oninput="filterCheckboxList('prest_lib')">
|
|
|
+ <div class="filter-checkbox-container" id="filter-prest_lib"><small class="text-muted">Chargement...</small></div>
|
|
|
+ </div>
|
|
|
+ <div class="col-lg-2 col-md-4 mb-2">
|
|
|
+ <label class="form-label fw-bold">Etat</label>
|
|
|
+ <input type="text" class="form-control form-control-sm mb-1 filter-search-input" id="search-etdoss_lib" placeholder="Rechercher..." oninput="filterCheckboxList('etdoss_lib')">
|
|
|
+ <div class="filter-checkbox-container" id="filter-etdoss_lib"><small class="text-muted">Chargement...</small></div>
|
|
|
+ </div>
|
|
|
+ <div class="col-lg-2 col-md-4 mb-2">
|
|
|
+ <label class="form-label fw-bold">Commission</label>
|
|
|
+ <input type="text" class="form-control form-control-sm mb-1 filter-search-input" id="search-comm_ref" placeholder="Rechercher..." oninput="filterCheckboxList('comm_ref')">
|
|
|
+ <div class="filter-checkbox-container" id="filter-comm_ref"><small class="text-muted">Chargement...</small></div>
|
|
|
+ </div>
|
|
|
+ <div class="col-lg-2 col-md-4 mb-2">
|
|
|
+ <label class="form-label fw-bold">Clôturé</label>
|
|
|
+ <input type="text" class="form-control form-control-sm mb-1 filter-search-input" id="search-prest_closed" placeholder="Rechercher..." oninput="filterCheckboxList('prest_closed')">
|
|
|
+ <div class="filter-checkbox-container" id="filter-prest_closed"><small class="text-muted">Chargement...</small></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="row">
|
|
|
+ <div class="col-lg-2 col-md-4 mb-2">
|
|
|
+ <label class="form-label fw-bold">Complément</label>
|
|
|
+ <input type="text" class="form-control form-control-sm mb-1 filter-search-input" id="search-prest_lib2" placeholder="Rechercher..." oninput="filterCheckboxList('prest_lib2')">
|
|
|
+ <div class="filter-checkbox-container" id="filter-prest_lib2"><small class="text-muted">Chargement...</small></div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="mt-2">
|
|
|
+ <button type="button" class="btn btn-sm btn-outline-secondary" onclick="clearAllAdvancedFilters()">
|
|
|
+ <i class="bi bi-x-circle"></i> Réinitialiser les filtres
|
|
|
+ </button>
|
|
|
+ <span class="ms-3 text-muted" id="filterInfo"></span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</div>
|
|
|
+
|
|
|
+<style>
|
|
|
+ .filter-checkbox-container {
|
|
|
+ max-height: 150px;
|
|
|
+ overflow-y: auto;
|
|
|
+ border: 1px solid #dee2e6;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 8px;
|
|
|
+ background: #fff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-checkbox-container .form-check {
|
|
|
+ margin-bottom: 2px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-checkbox-container .form-check-label {
|
|
|
+ font-size: 0.85rem;
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-checkbox-container .form-check-input {
|
|
|
+ cursor: pointer;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-checkbox-container .filter-option-disabled {
|
|
|
+ opacity: 0.5;
|
|
|
+ text-decoration: line-through;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-checkbox-container .filter-option-count {
|
|
|
+ font-size: 0.75rem;
|
|
|
+ color: #6c757d;
|
|
|
+ margin-left: 4px;
|
|
|
+ }
|
|
|
+
|
|
|
+ #advancedFiltersPanel .card-header:hover {
|
|
|
+ background-color: #f8f9fa;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-search-input {
|
|
|
+ font-size: 0.8rem;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-search-input::placeholder {
|
|
|
+ font-style: italic;
|
|
|
+ color: #adb5bd;
|
|
|
+ }
|
|
|
+
|
|
|
+ .filter-checkbox-hidden {
|
|
|
+ display: none !important;
|
|
|
+ }
|
|
|
+</style>
|
|
|
+
|
|
|
<div>
|
|
|
<table
|
|
|
- id="ProWebSalary"
|
|
|
+ id="ProWebDossiers"
|
|
|
class="table-striped table-hover table-sm"
|
|
|
data-sort-name="hismo_date_crea"
|
|
|
data-sort-order="desc"
|
|
|
@@ -74,6 +182,13 @@ echo core::filAriane(array(
|
|
|
currency: 'EUR',
|
|
|
});
|
|
|
|
|
|
+ // Données originales et filtrées
|
|
|
+ let originalData = [];
|
|
|
+ let currentFilters = {};
|
|
|
+
|
|
|
+ // Champs pour les filtres avancés avec checkboxes
|
|
|
+ const advancedFilterFields = ['exe_ref', 'catprest_lib', 'prest_lib', 'etdoss_lib', 'comm_ref', 'prest_closed', 'prest_lib2'];
|
|
|
+
|
|
|
function dataFormatter(value) {
|
|
|
return euro.format(value);
|
|
|
}
|
|
|
@@ -135,4 +250,263 @@ echo core::filAriane(array(
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ // Toggle du panneau de filtres avancés
|
|
|
+ function toggleAdvancedFilters() {
|
|
|
+ const body = document.getElementById('advancedFiltersBody');
|
|
|
+ const icon = document.getElementById('advancedFiltersIcon');
|
|
|
+ if (body.style.display === 'none') {
|
|
|
+ body.style.display = 'block';
|
|
|
+ icon.className = 'bi bi-chevron-up';
|
|
|
+ } else {
|
|
|
+ body.style.display = 'none';
|
|
|
+ icon.className = 'bi bi-chevron-down';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Filtrer la liste des checkboxes par recherche textuelle
|
|
|
+ function filterCheckboxList(field) {
|
|
|
+ const searchInput = document.getElementById(`search-${field}`);
|
|
|
+ const container = document.getElementById(`filter-${field}`);
|
|
|
+ if (!searchInput || !container) return;
|
|
|
+
|
|
|
+ const searchText = searchInput.value.toLowerCase().trim();
|
|
|
+ const checkboxes = container.querySelectorAll('.form-check');
|
|
|
+
|
|
|
+ checkboxes.forEach(checkDiv => {
|
|
|
+ const label = checkDiv.querySelector('.form-check-label');
|
|
|
+ if (label) {
|
|
|
+ // Exclure le compteur entre parenthèses de la recherche
|
|
|
+ const countSpan = label.querySelector('.filter-option-count');
|
|
|
+ let labelText = label.textContent.toLowerCase();
|
|
|
+ if (countSpan) {
|
|
|
+ labelText = labelText.replace(countSpan.textContent.toLowerCase(), '').trim();
|
|
|
+ }
|
|
|
+ if (searchText === '' || labelText.includes(searchText)) {
|
|
|
+ checkDiv.classList.remove('filter-checkbox-hidden');
|
|
|
+ } else {
|
|
|
+ checkDiv.classList.add('filter-checkbox-hidden');
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // Réinitialiser les barres de recherche
|
|
|
+ function clearSearchInputs() {
|
|
|
+ advancedFilterFields.forEach(field => {
|
|
|
+ const searchInput = document.getElementById(`search-${field}`);
|
|
|
+ if (searchInput) {
|
|
|
+ searchInput.value = '';
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // Formater la valeur pour l'affichage
|
|
|
+ function formatFilterValue(field, value) {
|
|
|
+ if (field === 'prest_closed') {
|
|
|
+ return value == 1 ? 'Clôturé' : 'Ouvert';
|
|
|
+ }
|
|
|
+ return value || '(vide)';
|
|
|
+ }
|
|
|
+
|
|
|
+ // Obtenir les valeurs uniques pour un champ dans les données filtrées
|
|
|
+ function getUniqueValues(data, field) {
|
|
|
+ const values = new Map();
|
|
|
+ data.forEach(row => {
|
|
|
+ const val = row[field];
|
|
|
+ const key = val === null || val === undefined || val === '' ? '__empty__' : String(val);
|
|
|
+ values.set(key, (values.get(key) || 0) + 1);
|
|
|
+ });
|
|
|
+ return values;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Obtenir les données filtrées selon les filtres actuels (sauf un champ exclu)
|
|
|
+ function getFilteredData(excludeField = null) {
|
|
|
+ return originalData.filter(row => {
|
|
|
+ for (const [field, selectedValues] of Object.entries(currentFilters)) {
|
|
|
+ if (field === excludeField) continue;
|
|
|
+ if (selectedValues.length === 0) continue;
|
|
|
+
|
|
|
+ const rowValue = row[field];
|
|
|
+ const rowKey = rowValue === null || rowValue === undefined || rowValue === '' ? '__empty__' : String(rowValue);
|
|
|
+
|
|
|
+ if (!selectedValues.includes(rowKey)) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // Mettre à jour les checkboxes d'un filtre
|
|
|
+ function updateFilterCheckboxes(field) {
|
|
|
+ const container = document.getElementById(`filter-${field}`);
|
|
|
+ if (!container) return;
|
|
|
+
|
|
|
+ // Obtenir les données filtrées en excluant ce champ
|
|
|
+ const filteredData = getFilteredData(field);
|
|
|
+ const availableValues = getUniqueValues(filteredData, field);
|
|
|
+ const allValues = getUniqueValues(originalData, field);
|
|
|
+
|
|
|
+ // Sauvegarder les valeurs actuellement sélectionnées
|
|
|
+ const currentSelected = currentFilters[field] || [];
|
|
|
+
|
|
|
+ container.innerHTML = '';
|
|
|
+
|
|
|
+ // Trier les valeurs
|
|
|
+ const sortedKeys = Array.from(allValues.keys()).sort((a, b) => {
|
|
|
+ if (a === '__empty__') return 1;
|
|
|
+ if (b === '__empty__') return -1;
|
|
|
+ return String(a).localeCompare(String(b));
|
|
|
+ });
|
|
|
+
|
|
|
+ sortedKeys.forEach(key => {
|
|
|
+ const count = availableValues.get(key) || 0;
|
|
|
+ const totalCount = allValues.get(key);
|
|
|
+ const isDisabled = count === 0;
|
|
|
+ const isChecked = currentSelected.includes(key);
|
|
|
+
|
|
|
+ const div = document.createElement('div');
|
|
|
+ div.className = 'form-check';
|
|
|
+
|
|
|
+ const input = document.createElement('input');
|
|
|
+ input.type = 'checkbox';
|
|
|
+ input.className = 'form-check-input';
|
|
|
+ input.id = `chk-${field}-${key}`;
|
|
|
+ input.value = key;
|
|
|
+ input.checked = isChecked;
|
|
|
+ input.dataset.field = field;
|
|
|
+ input.addEventListener('change', onFilterCheckboxChange);
|
|
|
+
|
|
|
+ const label = document.createElement('label');
|
|
|
+ label.className = 'form-check-label' + (isDisabled && !isChecked ? ' filter-option-disabled' : '');
|
|
|
+ label.htmlFor = input.id;
|
|
|
+ label.innerHTML = `${formatFilterValue(field, key === '__empty__' ? '' : key)} <span class="filter-option-count">(${count}/${totalCount})</span>`;
|
|
|
+
|
|
|
+ div.appendChild(input);
|
|
|
+ div.appendChild(label);
|
|
|
+ container.appendChild(div);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ // Mettre à jour tous les filtres
|
|
|
+ function updateAllFilterCheckboxes() {
|
|
|
+ advancedFilterFields.forEach(field => {
|
|
|
+ updateFilterCheckboxes(field);
|
|
|
+ });
|
|
|
+ updateFilterInfo();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Quand une checkbox change
|
|
|
+ function onFilterCheckboxChange(event) {
|
|
|
+ const field = event.target.dataset.field;
|
|
|
+ const value = event.target.value;
|
|
|
+ const isChecked = event.target.checked;
|
|
|
+
|
|
|
+ if (!currentFilters[field]) {
|
|
|
+ currentFilters[field] = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (isChecked) {
|
|
|
+ if (!currentFilters[field].includes(value)) {
|
|
|
+ currentFilters[field].push(value);
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ currentFilters[field] = currentFilters[field].filter(v => v !== value);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Mettre à jour les autres filtres (cascade)
|
|
|
+ advancedFilterFields.forEach(f => {
|
|
|
+ if (f !== field) {
|
|
|
+ updateFilterCheckboxes(f);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // Appliquer le filtre à la table
|
|
|
+ applyAdvancedFilters();
|
|
|
+ updateFilterInfo();
|
|
|
+ }
|
|
|
+
|
|
|
+ // Appliquer les filtres avancés à la table
|
|
|
+ function applyAdvancedFilters() {
|
|
|
+ const $table = $('#ProWebDossiers');
|
|
|
+
|
|
|
+ // Filtrer les données
|
|
|
+ const filteredData = getFilteredData();
|
|
|
+
|
|
|
+ // Recharger la table avec les données filtrées
|
|
|
+ $table.bootstrapTable('load', filteredData);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Réinitialiser tous les filtres avancés
|
|
|
+ function clearAllAdvancedFilters() {
|
|
|
+ currentFilters = {};
|
|
|
+ advancedFilterFields.forEach(field => {
|
|
|
+ currentFilters[field] = [];
|
|
|
+ });
|
|
|
+
|
|
|
+ // Réinitialiser les barres de recherche
|
|
|
+ clearSearchInputs();
|
|
|
+
|
|
|
+ // Recharger les données originales
|
|
|
+ $('#ProWebDossiers').bootstrapTable('load', originalData);
|
|
|
+
|
|
|
+ // Mettre à jour les checkboxes
|
|
|
+ updateAllFilterCheckboxes();
|
|
|
+
|
|
|
+ // Réinitialiser aussi les filtres de la table
|
|
|
+ $('#ProWebDossiers').bootstrapTable('clearFilterControl');
|
|
|
+ }
|
|
|
+
|
|
|
+ // Mettre à jour l'info des filtres actifs
|
|
|
+ function updateFilterInfo() {
|
|
|
+ const activeFilters = [];
|
|
|
+ for (const [field, values] of Object.entries(currentFilters)) {
|
|
|
+ if (values.length > 0) {
|
|
|
+ const fieldNames = {
|
|
|
+ 'exe_ref': 'Année',
|
|
|
+ 'catprest_lib': 'Catégorie',
|
|
|
+ 'prest_lib': 'Prestation',
|
|
|
+ 'etdoss_lib': 'Etat',
|
|
|
+ 'comm_ref': 'Commission',
|
|
|
+ 'prest_closed': 'Clôturé',
|
|
|
+ 'prest_lib2': 'Complément'
|
|
|
+ };
|
|
|
+ activeFilters.push(`${fieldNames[field]}: ${values.length} sélection(s)`);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const infoEl = document.getElementById('filterInfo');
|
|
|
+ if (activeFilters.length > 0) {
|
|
|
+ const filteredCount = getFilteredData().length;
|
|
|
+ infoEl.innerHTML = `<strong>${filteredCount}</strong> dossier(s) affiché(s) | Filtres actifs: ${activeFilters.join(', ')}`;
|
|
|
+ } else {
|
|
|
+ infoEl.innerHTML = `<strong>${originalData.length}</strong> dossier(s) au total`;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Initialisation
|
|
|
+ $(document).ready(function() {
|
|
|
+ const $table = $('#ProWebDossiers');
|
|
|
+
|
|
|
+ // Attendre que les données soient chargées
|
|
|
+ $table.on('load-success.bs.table', function(e, data) {
|
|
|
+ // Sauvegarder les données originales
|
|
|
+ originalData = data;
|
|
|
+
|
|
|
+ // Initialiser les filtres
|
|
|
+ advancedFilterFields.forEach(field => {
|
|
|
+ currentFilters[field] = [];
|
|
|
+ });
|
|
|
+
|
|
|
+ // Construire les checkboxes des filtres
|
|
|
+ updateAllFilterCheckboxes();
|
|
|
+ });
|
|
|
+
|
|
|
+ // Synchroniser avec les filtres de la table Bootstrap
|
|
|
+ $table.on('column-search.bs.table', function(e, field, text) {
|
|
|
+ // Quand un filtre de colonne change, mettre à jour les compteurs
|
|
|
+ setTimeout(updateFilterInfo, 100);
|
|
|
+ });
|
|
|
+ });
|
|
|
</script>
|