Explorar o código

feat(filters): Ajoute un panneau de filtres avancés avec options de recherche et réinitialisation

stany.ferer hai 2 semanas
pai
achega
690bbb3ed5
Modificáronse 1 ficheiros con 375 adicións e 1 borrados
  1. 375 1
      core/views/pages/cms.proweb-dossiers.php

+ 375 - 1
core/views/pages/cms.proweb-dossiers.php

@@ -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>