diff --git a/.env b/.env old mode 100644 new mode 100755 diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/index.html b/index.html old mode 100644 new mode 100755 diff --git a/package-lock.json b/package-lock.json old mode 100644 new mode 100755 diff --git a/package.json b/package.json old mode 100644 new mode 100755 diff --git a/public/vite.svg b/public/vite.svg old mode 100644 new mode 100755 diff --git a/src/components/InvoiceItem.js b/src/components/InvoiceItem.js new file mode 100644 index 0000000..5bc89d2 --- /dev/null +++ b/src/components/InvoiceItem.js @@ -0,0 +1,76 @@ +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.clientName || 'Sin nombre'} +
+
${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/components/Login.js b/src/components/Login.js old mode 100644 new mode 100755 diff --git a/src/components/Sidebar.js b/src/components/Sidebar.js old mode 100644 new mode 100755 diff --git a/src/counter.js b/src/counter.js old mode 100644 new mode 100755 diff --git a/src/javascript.svg b/src/javascript.svg old mode 100644 new mode 100755 diff --git a/src/main.js b/src/main.js old mode 100644 new mode 100755 diff --git a/src/pages/CreateInvoicePage.js b/src/pages/CreateInvoicePage.js new file mode 100644 index 0000000..c043d6f --- /dev/null +++ b/src/pages/CreateInvoicePage.js @@ -0,0 +1,297 @@ +export function renderCreateInvoicePage() { + const container = document.createElement('div'); + container.className = 'create-invoice-page'; + + container.innerHTML = /*html*/` +
+
+

Nuevo Borrador

+

Crear un nuevo borrador

+
+ +
+ +
+
+

Información General

+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ +
+
+ + +
+
+ +
+
+ + +
+
+
+ +
+
+

Líneas de Borrador

+ +
+ +
+ +
+
+ +
+ + +
+
+ `; + + // Establecer fecha de hoy por defecto + const today = new Date().toISOString().split('T')[0]; + container.querySelector('#date').value = today; + + // Establecer fecha de vencimiento (30 días después) + const expireDate = new Date(); + expireDate.setDate(expireDate.getDate() + 30); + container.querySelector('#expireDate').value = expireDate.toISOString().split('T')[0]; + + // Contador de líneas + let lineCounter = 0; + + // Función para crear una nueva línea de borrador + function createInvoiceLine() { + lineCounter++; + const lineDiv = document.createElement('div'); + lineDiv.className = 'invoice-line'; + lineDiv.dataset.lineId = lineCounter; + + lineDiv.innerHTML = /*html*/` +
+ Línea ${lineCounter} + +
+ +
+
+ + +
+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ `; + + // Event listeners para calcular subtotal + const quantityInput = lineDiv.querySelector('.line-quantity'); + const priceInput = lineDiv.querySelector('.line-unitPrice'); + const subtotalInput = lineDiv.querySelector('.line-subtotal'); + + function updateSubtotal() { + const quantity = parseFloat(quantityInput.value) || 0; + const price = parseFloat(priceInput.value) || 0; + const subtotal = quantity * price; + subtotalInput.value = `${subtotal.toFixed(2)} €`; + updateTotal(); + } + + quantityInput.addEventListener('input', updateSubtotal); + priceInput.addEventListener('input', updateSubtotal); + + // Event listener para eliminar línea + lineDiv.querySelector('.btn-remove-line').addEventListener('click', () => { + lineDiv.remove(); + updateLineNumbers(); + updateTotal(); + }); + + return lineDiv; + } + + // Función para actualizar números de línea + function updateLineNumbers() { + const lines = container.querySelectorAll('.invoice-line'); + lines.forEach((line, index) => { + line.querySelector('.line-number').textContent = `Línea ${index + 1}`; + }); + } + + // Función para actualizar el total + function updateTotal() { + const lines = container.querySelectorAll('.invoice-line'); + let total = 0; + lines.forEach(line => { + const quantity = parseFloat(line.querySelector('.line-quantity').value) || 0; + const price = parseFloat(line.querySelector('.line-unitPrice').value) || 0; + const taxRate = parseFloat(line.querySelector('.line-taxRate').value) || 0; + const subtotal = quantity * price; + const withTax = subtotal * (1 + taxRate / 100); + total += withTax; + }); + + // Mostrar total en algún lugar si lo deseas + console.log('Total borrador:', total.toFixed(2)); + } + + // Agregar primera línea por defecto + const linesContainer = container.querySelector('#invoice-lines-container'); + linesContainer.appendChild(createInvoiceLine()); + + // Event listener para añadir línea + container.querySelector('#add-line-btn').addEventListener('click', () => { + linesContainer.appendChild(createInvoiceLine()); + }); + + // Event listener para volver + container.querySelector('#back-btn').addEventListener('click', () => { + window.location.hash = '#invoices'; + }); + + // Event listener para cancelar + container.querySelector('#cancel-btn').addEventListener('click', () => { + if (confirm('¿Estás seguro de que quieres cancelar? Se perderán los datos no guardados.')) { + window.location.hash = '#invoices'; + } + }); + + // Event listener para enviar formulario + container.querySelector('#invoice-form').addEventListener('submit', async (e) => { + e.preventDefault(); + + // Recopilar datos del formulario + const clientIdValue = parseInt(container.querySelector('#clientId').value); + + // Validar clientId + if (!clientIdValue || isNaN(clientIdValue) || clientIdValue < 1) { + alert('Por favor ingresa un ID de cliente válido'); + return; + } + + const formData = { + clientId: clientIdValue, + date: container.querySelector('#date').value + 'T00:00:00', + expireDate: container.querySelector('#expireDate').value + 'T00:00:00', + reference: container.querySelector('#reference').value || null, + notePublic: container.querySelector('#notePublic').value || null, + notePrivate: container.querySelector('#notePrivate').value || null, + lines: [] + }; + + // Recopilar líneas + const lines = container.querySelectorAll('.invoice-line'); + if (lines.length === 0) { + alert('Debes agregar al menos una línea al borrador'); + return; + } + + lines.forEach(line => { + const lineData = { + description: line.querySelector('.line-description').value, + quantity: parseFloat(line.querySelector('.line-quantity').value), + unitPrice: parseFloat(line.querySelector('.line-unitPrice').value), + taxRate: parseFloat(line.querySelector('.line-taxRate').value) + }; + formData.lines.push(lineData); + }); + + // Validar que todos los campos requeridos estén completos + if (!formData.clientId || formData.lines.some(l => !l.description || !l.quantity || !l.unitPrice)) { + alert('Por favor completa todos los campos obligatorios'); + return; + } + + // Enviar a la API + try { + const submitBtn = container.querySelector('.btn-submit'); + submitBtn.disabled = true; + submitBtn.textContent = 'Creando...'; + + const token = localStorage.getItem('token'); + const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/Invoices`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify(formData) + }); + + if (!response.ok) { + const error = await response.text(); + throw new Error(error || 'Error al crear el borrador'); + } + + const invoiceId = await response.json(); + alert(`Borrador creado exitosamente con ID: ${invoiceId}`); + + // Redirigir inmediatamente a la lista de facturas + window.location.hash = '#invoices'; + + } catch (error) { + console.error('Error al crear borrador:', error); + + // Mensaje de error más específico + let errorMessage = 'Error al crear el borrador'; + if (error.message.includes('foreign key constraint')) { + errorMessage = 'Error: El cliente con ese ID no existe. Por favor verifica el ID del cliente.'; + } else if (error.message) { + errorMessage = `Error: ${error.message}`; + } + + alert(errorMessage); + + const submitBtn = container.querySelector('.btn-submit'); + if (submitBtn) { + submitBtn.disabled = false; + submitBtn.textContent = 'Crear Borrador'; + } + } + }); + + return container; +} diff --git a/src/pages/DashboardPage.js b/src/pages/DashboardPage.js old mode 100644 new mode 100755 diff --git a/src/pages/Facturas.js b/src/pages/Facturas.js new file mode 100755 index 0000000..e211206 --- /dev/null +++ b/src/pages/Facturas.js @@ -0,0 +1,235 @@ +import { InvoiceItem } from '../components/InvoiceItem.js'; + +export function renderFacturasPage() { + const container = document.createElement('div'); + container.className = 'facturas-page'; + + // Estado de paginación + let currentPage = 1; + let totalPages = 1; + const pageSize = 20; + let allInvoices = []; // Almacenar todas las facturas + let filteredInvoices = []; // Facturas filtradas + let currentFilter = ''; // Filtro actual de estado + let searchTerm = ''; // Término de búsqueda actual + + container.innerHTML = /*html*/` +
+

