templates/admin/tresorerie.html.twig line 1

Open in your IDE?
  1. {% extends 'sidebar.twig' %}
  2. {% block title %}Dashboard Trésorerie{% endblock %}
  3. {% block page_emoji %}💰{% endblock %}
  4. {% block page_titre %}Trésorerie{% endblock %}
  5. {% block page_info %}Vue d'ensemble de vos revenus{% endblock %}
  6. {% block admin_content %}
  7.     <style>
  8.         .dark .dark\:bg-gray-800 {
  9.             background-color: #1f2937;
  10.         }
  11.         .dark .dark\:text-white {
  12.             color: #ffffff;
  13.         }
  14.         .dark .dark\:text-gray-400 {
  15.             color: #9ca3af;
  16.         }
  17.         .transition-all {
  18.             transition-property: all;
  19.             transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
  20.             transition-duration: 150ms;
  21.         }
  22.         .hover\:shadow-md:hover {
  23.             box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
  24.         }
  25.     </style>
  26.     {% set moisFr = {
  27.         '1': 'Janvier',
  28.         '2': 'Février',
  29.         '3': 'Mars',
  30.         '4': 'Avril',
  31.         '5': 'Mai',
  32.         '6': 'Juin',
  33.         '7': 'Juillet',
  34.         '8': 'Août',
  35.         '9': 'Septembre',
  36.         '10': 'Octobre',
  37.         '11': 'Novembre',
  38.         '12': 'Décembre'
  39.     } %}
  40.     <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
  41.         <div class="space-y-8">
  42.             {# Première section : KPIs principaux #}
  43.             {# Première section : KPIs principaux #}
  44.             <div class="grid grid-cols-1 md:grid-cols-5 gap-4">
  45.                 {# CA Annuel #}
  46.                 <div class="bg-white dark:bg-gray-800 rounded-xl p-6 shadow-sm hover:shadow-md transition-all">
  47.                     <div class="flex items-center justify-between">
  48.                         <div>
  49.                             <p class="text-sm font-medium text-gray-600 dark:text-gray-400">CA Annuel {{ "now"|date('Y') }}</p>
  50.                             <p class="text-2xl font-bold text-gray-900 dark:text-white mt-2">
  51.                                 {{ caAnnuel|number_format(2, ',', ' ') }} â‚¬
  52.                             </p>
  53.                         </div>
  54.                         <div class="p-3 bg-blue-50 dark:bg-blue-900/20 rounded-full">
  55.                             <svg class="w-6 h-6 text-blue-600 dark:text-blue-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  56.                                 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
  57.                             </svg>
  58.                         </div>
  59.                     </div>
  60.                 </div>
  61.                 {# CA Mois en cours #}
  62.                 <div class="bg-white dark:bg-gray-800 rounded-xl p-6 shadow-sm hover:shadow-md transition-all">
  63.                     <div class="flex items-center justify-between">
  64.                         <div>
  65.                             <p class="text-sm font-medium text-gray-600 dark:text-gray-400">
  66.                                 CA {{ moisFr[("now"|date('n'))] }}
  67.                             </p>
  68.                             <p class="text-2xl font-bold text-gray-900 dark:text-white mt-2">
  69.                                 {{ caMoisEnCours|number_format(2, ',', ' ') }} â‚¬
  70.                             </p>
  71.                         </div>
  72.                         <div class="p-3 bg-green-50 dark:bg-green-900/20 rounded-full">
  73.                             <svg class="w-6 h-6 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  74.                                 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
  75.                             </svg>
  76.                         </div>
  77.                     </div>
  78.                 </div>
  79.                 {# Card CB #}
  80.                 <div class="bg-white dark:bg-gray-800 rounded-xl p-6 shadow-sm hover:shadow-md transition-all cursor-pointer" onclick="openModal('cb')">
  81.                     <div class="flex items-center justify-between">
  82.                         <div>
  83.                             <p class="text-sm font-medium text-gray-600 dark:text-gray-400">Carte Bancaire</p>
  84.                             <p class="text-2xl font-bold text-gray-900 dark:text-white mt-2">
  85.                                 {{ paiementCB|number_format(2, ',', ' ') }} â‚¬
  86.                             </p>
  87.                         </div>
  88.                         <div class="p-3 bg-indigo-50 dark:bg-indigo-900/20 rounded-full">
  89.                             <svg class="w-6 h-6 text-indigo-600 dark:text-indigo-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  90.                                 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h18M7 15h1m4 0h1m-7 4h12a3 3 0 003-3V8a3 3 0 00-3-3H6a3 3 0 00-3 3v8a3 3 0 003 3z"></path>
  91.                             </svg>
  92.                         </div>
  93.                     </div>
  94.                 </div>
  95.                 {# Card Acomptes #}
  96.                 <div class="bg-white dark:bg-gray-800 rounded-xl p-6 shadow-sm hover:shadow-md transition-all cursor-pointer" onclick="openModal('acompte')">
  97.                     <div class="flex items-center justify-between">
  98.                         <div>
  99.                             <p class="text-sm font-medium text-gray-600 dark:text-gray-400">Planity</p>
  100.                             <p class="text-2xl font-bold text-gray-900 dark:text-white mt-2">
  101.                                 {{ acomptes|number_format(2, ',', ' ') }} â‚¬
  102.                             </p>
  103.                         </div>
  104.                         <div class="p-3 bg-purple-50 dark:bg-purple-900/20 rounded-full">
  105.                             <svg class="w-6 h-6 text-purple-600 dark:text-purple-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  106.                                 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"></path>
  107.                             </svg>
  108.                         </div>
  109.                     </div>
  110.                 </div>
  111.                 {# Card Espèces #}
  112.                 <div class="bg-white dark:bg-gray-800 rounded-xl p-6 shadow-sm hover:shadow-md transition-all cursor-pointer" onclick="openModal('especes')">
  113.                     <div class="flex items-center justify-between">
  114.                         <div>
  115.                             <p class="text-sm font-medium text-gray-600 dark:text-gray-400">Espèces</p>
  116.                             <p class="text-2xl font-bold text-gray-900 dark:text-white mt-2">
  117.                                 {{ paiementEspeces|number_format(2, ',', ' ') }} â‚¬
  118.                             </p>
  119.                         </div>
  120.                         <div class="p-3 bg-yellow-50 dark:bg-yellow-900/20 rounded-full">
  121.                             <svg class="w-6 h-6 text-yellow-600 dark:text-yellow-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  122.                                 <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 9V7a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2m2 4h10a2 2 0 002-2v-6a2 2 0 00-2-2H9a2 2 0 00-2 2v6a2 2 0 002 2z"></path>
  123.                             </svg>
  124.                         </div>
  125.                     </div>
  126.                 </div>
  127.             </div>
  128.             {# Graphique d'évolution #}
  129.             <div class="bg-white dark:bg-gray-800 rounded-xl p-6 shadow-sm">
  130.                 <h3 class="text-lg font-medium text-gray-900 dark:text-white mb-6">Évolution mensuelle du CA</h3>
  131.                 <div class="h-[400px]">
  132.                     <canvas id="evolutionChart"></canvas>
  133.                 </div>
  134.             </div>
  135.             {# Comparaison périodes #}
  136.             <div class="bg-white dark:bg-gray-800 rounded-xl p-6 shadow-sm">
  137.                 <h3 class="text-lg font-medium text-gray-900 dark:text-white mb-6">Comparaison des périodes</h3>
  138.                 <div class="grid grid-cols-1 md:grid-cols-2 gap-8 relative">
  139.                     {# Période actuelle #}
  140.                     <div class="bg-gray-50 dark:bg-gray-700/50 rounded-xl p-6">
  141.                         <div class="flex items-center space-x-3 mb-4">
  142.                             <div class="p-2 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
  143.                                 <svg class="w-5 h-5 text-blue-600 dark:text-blue-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  144.                                     <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
  145.                                 </svg>
  146.                             </div>
  147.                             <span class="text-sm font-medium text-gray-900 dark:text-white">
  148.     1-{{ "now"|date('j') }} {{ moisFr[("now"|date('n'))] }}
  149. </span>
  150.                         </div>
  151.                         <p class="text-2xl font-bold text-gray-900 dark:text-white">
  152.                             {{ comparaisonMois.mois_actuel|number_format(2, ',', ' ') }} â‚¬
  153.                         </p>
  154.                         <p class="text-sm text-gray-500 dark:text-gray-400 mt-2">Période en cours</p>
  155.                     </div>
  156.                     {# Période précédente #}
  157.                     <div class="bg-gray-50 dark:bg-gray-700/50 rounded-xl p-6">
  158.                         <div class="flex items-center space-x-3 mb-4">
  159.                             <div class="p-2 bg-gray-100 dark:bg-gray-600 rounded-lg">
  160.                                 <svg class="w-5 h-5 text-gray-600 dark:text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  161.                                     <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
  162.                                 </svg>
  163.                             </div>
  164.                             <span class="text-sm font-medium text-gray-900 dark:text-white">
  165.     1-{{ "now"|date('j') }} {{ moisFr[("first day of last month"|date('n'))] }}
  166. </span>
  167.                         </div>
  168.                         <p class="text-2xl font-bold text-gray-900 dark:text-white">
  169.                             {{ comparaisonMois.mois_precedent|number_format(2, ',', ' ') }} â‚¬
  170.                         </p>
  171.                         <p class="text-sm text-gray-500 dark:text-gray-400 mt-2">Même période mois précédent</p>
  172.                     </div>
  173.                 </div>
  174.                 {% if comparaisonMois.mois_precedent != 0 %}
  175.                     {% set evolution = ((comparaisonMois.mois_actuel - comparaisonMois.mois_precedent) / comparaisonMois.mois_precedent * 100)|round %}
  176.                 {% else %}
  177.                     {% if comparaisonMois.mois_actuel > 0 %}
  178.                         {% set evolution = 100 %}
  179.                     {% elseif comparaisonMois.mois_actuel == 0 %}
  180.                         {% set evolution = 0 %}
  181.                     {% else %}
  182.                         {% set evolution = -100 %}
  183.                     {% endif %}
  184.                 {% endif %}
  185.                 <div class="mt-6 p-4 rounded-xl {% if evolution > 0 %}bg-green-50 dark:bg-green-900/20{% else %}bg-red-50 dark:bg-red-900/20{% endif %}">
  186.                     <div class="flex items-center justify-between">
  187.                         <div class="flex items-center space-x-3">
  188.                             <div class="p-2 rounded-lg {% if evolution > 0 %}bg-green-100 dark:bg-green-800{% else %}bg-red-100 dark:bg-red-800{% endif %}">
  189.                                 <svg class="w-5 h-5 {% if evolution > 0 %}text-green-600 dark:text-green-400{% else %}text-red-600 dark:text-red-400{% endif %}" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  190.                                     <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="{% if evolution > 0 %}M13 7h8m0 0v8m0-8l-8 8-4-4-6 6{% else %}M13 17h8m0 0V9m0 8l-8-8-4 4-6-6{% endif %}"/>
  191.                                 </svg>
  192.                             </div>
  193.                             <div>
  194.                                 <p class="font-medium {% if evolution > 0 %}text-green-800 dark:text-green-400{% else %}text-red-800 dark:text-red-400{% endif %}">
  195.                                     {% if evolution > 0 %}
  196.                                         Progression de {{ evolution }}%
  197.                                     {% else %}
  198.                                         Baisse de {{ evolution|abs }}%
  199.                                     {% endif %}
  200.                                 </p>
  201.                                 <p class="text-sm text-gray-600 dark:text-gray-400">par rapport au mois précédent</p>
  202.                             </div>
  203.                         </div>
  204.                         <div class="text-2xl">
  205.                             {% if evolution > 0 %}📈{% else %}📉{% endif %}
  206.                         </div>
  207.                     </div>
  208.                 </div>
  209.                 {# Info supplémentaire #}
  210.                 <div class="mt-4 flex items-center space-x-2 text-sm text-gray-600 dark:text-gray-400">
  211.                     <svg class="w-4 h-4 text-blue-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  212.                         <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
  213.                     </svg>
  214.                     <span>Comparaison basée sur les {{ "now"|date('j') }} premiers jours de chaque mois</span>
  215.                 </div>
  216.             </div>
  217.         </div>
  218.     </div>
  219.     <!-- Modals -->
  220.     <div id="modalContainer" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
  221.         <div class="relative top-20 mx-auto p-5 border w-11/12 max-w-4xl shadow-lg rounded-md bg-white">
  222.             <div class="flex justify-between items-center mb-4">
  223.                 <h3 class="text-lg font-medium text-gray-900" id="modalTitle"></h3>
  224.                 <button onclick="closeModal()" class="text-gray-400 hover:text-gray-500">
  225.                     <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  226.                         <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
  227.                     </svg>
  228.                 </button>
  229.             </div>
  230.             <div id="modalContent" class="mt-4"></div>
  231.         </div>
  232.     </div>
  233.     <style>
  234.         .modal-enter {
  235.             animation: fadeIn 0.3s ease-out;
  236.         }
  237.         @keyframes fadeIn {
  238.             from {
  239.                 opacity: 0;
  240.                 transform: translateY(-20px);
  241.             }
  242.             to {
  243.                 opacity: 1;
  244.                 transform: translateY(0);
  245.             }
  246.         }
  247.     </style>
  248. {% endblock %}
  249. {% block javascripts %}
  250.     {{ parent() }}
  251.     <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  252.     <script>
  253.         document.addEventListener('DOMContentLoaded', function() {
  254.             const ctx = document.getElementById('evolutionChart').getContext('2d');
  255.             const data = {{ evolutionMensuelle|json_encode|raw }};
  256.             const months = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin',
  257.                 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
  258.             new Chart(ctx, {
  259.                 type: 'line',
  260.                 data: {
  261.                     labels: data.map(item => months[item.mois - 1]),
  262.                     datasets: [{
  263.                         label: 'Chiffre d\'affaires',
  264.                         data: data.map(item => item.total),
  265.                         borderColor: '#4f46e5',
  266.                         backgroundColor: 'rgba(79, 70, 229, 0.1)',
  267.                         tension: 0.4,
  268.                         fill: true,
  269.                         pointBackgroundColor: '#4f46e5',
  270.                         pointBorderColor: '#ffffff',
  271.                         pointBorderWidth: 2,
  272.                         pointRadius: 4,
  273.                         pointHoverRadius: 6
  274.                     }]
  275.                 },
  276.                 options: {
  277.                     responsive: true,
  278.                     maintainAspectRatio: false,
  279.                     plugins: {
  280.                         legend: {
  281.                             display: false
  282.                         },
  283.                         tooltip: {
  284.                             backgroundColor: '#1f2937',
  285.                             titleColor: '#ffffff',
  286.                             bodyColor: '#ffffff',
  287.                             padding: 12,
  288.                             displayColors: false,
  289.                             callbacks: {
  290.                                 label: function(context) {
  291.                                     return new Intl.NumberFormat('fr-FR', {
  292.                                         style: 'currency',
  293.                                         currency: 'EUR'
  294.                                     }).format(context.parsed.y);
  295.                                 }
  296.                             }
  297.                         }
  298.                     },
  299.                     scales: {
  300.                         x: {
  301.                             grid: {
  302.                                 display: false
  303.                             },
  304.                             ticks: {
  305.                                 color: '#6b7280'
  306.                             }
  307.                         },
  308.                         y: {
  309.                             beginAtZero: true,
  310.                             grid: {
  311.                                 color: '#e5e7eb'
  312.                             },
  313.                             ticks: {
  314.                                 color: '#6b7280',
  315.                                 callback: function(value) {
  316.                                     return new Intl.NumberFormat('fr-FR', {
  317.                                         style: 'currency',
  318.                                         currency: 'EUR',
  319.                                         maximumFractionDigits: 0
  320.                                     }).format(value);
  321.                                 }
  322.                             }
  323.                         }
  324.                     }
  325.                 }
  326.             });
  327.         });
  328.         function openModal(type) {
  329.             const modal = document.getElementById('modalContainer');
  330.             const title = document.getElementById('modalTitle');
  331.             modal.classList.remove('hidden');
  332.             switch(type) {
  333.                 case 'cb':
  334.                     title.textContent = 'Détails des paiements par Carte Bancaire';
  335.                     loadDetails('cb');
  336.                     break;
  337.                 case 'acompte':
  338.                     title.textContent = 'Détails des acomptes Planity';
  339.                     loadDetails('acompte');
  340.                     break;
  341.                 case 'especes':
  342.                     title.textContent = 'Détails des paiements en espèces';
  343.                     loadDetails('especes');
  344.                     break;
  345.             }
  346.         }
  347.         function closeModal() {
  348.             document.getElementById('modalContainer').classList.add('hidden');
  349.         }
  350.         function loadDetails(type) {
  351.             const routes = {
  352.                 'cb': '{{ path('app_dashboard_cb_details') }}',
  353.                 'acompte': '{{ path('app_dashboard_acompte_details') }}',
  354.                 'especes': '{{ path('app_dashboard_especes_details') }}'
  355.             };
  356.             fetch(routes[type])
  357.                 .then(response => response.json())
  358.                 .then(data => {
  359.                     document.getElementById('modalContent').innerHTML = generateTableHTML(data);
  360.                 });
  361.         }
  362.         function generateTableHTML(data) {
  363.             return `
  364.         <div class="mb-4 grid grid-cols-1 sm:grid-cols-3 gap-4">
  365.             <!-- Filtres -->
  366.             <div class="relative">
  367.                 <input type="text"
  368.                        id="dateFilter"
  369.                        placeholder="Filtrer par date"
  370.                        class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
  371.                        onkeyup="filterTable()">
  372.             </div>
  373.             <div class="relative">
  374.                 <input type="text"
  375.                        id="clientFilter"
  376.                        placeholder="Filtrer par client"
  377.                        class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
  378.                        onkeyup="filterTable()">
  379.             </div>
  380.             <div class="relative">
  381.                 <input type="text"
  382.                        id="montantFilter"
  383.                        placeholder="Filtrer par montant"
  384.                        class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
  385.                        onkeyup="filterTable()">
  386.             </div>
  387.         </div>
  388.         <div class="overflow-x-auto">
  389.             <div class="inline-block min-w-full align-middle">
  390.                 <div class="overflow-hidden shadow-sm ring-1 ring-black ring-opacity-5">
  391.                     <!-- Version mobile -->
  392.                     <div class="block sm:hidden" id="mobileContent">
  393.                         ${generateMobileRows(data)}
  394.                     </div>
  395.                     <!-- Version desktop -->
  396.                     <table class="hidden sm:table min-w-full divide-y divide-gray-300">
  397. <thead class="bg-gray-50">
  398.     <tr>
  399.         <th class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 cursor-pointer" onclick="sortTable(0)">
  400.             <div class="flex items-center">
  401.                 Date
  402.                 <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  403.                     <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"/>
  404.                 </svg>
  405.             </div>
  406.         </th>
  407.         <th class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 cursor-pointer" onclick="sortTable(1)">
  408.             <div class="flex items-center">
  409.                 Client
  410.                 <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  411.                     <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"/>
  412.                 </svg>
  413.             </div>
  414.         </th>
  415.         <th class="px-3 py-3.5 text-right text-sm font-semibold text-gray-900 cursor-pointer" onclick="sortTable(2)">
  416.             <div class="flex items-center justify-end">
  417.                 Montant
  418.                 <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  419.                     <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"/>
  420.                 </svg>
  421.             </div>
  422.         </th>
  423.     </tr>
  424. </thead>
  425.                         <tbody class="divide-y divide-gray-200 bg-white" id="tableBody">
  426.                             ${generateTableRows(data)}
  427.                         </tbody>
  428.                         <tfoot class="bg-gray-50">
  429.                             <tr>
  430.                                 <td colspan="2" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Total</td>
  431.                                 <td class="px-3 py-3.5 text-right text-sm font-semibold text-gray-900" id="totalMontant">
  432.                                     ${calculateTotal(data)} â‚¬
  433.                                 </td>
  434.                             </tr>
  435.                         </tfoot>
  436.                     </table>
  437.                 </div>
  438.             </div>
  439.         </div>
  440.     `;
  441.         }
  442.         // Fonction pour générer les lignes du tableau
  443.         function generateTableRows(data) {
  444.             return data.map(item => `
  445.         <tr class="hover:bg-gray-50">
  446.             <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">${item.date}</td>
  447.             <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">${item.client}</td>
  448.             <td class="whitespace-nowrap px-3 py-4 text-sm text-right font-medium text-gray-900">${item.montant} â‚¬</td>
  449.         </tr>
  450.     `).join('');
  451.         }
  452.         // Fonction pour générer les lignes mobiles
  453.         function generateMobileRows(data) {
  454.             return data.map(item => `
  455.         <div class="bg-white border-b border-gray-200 p-4">
  456.             <div class="flex justify-between items-center mb-2">
  457.                 <div class="font-medium text-gray-900">${item.client}</div>
  458.                 <div class="text-lg font-bold text-gray-900">${item.montant} â‚¬</div>
  459.             </div>
  460.             <div class="text-sm text-gray-500">${item.date}</div>
  461.         </div>
  462.     `).join('');
  463.         }
  464.         // Fonction pour calculer le total
  465.         function calculateTotal(data) {
  466.             return data.reduce((sum, item) => sum + parseFloat(item.montant.replace(',', '.')), 0)
  467.                 .toFixed(2)
  468.                 .replace('.', ',');
  469.         }
  470.         // Fonction de filtrage
  471.         function filterTable() {
  472.             const dateFilter = document.getElementById('dateFilter').value.toLowerCase();
  473.             const clientFilter = document.getElementById('clientFilter').value.toLowerCase();
  474.             const montantFilter = document.getElementById('montantFilter').value.toLowerCase();
  475.             const rows = document.getElementById('tableBody').getElementsByTagName('tr');
  476.             let totalMontant = 0;
  477.             for (let row of rows) {
  478.                 const dateCell = row.cells[0].textContent.toLowerCase();
  479.                 const clientCell = row.cells[1].textContent.toLowerCase();
  480.                 const montantCell = row.cells[2].textContent.toLowerCase();
  481.                 const dateMatch = dateCell.includes(dateFilter);
  482.                 const clientMatch = clientCell.includes(clientFilter);
  483.                 const montantMatch = montantCell.includes(montantFilter);
  484.                 if (dateMatch && clientMatch && montantMatch) {
  485.                     row.style.display = '';
  486.                     totalMontant += parseFloat(montantCell.replace('€', '').replace(',', '.').trim());
  487.                 } else {
  488.                     row.style.display = 'none';
  489.                 }
  490.             }
  491.             // Mise Ã  jour du total
  492.             document.getElementById('totalMontant').textContent =
  493.                 totalMontant.toFixed(2).replace('.', ',') + ' â‚¬';
  494.         }
  495.         // Fonction de tri
  496.         let sortDirection = 1;
  497.         function sortTable(columnIndex) {
  498.             const table = document.getElementById('tableBody');
  499.             const rows = Array.from(table.getElementsByTagName('tr'));
  500.             rows.sort((a, b) => {
  501.                 let aValue = a.cells[columnIndex].textContent;
  502.                 let bValue = b.cells[columnIndex].textContent;
  503.                 // Pour les montants, convertir en nombres
  504.                 if (columnIndex === 2) {
  505.                     aValue = parseFloat(aValue.replace('€', '').replace(',', '.').trim());
  506.                     bValue = parseFloat(bValue.replace('€', '').replace(',', '.').trim());
  507.                 }
  508.                 if (aValue < bValue) return -1 * sortDirection;
  509.                 if (aValue > bValue) return 1 * sortDirection;
  510.                 return 0;
  511.             });
  512.             // Inverser la direction pour le prochain tri
  513.             sortDirection *= -1;
  514.             // Réinsérer les lignes triées
  515.             rows.forEach(row => table.appendChild(row));
  516.         }
  517.     </script>
  518. {% endblock %}