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.                 {% set evolution = ((comparaisonMois.mois_actuel - comparaisonMois.mois_precedent) / comparaisonMois.mois_precedent * 100)|round %}
  175.                 <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 %}">
  176.                     <div class="flex items-center justify-between">
  177.                         <div class="flex items-center space-x-3">
  178.                             <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 %}">
  179.                                 <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">
  180.                                     <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 %}"/>
  181.                                 </svg>
  182.                             </div>
  183.                             <div>
  184.                                 <p class="font-medium {% if evolution > 0 %}text-green-800 dark:text-green-400{% else %}text-red-800 dark:text-red-400{% endif %}">
  185.                                     {% if evolution > 0 %}
  186.                                         Progression de {{ evolution }}%
  187.                                     {% else %}
  188.                                         Baisse de {{ evolution|abs }}%
  189.                                     {% endif %}
  190.                                 </p>
  191.                                 <p class="text-sm text-gray-600 dark:text-gray-400">par rapport au mois précédent</p>
  192.                             </div>
  193.                         </div>
  194.                         <div class="text-2xl">
  195.                             {% if evolution > 0 %}📈{% else %}📉{% endif %}
  196.                         </div>
  197.                     </div>
  198.                 </div>
  199.                 {# Info supplémentaire #}
  200.                 <div class="mt-4 flex items-center space-x-2 text-sm text-gray-600 dark:text-gray-400">
  201.                     <svg class="w-4 h-4 text-blue-500 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  202.                         <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"/>
  203.                     </svg>
  204.                     <span>Comparaison basée sur les {{ "now"|date('j') }} premiers jours de chaque mois</span>
  205.                 </div>
  206.             </div>
  207.         </div>
  208.     </div>
  209.     <!-- Modals -->
  210.     <div id="modalContainer" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full z-50">
  211.         <div class="relative top-20 mx-auto p-5 border w-11/12 max-w-4xl shadow-lg rounded-md bg-white">
  212.             <div class="flex justify-between items-center mb-4">
  213.                 <h3 class="text-lg font-medium text-gray-900" id="modalTitle"></h3>
  214.                 <button onclick="closeModal()" class="text-gray-400 hover:text-gray-500">
  215.                     <svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  216.                         <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
  217.                     </svg>
  218.                 </button>
  219.             </div>
  220.             <div id="modalContent" class="mt-4"></div>
  221.         </div>
  222.     </div>
  223.     <style>
  224.         .modal-enter {
  225.             animation: fadeIn 0.3s ease-out;
  226.         }
  227.         @keyframes fadeIn {
  228.             from {
  229.                 opacity: 0;
  230.                 transform: translateY(-20px);
  231.             }
  232.             to {
  233.                 opacity: 1;
  234.                 transform: translateY(0);
  235.             }
  236.         }
  237.     </style>
  238. {% endblock %}
  239. {% block javascripts %}
  240.     {{ parent() }}
  241.     <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
  242.     <script>
  243.         document.addEventListener('DOMContentLoaded', function() {
  244.             const ctx = document.getElementById('evolutionChart').getContext('2d');
  245.             const data = {{ evolutionMensuelle|json_encode|raw }};
  246.             const months = ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin',
  247.                 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'];
  248.             new Chart(ctx, {
  249.                 type: 'line',
  250.                 data: {
  251.                     labels: data.map(item => months[item.mois - 1]),
  252.                     datasets: [{
  253.                         label: 'Chiffre d\'affaires',
  254.                         data: data.map(item => item.total),
  255.                         borderColor: '#4f46e5',
  256.                         backgroundColor: 'rgba(79, 70, 229, 0.1)',
  257.                         tension: 0.4,
  258.                         fill: true,
  259.                         pointBackgroundColor: '#4f46e5',
  260.                         pointBorderColor: '#ffffff',
  261.                         pointBorderWidth: 2,
  262.                         pointRadius: 4,
  263.                         pointHoverRadius: 6
  264.                     }]
  265.                 },
  266.                 options: {
  267.                     responsive: true,
  268.                     maintainAspectRatio: false,
  269.                     plugins: {
  270.                         legend: {
  271.                             display: false
  272.                         },
  273.                         tooltip: {
  274.                             backgroundColor: '#1f2937',
  275.                             titleColor: '#ffffff',
  276.                             bodyColor: '#ffffff',
  277.                             padding: 12,
  278.                             displayColors: false,
  279.                             callbacks: {
  280.                                 label: function(context) {
  281.                                     return new Intl.NumberFormat('fr-FR', {
  282.                                         style: 'currency',
  283.                                         currency: 'EUR'
  284.                                     }).format(context.parsed.y);
  285.                                 }
  286.                             }
  287.                         }
  288.                     },
  289.                     scales: {
  290.                         x: {
  291.                             grid: {
  292.                                 display: false
  293.                             },
  294.                             ticks: {
  295.                                 color: '#6b7280'
  296.                             }
  297.                         },
  298.                         y: {
  299.                             beginAtZero: true,
  300.                             grid: {
  301.                                 color: '#e5e7eb'
  302.                             },
  303.                             ticks: {
  304.                                 color: '#6b7280',
  305.                                 callback: function(value) {
  306.                                     return new Intl.NumberFormat('fr-FR', {
  307.                                         style: 'currency',
  308.                                         currency: 'EUR',
  309.                                         maximumFractionDigits: 0
  310.                                     }).format(value);
  311.                                 }
  312.                             }
  313.                         }
  314.                     }
  315.                 }
  316.             });
  317.         });
  318.         function openModal(type) {
  319.             const modal = document.getElementById('modalContainer');
  320.             const title = document.getElementById('modalTitle');
  321.             modal.classList.remove('hidden');
  322.             switch(type) {
  323.                 case 'cb':
  324.                     title.textContent = 'Détails des paiements par Carte Bancaire';
  325.                     loadDetails('cb');
  326.                     break;
  327.                 case 'acompte':
  328.                     title.textContent = 'Détails des acomptes Planity';
  329.                     loadDetails('acompte');
  330.                     break;
  331.                 case 'especes':
  332.                     title.textContent = 'Détails des paiements en espèces';
  333.                     loadDetails('especes');
  334.                     break;
  335.             }
  336.         }
  337.         function closeModal() {
  338.             document.getElementById('modalContainer').classList.add('hidden');
  339.         }
  340.         function loadDetails(type) {
  341.             const routes = {
  342.                 'cb': '{{ path('app_dashboard_cb_details') }}',
  343.                 'acompte': '{{ path('app_dashboard_acompte_details') }}',
  344.                 'especes': '{{ path('app_dashboard_especes_details') }}'
  345.             };
  346.             fetch(routes[type])
  347.                 .then(response => response.json())
  348.                 .then(data => {
  349.                     document.getElementById('modalContent').innerHTML = generateTableHTML(data);
  350.                 });
  351.         }
  352.         function generateTableHTML(data) {
  353.             return `
  354.         <div class="mb-4 grid grid-cols-1 sm:grid-cols-3 gap-4">
  355.             <!-- Filtres -->
  356.             <div class="relative">
  357.                 <input type="text"
  358.                        id="dateFilter"
  359.                        placeholder="Filtrer par date"
  360.                        class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
  361.                        onkeyup="filterTable()">
  362.             </div>
  363.             <div class="relative">
  364.                 <input type="text"
  365.                        id="clientFilter"
  366.                        placeholder="Filtrer par client"
  367.                        class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
  368.                        onkeyup="filterTable()">
  369.             </div>
  370.             <div class="relative">
  371.                 <input type="text"
  372.                        id="montantFilter"
  373.                        placeholder="Filtrer par montant"
  374.                        class="w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:ring-indigo-500 focus:border-indigo-500"
  375.                        onkeyup="filterTable()">
  376.             </div>
  377.         </div>
  378.         <div class="overflow-x-auto">
  379.             <div class="inline-block min-w-full align-middle">
  380.                 <div class="overflow-hidden shadow-sm ring-1 ring-black ring-opacity-5">
  381.                     <!-- Version mobile -->
  382.                     <div class="block sm:hidden" id="mobileContent">
  383.                         ${generateMobileRows(data)}
  384.                     </div>
  385.                     <!-- Version desktop -->
  386.                     <table class="hidden sm:table min-w-full divide-y divide-gray-300">
  387. <thead class="bg-gray-50">
  388.     <tr>
  389.         <th class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 cursor-pointer" onclick="sortTable(0)">
  390.             <div class="flex items-center">
  391.                 Date
  392.                 <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  393.                     <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"/>
  394.                 </svg>
  395.             </div>
  396.         </th>
  397.         <th class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 cursor-pointer" onclick="sortTable(1)">
  398.             <div class="flex items-center">
  399.                 Client
  400.                 <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  401.                     <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"/>
  402.                 </svg>
  403.             </div>
  404.         </th>
  405.         <th class="px-3 py-3.5 text-right text-sm font-semibold text-gray-900 cursor-pointer" onclick="sortTable(2)">
  406.             <div class="flex items-center justify-end">
  407.                 Montant
  408.                 <svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
  409.                     <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 16V4m0 0L3 8m4-4l4 4m6 0v12m0 0l4-4m-4 4l-4-4"/>
  410.                 </svg>
  411.             </div>
  412.         </th>
  413.     </tr>
  414. </thead>
  415.                         <tbody class="divide-y divide-gray-200 bg-white" id="tableBody">
  416.                             ${generateTableRows(data)}
  417.                         </tbody>
  418.                         <tfoot class="bg-gray-50">
  419.                             <tr>
  420.                                 <td colspan="2" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Total</td>
  421.                                 <td class="px-3 py-3.5 text-right text-sm font-semibold text-gray-900" id="totalMontant">
  422.                                     ${calculateTotal(data)} â‚¬
  423.                                 </td>
  424.                             </tr>
  425.                         </tfoot>
  426.                     </table>
  427.                 </div>
  428.             </div>
  429.         </div>
  430.     `;
  431.         }
  432.         // Fonction pour générer les lignes du tableau
  433.         function generateTableRows(data) {
  434.             return data.map(item => `
  435.         <tr class="hover:bg-gray-50">
  436.             <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">${item.date}</td>
  437.             <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-900">${item.client}</td>
  438.             <td class="whitespace-nowrap px-3 py-4 text-sm text-right font-medium text-gray-900">${item.montant} â‚¬</td>
  439.         </tr>
  440.     `).join('');
  441.         }
  442.         // Fonction pour générer les lignes mobiles
  443.         function generateMobileRows(data) {
  444.             return data.map(item => `
  445.         <div class="bg-white border-b border-gray-200 p-4">
  446.             <div class="flex justify-between items-center mb-2">
  447.                 <div class="font-medium text-gray-900">${item.client}</div>
  448.                 <div class="text-lg font-bold text-gray-900">${item.montant} â‚¬</div>
  449.             </div>
  450.             <div class="text-sm text-gray-500">${item.date}</div>
  451.         </div>
  452.     `).join('');
  453.         }
  454.         // Fonction pour calculer le total
  455.         function calculateTotal(data) {
  456.             return data.reduce((sum, item) => sum + parseFloat(item.montant.replace(',', '.')), 0)
  457.                 .toFixed(2)
  458.                 .replace('.', ',');
  459.         }
  460.         // Fonction de filtrage
  461.         function filterTable() {
  462.             const dateFilter = document.getElementById('dateFilter').value.toLowerCase();
  463.             const clientFilter = document.getElementById('clientFilter').value.toLowerCase();
  464.             const montantFilter = document.getElementById('montantFilter').value.toLowerCase();
  465.             const rows = document.getElementById('tableBody').getElementsByTagName('tr');
  466.             let totalMontant = 0;
  467.             for (let row of rows) {
  468.                 const dateCell = row.cells[0].textContent.toLowerCase();
  469.                 const clientCell = row.cells[1].textContent.toLowerCase();
  470.                 const montantCell = row.cells[2].textContent.toLowerCase();
  471.                 const dateMatch = dateCell.includes(dateFilter);
  472.                 const clientMatch = clientCell.includes(clientFilter);
  473.                 const montantMatch = montantCell.includes(montantFilter);
  474.                 if (dateMatch && clientMatch && montantMatch) {
  475.                     row.style.display = '';
  476.                     totalMontant += parseFloat(montantCell.replace('€', '').replace(',', '.').trim());
  477.                 } else {
  478.                     row.style.display = 'none';
  479.                 }
  480.             }
  481.             // Mise Ã  jour du total
  482.             document.getElementById('totalMontant').textContent =
  483.                 totalMontant.toFixed(2).replace('.', ',') + ' â‚¬';
  484.         }
  485.         // Fonction de tri
  486.         let sortDirection = 1;
  487.         function sortTable(columnIndex) {
  488.             const table = document.getElementById('tableBody');
  489.             const rows = Array.from(table.getElementsByTagName('tr'));
  490.             rows.sort((a, b) => {
  491.                 let aValue = a.cells[columnIndex].textContent;
  492.                 let bValue = b.cells[columnIndex].textContent;
  493.                 // Pour les montants, convertir en nombres
  494.                 if (columnIndex === 2) {
  495.                     aValue = parseFloat(aValue.replace('€', '').replace(',', '.').trim());
  496.                     bValue = parseFloat(bValue.replace('€', '').replace(',', '.').trim());
  497.                 }
  498.                 if (aValue < bValue) return -1 * sortDirection;
  499.                 if (aValue > bValue) return 1 * sortDirection;
  500.                 return 0;
  501.             });
  502.             // Inverser la direction pour le prochain tri
  503.             sortDirection *= -1;
  504.             // Réinsérer les lignes triées
  505.             rows.forEach(row => table.appendChild(row));
  506.         }
  507.     </script>
  508. {% endblock %}