templates/listusers.html.twig line 1

Open in your IDE?
  1. {% extends "base.html.twig" %}
  2. {% block body %}
  3.     <div class="container-fluid min-h-100 ">
  4.         <div class="container mt-10 mb-10 min-h-100" style="min-height: 100dvh">
  5.             <div class="d-flex align-items-center justify-content-between w-100">
  6.                 <h1 class="mb-4 text-white">Empresas Registradas</h1>
  7.                 <div class="d-flex gap-2">
  8.                     <a href="{{ path('superusers_list') }}" class="btn btn-warning mb-3">Usuarios globales</a>
  9.                     <a href="{{ path('app_changes') }}" class="btn btn-info mb-3">Ajustes App</a>
  10.                     <a href="{{ path('alteraciones_sql') }}" class="btn btn-success mb-3">Alteraciones SQL</a>
  11.                     <a href="{{ path('azure_resources_list') }}" class="btn btn-light mb-3">Recursos IA</a>
  12.                     <a href="{{ path('app_empresa_new') }}" class="btn mb-3" style="background-color: #0d6efd; border-color: #0d6efd; color: #fff;">Crear empresa</a>
  13.                     <a href="{{ path('logout') }}" class="btn btn-danger mb-3" title="Cerrar sesión" aria-label="Cerrar sesión">
  14.                         <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
  15.                             <path d="M10 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h5v-2H5V5h5V3z"/>
  16.                             <path d="M21 12l-4-4v3H9v2h8v3l4-4z"/>
  17.                         </svg>
  18.                     </a>
  19.                 </div>
  20.             </div>
  21.             <div class="card">
  22.                 <div class="card-body p-5">
  23.                     <div class="mb-4">
  24.                         <div class="row align-items-end g-3">
  25.                             <div class="col-lg-8">
  26.                                 <label for="empresasSearch" class="form-label fw-semibold">Buscar empresa</label>
  27.                                 <input
  28.                                     type="text"
  29.                                     id="empresasSearch"
  30.                                     class="form-control"
  31.                                     placeholder="Buscar por ID o nombre..."
  32.                                     autocomplete="off"
  33.                                 >
  34.                             </div>
  35.                             <div class="col-lg-4 d-flex justify-content-lg-end">
  36.                                 <button
  37.                                     type="button"
  38.                                     class="btn btn-success w-100"
  39.                                     data-modal-open="#licenseExportAllModal"
  40.                                 >
  41.                                     Exportar CSV
  42.                                 </button>
  43.                             </div>
  44.                         </div>
  45.                     </div>
  46.                     <table id="empresasTable" class="table table-striped table-bordered p-4 max-h-400px overflow-scroll">
  47.                         <thead class="table-dark">
  48.                             <tr>
  49.                                 <th class="p-4" data-sort-col="0">ID <span class="sort-indicator"></span></th>
  50.                                 <th data-sort-col="1">Nombre <span class="sort-indicator"></span></th>
  51.                                 <th data-sort-col="2">Cuota Máxima Disco (Gb) <span class="sort-indicator"></span></th>
  52.                                 <th data-sort-col="3">Usuarios <span class="sort-indicator"></span></th>
  53.                                 <th>Acciones</th>
  54.                             </tr>
  55.                         </thead>
  56.                         <tbody>
  57.                             {% for empresa in empresas %}
  58.                                 <tr>
  59.                                     <td class="p-4" data-order="{{ empresa.id }}">{{ empresa.id }}</td>
  60.                                     <td>{{ empresa.name }}</td>
  61.                                     <td data-order="{{ empresa.maxDiskQuota }}">{{ empresa.maxDiskQuota }}</td>
  62.                                     <td data-order="{{ empresa.getId() | getUsers() }}">{{ empresa.getId() | getUsers() }}</td>
  63.                                     <td style="display:flex; justify-content: center; align-items: center; gap:10px;">
  64.                                         <a href="{{ path('app_empresa_show', {'id': empresa.id}) }}" class="btn btn-sm btn-outline-info">Ver</a>
  65.                                         {{ include('empresa/_delete_form.html.twig', { 'empresa': empresa }) }}
  66.                                     </td>
  67.                                 </tr>
  68.                             {% else %}
  69.                                 <tr>
  70.                                     <td colspan="5" class="text-center">No hay empresas registradas.</td>
  71.                                 </tr>
  72.                             {% endfor %}
  73.                         </tbody>
  74.                     </table>
  75.                 </div>
  76.             </div>
  77.         </div>
  78.     </div>
  79.     <div class="modal fade" id="licenseExportAllModal" tabindex="-1" aria-labelledby="licenseExportAllModalLabel" aria-hidden="true">
  80.         <div class="modal-dialog modal-dialog-centered">
  81.             <div class="modal-content">
  82.                 <form method="GET" action="{{ path('app_empresa_license_export_all') }}">
  83.                     <div class="modal-header">
  84.                         <h5 class="modal-title" id="licenseExportAllModalLabel">Exportar resumen global CSV</h5>
  85.                         <button type="button" class="btn-close" data-modal-close aria-label="Cerrar"></button>
  86.                     </div>
  87.                     <div class="modal-body">
  88.                         <div class="mb-3">
  89.                             <label for="global_export_start_date" class="form-label">Fecha inicio</label>
  90.                             <input
  91.                                 type="date"
  92.                                 id="global_export_start_date"
  93.                                 name="export_start_date"
  94.                                 class="form-control"
  95.                                 value="{{ app.request.query.get('export_start_date', 'now'|date('Y-m-01')) }}"
  96.                                 required
  97.                             >
  98.                         </div>
  99.                         <div class="mb-0">
  100.                             <label for="global_export_end_date" class="form-label">Fecha fin</label>
  101.                             <input
  102.                                 type="date"
  103.                                 id="global_export_end_date"
  104.                                 name="export_end_date"
  105.                                 class="form-control"
  106.                                 value="{{ app.request.query.get('export_end_date', 'now'|date('Y-m-d')) }}"
  107.                                 required
  108.                             >
  109.                         </div>
  110.                     </div>
  111.                     <div class="modal-footer">
  112.                         <button type="button" class="btn btn-outline-secondary" data-modal-close>Cancelar</button>
  113.                         <button type="submit" class="btn btn-success">Descargar CSV</button>
  114.                     </div>
  115.                 </form>
  116.             </div>
  117.         </div>
  118.     </div>
  119. {% endblock %}
  120. {% block footerjs %}
  121.     {{ parent() }}
  122.     <script>
  123.         document.addEventListener('DOMContentLoaded', function () {
  124.             const modalBackdropClass = 'custom-modal-backdrop';
  125.             function closeModal(modal) {
  126.                 if (!modal) {
  127.                     return;
  128.                 }
  129.                 modal.classList.remove('show');
  130.                 modal.style.display = 'none';
  131.                 modal.setAttribute('aria-hidden', 'true');
  132.                 document.body.classList.remove('modal-open');
  133.                 document.body.style.removeProperty('overflow');
  134.                 const backdrop = document.querySelector('.' + modalBackdropClass);
  135.                 if (backdrop) {
  136.                     backdrop.remove();
  137.                 }
  138.             }
  139.             function openModal(modal) {
  140.                 if (!modal) {
  141.                     return;
  142.                 }
  143.                 const existingBackdrop = document.querySelector('.' + modalBackdropClass);
  144.                 if (existingBackdrop) {
  145.                     existingBackdrop.remove();
  146.                 }
  147.                 const backdrop = document.createElement('div');
  148.                 backdrop.className = 'modal-backdrop fade show ' + modalBackdropClass;
  149.                 backdrop.addEventListener('click', function () {
  150.                     closeModal(modal);
  151.                 });
  152.                 document.body.appendChild(backdrop);
  153.                 modal.style.display = 'block';
  154.                 modal.classList.add('show');
  155.                 modal.removeAttribute('aria-hidden');
  156.                 document.body.classList.add('modal-open');
  157.                 document.body.style.overflow = 'hidden';
  158.             }
  159.             document.querySelectorAll('[data-modal-open]').forEach(function (button) {
  160.                 button.addEventListener('click', function () {
  161.                     const selector = button.getAttribute('data-modal-open');
  162.                     if (!selector) {
  163.                         return;
  164.                     }
  165.                     openModal(document.querySelector(selector));
  166.                 });
  167.             });
  168.             document.querySelectorAll('[data-modal-close]').forEach(function (button) {
  169.                 button.addEventListener('click', function () {
  170.                     closeModal(button.closest('.modal'));
  171.                 });
  172.             });
  173.             document.querySelectorAll('.modal').forEach(function (modal) {
  174.                 modal.addEventListener('click', function (event) {
  175.                     if (event.target === modal) {
  176.                         closeModal(modal);
  177.                     }
  178.                 });
  179.             });
  180.             document.addEventListener('keydown', function (event) {
  181.                 if (event.key !== 'Escape') {
  182.                     return;
  183.                 }
  184.                 const openModalElement = document.querySelector('.modal.show');
  185.                 if (openModalElement) {
  186.                     closeModal(openModalElement);
  187.                 }
  188.             });
  189.             const table = document.getElementById('empresasTable');
  190.             const searchInput = document.getElementById('empresasSearch');
  191.             if (!table || !searchInput) {
  192.                 return;
  193.             }
  194.             const tbody = table.tBodies[0];
  195.             if (!tbody) {
  196.                 return;
  197.             }
  198.             const allRows = Array.from(tbody.querySelectorAll('tr'));
  199.             const noResultsRow = document.createElement('tr');
  200.             noResultsRow.innerHTML = '<td colspan="5" class="text-center">No se encontraron empresas para ese filtro.</td>';
  201.             let filteredRows = allRows.slice();
  202.             let currentSort = { index: 0, direction: 'asc' };
  203.             function getCellValue(row, index) {
  204.                 const cell = row.cells[index];
  205.                 if (!cell) {
  206.                     return '';
  207.                 }
  208.                 const dataOrder = cell.getAttribute('data-order');
  209.                 return (dataOrder !== null ? dataOrder : cell.textContent || '').trim();
  210.             }
  211.             function isNumericColumn(index) {
  212.                 return index === 0 || index === 2 || index === 3;
  213.             }
  214.             function sortRows(index, direction) {
  215.                 filteredRows.sort(function (a, b) {
  216.                     const aValue = getCellValue(a, index);
  217.                     const bValue = getCellValue(b, index);
  218.                     if (isNumericColumn(index)) {
  219.                         const numA = Number(aValue);
  220.                         const numB = Number(bValue);
  221.                         const result = numA - numB;
  222.                         return direction === 'asc' ? result : -result;
  223.                     }
  224.                     const result = aValue.localeCompare(bValue, 'es', { sensitivity: 'base' });
  225.                     return direction === 'asc' ? result : -result;
  226.                 });
  227.             }
  228.             function renderRows() {
  229.                 tbody.innerHTML = '';
  230.                 if (filteredRows.length === 0) {
  231.                     tbody.appendChild(noResultsRow);
  232.                     return;
  233.                 }
  234.                 filteredRows.forEach(function (row) {
  235.                     tbody.appendChild(row);
  236.                 });
  237.             }
  238.             function updateSortIndicators() {
  239.                 const headers = table.querySelectorAll('thead th[data-sort-col]');
  240.                 headers.forEach(function (header) {
  241.                     const indicator = header.querySelector('.sort-indicator');
  242.                     if (!indicator) {
  243.                         return;
  244.                     }
  245.                     const columnIndex = Number(header.getAttribute('data-sort-col'));
  246.                     if (columnIndex === currentSort.index) {
  247.                         indicator.innerHTML = currentSort.direction === 'asc' ? '&uarr;' : '&darr;';
  248.                     } else {
  249.                         indicator.textContent = '';
  250.                     }
  251.                 });
  252.             }
  253.             function applySearch() {
  254.                 const query = searchInput.value.toLowerCase().trim();
  255.                 filteredRows = allRows.filter(function (row) {
  256.                     const idValue = getCellValue(row, 0).toLowerCase();
  257.                     const nameValue = getCellValue(row, 1).toLowerCase();
  258.                     return query === '' || idValue.includes(query) || nameValue.includes(query);
  259.                 });
  260.                 sortRows(currentSort.index, currentSort.direction);
  261.                 renderRows();
  262.             }
  263.             const sortableHeaders = table.querySelectorAll('thead th');
  264.             sortableHeaders.forEach(function (header, index) {
  265.                 if (index === 4) {
  266.                     return;
  267.                 }
  268.                 header.style.cursor = 'pointer';
  269.                 header.setAttribute('title', 'Ordenar');
  270.                 header.addEventListener('click', function () {
  271.                     const direction = currentSort.index === index && currentSort.direction === 'asc' ? 'desc' : 'asc';
  272.                     currentSort = { index: index, direction: direction };
  273.                     sortRows(index, direction);
  274.                     renderRows();
  275.                     updateSortIndicators();
  276.                 });
  277.             });
  278.             searchInput.addEventListener('input', applySearch);
  279.             sortRows(0, 'asc');
  280.             renderRows();
  281.             updateSortIndicators();
  282.             applySearch();
  283.         });
  284.     </script>
  285. {% endblock %}