pull que arregla error de login

This commit is contained in:
Алекс 2026-01-23 19:53:02 +01:00
parent 713ef5d76b
commit 3241c77646
5 changed files with 472 additions and 8 deletions

View File

@ -0,0 +1,77 @@
export function InvoiceItem(invoice) {
const item = document.createElement('div');
item.className = 'invoice-item';
// Formatear fecha
const formatDate = (dateString) => {
const date = new Date(dateString);
return date.toLocaleDateString('es-ES');
};
// Formatear moneda
const formatCurrency = (amount) => {
return new Intl.NumberFormat('es-ES', {
style: 'currency',
currency: 'EUR'
}).format(amount);
};
// Obtener clase de estado
const getStatusClass = (status) => {
const statusMap = {
'draft': 'status-draft',
'validated': 'status-validated',
'paid': 'status-paid',
'unpaid': 'status-unpaid',
'canceled': 'status-canceled'
};
return statusMap[status] || 'status-default';
};
// Obtener texto de estado
const getStatusText = (status) => {
const statusTextMap = {
'draft': 'Borrador',
'validated': 'Validada',
'paid': 'Pagada',
'unpaid': 'Impagada',
'canceled': 'Cancelada'
};
return statusTextMap[status] || status;
};
item.innerHTML = /*html*/`
<div class="invoice-checkbox">
<input type="checkbox" id="invoice-${invoice.id}" />
</div>
<div class="invoice-number">${invoice.number}</div>
<div class="invoice-status">
<span class="status-badge ${getStatusClass(invoice.status)}">
${getStatusText(invoice.status)}
</span>
</div>
<div class="invoice-id">${invoice.id}</div>
<div class="invoice-client">
<span class="client-icon">👤</span>
Cliente #${invoice.clientId}
</div>
<div class="invoice-date">${formatDate(invoice.date)}</div>
<div class="invoice-total">${formatCurrency(invoice.total)}</div>
<div class="invoice-remain">${formatCurrency(invoice.remainToPay)}</div>
<div class="invoice-actions">
<button class="btn-view" data-invoice-id="${invoice.id}" title="Ver detalles">
👁️
</button>
</div>
`;
// Event listener para el botón de ver
const viewBtn = item.querySelector('.btn-view');
viewBtn.addEventListener('click', () => {
console.log('Ver factura:', invoice.id);
// Aquí puedes agregar la lógica para ver los detalles
alert(`Ver detalles de factura: ${invoice.number}`);
});
return item;
}

128
src/pages/Facturas.js Executable file
View File