Facturas

+ +
+ +
+ + +
+ +
+
+
+ +
+
Número
+
Estado
+
Cliente
+
Fecha
+
Total
+
Pendiente
+
Acciones
+
+
+
Cargando facturas...
+
+
+ + + `; + + // Función para aplicar filtros combinados + function applyFilters() { + // Ya no filtra localmente, hace llamada a la API + loadAllInvoices(); + } + + // Función para renderizar la página actual + function renderPage(page = 1) { + const invoicesList = container.querySelector('.invoices-list'); + + currentPage = page; + totalPages = Math.ceil(filteredInvoices.length / pageSize); + + // Calcular índices para la página + const startIndex = (page - 1) * pageSize; + const endIndex = Math.min(startIndex + pageSize, filteredInvoices.length); + const invoices = filteredInvoices.slice(startIndex, endIndex); + + console.log(`Página ${currentPage}/${totalPages}, Facturas en esta página: ${invoices.length}, Total filtrado: ${filteredInvoices.length}`); + + // Actualizar UI de paginación + updatePaginationUI(); + + // 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); + }); + } + + // Función para cargar todas las facturas + async function loadAllInvoices() { + const invoicesList = container.querySelector('.invoices-list'); + + try { + invoicesList.innerHTML = '
Cargando facturas...
'; + + const token = localStorage.getItem('token'); + + // Construir URL con parámetros + let url = `${import.meta.env.VITE_API_BASE_URL}/api/Invoices?limit=1000`; + + if (currentFilter) { + url += `&status=${currentFilter}`; + } + + if (searchTerm) { + url += `&search=${encodeURIComponent(searchTerm)}`; + } + + const response = await fetch(url, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${token}`, + 'Content-Type': 'application/json' + } + }); + + if (!response.ok) { + throw new Error('Error al cargar facturas'); + } + + const data = await response.json(); + + // Manejo de respuesta - obtener todas las facturas + allInvoices = Array.isArray(data) ? data : data.data || data.invoices || []; + filteredInvoices = [...allInvoices]; + + console.log(`Total de facturas cargadas: ${allInvoices.length}`); + + // Renderizar la primera página + renderPage(1); + + } catch (error) { + console.error('Error al cargar facturas:', error); + invoicesList.innerHTML = '
Error al cargar las facturas. Por favor, intenta de nuevo.
'; + } + } + + // Función para actualizar la UI de paginación + function updatePaginationUI() { + const paginationDiv = container.querySelector('.pagination'); + const currentPageSpan = container.querySelector('.current-page'); + const totalPagesSpan = container.querySelector('.total-pages'); + const btnPrev = container.querySelector('.btn-prev'); + const btnNext = container.querySelector('.btn-next'); + + currentPageSpan.textContent = currentPage; + totalPagesSpan.textContent = totalPages; + + // Mostrar u ocultar la paginación según el número de páginas + if (totalPages <= 1) { + paginationDiv.style.display = 'none'; + } else { + paginationDiv.style.display = 'flex'; + } + + // Deshabilitar botones según corresponda + btnPrev.disabled = currentPage === 1; + btnNext.disabled = currentPage === totalPages; + } + + // Event listeners + const newInvoiceBtn = container.querySelector('.btn-new-invoice'); + newInvoiceBtn.addEventListener('click', () => { + window.location.hash = '#create-invoice'; + }); + + 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 inputValue = e.target.value; + // Debounce para no hacer muchas llamadas + clearTimeout(searchInput.debounceTimer); + searchInput.debounceTimer = setTimeout(() => { + // Solo aplicar búsqueda si contiene al menos un número + const hasNumber = /\d/.test(inputValue); + if (hasNumber && inputValue !== 'I' && inputValue !== 'IN' && inputValue !== 'N') { + searchTerm = inputValue; + } else { + searchTerm = ''; // Si no tiene número, mostrar todas + } + applyFilters(); + }, 500); // Espera 500ms después de que el usuario deja de escribir + }); + + const filterStatus = container.querySelector('.filter-status'); + filterStatus.addEventListener('change', (e) => { + currentFilter = e.target.value; + console.log(`Filtro aplicado: ${currentFilter || 'Todos'}`); + applyFilters(); + }); + + // Event listeners de paginación + const btnPrev = container.querySelector('.btn-prev'); + const btnNext = container.querySelector('.btn-next'); + + btnPrev.addEventListener('click', () => { + if (currentPage > 1) { + renderPage(currentPage - 1); + scrollToTop(); + } + }); + + btnNext.addEventListener('click', () => { + if (currentPage < totalPages) { + renderPage(currentPage + 1); + scrollToTop(); + } + }); + + // Función para scroll al inicio + function scrollToTop() { + container.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + + // Cargar todas las facturas al montar el componente + loadAllInvoices(); + + return container; +} diff --git a/src/pages/LoginPage.js b/src/pages/LoginPage.js old mode 100644 new mode 100755 diff --git a/src/pages/pagesRegistry.js b/src/pages/pagesRegistry.js old mode 100644 new mode 100755 index 8a7bf57..0e7814a --- a/src/pages/pagesRegistry.js +++ b/src/pages/pagesRegistry.js @@ -1,5 +1,7 @@ import { renderDashboard } from './DashboardPage.js'; import { renderTestPage } from './test.js'; +import { renderFacturasPage } from './Facturas.js'; +import { renderCreateInvoicePage } from './CreateInvoicePage.js'; // Registry of all pages available in the application export const pagesRegistry = [ @@ -25,11 +27,15 @@ 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: 'create-invoice', + name: 'Nueva Factura', + icon: '➕', + requiresAuth: true, + showInSidebar: false, // No mostrar en sidebar + render: renderCreateInvoicePage }, { route: 'clients', diff --git a/src/pages/test.js b/src/pages/test.js old mode 100644 new mode 100755 diff --git a/src/router.js b/src/router.js old mode 100644 new mode 100755 diff --git a/src/services/auth.js b/src/services/auth.js old mode 100644 new mode 100755 diff --git a/src/style.css b/src/style.css old mode 100644 new mode 100755 index 17b2db5..b566d17 --- a/src/style.css +++ b/src/style.css @@ -229,22 +229,22 @@ button:focus-visible { .status-pending { background-color: #fef3c7; - color: #d97706; + color: #000000; } .status-paid { background-color: #d1fae5; - color: #059669; + color: #000000; } .status-overdue { background-color: #fee2e2; - color: #dc2626; + color: #000000; } .status-draft { background-color: #f1f5f9; - color: #64748b; + color: #000000; } .amount-display { @@ -432,9 +432,350 @@ 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); + color: #000000; +} + +.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 120px 120px 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: #000000; +} + +.status-validated { + background: #dbeafe; + color: #000000; +} + +.status-paid { + background: #d1fae5; + color: #000000; +} + +.status-unpaid { + background: #fee2e2; + color: #000000; +} + +.status-canceled { + background: #f3f4f6; + color: #000000; + 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); +} + +/* Pagination Styles */ +.pagination { + display: flex; + justify-content: center; + align-items: center; + gap: 2rem; + margin-top: 2rem; + padding: 1.5rem; + background: var(--card-bg); + border-radius: 12px; + border: 1px solid var(--border-color); +} + +.pagination-info { + font-weight: 600; + color: var(--text-primary); + font-size: 1rem; + display: flex; + gap: 0.5rem; + align-items: center; +} + +.pagination-info .current-page, +.pagination-info .total-pages { + font-weight: 700; + color: var(--primary); + min-width: 30px; + text-align: center; +} + +.btn-pagination { + padding: 0.75rem 1.5rem; + background-color: var(--primary); + color: white; + border: none; + border-radius: 8px; + cursor: pointer; + font-weight: 600; + transition: all 0.2s; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.btn-pagination:hover:not(:disabled) { + background-color: var(--primary-hover); + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3); +} + +.btn-pagination:disabled { + background-color: #cbd5e1; + cursor: not-allowed; + opacity: 0.6; +} + +.btn-pagination:active:not(:disabled) { + transform: translateY(0); +} + +/* Responsive */ +@media (max-width: 768px) { + .pagination { + flex-wrap: wrap; + gap: 1rem; + } + + .btn-pagination { + flex: 1; + min-width: 120px; + } + + .pagination-info { + order: 3; + width: 100%; + justify-content: center; + } +} + +@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 { @@ -474,3 +815,249 @@ button:focus-visible { min-height: 100vh; width: 100%; } +/* Create Invoice Page Styles */ +.create-invoice-page { + padding: 2rem; + max-width: 1200px; + margin: 0 auto; +} + +.create-invoice-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; +} + +.create-invoice-header h1 { + margin: 0; + font-size: 2rem; + color: var(--text-primary); +} + +.create-invoice-header .subtitle { + margin: 0.5rem 0 0 0; + color: var(--text-secondary); +} + +.btn-back { + background: var(--card-bg); + color: var(--text-primary); + border: 1px solid var(--border-color); + padding: 0.75rem 1.5rem; + border-radius: 8px; + cursor: pointer; + font-weight: 600; + transition: all 0.2s; +} + +.btn-back:hover { + background: #f1f5f9; +} + +.invoice-form { + background: var(--card-bg); + border-radius: 12px; + border: 1px solid var(--border-color); + padding: 2rem; +} + +.form-section { + margin-bottom: 2rem; +} + +.form-section h2 { + font-size: 1.5rem; + margin: 0 0 1.5rem 0; + color: var(--text-primary); + border-bottom: 2px solid var(--primary); + padding-bottom: 0.5rem; +} + +.section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; +} + +.section-header h2 { + margin: 0; + border: none; + padding: 0; +} + +.btn-add-line { + background: var(--success); + color: white; + border: none; + padding: 0.5rem 1rem; + border-radius: 6px; + cursor: pointer; + font-weight: 600; + transition: background 0.2s; +} + +.btn-add-line:hover { + background: var(--success-hover); +} + +.form-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1.5rem; + margin-bottom: 1.5rem; +} + +.form-row-inline { + display: grid; + grid-template-columns: 1fr 1fr 1fr 1fr; + gap: 1rem; + margin-bottom: 1rem; +} + +.form-group { + display: flex; + flex-direction: column; +} + +.form-group.full-width { + grid-column: 1 / -1; +} + +.form-group label { + font-weight: 600; + margin-bottom: 0.5rem; + color: var(--text-primary); +} + +.form-group input, +.form-group textarea, +.form-group select { + padding: 0.75rem; + border: 1px solid var(--border-color); + border-radius: 6px; + font-size: 1rem; + color: #000000; + background: var(--card-bg); + transition: border-color 0.2s; +} + +.form-group input:focus, +.form-group textarea:focus, +.form-group select:focus { + outline: none; + border-color: var(--primary); +} + +.form-group input[readonly] { + background: #f1f5f9; + cursor: not-allowed; +} + +.invoice-line { + background: #f8fafc; + border: 1px solid var(--border-color); + border-radius: 8px; + padding: 1.5rem; + margin-bottom: 1rem; +} + +.line-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.line-number { + font-weight: 700; + color: var(--text-primary); + font-size: 1.1rem; +} + +.btn-remove-line { + background: var(--danger); + color: white; + border: none; + padding: 0.5rem 1rem; + border-radius: 6px; + cursor: pointer; + font-size: 0.9rem; + transition: background 0.2s; +} + +.btn-remove-line:hover { + background: var(--danger-hover); +} + +.line-content { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.form-actions { + display: flex; + justify-content: flex-end; + gap: 1rem; + margin-top: 2rem; + padding-top: 2rem; + border-top: 1px solid var(--border-color); +} + +.btn-cancel { + background: var(--card-bg); + color: var(--text-primary); + border: 1px solid var(--border-color); + padding: 0.75rem 2rem; + border-radius: 8px; + cursor: pointer; + font-weight: 600; + transition: all 0.2s; +} + +.btn-cancel:hover { + background: #f1f5f9; +} + +.btn-submit { + background: var(--primary); + color: white; + border: none; + padding: 0.75rem 2rem; + border-radius: 8px; + cursor: pointer; + font-weight: 600; + transition: background 0.2s; +} + +.btn-submit:hover { + background: var(--primary-hover); +} + +.btn-submit:disabled { + background: #cbd5e1; + cursor: not-allowed; +} + +@media (max-width: 768px) { + .create-invoice-page { + padding: 1rem; + } + + .create-invoice-header { + flex-direction: column; + align-items: flex-start; + gap: 1rem; + } + + .form-row, + .form-row-inline { + grid-template-columns: 1fr; + } + + .invoice-form { + padding: 1rem; + } +} \ No newline at end of file