{% extends "base.html.twig" %}
{% block body %}
<div class="container-fluid min-h-100 ">
<div class="container mt-10 mb-10 min-h-100" style="min-height: 100dvh">
<div class="d-flex align-items-center justify-content-between w-100">
<h1 class="mb-4 text-white">Empresas Registradas</h1>
<div class="d-flex gap-2">
<a href="{{ path('superusers_list') }}" class="btn btn-warning mb-3">Usuarios globales</a>
<a href="{{ path('app_changes') }}" class="btn btn-info mb-3">Ajustes App</a>
<a href="{{ path('alteraciones_sql') }}" class="btn btn-success mb-3">Alteraciones SQL</a>
<a href="{{ path('azure_resources_list') }}" class="btn btn-light mb-3">Recursos IA</a>
<a href="{{ path('app_empresa_new') }}" class="btn mb-3" style="background-color: #0d6efd; border-color: #0d6efd; color: #fff;">Crear empresa</a>
<a href="{{ path('logout') }}" class="btn btn-danger mb-3" title="Cerrar sesión" aria-label="Cerrar sesión">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path d="M10 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h5v-2H5V5h5V3z"/>
<path d="M21 12l-4-4v3H9v2h8v3l4-4z"/>
</svg>
</a>
</div>
</div>
<div class="card">
<div class="card-body p-5">
<div class="mb-4">
<div class="row align-items-end g-3">
<div class="col-lg-8">
<label for="empresasSearch" class="form-label fw-semibold">Buscar empresa</label>
<input
type="text"
id="empresasSearch"
class="form-control"
placeholder="Buscar por ID o nombre..."
autocomplete="off"
>
</div>
<div class="col-lg-4 d-flex justify-content-lg-end">
<button
type="button"
class="btn btn-success w-100"
data-modal-open="#licenseExportAllModal"
>
Exportar CSV
</button>
</div>
</div>
</div>
<table id="empresasTable" class="table table-striped table-bordered p-4 max-h-400px overflow-scroll">
<thead class="table-dark">
<tr>
<th class="p-4" data-sort-col="0">ID <span class="sort-indicator"></span></th>
<th data-sort-col="1">Nombre <span class="sort-indicator"></span></th>
<th data-sort-col="2">Cuota Máxima Disco (Gb) <span class="sort-indicator"></span></th>
<th data-sort-col="3">Usuarios <span class="sort-indicator"></span></th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
{% for empresa in empresas %}
<tr>
<td class="p-4" data-order="{{ empresa.id }}">{{ empresa.id }}</td>
<td>{{ empresa.name }}</td>
<td data-order="{{ empresa.maxDiskQuota }}">{{ empresa.maxDiskQuota }}</td>
<td data-order="{{ empresa.getId() | getUsers() }}">{{ empresa.getId() | getUsers() }}</td>
<td style="display:flex; justify-content: center; align-items: center; gap:10px;">
<a href="{{ path('app_empresa_show', {'id': empresa.id}) }}" class="btn btn-sm btn-outline-info">Ver</a>
{{ include('empresa/_delete_form.html.twig', { 'empresa': empresa }) }}
</td>
</tr>
{% else %}
<tr>
<td colspan="5" class="text-center">No hay empresas registradas.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="modal fade" id="licenseExportAllModal" tabindex="-1" aria-labelledby="licenseExportAllModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<form method="GET" action="{{ path('app_empresa_license_export_all') }}">
<div class="modal-header">
<h5 class="modal-title" id="licenseExportAllModalLabel">Exportar resumen global CSV</h5>
<button type="button" class="btn-close" data-modal-close aria-label="Cerrar"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label for="global_export_start_date" class="form-label">Fecha inicio</label>
<input
type="date"
id="global_export_start_date"
name="export_start_date"
class="form-control"
value="{{ app.request.query.get('export_start_date', 'now'|date('Y-m-01')) }}"
required
>
</div>
<div class="mb-0">
<label for="global_export_end_date" class="form-label">Fecha fin</label>
<input
type="date"
id="global_export_end_date"
name="export_end_date"
class="form-control"
value="{{ app.request.query.get('export_end_date', 'now'|date('Y-m-d')) }}"
required
>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-modal-close>Cancelar</button>
<button type="submit" class="btn btn-success">Descargar CSV</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
{% block footerjs %}
{{ parent() }}
<script>
document.addEventListener('DOMContentLoaded', function () {
const modalBackdropClass = 'custom-modal-backdrop';
function closeModal(modal) {
if (!modal) {
return;
}
modal.classList.remove('show');
modal.style.display = 'none';
modal.setAttribute('aria-hidden', 'true');
document.body.classList.remove('modal-open');
document.body.style.removeProperty('overflow');
const backdrop = document.querySelector('.' + modalBackdropClass);
if (backdrop) {
backdrop.remove();
}
}
function openModal(modal) {
if (!modal) {
return;
}
const existingBackdrop = document.querySelector('.' + modalBackdropClass);
if (existingBackdrop) {
existingBackdrop.remove();
}
const backdrop = document.createElement('div');
backdrop.className = 'modal-backdrop fade show ' + modalBackdropClass;
backdrop.addEventListener('click', function () {
closeModal(modal);
});
document.body.appendChild(backdrop);
modal.style.display = 'block';
modal.classList.add('show');
modal.removeAttribute('aria-hidden');
document.body.classList.add('modal-open');
document.body.style.overflow = 'hidden';
}
document.querySelectorAll('[data-modal-open]').forEach(function (button) {
button.addEventListener('click', function () {
const selector = button.getAttribute('data-modal-open');
if (!selector) {
return;
}
openModal(document.querySelector(selector));
});
});
document.querySelectorAll('[data-modal-close]').forEach(function (button) {
button.addEventListener('click', function () {
closeModal(button.closest('.modal'));
});
});
document.querySelectorAll('.modal').forEach(function (modal) {
modal.addEventListener('click', function (event) {
if (event.target === modal) {
closeModal(modal);
}
});
});
document.addEventListener('keydown', function (event) {
if (event.key !== 'Escape') {
return;
}
const openModalElement = document.querySelector('.modal.show');
if (openModalElement) {
closeModal(openModalElement);
}
});
const table = document.getElementById('empresasTable');
const searchInput = document.getElementById('empresasSearch');
if (!table || !searchInput) {
return;
}
const tbody = table.tBodies[0];
if (!tbody) {
return;
}
const allRows = Array.from(tbody.querySelectorAll('tr'));
const noResultsRow = document.createElement('tr');
noResultsRow.innerHTML = '<td colspan="5" class="text-center">No se encontraron empresas para ese filtro.</td>';
let filteredRows = allRows.slice();
let currentSort = { index: 0, direction: 'asc' };
function getCellValue(row, index) {
const cell = row.cells[index];
if (!cell) {
return '';
}
const dataOrder = cell.getAttribute('data-order');
return (dataOrder !== null ? dataOrder : cell.textContent || '').trim();
}
function isNumericColumn(index) {
return index === 0 || index === 2 || index === 3;
}
function sortRows(index, direction) {
filteredRows.sort(function (a, b) {
const aValue = getCellValue(a, index);
const bValue = getCellValue(b, index);
if (isNumericColumn(index)) {
const numA = Number(aValue);
const numB = Number(bValue);
const result = numA - numB;
return direction === 'asc' ? result : -result;
}
const result = aValue.localeCompare(bValue, 'es', { sensitivity: 'base' });
return direction === 'asc' ? result : -result;
});
}
function renderRows() {
tbody.innerHTML = '';
if (filteredRows.length === 0) {
tbody.appendChild(noResultsRow);
return;
}
filteredRows.forEach(function (row) {
tbody.appendChild(row);
});
}
function updateSortIndicators() {
const headers = table.querySelectorAll('thead th[data-sort-col]');
headers.forEach(function (header) {
const indicator = header.querySelector('.sort-indicator');
if (!indicator) {
return;
}
const columnIndex = Number(header.getAttribute('data-sort-col'));
if (columnIndex === currentSort.index) {
indicator.innerHTML = currentSort.direction === 'asc' ? '↑' : '↓';
} else {
indicator.textContent = '';
}
});
}
function applySearch() {
const query = searchInput.value.toLowerCase().trim();
filteredRows = allRows.filter(function (row) {
const idValue = getCellValue(row, 0).toLowerCase();
const nameValue = getCellValue(row, 1).toLowerCase();
return query === '' || idValue.includes(query) || nameValue.includes(query);
});
sortRows(currentSort.index, currentSort.direction);
renderRows();
}
const sortableHeaders = table.querySelectorAll('thead th');
sortableHeaders.forEach(function (header, index) {
if (index === 4) {
return;
}
header.style.cursor = 'pointer';
header.setAttribute('title', 'Ordenar');
header.addEventListener('click', function () {
const direction = currentSort.index === index && currentSort.direction === 'asc' ? 'desc' : 'asc';
currentSort = { index: index, direction: direction };
sortRows(index, direction);
renderRows();
updateSortIndicators();
});
});
searchInput.addEventListener('input', applySearch);
sortRows(0, 'asc');
renderRows();
updateSortIndicators();
applySearch();
});
</script>
{% endblock %}