From 3241c77646b4c8778c1d95ba2c141faff36c7e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81?= Date: Fri, 23 Jan 2026 19:53:02 +0100 Subject: [PATCH] pull que arregla error de login --- src/components/InvoiceItem.js | 77 ++++++++++ src/pages/Facturas.js | 128 ++++++++++++++++ src/pages/pagesRegistry.js | 7 +- src/router.js | 2 +- src/style.css | 266 +++++++++++++++++++++++++++++++++- 5 files changed, 472 insertions(+), 8 deletions(-) create mode 100644 src/components/InvoiceItem.js create mode 100755 src/pages/Facturas.js diff --git a/src/components/InvoiceItem.js b/src/components/InvoiceItem.js new file mode 100644 index 0000000..2f6e782 --- /dev/null +++ b/src/components/InvoiceItem.js @@ -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*/` +
+ +
+
${invoice.number}
+
+ + ${getStatusText(invoice.status)} + +
+
${invoice.id}
+
+ 👤 + Cliente #${invoice.clientId} +
+
${formatDate(invoice.date)}
+
${formatCurrency(invoice.total)}
+
${formatCurrency(invoice.remainToPay)}
+
+ +
+ `; + + // 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; +} diff --git a/src/pages/Facturas.js b/src/pages/Facturas.js new file mode 100755 index 0000000..b6064ec --- /dev/null +++ b/src/pages/Facturas.js @@ -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*/` +
+

Facturas

+ +
+ +
+ + +
+ +
+
+
+ +
+
Número
+
Estado
+
ID
+
Cliente
+
Fecha
+
Total
+
Pendiente
+
Acciones
+
+
+
Cargando facturas...
+
+
+ `; + + // 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 = '
No hay facturas disponibles
'; + 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 = '
Error al cargar las facturas. Por favor, intenta de nuevo.
'; + } + } + + // 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; +} diff --git a/src/pages/pagesRegistry.js b/src/pages/pagesRegistry.js index 8a7bf57..995409a 100755 --- a/src/pages/pagesRegistry.js +++ b/src/pages/pagesRegistry.js @@ -1,5 +1,6 @@ import { renderDashboard } from './DashboardPage.js'; import { renderTestPage } from './test.js'; +import { renderFacturasPage } from './Facturas.js'; // Registry of all pages available in the application export const pagesRegistry = [ @@ -25,11 +26,7 @@ export const pagesRegistry = [ icon: '📄', requiresAuth: true, showInSidebar: true, - render: () => { - const div = document.createElement('div'); - div.innerHTML = '

Facturas

Página en construcción...

'; - return div; - } + render: renderFacturasPage }, { route: 'clients', diff --git a/src/router.js b/src/router.js index a5f7380..142b3f6 100755 --- a/src/router.js +++ b/src/router.js @@ -4,7 +4,7 @@ import { createSidebar } from './components/Sidebar.js'; import { getAvailablePages, getPageByRoute } from './pages/pagesRegistry.js'; // 🔧 Modo DEV: cambiar a false para activar login -const DEV_MODE = false; +const DEV_MODE = true; export function initRouter() { const app = document.querySelector('#app'); diff --git a/src/style.css b/src/style.css index 17b2db5..d25beb5 100755 --- a/src/style.css +++ b/src/style.css @@ -432,9 +432,271 @@ button:focus-visible { } } -/* Test Page Styles */ -.test-page { +/* Facturas Page Styles */ +.facturas-page { 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 {