@ -0,0 +1,128 @@
import { InvoiceItem } from '../components/InvoiceItem.js';
export function renderFacturasPage() {
const container = document.createElement('div');
container.className = 'facturas-page';
container.innerHTML = /*html*/`
<div class="facturas-header">
<h1>Facturas</h1>
<button class="btn-new-invoice">+ Nueva Factura</button>
</div>
<div class="facturas-filters">
<input type="search" placeholder="Buscar facturas..." class="search-input" />
<select class="filter-status">
<option value="">Todos los estados</option>
<option value="draft">Borrador</option>
<option value="validated">Validada</option>
<option value="paid">Pagada</option>
<option value="unpaid">Impagada</option>
<option value="canceled">Cancelada</option>
</select>
</div>
<div class="invoices-table">
<div class="invoices-header">
<div class="invoice-checkbox">
<input type="checkbox" id="select-all" />
</div>
<div class="invoice-number">Número</div>
<div class="invoice-status">Estado</div>
<div class="invoice-id">ID</div>
<div class="invoice-client">Cliente</div>
<div class="invoice-date">Fecha</div>
<div class="invoice-total">Total</div>
<div class="invoice-remain">Pendiente</div>
<div class="invoice-actions">Acciones</div>
</div>
<div class="invoices-list">
<div class="loading">Cargando facturas...</div>
</div>
</div>
`;
// Función para cargar las facturas
async function loadInvoices() {
const invoicesList = container.querySelector('.invoices-list');
try {
const token = localStorage.getItem('token');
const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/Invoices`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error('Error al cargar facturas');
}
const invoices = await response.json();
// Limpiar lista
invoicesList.innerHTML = '';
// Si no hay facturas
if (invoices.length === 0) {
invoicesList.innerHTML = '<div class="no-invoices">No hay facturas disponibles</div>';
return;
}
// Renderizar cada factura
invoices.forEach(invoice => {
const invoiceItem = InvoiceItem(invoice);
invoicesList.appendChild(invoiceItem);
});
} catch (error) {
console.error('Error al cargar facturas:', error);
invoicesList.innerHTML = '<div class="error">Error al cargar las facturas. Por favor, intenta de nuevo.</div>';
}
}
// Event listeners
const newInvoiceBtn = container.querySelector('.btn-new-invoice');
newInvoiceBtn.addEventListener('click', () => {
console.log('Crear nueva factura');
alert('Funcionalidad de crear factura pendiente');
});
const selectAllCheckbox = container.querySelector('#select-all');
selectAllCheckbox.addEventListener('change', (e) => {
const checkboxes = container.querySelectorAll('.invoice-item input[type="checkbox"]');
checkboxes.forEach(cb => cb.checked = e.target.checked);
});
const searchInput = container.querySelector('.search-input');
searchInput.addEventListener('input', (e) => {
const searchTerm = e.target.value.toLowerCase();
const items = container.querySelectorAll('.invoice-item');
items.forEach(item => {
const text = item.textContent.toLowerCase();
item.style.display = text.includes(searchTerm) ? 'grid' : 'none';
});
});
const filterStatus = container.querySelector('.filter-status');
filterStatus.addEventListener('change', (e) => {
const status = e.target.value;
const items = container.querySelectorAll('.invoice-item');
items.forEach(item => {
if (!status) {
item.style.display = 'grid';
} else {
const badge = item.querySelector('.status-badge');
const itemStatus = badge.className.split(' ').find(c => c.startsWith('status-'))?.replace('status-', '');
item.style.display = itemStatus === status ? 'grid' : 'none';
}
});
});
// Cargar facturas al montar el componente
loadInvoices();
return container;
}

View File

@ -1,5 +1,6 @@
import { renderDashboard } from './DashboardPage.js'; import { renderDashboard } from './DashboardPage.js';
import { renderTestPage } from './test.js'; import { renderTestPage } from './test.js';
import { renderFacturasPage } from './Facturas.js';
// Registry of all pages available in the application // Registry of all pages available in the application
export const pagesRegistry = [ export const pagesRegistry = [
@ -25,11 +26,7 @@ export const pagesRegistry = [
icon: '📄', icon: '📄',
requiresAuth: true, requiresAuth: true,
showInSidebar: true, showInSidebar: true,
render: () => { render: renderFacturasPage
const div = document.createElement('div');
div.innerHTML = '<h1>Facturas</h1><p>Página en construcción...</p>';
return div;
}
}, },
{ {
route: 'clients', route: 'clients',

View File

@ -4,7 +4,7 @@ import { createSidebar } from './components/Sidebar.js';
import { getAvailablePages, getPageByRoute } from './pages/pagesRegistry.js'; import { getAvailablePages, getPageByRoute } from './pages/pagesRegistry.js';
// 🔧 Modo DEV: cambiar a false para activar login // 🔧 Modo DEV: cambiar a false para activar login
const DEV_MODE = false; const DEV_MODE = true;
export function initRouter() { export function initRouter() {
const app = document.querySelector('#app'); const app = document.querySelector('#app');

View File

@ -432,9 +432,271 @@ button:focus-visible {
} }
} }
/* Test Page Styles */ /* Facturas Page Styles */
.test-page { .facturas-page {
padding: 2rem; padding: 2rem;
max-width: 1600px;
margin: 0 auto;
}
.facturas-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
}
.facturas-header h1 {
color: var(--text-primary);
font-size: 2rem;
margin: 0;
}
.btn-new-invoice {
background-color: var(--primary);
color: white;
padding: 0.75rem 1.5rem;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
transition: background-color 0.2s;
}
.btn-new-invoice:hover {
background-color: var(--primary-hover);
}
.facturas-filters {
display: flex;
gap: 1rem;
margin-bottom: 1.5rem;
}
.search-input,
.filter-status {
padding: 0.75rem 1rem;
border: 1px solid var(--border-color);
border-radius: 8px;
font-size: 0.95rem;
background: var(--card-bg);
}
.search-input {
flex: 1;
max-width: 400px;
}
.filter-status {
min-width: 200px;
cursor: pointer;
}
.invoices-table {
background: var(--card-bg);
border-radius: 12px;
border: 1px solid var(--border-color);
overflow: hidden;
}
.invoices-header,
.invoice-item {
display: grid;
grid-template-columns: 40px 150px 120px 60px 150px 120px 120px 120px 80px;
gap: 1rem;
padding: 1rem 1.5rem;
align-items: center;
}
.invoices-header {
background: #f8fafc;
font-weight: 600;
color: var(--text-secondary);
font-size: 0.85rem;
text-transform: uppercase;
border-bottom: 1px solid var(--border-color);
}
.invoice-item {
border-bottom: 1px solid var(--border-color);
transition: background-color 0.2s;
}
.invoice-item:hover {
background-color: #f8fafc;
}
.invoice-item:last-child {
border-bottom: none;
}
.invoice-checkbox input {
width: 18px;
height: 18px;
cursor: pointer;
}
.invoice-number {
font-weight: 600;
color: var(--text-primary);
}
.status-badge {
display: inline-block;
padding: 0.4rem 0.8rem;
border-radius: 20px;
font-size: 0.8rem;
font-weight: 600;
text-align: center;
}
.status-draft {
background: #f3f4f6;
color: #6b7280;
}
.status-validated {
background: #dbeafe;
color: #1d4ed8;
}
.status-paid {
background: #d1fae5;
color: #059669;
}
.status-unpaid {
background: #fee2e2;
color: #dc2626;
}
.status-canceled {
background: #f3f4f6;
color: #374151;
text-decoration: line-through;
}
.invoice-client {
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--text-secondary);
}
.client-icon {
font-size: 1rem;
}
.invoice-date,
.invoice-total,
.invoice-remain {
color: var(--text-primary);
}
.invoice-total,
.invoice-remain {
font-weight: 600;
}
.invoice-actions {
display: flex;
gap: 0.5rem;
justify-content: center;
}
.btn-view {
background: transparent;
border: 1px solid var(--border-color);
padding: 0.5rem;
border-radius: 6px;
cursor: pointer;
font-size: 1.1rem;
transition: all 0.2s;
}
.btn-view:hover {
background: var(--primary);
border-color: var(--primary);
transform: scale(1.05);
}
.loading,
.error,
.no-invoices {
padding: 3rem;
text-align: center;
color: var(--text-secondary);
font-size: 1.1rem;
}
.error {
color: var(--danger);
}
/* Responsive */
@media (max-width: 1400px) {
.invoices-header,
.invoice-item {
grid-template-columns: 40px 120px 100px 50px 130px 100px 100px 100px 70px;
font-size: 0.9rem;
}
}
@media (max-width: 1024px) {
.facturas-page {
padding: 1rem;
}
.invoices-header,
.invoice-item {
grid-template-columns: 40px 100px 90px 130px 90px 90px 60px;
gap: 0.5rem;
padding: 1rem;
}
.invoice-id,
.invoice-remain {
display: none;
}
}
@media (max-width: 768px) {
.facturas-filters {
flex-direction: column;
}
.search-input {
max-width: 100%;
}
.invoices-header {
display: none;
}
.invoice-item {
grid-template-columns: 1fr;
gap: 0.5rem;
padding: 1.5rem;
}
.invoice-item > div {
display: flex;
justify-content: space-between;
align-items: center;
}
.invoice-item > div::before {
content: attr(class);
font-weight: 600;
color: var(--text-secondary);
text-transform: capitalize;
}
.invoice-checkbox {
position: absolute;
top: 1rem;
right: 1rem;
}
} }
.test-page h1 { .test-page h1 {