| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 |
- <?php
- $jsonTarget = "/json.php?file=banque-lignes-" . core::getGet("id");
- if (debug::isFile("debug")) {
- debug::log(debug::getBadge($jsonTarget, "OUVRIR LE JSON : " . $jsonTarget), "JSON chargé en arrière plan");
- }
- $banque = banque::getInitialCompte(core::getGet("id"));
- $etatCompte = banque::getEtatCompte(core::getGet("id"));
- ?>
- <header class="d-flex flex-column flex-md-row align-items-md-center p-3 bg-light ">
- <h2 class="bd-title" id="content">
- <span>Etat des comptes</span>
- </h2>
- <?php
- if (access::ifAccesss("compte-upload")) { ?>
- <div class="fix-container-button-nav">
- <?php if ($banque["import"] == "manuel") { ?>
- <a href="/?p=compte-insert&add=<?php echo core::getGet("id") ?>"><button type="submit" class="btn btn-outline-success btn-sm"><?php icon::getFont(["icon" => "bi bi-file-earmark-plus"]) ?> Ajouter une ligne</button></a>
- <?php } else { ?>
- <a href="/?p=compte-upload&add=<?php echo core::getGet("id") ?>"><button type="submit" class="btn btn-outline-success btn-sm"><?php icon::getFont(["icon" => "bi bi-file-earmark-plus"]) ?> Charger un CSV</button></a>
- <?php } ?>
- </div>
- <?php } ?>
- </header>
- <?php
- echo core::filAriane(array(
- "current" => $banque["label"] . ((!empty($banque["compte"])) ? " [" . $banque["compte"] . "]" : NULL),
- "arbo" => array(
- "Comptes bancaires" => NULL,
- $banque["label"] . ((!empty($banque["compte"])) ? " [" . $banque["compte"] . "]" : NULL) => "/compte-" . core::getGet("id") . ".html"
- ),
- "refresh-json" => "banque-lignes-" . core::getGet("id")
- ));
- $lastRecord = banque::lastRecord(core::getGet("id"));
- if (!empty($lastRecord)) {
- $callout = [
- "type" => "info",
- "h4" => "Note",
- "p" => "La dernier import des données du compte a été réalisée le <span class=\"fw-bold\">" . core::convertDate($lastRecord) . "</span> et à cette même date le solde était de <span class=\"fw-bold\">" . banque::getEuro($etatCompte["solde"]) . "</span>",
- ];
- callout::print($callout);
- }
- ?>
- <!-- 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</span>
- <i class="bi bi-chevron-up" id="advancedFiltersIcon"></i>
- </div>
- <div class="card-body" id="advancedFiltersBody">
- <div class="row">
- <!-- Filtre par plage de dates -->
- <div class="col-lg-3 col-md-6 mb-3">
- <label class="form-label fw-bold">Période</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>
- <small class="text-muted">Filtrer entre deux dates</small>
- </div>
- <!-- Filtre par mots-clés sur le label -->
- <div class="col-lg-3 col-md-6 mb-3">
- <label class="form-label fw-bold">Mots-clés dans le Label</label>
- <input type="text" class="form-control form-control-sm" id="search-label-keywords" placeholder="Ex: bourse enpayout">
- <small class="text-muted">Mots-clés séparés par des espaces</small>
- </div>
- <!-- Filtre par montant (recherche exacte ou plage) -->
- <div class="col-lg-3 col-md-6 mb-3">
- <label class="form-label fw-bold">Recherche montant</label>
- <div class="d-flex align-items-center">
- <select id="searchMontantColonne" class="form-select form-select-sm me-2" style="width: 100px;">
- <option value="all">Tous</option>
- <option value="debit">Débit</option>
- <option value="credit">Crédit</option>
- <option value="solde">Solde</option>
- </select>
- <input type="text" class="form-control form-control-sm" id="searchMontant" placeholder="Ex: 5086 ou >1000">
- </div>
- <small class="text-muted">Recherche exacte ou avec opérateur</small>
- </div>
- </div>
- <div class="row">
- <!-- Filtre par plage de montant Débit -->
- <div class="col-lg-3 col-md-6 mb-3">
- <label class="form-label fw-bold">Plage Débit</label>
- <div class="d-flex align-items-center">
- <input type="number" step="0.01" id="minDebit" 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="maxDebit" class="form-control form-control-sm" placeholder="Max" style="width: 100px;">
- </div>
- </div>
- <!-- Filtre par plage de montant Crédit -->
- <div class="col-lg-3 col-md-6 mb-3">
- <label class="form-label fw-bold">Plage Crédit</label>
- <div class="d-flex align-items-center">
- <input type="number" step="0.01" id="minCredit" 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="maxCredit" class="form-control form-control-sm" placeholder="Max" style="width: 100px;">
- </div>
- </div>
- <!-- Filtre par plage de Solde -->
- <div class="col-lg-3 col-md-6 mb-3">
- <label class="form-label fw-bold">Plage Solde</label>
- <div class="d-flex align-items-center">
- <input type="number" step="0.01" id="minSolde" 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="maxSolde" class="form-control form-control-sm" placeholder="Max" style="width: 100px;">
- </div>
- </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;
- }
- #search-label-keywords::placeholder {
- font-style: italic;
- color: #adb5bd;
- }
- </style>
- <div>
- <table
- id="table"
- class="table-striped table-hover table-sm"
- data-toggle="table"
- data-show-footer="true"
- data-buttons-align="left"
- data-filter-control="true"
- data-flat="true"
- data-sort-name="num"
- data-sort-order="desc"
- data-show-export="true"
- data-pagination="true"
- data-side-pagination="client"
- data-page-size="25"
- data-page-list="[25, 50, 100, 250, all]"
- data-url="<?php echo $jsonTarget ?>"
- data-filter-control-visible="false">
- <thead>
- <tr>
- <th data-sortable="true" data-field="num" data-width="20">#</th>
- <th data-sortable="true" data-field="date" data-filter-control="input" data-width="160">Date</th>
- <th data-sortable="true" data-field="label" data-filter-control="input">Label</th>
- <th data-sortable="true" data-formatter="dataFormatter" data-field="debit" data-width="150" data-footer-formatter="debitFormatter">Débit</th>
- <th data-sortable="true" data-formatter="dataFormatter" data-field="credit" data-width="150" data-footer-formatter="creditFormatter">Crédit</th>
- <th data-sortable="true" data-formatter="dataFormatter" data-field="solde" data-width="150">Solde</th>
- </tr>
- </thead>
- </table>
- </div>
- <script>
- let euro = Intl.NumberFormat('de-DE', {
- style: 'currency',
- currency: 'EUR',
- });
- // Données originales
- let originalData = [];
- // Fonction de filtre personnalisée pour les montants (accepte format décimal et euro)
- function filterMontant(searchValue, value, field, data) {
- if (!searchValue || searchValue.trim() === '') return true;
- // Valeur numérique brute de la donnée
- const numValue = parseFloat(data[field]) || 0;
- // Valeur absolue pour faciliter la recherche
- const absNumValue = Math.abs(numValue);
- // Normaliser la recherche : remplacer virgule par point et supprimer les espaces/symboles
- let searchNormalized = searchValue.trim()
- .replace(/\s/g, '') // Supprimer espaces
- .replace(/€/g, '') // Supprimer symbole euro
- .replace(/\./g, '') // Supprimer les points (séparateurs milliers)
- .replace(/,/g, '.'); // Remplacer virgule par point (décimale)
- // Vérifier si c'est une comparaison (>, <, >=, <=, =)
- 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)) {
- // Si ce n'est pas un nombre, faire une recherche textuelle sur la valeur formatée
- const formattedValue = euro.format(numValue).toLowerCase();
- return formattedValue.includes(searchValue.toLowerCase());
- }
- // Comparaison numérique
- 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;
- }
- }
- // Sans opérateur : recherche de correspondance exacte ou partielle
- // Correspondance exacte (avec tolérance)
- if (Math.abs(numValue - searchNum) < 0.01 || Math.abs(absNumValue - searchNum) < 0.01) {
- return true;
- }
- // Recherche partielle : vérifier si le nombre entier recherché est présent
- // Convertir en entier pour la comparaison (ex: 5086 dans 5086.00)
- const intPart = Math.floor(absNumValue);
- const searchInt = Math.floor(searchNum);
- if (intPart === searchInt) {
- return true;
- }
- // Recherche textuelle dans le nombre formaté
- const numStr = absNumValue.toFixed(2);
- const searchStr = searchInt.toString();
- return numStr.startsWith(searchStr) || intPart.toString().includes(searchStr);
- }
- function dataFormatter(value) {
- return euro.format(value);
- }
- function debitFormatter(data) {
- var total = 0;
- data.forEach(function(row) {
- total += parseFloat(row.debit);
- });
- return parseFloat(total) === 0 ? euro.format(0.00) : euro.format(total.toFixed(2));
- }
- function creditFormatter(data) {
- var total = 0;
- data.forEach(function(row) {
- total += parseFloat(row.credit);
- });
- return parseFloat(total) === 0 ? euro.format(0.00) : euro.format(total.toFixed(2));
- }
- // 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;
- }
- // Appliquer tous les filtres avancés
- function applyAdvancedFilters() {
- // Récupérer les dates (format ISO: YYYY-MM-DD depuis input type="date")
- const dateDebutStr = document.getElementById('dateDebut').value;
- const dateFinStr = document.getElementById('dateFin').value;
- let dateDebut = null;
- let dateFin = 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('-');
- // Mettre la date de fin à 23:59:59 pour inclure toute la journée
- dateFin = 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 saisis (séparés par espaces)
- const keywordsInput = document.getElementById('search-label-keywords').value.trim();
- const keywords = keywordsInput.split(/\s+/).filter(k => k.length > 0).map(k => normalizeText(k));
- // Récupérer la recherche de montant et la colonne sélectionnée
- const searchMontant = document.getElementById('searchMontant').value.trim();
- const searchMontantColonne = document.getElementById('searchMontantColonne').value;
- const minDebit = parseFloat(document.getElementById('minDebit').value);
- const maxDebit = parseFloat(document.getElementById('maxDebit').value);
- const minCredit = parseFloat(document.getElementById('minCredit').value);
- const maxCredit = parseFloat(document.getElementById('maxCredit').value);
- const minSolde = parseFloat(document.getElementById('minSolde').value);
- const maxSolde = parseFloat(document.getElementById('maxSolde').value);
- let filteredData = originalData.filter(row => {
- // Filtre par plage de dates
- if (dateDebut || dateFin) {
- const rowDate = parseDate(row.date);
- // Si pas de date valide sur la ligne et qu'on filtre par date, exclure la ligne
- if (!rowDate) return false;
- if (dateDebut && rowDate < dateDebut) return false;
- if (dateFin && rowDate > dateFin) return false;
- }
- // Filtre par mots-clés dans le label (TOUS les mots-clés doivent être présents)
- if (keywords.length > 0) {
- const labelNorm = normalizeText(row.label);
- const hasAllKeywords = keywords.every(keyword => labelNorm.includes(keyword));
- if (!hasAllKeywords) return false;
- }
- // Filtre par recherche de montant (selon la colonne sélectionnée)
- if (searchMontant) {
- let match = false;
- if (searchMontantColonne === 'all') {
- const matchDebit = filterMontant(searchMontant, null, 'debit', row);
- const matchCredit = filterMontant(searchMontant, null, 'credit', row);
- const matchSolde = filterMontant(searchMontant, null, 'solde', row);
- match = matchDebit || matchCredit || matchSolde;
- } else {
- match = filterMontant(searchMontant, null, searchMontantColonne, row);
- }
- if (!match) return false;
- }
- // Filtre par plage de débit
- const debit = parseFloat(row.debit) || 0;
- if (!isNaN(minDebit) && debit < minDebit) return false;
- if (!isNaN(maxDebit) && debit > maxDebit) return false;
- // Filtre par plage de crédit
- const credit = parseFloat(row.credit) || 0;
- if (!isNaN(minCredit) && credit < minCredit) return false;
- if (!isNaN(maxCredit) && credit > maxCredit) return false;
- // Filtre par plage de solde
- const solde = parseFloat(row.solde) || 0;
- if (!isNaN(minSolde) && solde < minSolde) return false;
- if (!isNaN(maxSolde) && solde > maxSolde) return false;
- return true;
- });
- // Recharger le tableau avec les données filtrées
- $('#table').bootstrapTable('load', filteredData);
- updateFilterInfo(filteredData.length, keywords);
- }
- // Réinitialiser tous les filtres avancés
- function clearAllAdvancedFilters() {
- // Réinitialiser les dates
- document.getElementById('dateDebut').value = '';
- document.getElementById('dateFin').value = '';
- // Réinitialiser les champs numériques
- document.getElementById('minDebit').value = '';
- document.getElementById('maxDebit').value = '';
- document.getElementById('minCredit').value = '';
- document.getElementById('maxCredit').value = '';
- document.getElementById('minSolde').value = '';
- document.getElementById('maxSolde').value = '';
- // Réinitialiser la barre de recherche des mots-clés
- document.getElementById('search-label-keywords').value = '';
- // Réinitialiser la recherche de montant
- document.getElementById('searchMontant').value = '';
- document.getElementById('searchMontantColonne').value = 'all';
- // Recharger les données originales
- $('#table').bootstrapTable('load', originalData);
- updateFilterInfo(originalData.length, []);
- // Réinitialiser aussi les filtres de la table
- $('#table').bootstrapTable('clearFilterControl');
- }
- // 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(`Période: ${dateDebut || 'début'} à ${dateFin || 'fin'}`);
- }
- if (keywords && keywords.length > 0) {
- activeFilters.push(`Mots-clés: "${keywords.join(' + ')}"`);
- }
- const searchMontant = document.getElementById('searchMontant').value;
- if (searchMontant) {
- const colonneLabels = {
- 'all': 'Tous',
- 'debit': 'Débit',
- 'credit': 'Crédit',
- 'solde': 'Solde'
- };
- const colonne = document.getElementById('searchMontantColonne').value;
- activeFilters.push(`Montant (${colonneLabels[colonne]}): "${searchMontant}"`);
- }
- const minDebit = document.getElementById('minDebit').value;
- const maxDebit = document.getElementById('maxDebit').value;
- if (minDebit || maxDebit) {
- activeFilters.push(`Débit: ${minDebit || '0'} - ${maxDebit || '∞'}`);
- }
- const minCredit = document.getElementById('minCredit').value;
- const maxCredit = document.getElementById('maxCredit').value;
- if (minCredit || maxCredit) {
- activeFilters.push(`Crédit: ${minCredit || '0'} - ${maxCredit || '∞'}`);
- }
- const minSolde = document.getElementById('minSolde').value;
- const maxSolde = document.getElementById('maxSolde').value;
- if (minSolde || maxSolde) {
- activeFilters.push(`Solde: ${minSolde || '0'} - ${maxSolde || '∞'}`);
- }
- if (activeFilters.length > 0) {
- infoEl.innerHTML = `<strong>${count}</strong> ligne(s) affichée(s) | Filtres actifs: ${activeFilters.join(', ')}`;
- } else {
- infoEl.innerHTML = `<strong>${count || originalData.length}</strong> ligne(s) au total`;
- }
- }
- // Initialisation
- $(document).ready(function() {
- const $table = $('#table');
- // Attendre que les données soient chargées
- $table.on('load-success.bs.table', function(e, data) {
- // Sauvegarder les données originales
- originalData = data;
- updateFilterInfo(data.length, []);
- });
- // Permettre d'appliquer le filtre avec la touche Entrée sur les champs texte
- ['search-label-keywords', 'searchMontant'].forEach(id => {
- document.getElementById(id).addEventListener('keypress', function(e) {
- if (e.key === 'Enter') {
- applyAdvancedFilters();
- }
- });
- });
- });
- </script>
|