pull que arregla error de login
This commit is contained in:
parent
713ef5d76b
commit
3241c77646
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
|
||||||
266
src/style.css
266
src/style.css
|
|
@ -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 {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue