| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598 |
- <?php
- $jsonTarget = "/json.php?file=documents";
- if (debug::isFile("debug")) {
- debug::log(debug::getBadge($jsonTarget, "OUVRIR LE JSON : " . $jsonTarget), "JSON chargé en arrière plan");
- }
- ?>
- <header class="d-flex flex-column flex-md-row align-items-md-center p-3 bg-light ">
- <h2 class="bd-title" id="content">
- <span>Listes des documents</span>
- </h2>
- <?php if (access::ifAccesss("add-document")) { ?>
- <div class="fix-container-button-nav">
- <a href="/add-document.html"><button type="submit" class="btn btn-outline-success btn-sm"><?php icon::getFont(["icon" => "bi bi-file-earmark-plus"]) ?> Ajouter un document</button></a>
- </div>
- <?php } ?>
- </header>
- <?php
- echo core::filAriane(array(
- "current" => "Listes des documents",
- "arbo" => array(
- "Documents" => NULL,
- "Listes des documents" => "/documents.html"
- ),
- "refresh-json" => "documents"
- ));
- ?>
- <!-- Panel de filtres avancés -->
- <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</span>
- <i class="bi bi-chevron-up" id="advancedFiltersIcon"></i>
- </div>
- <div class="card-body" id="advancedFiltersBody">
- <div class="row">
- <!-- Filtre par mots-clés sur le titre -->
- <div class="col-lg-3 col-md-6 mb-3">
- <label class="form-label fw-bold">Mots-clés dans le Titre</label>
- <input type="text" class="form-control form-control-sm" id="search-titre-keywords" placeholder="Ex: facture contrat">
- <small class="text-muted">Mots-clés séparés par espaces</small>
- </div>
- <!-- Filtre par plage de dates -->
- <div class="col-lg-3 col-md-6 mb-3">
- <label class="form-label fw-bold">Période (Date)</label>
- <div class="d-flex align-items-center">
- <input type="date" id="dateDebut" class="form-control form-control-sm me-2">
- <span class="me-2">à</span>
- <input type="date" id="dateFin" class="form-control form-control-sm">
- </div>
- </div>
- <!-- Filtre par échéance -->
- <div class="col-lg-3 col-md-6 mb-3">
- <label class="form-label fw-bold">Période (Échéance)</label>
- <div class="d-flex align-items-center">
- <input type="date" id="deadlineDebut" class="form-control form-control-sm me-2">
- <span class="me-2">à</span>
- <input type="date" id="deadlineFin" class="form-control form-control-sm">
- </div>
- </div>
- </div>
- <div class="row">
- <!-- Filtre par montant -->
- <div class="col-lg-3 col-md-6 mb-3">
- <label class="form-label fw-bold">Recherche montant</label>
- <input type="text" class="form-control form-control-sm" id="searchMontant" placeholder="Ex: 5086 ou >1000">
- <small class="text-muted">Recherche exacte ou avec opérateur</small>
- </div>
- <!-- Filtre par plage de montant -->
- <div class="col-lg-3 col-md-6 mb-3">
- <label class="form-label fw-bold">Plage Montant</label>
- <div class="d-flex align-items-center">
- <input type="number" step="0.01" id="minMontant" class="form-control form-control-sm me-2" placeholder="Min" style="width: 100px;">
- <span class="me-2">à</span>
- <input type="number" step="0.01" id="maxMontant" class="form-control form-control-sm" placeholder="Max" style="width: 100px;">
- </div>
- </div>
- <!-- Filtre par tags -->
- <div class="col-lg-3 col-md-6 mb-3">
- <label class="form-label fw-bold">Tags</label>
- <input type="text" class="form-control form-control-sm" id="searchTags" placeholder="Ex: urgent important">
- <small class="text-muted">Mots-clés séparés par espaces</small>
- </div>
- </div>
- <div class="row">
- <!-- Filtre par Type -->
- <div class="col-lg-3 col-md-6 mb-3">
- <label class="form-label fw-bold">Type</label>
- <select class="form-select form-select-sm" id="filterType">
- <option value="">-- Tous les types --</option>
- </select>
- </div>
- <!-- Filtre par Attachement -->
- <div class="col-lg-3 col-md-6 mb-3">
- <label class="form-label fw-bold">Attachement</label>
- <select class="form-select form-select-sm" id="filterAttach">
- <option value="">-- Tous les attachements --</option>
- </select>
- </div>
- <!-- Filtre par Attribution -->
- <div class="col-lg-3 col-md-6 mb-3">
- <label class="form-label fw-bold">Attribution</label>
- <select class="form-select form-select-sm" id="filterAssign">
- <option value="">-- Toutes les attributions --</option>
- </select>
- </div>
- <!-- Filtre par Statut -->
- <div class="col-lg-3 col-md-6 mb-3">
- <label class="form-label fw-bold">Statut</label>
- <select class="form-select form-select-sm" id="filterStatut">
- <option value="">-- Tous les statuts --</option>
- </select>
- </div>
- </div>
- <div class="mt-2">
- <button type="button" class="btn btn-sm btn-primary me-2" onclick="applyAdvancedFilters()">
- <i class="bi bi-funnel-fill"></i> Appliquer les filtres
- </button>
- <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>
- #advancedFiltersPanel .card-header:hover {
- background-color: #f8f9fa;
- }
- </style>
- <div>
- <table
- id="table"
- class="table-striped table-hover table-sm"
- data-page-size="25"
- data-toggle="table"
- data-show-columns="true"
- data-search="true"
- data-buttons-align="left"
- data-show-footer="true"
- data-pagination="true"
- data-flat="true"
- data-sort-name="date"
- data-sort-order="desc"
- data-show-export="true"
- data-page-list="[25, 50, 100, 250, all]"
- data-url="<?php echo $jsonTarget ?>">
- <thead>
- <tr>
- <th data-sortable="true" data-field="date" data-width="90">Date</th>
- <th data-sortable="true" data-field="titre">Titre</th>
- <th data-sortable="true" data-field="label" data-width="125">Type</th>
- <th data-sortable="true" data-field="attach" data-width="180">Attachement</th>
- <th data-sortable="true" data-field="deadline" data-width="90">Echéance</th>
- <th data-sortable="true" data-field="tags" data-width="200">Tags</th>
- <th data-sortable="true" data-field="assign" data-width="200">Attribution</th>
- <th data-sortable="true" data-field="done" data-width="100">Statut</th>
- <th data-sortable="true" data-field="montant" data-width="110" data-formatter="dataFormatter" data-footer-formatter="montantFormatter">Montant</th>
- <th data-field="id" data-formatter="selectFormatter" data-width="60"></th>
- </tr>
- </thead>
- </table>
- </div>
- <script>
- <?php
- if (core::ifGet("tag")) { ?>
- $(function() {
- tagValue = "<?php echo core::getGet("tag"); ?>";
- if (tagValue) {
- function applyTagFilter() {
- var $input = $('#table input.bootstrap-table-filter-control-tags');
- console.log('input:', $input.length, 'valeur:', tagValue);
- if ($input.length) {
- $input.val(tagValue).trigger('input');
- }
- }
- $('#table').on('post-body.bs.table post-header.bs.table', applyTagFilter);
- setTimeout(applyTagFilter, 800);
- }
- });
- <?php
- }
- ?>
- function selectFormatter(value, row) {
- return '<a href="/document-' + row.id + '.html"><button type="submit" class="btn btn-outline-primary btn-sm">Ouvrir</button></a>';
- }
- let euro = Intl.NumberFormat('de-DE', {
- style: 'currency',
- currency: 'EUR',
- });
- function dataFormatter(value) {
- return euro.format(value);
- }
- function montantFormatter(data) {
- var total = 0;
- data.forEach(function(row) {
- total += parseFloat(row.montant);
- });
- return parseFloat(total) === 0 ? euro.format(0.00) : euro.format(total.toFixed(2));
- }
- // Données originales
- let originalData = [];
- // 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';
- }
- }
- // Convertir une date en objet Date (supporte DD/MM/YYYY et YYYY-MM-DD)
- function parseDate(dateStr) {
- if (!dateStr) return null;
- // Format DD/MM/YYYY
- if (dateStr.includes('/')) {
- const parts = dateStr.split('/');
- if (parts.length === 3) {
- return new Date(parseInt(parts[2]), parseInt(parts[1]) - 1, parseInt(parts[0]), 0, 0, 0);
- }
- }
- // Format YYYY-MM-DD (format ISO/MySQL)
- if (dateStr.includes('-')) {
- const parts = dateStr.split('-');
- if (parts.length === 3 && parts[0].length === 4) {
- return new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]), 0, 0, 0);
- }
- }
- return null;
- }
- // Fonction de filtre pour les montants
- function filterMontant(searchValue, numValue) {
- if (!searchValue || searchValue.trim() === '') return true;
- const absNumValue = Math.abs(numValue);
- let searchNormalized = searchValue.trim()
- .replace(/\s/g, '')
- .replace(/€/g, '')
- .replace(/\./g, '')
- .replace(/,/g, '.');
- let operator = null;
- if (searchNormalized.startsWith('>=')) {
- operator = '>=';
- searchNormalized = searchNormalized.substring(2);
- } else if (searchNormalized.startsWith('<=')) {
- operator = '<=';
- searchNormalized = searchNormalized.substring(2);
- } else if (searchNormalized.startsWith('>')) {
- operator = '>';
- searchNormalized = searchNormalized.substring(1);
- } else if (searchNormalized.startsWith('<')) {
- operator = '<';
- searchNormalized = searchNormalized.substring(1);
- } else if (searchNormalized.startsWith('=')) {
- operator = '=';
- searchNormalized = searchNormalized.substring(1);
- }
- const searchNum = parseFloat(searchNormalized);
- if (isNaN(searchNum)) {
- const formattedValue = euro.format(numValue).toLowerCase();
- return formattedValue.includes(searchValue.toLowerCase());
- }
- if (operator) {
- switch (operator) {
- case '>':
- return numValue > searchNum;
- case '<':
- return numValue < searchNum;
- case '>=':
- return numValue >= searchNum;
- case '<=':
- return numValue <= searchNum;
- case '=':
- return Math.abs(numValue - searchNum) < 0.01;
- }
- }
- if (Math.abs(numValue - searchNum) < 0.01 || Math.abs(absNumValue - searchNum) < 0.01) {
- return true;
- }
- const intPart = Math.floor(absNumValue);
- const searchInt = Math.floor(searchNum);
- if (intPart === searchInt) {
- return true;
- }
- const numStr = absNumValue.toFixed(2);
- const searchStr = searchInt.toString();
- return numStr.startsWith(searchStr) || intPart.toString().includes(searchStr);
- }
- // Appliquer tous les filtres avancés
- function applyAdvancedFilters() {
- // Récupérer les dates
- const dateDebutStr = document.getElementById('dateDebut').value;
- const dateFinStr = document.getElementById('dateFin').value;
- const deadlineDebutStr = document.getElementById('deadlineDebut').value;
- const deadlineFinStr = document.getElementById('deadlineFin').value;
- let dateDebut = null,
- dateFin = null,
- deadlineDebut = null,
- deadlineFin = null;
- if (dateDebutStr) {
- const parts = dateDebutStr.split('-');
- dateDebut = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]), 0, 0, 0);
- }
- if (dateFinStr) {
- const parts = dateFinStr.split('-');
- dateFin = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]), 23, 59, 59);
- }
- if (deadlineDebutStr) {
- const parts = deadlineDebutStr.split('-');
- deadlineDebut = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]), 0, 0, 0);
- }
- if (deadlineFinStr) {
- const parts = deadlineFinStr.split('-');
- deadlineFin = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]), 23, 59, 59);
- }
- // Fonction pour normaliser le texte (retirer accents et passer en majuscules)
- const normalizeText = (text) => (text || '').normalize('NFD').replace(/[\u0300-\u036f]/g, '').toUpperCase();
- // Récupérer les mots-clés du titre
- const titreInput = document.getElementById('search-titre-keywords').value.trim();
- const titreKeywords = titreInput.split(/\s+/).filter(k => k.length > 0).map(k => normalizeText(k));
- // Récupérer les tags
- const tagsInput = document.getElementById('searchTags').value.trim();
- const tagsKeywords = tagsInput.split(/\s+/).filter(k => k.length > 0).map(k => normalizeText(k));
- // Récupérer la recherche de montant
- const searchMontant = document.getElementById('searchMontant').value.trim();
- const minMontant = parseFloat(document.getElementById('minMontant').value);
- const maxMontant = parseFloat(document.getElementById('maxMontant').value);
- // Récupérer les filtres Type, Attachement, Attribution, Statut
- const filterType = document.getElementById('filterType').value;
- const filterAttach = document.getElementById('filterAttach').value;
- const filterAssign = document.getElementById('filterAssign').value;
- const filterStatut = document.getElementById('filterStatut').value;
- let filteredData = originalData.filter(row => {
- // Filtre par plage de dates
- if (dateDebut || dateFin) {
- const rowDate = parseDate(row.date);
- if (!rowDate) return false;
- if (dateDebut && rowDate < dateDebut) return false;
- if (dateFin && rowDate > dateFin) return false;
- }
- // Filtre par plage d'échéance
- if (deadlineDebut || deadlineFin) {
- const rowDeadline = parseDate(row.deadline);
- if (!rowDeadline) return false;
- if (deadlineDebut && rowDeadline < deadlineDebut) return false;
- if (deadlineFin && rowDeadline > deadlineFin) return false;
- }
- // Filtre par mots-clés dans le titre
- if (titreKeywords.length > 0) {
- const titreNorm = normalizeText(row.titre);
- const hasAllKeywords = titreKeywords.every(keyword => titreNorm.includes(keyword));
- if (!hasAllKeywords) return false;
- }
- // Filtre par tags
- if (tagsKeywords.length > 0) {
- const tagsNorm = normalizeText(row.tags);
- const hasAllTags = tagsKeywords.every(keyword => tagsNorm.includes(keyword));
- if (!hasAllTags) return false;
- }
- // Filtre par recherche de montant
- if (searchMontant) {
- const montant = parseFloat(row.montant) || 0;
- if (!filterMontant(searchMontant, montant)) return false;
- }
- // Filtre par plage de montant
- const montant = parseFloat(row.montant) || 0;
- if (!isNaN(minMontant) && montant < minMontant) return false;
- if (!isNaN(maxMontant) && montant > maxMontant) return false;
- // Filtre par Type
- if (filterType && (row.label || '') !== filterType) return false;
- // Filtre par Attachement
- if (filterAttach && (row.attach || '') !== filterAttach) return false;
- // Filtre par Attribution
- if (filterAssign && (row.assign || '') !== filterAssign) return false;
- // Filtre par Statut
- if (filterStatut && (row.done || '') !== filterStatut) return false;
- return true;
- });
- // Recharger le tableau avec les données filtrées
- $('#table').bootstrapTable('load', filteredData);
- updateFilterInfo(filteredData.length, titreKeywords);
- }
- // Réinitialiser tous les filtres avancés
- function clearAllAdvancedFilters() {
- document.getElementById('dateDebut').value = '';
- document.getElementById('dateFin').value = '';
- document.getElementById('deadlineDebut').value = '';
- document.getElementById('deadlineFin').value = '';
- document.getElementById('search-titre-keywords').value = '';
- document.getElementById('searchTags').value = '';
- document.getElementById('searchMontant').value = '';
- document.getElementById('minMontant').value = '';
- document.getElementById('maxMontant').value = '';
- document.getElementById('filterType').value = '';
- document.getElementById('filterAttach').value = '';
- document.getElementById('filterAssign').value = '';
- document.getElementById('filterStatut').value = '';
- $('#table').bootstrapTable('load', originalData);
- updateFilterInfo(originalData.length, []);
- }
- // Mettre à jour l'info des filtres actifs
- function updateFilterInfo(count, keywords) {
- const infoEl = document.getElementById('filterInfo');
- const activeFilters = [];
- const dateDebut = document.getElementById('dateDebut').value;
- const dateFin = document.getElementById('dateFin').value;
- if (dateDebut || dateFin) {
- activeFilters.push(`Date: ${dateDebut || 'début'} à ${dateFin || 'fin'}`);
- }
- const deadlineDebut = document.getElementById('deadlineDebut').value;
- const deadlineFin = document.getElementById('deadlineFin').value;
- if (deadlineDebut || deadlineFin) {
- activeFilters.push(`Échéance: ${deadlineDebut || 'début'} à ${deadlineFin || 'fin'}`);
- }
- if (keywords && keywords.length > 0) {
- activeFilters.push(`Titre: "${keywords.join(' + ')}"`);
- }
- const searchTags = document.getElementById('searchTags').value;
- if (searchTags) {
- activeFilters.push(`Tags: "${searchTags}"`);
- }
- const searchMontant = document.getElementById('searchMontant').value;
- if (searchMontant) {
- activeFilters.push(`Montant: "${searchMontant}"`);
- }
- const minMontant = document.getElementById('minMontant').value;
- const maxMontant = document.getElementById('maxMontant').value;
- if (minMontant || maxMontant) {
- activeFilters.push(`Plage: ${minMontant || '0'} - ${maxMontant || '∞'}`);
- }
- const filterType = document.getElementById('filterType').value;
- if (filterType) {
- activeFilters.push(`Type: "${filterType}"`);
- }
- const filterAttach = document.getElementById('filterAttach').value;
- if (filterAttach) {
- activeFilters.push(`Attachement: "${filterAttach}"`);
- }
- const filterAssign = document.getElementById('filterAssign').value;
- if (filterAssign) {
- activeFilters.push(`Attribution: "${filterAssign}"`);
- }
- const filterStatut = document.getElementById('filterStatut').value;
- if (filterStatut) {
- activeFilters.push(`Statut: "${filterStatut}"`);
- }
- if (activeFilters.length > 0) {
- infoEl.innerHTML = `<strong>${count}</strong> document(s) affiché(s) | Filtres: ${activeFilters.join(', ')}`;
- } else {
- infoEl.innerHTML = `<strong>${count || originalData.length}</strong> document(s) au total`;
- }
- }
- // Populer les selects avec les valeurs uniques
- function populateSelectFilters(data) {
- const types = [...new Set(data.map(row => row.label).filter(v => v))].sort();
- const attachs = [...new Set(data.map(row => row.attach).filter(v => v))].sort();
- const assigns = [...new Set(data.map(row => row.assign).filter(v => v))].sort();
- const statuts = [...new Set(data.map(row => row.done).filter(v => v))].sort();
- const selectType = document.getElementById('filterType');
- const selectAttach = document.getElementById('filterAttach');
- const selectAssign = document.getElementById('filterAssign');
- const selectStatut = document.getElementById('filterStatut');
- // Réinitialiser et repeupler Type
- selectType.innerHTML = '<option value="">-- Tous les types --</option>';
- types.forEach(type => {
- const option = document.createElement('option');
- option.value = type;
- option.textContent = type;
- selectType.appendChild(option);
- });
- // Réinitialiser et repeupler Attachement
- selectAttach.innerHTML = '<option value="">-- Tous les attachements --</option>';
- attachs.forEach(attach => {
- const option = document.createElement('option');
- option.value = attach;
- option.textContent = attach;
- selectAttach.appendChild(option);
- });
- // Réinitialiser et repeupler Attribution
- selectAssign.innerHTML = '<option value="">-- Toutes les attributions --</option>';
- assigns.forEach(assign => {
- const option = document.createElement('option');
- option.value = assign;
- option.textContent = assign;
- selectAssign.appendChild(option);
- });
- // Réinitialiser et repeupler Statut
- selectStatut.innerHTML = '<option value="">-- Tous les statuts --</option>';
- statuts.forEach(statut => {
- const option = document.createElement('option');
- option.value = statut;
- option.textContent = statut;
- selectStatut.appendChild(option);
- });
- }
- // Initialisation
- $(document).ready(function() {
- const $table = $('#table');
- $table.on('load-success.bs.table', function(e, data) {
- originalData = data;
- populateSelectFilters(data);
- updateFilterInfo(data.length, []);
- });
- // Appliquer le filtre avec Entrée sur les champs texte
- ['search-titre-keywords', 'searchTags', 'searchMontant'].forEach(id => {
- document.getElementById(id).addEventListener('keypress', function(e) {
- if (e.key === 'Enter') {
- applyAdvancedFilters();
- }
- });
- });
- });
- </script>
|