Refactor Create Invoice Page for ultra compact design and improved user experience

This commit is contained in:
javiermengual 2026-02-03 13:44:59 +01:00
parent aa8f049444
commit f7b376e10f
2 changed files with 332 additions and 336 deletions

View File

@ -3,212 +3,169 @@ export function renderCreateInvoicePage() {
container.className = 'create-invoice-page'; container.className = 'create-invoice-page';
container.innerHTML = /*html*/` container.innerHTML = /*html*/`
<div class="create-invoice-header"> <div class="invoice-header-compact">
<div> <h1>Nueva Factura</h1>
<h1>Nuevo Borrador</h1> <button class="btn-back" id="back-btn">← Volver</button>
<p class="subtitle">Crear un nuevo borrador</p>
</div>
<button class="btn-back" id="back-btn">← Volver a Facturas</button>
</div> </div>
<form class="invoice-form" id="invoice-form"> <form class="invoice-form-compact" id="invoice-form">
<div class="form-section"> <div class="form-grid-compact">
<h2>Información General</h2> <div class="form-field-compact full-width">
<label>Cliente</label>
<div class="form-row"> <select id="clientId" name="clientId" required disabled>
<div class="form-group"> <option value="">Cargando...</option>
<label for="clientId">Cliente ID *</label> </select>
<input type="number" id="clientId" name="clientId" required min="1" placeholder="ID del cliente" />
</div> </div>
<div class="form-group"> <div class="form-field-compact">
<label for="reference">Referencia</label> <label>Fecha</label>
<input type="text" id="reference" name="reference" placeholder="Ej: FAC-2026-001" /> <input type="date" id="date" name="date" />
</div>
<div class="form-field-compact">
<label>Vencimiento</label>
<input type="date" id="expireDate" name="expireDate" />
</div> </div>
</div> </div>
<div class="form-row"> <div class="notes-grid-compact">
<div class="form-group"> <div class="form-field-compact">
<label for="date">Fecha *</label> <label>Nota pública</label>
<input type="date" id="date" name="date" required /> <textarea id="notePublic" name="notePublic" rows="2" placeholder="Visible para el cliente (opcional)"></textarea>
</div> </div>
<div class="form-group"> <div class="form-field-compact">
<label for="expireDate">Fecha de vencimiento *</label> <label>Nota privada</label>
<input type="date" id="expireDate" name="expireDate" required /> <textarea id="notePrivate" name="notePrivate" rows="2" placeholder="Interna (opcional)"></textarea>
</div> </div>
</div> </div>
<div class="form-row"> <div class="lines-section-compact">
<div class="form-group full-width"> <div class="lines-header-compact">
<label for="notePublic">Nota pública</label> <h3>Líneas de Factura</h3>
<textarea id="notePublic" name="notePublic" rows="3" placeholder="Nota visible para el cliente"></textarea> <button type="button" class="btn-add-compact" id="add-line-btn">+ Añadir</button>
</div>
</div> </div>
<div class="form-row"> <div class="lines-table-compact" id="invoice-lines-container"></div>
<div class="form-group full-width">
<label for="notePrivate">Nota privada</label>
<textarea id="notePrivate" name="notePrivate" rows="3" placeholder="Nota interna"></textarea>
</div>
</div>
</div> </div>
<div class="form-section"> <div class="form-actions-compact">
<div class="section-header"> <button type="button" class="btn-cancel-compact" id="cancel-btn">Cancelar</button>
<h2>Líneas de Borrador</h2> <button type="submit" class="btn-submit-compact">Crear Factura</button>
<button type="button" class="btn-add-line" id="add-line-btn">+ Añadir Línea</button>
</div>
<div id="invoice-lines-container">
<!-- Las líneas se agregarán dinámicamente aquí -->
</div>
</div>
<div class="form-actions">
<button type="button" class="btn-cancel" id="cancel-btn">Cancelar</button>
<button type="submit" class="btn-submit">Crear Borrador</button>
</div> </div>
</form> </form>
`; `;
// Establecer fecha de hoy por defecto const clientSelect = container.querySelector('#clientId');
async function loadClients() {
try {
const token = localStorage.getItem('token');
const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/Clients?limit=1000`, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (!response.ok) throw new Error('Error al cargar clientes');
const clients = await response.json();
clientSelect.innerHTML = '<option value="">Seleccionar cliente...</option>';
clients.forEach(client => {
const option = document.createElement('option');
option.value = client.id;
option.textContent = client.name;
clientSelect.appendChild(option);
});
clientSelect.disabled = false;
} catch (error) {
console.error('Error:', error);
clientSelect.innerHTML = '<option value="">Error al cargar</option>';
}
}
loadClients();
const today = new Date().toISOString().split('T')[0]; const today = new Date().toISOString().split('T')[0];
container.querySelector('#date').value = today; container.querySelector('#date').value = today;
// Establecer fecha de vencimiento (30 días después)
const expireDate = new Date(); const expireDate = new Date();
expireDate.setDate(expireDate.getDate() + 30); expireDate.setDate(expireDate.getDate() + 30);
container.querySelector('#expireDate').value = expireDate.toISOString().split('T')[0]; container.querySelector('#expireDate').value = expireDate.toISOString().split('T')[0];
// Contador de líneas
let lineCounter = 0; let lineCounter = 0;
// Función para crear una nueva línea de borrador
function createInvoiceLine() { function createInvoiceLine() {
lineCounter++; lineCounter++;
const lineDiv = document.createElement('div'); const lineDiv = document.createElement('div');
lineDiv.className = 'invoice-line'; lineDiv.className = 'line-row-compact';
lineDiv.dataset.lineId = lineCounter;
lineDiv.innerHTML = /*html*/` lineDiv.innerHTML = /*html*/`
<div class="line-header"> <input type="text" class="line-desc-compact" required placeholder="Descripción" />
<span class="line-number">Línea ${lineCounter}</span> <input type="number" class="line-qty-compact" required min="0.01" step="0.01" value="1" placeholder="Cant" />
<button type="button" class="btn-remove-line" data-line-id="${lineCounter}">🗑️ Eliminar</button> <input type="number" class="line-price-compact" required min="0" step="0.01" placeholder="Precio" />
</div> <input type="number" class="line-tax-compact" required min="0" max="100" step="0.01" value="21" placeholder="IVA %" />
<div class="line-total-compact">0.00 €</div>
<div class="line-content"> <button type="button" class="btn-delete-compact" data-line-id="${lineCounter}">×</button>
<div class="form-group">
<label>Descripción *</label>
<input type="text" class="line-description" required placeholder="Descripción del producto/servicio" />
</div>
<div class="form-row-inline">
<div class="form-group">
<label>Cantidad *</label>
<input type="number" class="line-quantity" required min="0.01" step="0.01" value="1" />
</div>
<div class="form-group">
<label>Precio unitario (€) *</label>
<input type="number" class="line-unitPrice" required min="0" step="0.01" placeholder="0.00" />
</div>
<div class="form-group">
<label>IVA (%) *</label>
<input type="number" class="line-taxRate" required min="0" max="100" step="0.01" value="21" />
</div>
<div class="form-group">
<label>Subtotal</label>
<input type="text" class="line-subtotal" readonly value="0.00 €" />
</div>
</div>
</div>
`; `;
// Event listeners para calcular subtotal const descInput = lineDiv.querySelector('.line-desc-compact');
const quantityInput = lineDiv.querySelector('.line-quantity'); const qtyInput = lineDiv.querySelector('.line-qty-compact');
const priceInput = lineDiv.querySelector('.line-unitPrice'); const priceInput = lineDiv.querySelector('.line-price-compact');
const subtotalInput = lineDiv.querySelector('.line-subtotal'); const taxInput = lineDiv.querySelector('.line-tax-compact');
const totalDiv = lineDiv.querySelector('.line-total-compact');
function updateSubtotal() { function updateTotal() {
const quantity = parseFloat(quantityInput.value) || 0; const qty = parseFloat(qtyInput.value) || 0;
const price = parseFloat(priceInput.value) || 0; const price = parseFloat(priceInput.value) || 0;
const subtotal = quantity * price; const tax = parseFloat(taxInput.value) || 0;
subtotalInput.value = `${subtotal.toFixed(2)} €`; const subtotal = qty * price;
updateTotal(); const total = subtotal * (1 + tax / 100);
totalDiv.textContent = `${total.toFixed(2)} €`;
} }
quantityInput.addEventListener('input', updateSubtotal); qtyInput.addEventListener('input', updateTotal);
priceInput.addEventListener('input', updateSubtotal); priceInput.addEventListener('input', updateTotal);
taxInput.addEventListener('input', updateTotal);
// Event listener para eliminar línea lineDiv.querySelector('.btn-delete-compact').addEventListener('click', () => {
lineDiv.querySelector('.btn-remove-line').addEventListener('click', () => {
lineDiv.remove(); lineDiv.remove();
updateLineNumbers();
updateTotal();
}); });
return lineDiv; 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'); const linesContainer = container.querySelector('#invoice-lines-container');
linesContainer.appendChild(createInvoiceLine()); linesContainer.appendChild(createInvoiceLine());
linesContainer.appendChild(createInvoiceLine());
// Event listener para añadir línea
container.querySelector('#add-line-btn').addEventListener('click', () => { container.querySelector('#add-line-btn').addEventListener('click', () => {
linesContainer.appendChild(createInvoiceLine()); linesContainer.appendChild(createInvoiceLine());
}); });
// Event listener para volver
container.querySelector('#back-btn').addEventListener('click', () => { container.querySelector('#back-btn').addEventListener('click', () => {
window.location.hash = '#invoices'; window.location.hash = '#invoices';
}); });
// Event listener para cancelar
container.querySelector('#cancel-btn').addEventListener('click', () => { container.querySelector('#cancel-btn').addEventListener('click', () => {
if (confirm('¿Estás seguro de que quieres cancelar? Se perderán los datos no guardados.')) { if (confirm('¿Cancelar? Se perderán los cambios.')) {
window.location.hash = '#invoices'; window.location.hash = '#invoices';
} }
}); });
// Event listener para enviar formulario
container.querySelector('#invoice-form').addEventListener('submit', async (e) => { container.querySelector('#invoice-form').addEventListener('submit', async (e) => {
e.preventDefault(); e.preventDefault();
// Recopilar datos del formulario
const clientIdValue = parseInt(container.querySelector('#clientId').value); const clientIdValue = parseInt(container.querySelector('#clientId').value);
// Validar clientId
if (!clientIdValue || isNaN(clientIdValue) || clientIdValue < 1) { if (!clientIdValue || isNaN(clientIdValue) || clientIdValue < 1) {
alert('Por favor ingresa un ID de cliente válido'); alert('Selecciona un cliente');
return;
}
const lines = container.querySelectorAll('.line-row-compact');
if (lines.length === 0) {
alert('Añade al menos una línea');
return; return;
} }
@ -216,40 +173,40 @@ export function renderCreateInvoicePage() {
clientId: clientIdValue, clientId: clientIdValue,
date: container.querySelector('#date').value + 'T00:00:00', date: container.querySelector('#date').value + 'T00:00:00',
expireDate: container.querySelector('#expireDate').value + 'T00:00:00', expireDate: container.querySelector('#expireDate').value + 'T00:00:00',
reference: container.querySelector('#reference').value || null,
notePublic: container.querySelector('#notePublic').value || null, notePublic: container.querySelector('#notePublic').value || null,
notePrivate: container.querySelector('#notePrivate').value || null, notePrivate: container.querySelector('#notePrivate').value || null,
lines: [] lines: []
}; };
// Recopilar líneas let hasEmptyLine = false;
const lines = container.querySelectorAll('.invoice-line'); lines.forEach(line => {
if (lines.length === 0) { const desc = line.querySelector('.line-desc-compact').value.trim();
alert('Debes agregar al menos una línea al borrador'); const qty = parseFloat(line.querySelector('.line-qty-compact').value);
const price = parseFloat(line.querySelector('.line-price-compact').value);
const tax = parseFloat(line.querySelector('.line-tax-compact').value);
if (!desc || !qty || !price) {
hasEmptyLine = true;
return; return;
} }
lines.forEach(line => { formData.lines.push({
const lineData = { description: desc,
description: line.querySelector('.line-description').value, quantity: qty,
quantity: parseFloat(line.querySelector('.line-quantity').value), unitPrice: price,
unitPrice: parseFloat(line.querySelector('.line-unitPrice').value), taxRate: tax
taxRate: parseFloat(line.querySelector('.line-taxRate').value) });
};
formData.lines.push(lineData);
}); });
// Validar que todos los campos requeridos estén completos if (hasEmptyLine) {
if (!formData.clientId || formData.lines.some(l => !l.description || !l.quantity || !l.unitPrice)) { alert('Completa todos los campos de las líneas');
alert('Por favor completa todos los campos obligatorios');
return; return;
} }
// Enviar a la API
try { try {
const submitBtn = container.querySelector('.btn-submit'); const submitBtn = container.querySelector('.btn-submit-compact');
submitBtn.disabled = true; submitBtn.disabled = true;
submitBtn.textContent = 'Creando...'; submitBtn.textContent = 'Guardando...';
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/Invoices`, { const response = await fetch(`${import.meta.env.VITE_API_BASE_URL}/api/Invoices`, {
@ -263,32 +220,21 @@ export function renderCreateInvoicePage() {
if (!response.ok) { if (!response.ok) {
const error = await response.text(); const error = await response.text();
throw new Error(error || 'Error al crear el borrador'); throw new Error(error || 'Error al crear');
} }
const invoiceId = await response.json(); const invoiceId = await response.json();
alert(`Borrador creado exitosamente con ID: ${invoiceId}`); alert(`Factura creada (ID: ${invoiceId})`);
// Redirigir inmediatamente a la lista de facturas
window.location.hash = '#invoices'; window.location.hash = '#invoices';
} catch (error) { } catch (error) {
console.error('Error al crear borrador:', error); console.error('Error:', error);
alert(error.message || 'Error al crear la factura');
// Mensaje de error más específico const submitBtn = container.querySelector('.btn-submit-compact');
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) { if (submitBtn) {
submitBtn.disabled = false; submitBtn.disabled = false;
submitBtn.textContent = 'Crear Borrador'; submitBtn.textContent = 'Crear Factura';
} }
} }
}); });

View File

@ -815,249 +815,299 @@ button:focus-visible {
min-height: 100vh; min-height: 100vh;
width: 100%; width: 100%;
} }
/* Create Invoice Page Styles */ /* Create Invoice Page - Ultra Compact */
.create-invoice-page { .create-invoice-page {
padding: 2rem; padding: 1rem;
max-width: 1200px; max-width: 1400px;
margin: 0 auto; margin: 0 auto;
min-height: 100vh;
} }
.create-invoice-header { .invoice-header-compact {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 2rem; margin-bottom: 1rem;
flex-shrink: 0;
} }
.create-invoice-header h1 { .invoice-header-compact h1 {
margin: 0; margin: 0;
font-size: 2rem; font-size: 1.5rem;
color: var(--text-primary); color: var(--text-primary);
} }
.create-invoice-header .subtitle {
margin: 0.5rem 0 0 0;
color: var(--text-secondary);
}
.btn-back { .btn-back {
background: var(--card-bg); background: transparent;
color: var(--text-primary); color: #64748b;
border: 1px solid var(--border-color); border: 1px solid #e2e8f0;
padding: 0.75rem 1.5rem; padding: 0.4rem 0.8rem;
border-radius: 8px; border-radius: 4px;
cursor: pointer; cursor: pointer;
font-weight: 600; font-weight: 500;
font-size: 0.9rem;
transition: all 0.2s; transition: all 0.2s;
} }
.btn-back:hover { .btn-back:hover {
background: #f1f5f9; background: #f1f5f9;
color: #475569;
border-color: #cbd5e1;
} }
.invoice-form { .invoice-form-compact {
background: var(--card-bg); background: var(--card-bg);
border-radius: 12px; border-radius: 8px;
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
padding: 2rem; padding: 1rem;
} }
.form-section { .form-grid-compact {
margin-bottom: 2rem; display: grid;
grid-template-columns: 2fr 1fr 1fr;
gap: 0.75rem;
margin-bottom: 0.75rem;
flex-shrink: 0;
} }
.form-section h2 { .notes-grid-compact {
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; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: 1fr 1fr;
gap: 1.5rem; gap: 0.75rem;
margin-bottom: 1.5rem; margin-bottom: 0.75rem;
} }
.form-row-inline { .form-field-compact {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 1rem;
margin-bottom: 1rem;
}
.form-group {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
.form-group.full-width { .form-field-compact.full-width {
grid-column: 1 / -1; grid-column: 1 / -1;
} }
.form-group label { .form-field-compact label {
font-weight: 600; font-weight: 500;
margin-bottom: 0.5rem; margin-bottom: 0.25rem;
color: var(--text-primary); color: var(--text-primary);
font-size: 0.8rem;
} }
.form-group input, .form-field-compact input,
.form-group textarea, .form-field-compact select,
.form-group select { .form-field-compact textarea {
padding: 0.75rem; padding: 0.4rem 0.5rem;
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-radius: 6px; border-radius: 4px;
font-size: 1rem; font-size: 0.9rem;
color: #000000; color: #000000;
background: var(--card-bg); background: var(--card-bg);
transition: border-color 0.2s; resize: vertical;
min-height: 38px;
} }
.form-group input:focus, .form-field-compact input:focus,
.form-group textarea:focus, .form-field-compact select:focus,
.form-group select:focus { .form-field-compact textarea:focus {
outline: none; outline: none;
border-color: var(--primary); border-color: var(--primary);
} }
.form-group input[readonly] { .lines-section-compact {
background: #f1f5f9; flex: 1;
cursor: not-allowed; display: flex;
flex-direction: column;
overflow: hidden;
} }
.invoice-line { .lines-header-compact {
background: #f8fafc;
border: 1px solid var(--border-color);
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1rem;
}
.line-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 1rem; margin-bottom: 0.5rem;
flex-shrink: 0;
} }
.line-number { .lines-header-compact h3 {
font-weight: 700; margin: 0;
font-size: 0.95rem;
color: var(--text-primary); color: var(--text-primary);
font-size: 1.1rem;
} }
.btn-remove-line { .btn-add-compact {
background: var(--danger); background: #f1f5f9;
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); color: var(--text-primary);
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
padding: 0.75rem 2rem; padding: 0.3rem 0.6rem;
border-radius: 8px; border-radius: 4px;
cursor: pointer; cursor: pointer;
font-weight: 600; font-weight: 500;
font-size: 0.85rem;
transition: all 0.2s; transition: all 0.2s;
} }
.btn-cancel:hover { .btn-add-compact:hover {
background: #f1f5f9; background: #e2e8f0;
border-color: #cbd5e1;
} }
.btn-submit { .lines-table-compact {
background: var(--primary); flex: 1;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.line-row-compact {
display: grid;
grid-template-columns: 4fr 0.8fr 1.2fr 0.8fr 1.2fr auto;
gap: 0.5rem;
align-items: center;
background: #f8fafc;
border: 1px solid var(--border-color);
border-radius: 4px;
padding: 0.4rem;
}
.line-desc-compact,
.line-qty-compact,
.line-price-compact,
.line-tax-compact {
padding: 0.35rem 0.4rem;
border: 1px solid var(--border-color);
border-radius: 3px;
font-size: 0.85rem;
background: white;
color: #000000;
}
.line-desc-compact {
width: 100%;
}
.line-desc-compact:focus,
.line-qty-compact:focus,
.line-price-compact:focus,
.line-tax-compact:focus {
outline: none;
border-color: var(--primary);
}
.line-total-compact {
font-weight: 600;
color: var(--text-primary);
font-size: 0.9rem;
text-align: right;
padding-right: 0.5rem;
}
.btn-delete-compact {
background: transparent;
color: #94a3b8;
border: 1px solid transparent;
width: 28px;
height: 28px;
border-radius: 4px;
cursor: pointer;
font-size: 1.4rem;
font-weight: 300;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s;
flex-shrink: 0;
}
.btn-delete-compact:hover {
color: #64748b;
background: #f1f5f9;
border-color: #e2e8f0;
}
.form-actions-compact {
display: flex;
justify-content: flex-end;
gap: 0.75rem;
margin-top: 1rem;
padding-top: 0.75rem;
border-top: 1px solid var(--border-color);
flex-shrink: 0;
}
.btn-cancel-compact {
background: #f8fafc;
color: #64748b;
border: 1px solid #e2e8f0;
padding: 0.5rem 1rem;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
font-size: 0.9rem;
transition: all 0.2s;
}
.btn-cancel-compact:hover {
background: #f1f5f9;
color: #475569;
}
.btn-submit-compact {
background: #334155;
color: white; color: white;
border: none; border: none;
padding: 0.75rem 2rem; padding: 0.5rem 1.5rem;
border-radius: 8px; border-radius: 4px;
cursor: pointer; cursor: pointer;
font-weight: 600; font-weight: 500;
transition: background 0.2s; font-size: 0.9rem;
transition: all 0.2s;
} }
.btn-submit:hover { .btn-submit-compact:hover {
background: var(--primary-hover); background: #475569;
} }
.btn-submit:disabled { .btn-submit-compact:disabled {
background: #cbd5e1; background: #e2e8f0;
color: #94a3b8;
cursor: not-allowed; cursor: not-allowed;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
.create-invoice-page { .create-invoice-page {
padding: 1rem; padding: 0.5rem;
height: calc(100vh - 1rem);
} }
.create-invoice-header { .form-grid-compact {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.form-row,
.form-row-inline {
grid-template-columns: 1fr; grid-template-columns: 1fr;
gap: 0.5rem;
} }
.invoice-form { .form-field-compact.full-width {
padding: 1rem; grid-column: 1;
}
.line-row-compact {
grid-template-columns: 1fr auto;
gap: 0.5rem;
padding: 0.75rem;
}
.line-desc-compact {
grid-column: 1;
}
.line-qty-compact,
.line-price-compact,
.line-tax-compact,
.line-total-compact {
width: 60px;
}
.btn-delete-compact {
grid-column: 2;
} }
} }