Añadir modo oscuro y mejorar la legibilidad del css separandolo en archivos

This commit is contained in:
Алекс 2026-04-16 18:43:15 +02:00
parent 9e93d9c9c6
commit 0366137e96
15 changed files with 2061 additions and 2444 deletions

View File

@ -1,6 +1,15 @@
import './style.css'
import './styles/sidebar.css'
import './styles/login.css'
import './styles/dashboard.css'
import './styles/facturas.css'
import './styles/clients.css'
import './styles/create-invoice.css'
import './styles/settings.css'
import { initRouter } from './router.js'
import { initSessionManager } from './services/session.js'
import { initTheme } from './services/theme.js'
initTheme()
initSessionManager()
initRouter()

View File

@ -66,14 +66,13 @@ function getStatusBadge(status) {
return `<span class="badge ${s.cls}">${s.label}</span>`;
}
function statusDotStyle(status) {
function statusDotClass(status) {
const map = {
paid: { bg: '#f0fdf4', color: '#16a34a' },
unpaid: { bg: '#fffbeb', color: '#b45309' },
draft: { bg: '#f3f4f6', color: '#6b7280' },
paid: 'status-paid',
unpaid: 'status-unpaid',
draft: 'status-draft',
};
const s = map[status] || map.draft;
return `background:${s.bg};color:${s.color}`;
return map[status] || 'status-draft';
}
function openInvoiceModal(invoiceId) {
@ -317,7 +316,7 @@ export function renderDashboard() {
} else {
recentList.innerHTML = recent.map(inv => `
<div class="db-recent-item" data-id="${inv.id}" role="button" tabindex="0">
<div class="db-recent-dot" style="${statusDotStyle(inv.status)}">
<div class="db-recent-dot ${statusDotClass(inv.status)}">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/>
<polyline points="14 2 14 8 20 8"/>

View File

@ -0,0 +1,121 @@
import { isDarkTheme, toggleTheme } from '../services/theme.js';
function decodeTokenPayload(token) {
try {
const payloadPart = token.split('.')[1];
if (!payloadPart) return null;
const base64 = payloadPart.replace(/-/g, '+').replace(/_/g, '/');
return JSON.parse(atob(base64));
} catch {
return null;
}
}
function formatSeconds(totalSeconds) {
if (totalSeconds <= 0) return 'Expirada';
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
const hh = String(hours).padStart(2, '0');
const mm = String(minutes).padStart(2, '0');
const ss = String(seconds).padStart(2, '0');
return `${hh}:${mm}:${ss}`;
}
function getTokenExpirationInfo() {
const token = localStorage.getItem('token');
if (!token) {
return { hasToken: false, expiresAtText: 'No hay token', remainingText: 'No autenticado' };
}
const payload = decodeTokenPayload(token);
if (!payload || !payload.exp) {
return { hasToken: true, expiresAtText: 'No disponible', remainingText: 'No disponible' };
}
const expiresAtMs = payload.exp * 1000;
const nowMs = Date.now();
const remainingSeconds = Math.max(0, Math.floor((expiresAtMs - nowMs) / 1000));
return {
hasToken: true,
expiresAtText: new Date(expiresAtMs).toLocaleString('es-ES'),
remainingText: formatSeconds(remainingSeconds),
remainingSeconds
};
}
export function renderSettingsPage() {
const container = document.createElement('div');
container.className = 'settings-page';
container.innerHTML = `
<div class="settings-header">
<h1>Configuracion</h1>
<p>Preferencias visuales y estado de sesion.</p>
</div>
<div class="settings-grid">
<section class="settings-card">
<h2>Apariencia</h2>
<div class="settings-row">
<div>
<p class="settings-label">Modo oscuro</p>
<p class="settings-help">Cambia entre tema claro y oscuro.</p>
</div>
<button type="button" class="settings-theme-toggle" id="settings-theme-toggle"></button>
</div>
</section>
<section class="settings-card">
<h2>Sesion</h2>
<div class="settings-session-list">
<div class="settings-session-item">
<span class="settings-label">Caducidad del token</span>
<strong id="token-expire-at">-</strong>
</div>
<div class="settings-session-item">
<span class="settings-label">Tiempo restante</span>
<strong id="token-remaining">-</strong>
</div>
</div>
</section>
</div>
`;
const themeBtn = container.querySelector('#settings-theme-toggle');
const expireAtEl = container.querySelector('#token-expire-at');
const remainingEl = container.querySelector('#token-remaining');
const renderThemeButton = () => {
const dark = isDarkTheme();
themeBtn.textContent = dark ? 'Activado' : 'Desactivado';
themeBtn.setAttribute('aria-pressed', dark ? 'true' : 'false');
};
const renderTokenInfo = () => {
const info = getTokenExpirationInfo();
expireAtEl.textContent = info.expiresAtText;
remainingEl.textContent = info.remainingText;
if (info.remainingSeconds === 0 && info.hasToken) {
remainingEl.classList.add('settings-token-expired');
} else {
remainingEl.classList.remove('settings-token-expired');
}
};
renderThemeButton();
renderTokenInfo();
themeBtn.addEventListener('click', () => {
toggleTheme();
renderThemeButton();
});
const intervalId = setInterval(renderTokenInfo, 1000);
container.cleanup = () => clearInterval(intervalId);
return container;
}

View File

@ -2,6 +2,7 @@ import { renderDashboard } from './DashboardPage.js';
import { renderFacturasPage } from './Facturas.js';
import { renderCreateInvoicePage } from './CreateInvoicePage.js';
import { renderClientesPage } from './ClientesPage.js';
import { renderSettingsPage } from './SettingsPage.js';
// Registry of all pages available in the application
export const pagesRegistry = [
@ -43,11 +44,7 @@ export const pagesRegistry = [
icon: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z"/></svg>',
requiresAuth: true,
showInSidebar: true,
render: () => {
const div = document.createElement('div');
div.innerHTML = '<h1>Configuración</h1><p>Página en construcción...</p>';
return div;
}
render: renderSettingsPage
}
];

View File

@ -3,6 +3,7 @@ import { renderLoginPage } from './pages/LoginPage.js';
import { createSidebar } from './components/Sidebar.js';
import { getAvailablePages, getPageByRoute } from './pages/pagesRegistry.js';
import { showConfirmExitModal } from './components/ConfirmExitModal.js';
import { applyLoginTheme, applySavedTheme } from './services/theme.js';
// 🔧 Modo DEV: cambiar a false para activar login
const DEV_MODE = false;
@ -34,6 +35,7 @@ export function initRouter() {
const app = document.querySelector('#app');
let isNavigating = false;
let pendingHash = null;
let currentPageCleanup = null;
async function navigate(forceNavigate = false) {
const hash = window.location.hash || (DEV_MODE ? '#dashboard' : '#login');
@ -71,6 +73,17 @@ export function initRouter() {
return;
}
if (typeof currentPageCleanup === 'function') {
currentPageCleanup();
currentPageCleanup = null;
}
if (hash === '#login') {
applyLoginTheme();
} else {
applySavedTheme();
}
app.innerHTML = '';
// For authenticated routes, add sidebar and main content wrapper
@ -105,6 +118,10 @@ export function initRouter() {
// Render page content using registry
const pageContent = page.render();
if (pageContent && typeof pageContent.cleanup === 'function') {
currentPageCleanup = pageContent.cleanup;
}
mainContent.appendChild(pageContent);
layout.appendChild(sidebar);
layout.appendChild(mainContent);

78
src/services/theme.js Normal file
View File

@ -0,0 +1,78 @@
const THEME_KEY = 'theme-preference';
function getStoredUser() {
try {
const raw = localStorage.getItem('user');
if (!raw) return null;
const parsed = JSON.parse(raw);
return parsed && typeof parsed === 'object' ? parsed : null;
} catch {
return null;
}
}
function getUserThemeKey() {
const user = getStoredUser();
if (!user) return null;
const identity = user.id || user.userId || user.email || user.username || user.identifier;
if (!identity) return null;
return `${THEME_KEY}:user:${String(identity).toLowerCase()}`;
}
function setRootTheme(theme) {
const root = document.documentElement;
const finalTheme = theme === 'dark' ? 'dark' : 'light';
root.setAttribute('data-theme', finalTheme);
root.style.colorScheme = finalTheme;
}
function getSystemPrefersDark() {
return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
}
export function getSavedTheme() {
const userKey = getUserThemeKey();
const saved = userKey ? localStorage.getItem(userKey) : localStorage.getItem(THEME_KEY);
if (saved === 'dark' || saved === 'light') return saved;
return getSystemPrefersDark() ? 'dark' : 'light';
}
export function applyTheme(theme) {
const finalTheme = theme === 'dark' ? 'dark' : 'light';
setRootTheme(finalTheme);
const userKey = getUserThemeKey();
if (userKey) {
localStorage.setItem(userKey, finalTheme);
return;
}
localStorage.setItem(THEME_KEY, finalTheme);
}
export function initTheme() {
applyTheme(getSavedTheme());
}
export function toggleTheme() {
const current = document.documentElement.getAttribute('data-theme') || getSavedTheme();
const next = current === 'dark' ? 'light' : 'dark';
applyTheme(next);
return next;
}
export function isDarkTheme() {
const current = document.documentElement.getAttribute('data-theme') || getSavedTheme();
return current === 'dark';
}
// Login should remain neutral and should not persist or inherit cross-user preferences.
export function applyLoginTheme() {
setRootTheme('light');
}
export function applySavedTheme() {
setRootTheme(getSavedTheme());
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,509 @@
/* ============================================
Clientes Page
============================================ */
.clientes-page {
padding: var(--space-6);
max-width: 1400px;
margin: 0 auto;
}
.clientes-header {
margin-bottom: var(--space-5);
}
.clientes-header h1 {
font-size: 1.25rem;
font-weight: 600;
}
.clientes-filters {
display: flex;
gap: var(--space-3);
margin-bottom: var(--space-4);
align-items: center;
}
.clientes-filters .search-input {
flex: 1;
max-width: 400px;
}
.client-avatar-col {
width: 48px;
}
.client-avatar-cell {
width: 48px;
}
.client-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 500;
font-size: 12px;
}
.client-name-wrapper {
display: flex;
flex-direction: column;
gap: 1px;
}
.client-company-name {
font-weight: 500;
color: var(--text-primary);
}
.client-code {
font-size: 11px;
color: var(--text-secondary);
font-family: 'SF Mono', 'Cascadia Code', 'Fira Code', monospace;
}
.contact-info {
display: flex;
flex-direction: column;
gap: 1px;
}
.contact-name {
color: var(--text-primary);
}
.contacts-count {
font-size: 11px;
color: var(--primary);
background: var(--blue-50);
padding: 1px var(--space-2);
border-radius: var(--radius-sm);
display: inline-block;
width: fit-content;
font-weight: 500;
}
.client-email .email-text {
color: var(--text-secondary);
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
font-size: 13px;
}
.client-phone {
color: var(--text-primary);
font-variant-numeric: tabular-nums;
font-size: 13px;
}
.client-actions {
text-align: center;
}
.clients-table .client-actions-col {
width: 60px;
text-align: center;
}
.clients-table td.client-actions {
text-align: center;
vertical-align: middle;
}
.clients-table td.client-actions .btn-view {
margin: 0 auto;
}
.status-badge-client {
display: inline-flex;
align-items: center;
padding: 2px var(--space-2);
border-radius: var(--radius-sm);
font-size: 12px;
font-weight: 500;
white-space: nowrap;
}
.status-badge-client.status-active {
background: var(--green-50);
color: var(--green-600);
}
.status-badge-client.status-inactive {
background: var(--red-50);
color: var(--red-600);
}
.status-badge-client.status-unknown {
background: var(--gray-100);
color: var(--gray-500);
}
.btn-contacts {
position: relative;
background: var(--blue-50);
border: 1px solid rgba(59, 130, 246, 0.15);
color: var(--primary);
border-radius: var(--radius-sm);
padding: 2px var(--space-2);
cursor: pointer;
transition: background-color var(--transition-fast);
display: flex;
align-items: center;
gap: 2px;
font-size: 12px;
}
.btn-contacts:hover {
background: var(--blue-100);
}
.btn-contacts.active {
background: var(--primary);
color: white;
border-color: var(--primary);
}
.contacts-badge {
font-size: 11px;
font-weight: 600;
min-width: 14px;
text-align: center;
}
.contacts-expand-row td {
padding: 0 !important;
background: var(--gray-50);
}
.contacts-expand-content {
padding: var(--space-3) var(--space-4);
border-top: 1px solid var(--border-color);
border-bottom: 2px solid var(--primary);
}
.contacts-expand-header h4 {
margin: 0 0 var(--space-3) 0;
font-size: 12px;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.04em;
font-weight: 500;
}
.contacts-expand-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
gap: var(--space-2);
}
.contact-expand-card {
background: white;
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
padding: var(--space-3);
}
.contact-expand-name {
display: flex;
align-items: center;
gap: var(--space-2);
font-weight: 500;
color: var(--text-primary);
margin-bottom: var(--space-2);
font-size: 14px;
}
.contact-expand-name svg {
color: var(--primary);
flex-shrink: 0;
}
.contact-expand-details {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 2px var(--space-3);
font-size: 12px;
color: var(--text-secondary);
}
.contact-expand-details span {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* Client Modal */
.client-modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: var(--space-4);
}
.client-modal {
background: var(--card-bg);
border-radius: var(--radius-lg);
width: 100%;
max-width: 560px;
max-height: 80vh;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: var(--shadow-lg);
}
.client-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--space-4) var(--space-5);
border-bottom: 1px solid var(--border-color);
}
.client-modal-header h2 {
margin: 0;
font-size: 1.125rem;
font-weight: 600;
}
.btn-close-modal {
background: transparent;
border: none;
font-size: 18px;
cursor: pointer;
color: var(--text-secondary);
padding: var(--space-1);
border-radius: var(--radius-sm);
transition: color var(--transition-fast), background-color var(--transition-fast);
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
}
.btn-close-modal:hover {
background: var(--gray-100);
color: var(--text-primary);
}
.client-modal-body {
padding: var(--space-5);
overflow-y: auto;
}
.client-info-section,
.contacts-section {
margin-bottom: var(--space-5);
}
.client-info-section:last-child,
.contacts-section:last-child {
margin-bottom: 0;
}
.client-info-section h3,
.contacts-section h3 {
margin: 0 0 var(--space-3) 0;
font-size: 12px;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.04em;
font-weight: 500;
}
.info-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: var(--space-3);
}
.info-item {
display: flex;
flex-direction: column;
gap: 1px;
}
.info-label {
font-size: 11px;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.04em;
font-weight: 500;
}
.info-value {
font-weight: 500;
color: var(--text-primary);
font-size: 14px;
}
.contacts-list {
display: flex;
flex-direction: column;
gap: var(--space-3);
}
.contact-card {
background: var(--card-bg);
border-radius: var(--radius-md);
padding: var(--space-4);
border: 1px solid var(--border-color);
}
.contact-header {
display: flex;
align-items: center;
gap: var(--space-2);
margin-bottom: var(--space-3);
padding-bottom: var(--space-3);
border-bottom: 1px solid var(--border-color);
}
.contact-avatar {
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--gray-100);
display: flex;
align-items: center;
justify-content: center;
color: var(--text-secondary);
flex-shrink: 0;
}
.contact-full-name {
font-weight: 600;
color: var(--text-primary);
font-size: 13px;
}
.contact-details {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.contact-row {
display: flex;
align-items: center;
gap: var(--space-2);
font-size: 13px;
color: var(--text-secondary);
}
.contact-icon {
display: flex;
align-items: center;
justify-content: center;
color: var(--gray-400);
flex-shrink: 0;
width: 16px;
}
.no-contacts {
color: var(--text-secondary);
text-align: center;
padding: var(--space-5);
background: var(--gray-50);
border-radius: var(--radius-md);
font-size: 13px;
}
@media (max-width: 768px) {
.clientes-filters {
flex-direction: column;
}
.clients-table thead {
display: none;
}
.clients-table tbody tr.client-item {
display: flex;
flex-wrap: wrap;
padding: var(--space-3);
gap: var(--space-2);
position: relative;
}
.clients-table tbody tr.contacts-expand-row {
display: block;
}
.clients-table td {
padding: 0;
}
.client-avatar-cell {
width: auto;
margin-right: var(--space-3);
}
.client-name {
flex: 1;
}
.client-type-col,
.client-type {
display: none;
}
.client-actions {
position: absolute;
right: var(--space-3);
top: 50%;
transform: translateY(-50%);
}
.contacts-expand-grid {
grid-template-columns: 1fr;
}
.info-grid {
grid-template-columns: 1fr;
}
}
@media (max-width: 1024px) {
.clientes-page {
padding: var(--space-4);
}
.clients-table th,
.clients-table td {
padding: var(--space-2) var(--space-3);
}
.client-email-col,
.client-email,
.client-type-col,
.client-type {
display: none;
}
}
:root[data-theme='dark'] .contact-expand-card {
background: #0f172a;
border-color: #1e2f4d;
}
:root[data-theme='dark'] .contacts-expand-row td,
:root[data-theme='dark'] .no-contacts {
background: rgba(15, 23, 42, 0.9);
}

View File

@ -1,6 +1,9 @@
/* ===================== CREATE INVOICE PAGE ===================== */
/* ============================================
Create Invoice Page
============================================ */
.create-invoice-page {
padding: 2rem;
padding: var(--space-6);
max-width: 960px;
margin: 0 auto;
min-height: 100vh;
@ -10,57 +13,53 @@
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1.5rem;
flex-shrink: 0;
margin-bottom: var(--space-5);
}
.invoice-header-compact h1 {
margin: 0;
font-size: 1.375rem;
color: var(--text-primary);
font-size: 1.25rem;
font-weight: 600;
letter-spacing: -0.02em;
}
.btn-back {
background: transparent;
color: var(--text-secondary);
border: 1px solid var(--border-color);
padding: 0.45rem 0.875rem;
border-radius: var(--radius-sm);
padding: var(--space-2) var(--space-3);
border-radius: var(--radius-md);
cursor: pointer;
font-weight: 500;
font-size: 0.8125rem;
transition: all 0.15s ease;
font-size: 13px;
transition: border-color var(--transition-fast), color var(--transition-fast);
height: 32px;
}
.btn-back:hover {
background: #f8fafc;
color: var(--text-primary);
border-color: #cbd5e1;
border-color: var(--gray-300);
background: transparent;
}
.invoice-form-compact {
background: var(--card-bg);
border-radius: var(--radius-md);
border-radius: var(--radius-lg);
border: 1px solid var(--border-color);
padding: 1.75rem;
padding: var(--space-5);
box-shadow: var(--shadow-xs);
}
.form-grid-compact {
display: grid;
grid-template-columns: 2fr 1fr 1fr;
gap: 1rem;
margin-bottom: 1.5rem;
flex-shrink: 0;
gap: var(--space-3);
margin-bottom: var(--space-5);
}
.notes-grid-compact {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
margin-bottom: 1.5rem;
gap: var(--space-3);
margin-bottom: var(--space-5);
}
.form-field-compact {
@ -74,24 +73,29 @@
.form-field-compact label {
font-weight: 500;
margin-bottom: 0.375rem;
margin-bottom: var(--space-1);
color: var(--text-secondary);
font-size: 0.8125rem;
font-size: 13px;
}
.form-field-compact input,
.form-field-compact select,
.form-field-compact textarea {
padding: 0.5rem 0.75rem;
padding: var(--space-2) var(--space-3);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
font-size: 0.875rem;
border-radius: var(--radius-md);
font-size: 14px;
color: var(--text-primary);
background: #ffffff;
background: var(--card-bg);
resize: vertical;
min-height: 36px;
font-family: inherit;
transition: border-color 0.15s ease, box-shadow 0.15s ease;
transition: border-color var(--transition-fast);
height: 36px;
}
.form-field-compact textarea {
height: auto;
min-height: 64px;
}
.form-field-compact input:focus,
@ -99,10 +103,9 @@
.form-field-compact textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.08);
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.12);
}
/* Date input wrapper */
.date-input-wrapper {
position: relative;
display: flex;
@ -124,61 +127,60 @@
display: flex;
align-items: center;
justify-content: center;
padding: 0 0.625rem;
padding: 0 0.75rem;
background: var(--primary);
color: white;
border: 1px solid var(--primary);
border-top-right-radius: var(--radius-sm);
border-bottom-right-radius: var(--radius-sm);
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
cursor: pointer;
transition: background 0.15s;
transition: background 0.2s;
}
.date-picker-btn:hover {
background: var(--primary-hover);
background: var(--primary-hover, #1d4ed8);
}
.date-picker-btn:active {
transform: scale(0.96);
}
/* Lines section */
.lines-section-compact {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
margin-bottom: 1.5rem;
margin-bottom: var(--space-5);
}
.lines-header-compact {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem;
flex-shrink: 0;
margin-bottom: var(--space-3);
}
.lines-header-compact h3 {
margin: 0;
font-size: 0.875rem;
color: var(--text-primary);
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.btn-add-compact {
background: #f8fafc;
background: var(--card-bg);
color: var(--text-primary);
border: 1px solid var(--border-color);
padding: 0.4rem 0.875rem;
border-radius: var(--radius-sm);
padding: var(--space-1) var(--space-3);
border-radius: var(--radius-md);
cursor: pointer;
font-weight: 500;
font-size: 0.8125rem;
transition: all 0.15s;
font-size: 13px;
transition: border-color var(--transition-fast);
height: 32px;
}
.btn-add-compact:hover {
background: #f1f5f9;
border-color: #cbd5e1;
border-color: var(--gray-300);
}
.lines-table-compact {
@ -186,57 +188,38 @@
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 0.375rem;
}
/* Column headers */
.lines-columns-header {
display: grid;
grid-template-columns: 4fr 0.8fr 1.2fr 0.8fr 1.2fr 40px;
gap: 0.75rem;
padding: 0.5rem 0.75rem;
font-size: 0.7rem;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.04em;
border-bottom: 1px solid var(--border-color);
margin-bottom: 0.25rem;
gap: var(--space-2);
}
.line-row-compact {
display: grid;
grid-template-columns: 4fr 0.8fr 1.2fr 0.8fr 1.2fr 40px;
gap: 0.75rem;
grid-template-columns: 4fr 0.8fr 1.2fr 0.8fr 1.2fr auto;
gap: var(--space-2);
align-items: center;
background: #f8fafc;
background: var(--gray-50);
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
padding: 0.625rem 0.75rem;
transition: border-color 0.15s ease;
border-radius: var(--radius-md);
padding: var(--space-2) var(--space-3);
}
.line-row-compact:hover {
border-color: #cbd5e1;
}
.line-label-mobile { display: none; }
.line-field { display: flex; flex-direction: column; }
.line-desc-compact,
.line-qty-compact,
.line-price-compact,
.line-tax-compact {
padding: 0.4rem 0.5rem;
padding: var(--space-1) var(--space-2);
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 0.8125rem;
border-radius: var(--radius-sm);
font-size: 13px;
background: white;
color: var(--text-primary);
transition: border-color 0.15s ease;
font-family: inherit;
transition: border-color var(--transition-fast);
height: 32px;
}
.line-desc-compact { width: 100%; }
.line-desc-compact {
width: 100%;
}
.line-desc-compact:focus,
.line-qty-compact:focus,
@ -244,114 +227,81 @@
.line-tax-compact:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.08);
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.12);
}
.line-total-compact {
font-weight: 600;
color: var(--primary);
font-size: 0.875rem;
font-weight: 500;
color: var(--text-primary);
font-size: 13px;
text-align: right;
padding: 0.4rem 0.25rem;
padding-right: var(--space-2);
font-variant-numeric: tabular-nums;
}
.btn-delete-compact {
background: transparent;
color: var(--text-tertiary);
border: 1px solid transparent;
width: 32px;
height: 32px;
color: var(--gray-400);
border: none;
width: 28px;
height: 28px;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 16px;
font-weight: 400;
line-height: 1;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.15s;
transition: color var(--transition-fast), background-color var(--transition-fast);
flex-shrink: 0;
padding: 0;
}
.btn-delete-compact:hover {
color: #ef4444;
background: #fee2e2;
color: var(--danger);
background: var(--red-50);
border-color: transparent;
}
/* Grand total */
.invoice-grand-total {
display: flex;
flex-direction: column;
gap: 0.4rem;
margin-top: 0.75rem;
padding: 0.875rem 1rem;
background: #f8fafc;
border: 1px solid var(--border-color);
border-radius: var(--radius-sm);
max-width: 280px;
margin-left: auto;
}
.grand-total-row {
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.8125rem;
color: var(--text-secondary);
}
.grand-total-row strong {
color: var(--text-primary);
font-weight: 600;
}
.grand-total-final {
padding-top: 0.4rem;
border-top: 1px solid var(--border-color);
font-size: 0.9375rem;
}
.grand-total-final strong {
color: var(--primary);
font-size: 1rem;
font-weight: 700;
}
/* Form actions */
.form-actions-compact {
display: flex;
justify-content: flex-end;
gap: 0.625rem;
margin-top: 0;
padding-top: 1.25rem;
gap: var(--space-2);
padding-top: var(--space-4);
border-top: 1px solid var(--border-color);
flex-shrink: 0;
}
.btn-cancel-compact {
background: transparent;
color: var(--text-secondary);
border: 1px solid var(--border-color);
padding: 0.5rem 1rem;
border-radius: var(--radius-sm);
padding: var(--space-2) var(--space-4);
border-radius: var(--radius-md);
cursor: pointer;
font-weight: 500;
font-size: 0.8125rem;
transition: all 0.15s;
font-size: 14px;
transition: border-color var(--transition-fast), color var(--transition-fast);
height: 36px;
}
.btn-cancel-compact:hover {
background: #f8fafc;
color: var(--text-primary);
border-color: var(--gray-300);
background: transparent;
}
.btn-submit-compact {
background: var(--primary);
color: white;
border: none;
padding: 0.5rem 1.25rem;
border-radius: var(--radius-sm);
padding: var(--space-2) var(--space-5);
border-radius: var(--radius-md);
cursor: pointer;
font-weight: 500;
font-size: 0.8125rem;
transition: background 0.15s;
font-size: 14px;
transition: background-color var(--transition-fast);
height: 36px;
}
.btn-submit-compact:hover {
@ -359,43 +309,31 @@
}
.btn-submit-compact:disabled {
background: #e2e8f0;
color: #94a3b8;
background: var(--gray-200);
color: var(--gray-400);
cursor: not-allowed;
}
/* Responsive */
@media (max-width: 768px) {
.create-invoice-page {
padding: 0.5rem;
height: calc(100vh - 1rem);
padding: var(--space-3);
}
.form-grid-compact {
grid-template-columns: 1fr;
gap: 0.5rem;
}
.form-field-compact.full-width { grid-column: 1; }
.lines-columns-header { display: none; }
.form-field-compact.full-width {
grid-column: 1;
}
.notes-grid-compact {
grid-template-columns: 1fr;
}
.line-row-compact {
grid-template-columns: 1fr 1fr;
gap: 0.5rem;
padding: 0.625rem;
grid-template-columns: 1fr auto;
gap: var(--space-2);
padding: var(--space-3);
}
.line-label-mobile {
display: block;
font-size: 0.65rem;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.04em;
margin-bottom: 0.15rem;
}
.line-field-desc { grid-column: 1 / -1; }
.line-field-actions { grid-column: 2; justify-self: end; }
.invoice-grand-total { max-width: 100%; }
}

View File

@ -1,58 +1,201 @@
/* ===================== DASHBOARD STYLES ===================== */
.dashboard {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
}
/* ============================================
Dashboard — Analytics Layout
============================================ */
.dashboard-header {
.db-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 1.75rem;
margin-bottom: var(--space-6);
gap: var(--space-4);
}
.dashboard-header h1 {
.db-greeting {
font-size: 1.5rem;
margin: 0;
font-weight: 700;
color: var(--text-primary);
font-weight: 600;
margin: 0 0 var(--space-1) 0;
letter-spacing: -0.02em;
}
.dashboard-subtitle {
margin: 0.2rem 0 0;
.db-subtitle {
font-size: 13px;
color: var(--text-secondary);
font-size: 0.875rem;
margin: 0;
}
/* Stats */
.dashboard-stats {
.db-kpi-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 1rem;
margin-bottom: 1.75rem;
grid-template-columns: repeat(4, 1fr);
gap: var(--space-4);
margin-bottom: var(--space-5);
}
.stat-card {
.db-kpi-card {
background: var(--card-bg);
border-radius: var(--radius-md);
padding: 1.25rem;
display: flex;
align-items: center;
gap: 0.875rem;
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
padding: var(--space-5);
box-shadow: var(--shadow-xs);
transition: box-shadow 0.15s ease;
display: flex;
flex-direction: column;
gap: var(--space-2);
transition: box-shadow var(--transition-fast);
}
.stat-card:hover {
.db-kpi-card:hover {
box-shadow: var(--shadow-sm);
}
.stat-icon {
width: 42px;
height: 42px;
.db-kpi-top {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--space-2);
}
.db-kpi-icon {
width: 28px;
height: 28px;
border-radius: var(--radius-md);
background: var(--gray-100);
display: flex;
align-items: center;
justify-content: center;
color: var(--text-secondary);
flex-shrink: 0;
}
.db-kpi-label {
font-size: 11px;
font-weight: 500;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.db-kpi-value {
font-size: 1.65rem;
font-weight: 700;
color: var(--text-primary);
letter-spacing: -0.03em;
line-height: 1.1;
min-height: 2rem;
}
.db-kpi-sub {
font-size: 12px;
color: var(--text-secondary);
min-height: 1rem;
}
.db-sub-green { color: var(--green-600); font-weight: 500; }
.db-sub-amber { color: var(--amber-600); font-weight: 500; }
.db-sub-red { color: var(--red-600); font-weight: 500; }
.db-sub-muted { color: var(--text-secondary); }
.db-kpi-skeleton {
display: inline-block;
background: linear-gradient(90deg, var(--gray-100) 25%, var(--gray-200) 50%, var(--gray-100) 75%);
background-size: 200% 100%;
animation: db-shimmer 1.4s infinite;
border-radius: var(--radius-sm);
width: 80px;
height: 1.6rem;
}
.db-kpi-skeleton-sm {
width: 120px;
height: 0.8rem;
}
@keyframes db-shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.db-mid-row {
display: grid;
grid-template-columns: 1fr 340px;
gap: var(--space-4);
margin-bottom: var(--space-5);
}
.db-chart-card,
.db-recent-card,
.db-table-card {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-xs);
overflow: hidden;
}
.db-card-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: var(--space-4) var(--space-5);
border-bottom: 1px solid var(--border-color);
}
.db-card-title {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
}
.db-card-badge,
.db-card-year {
font-size: 12px;
font-weight: 500;
color: var(--text-secondary);
background: var(--gray-100);
padding: 2px 8px;
border-radius: 99px;
}
.db-see-all {
font-size: 12px;
color: var(--blue-600);
text-decoration: none;
font-weight: 500;
}
.db-see-all:hover { text-decoration: underline; }
.db-chart-body {
padding: var(--space-5);
height: 260px;
position: relative;
}
.db-chart-body canvas {
width: 100% !important;
height: 100% !important;
}
.db-recent-list {
overflow-y: auto;
max-height: 320px;
}
.db-recent-item {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--border-color);
cursor: pointer;
transition: background var(--transition-fast);
}
.db-recent-item:last-child { border-bottom: none; }
.db-recent-item:hover { background: var(--gray-50); }
.db-recent-dot {
width: 30px;
height: 30px;
border-radius: var(--radius-md);
display: flex;
align-items: center;
@ -60,136 +203,258 @@
flex-shrink: 0;
}
.stat-icon-invoices {
background: rgba(37, 99, 235, 0.08);
color: #2563eb;
}
.stat-icon-paid {
background: rgba(22, 163, 74, 0.08);
.db-recent-dot.status-paid {
background: #f0fdf4;
color: #16a34a;
}
.stat-icon-unpaid {
background: rgba(202, 138, 4, 0.08);
color: #ca8a04;
.db-recent-dot.status-unpaid {
background: #fffbeb;
color: #b45309;
}
.stat-icon-clients {
background: rgba(99, 102, 241, 0.08);
color: #6366f1;
.db-recent-dot.status-draft {
background: #f3f4f6;
color: #6b7280;
}
.stat-info {
.db-recent-arrow {
color: var(--gray-300);
flex-shrink: 0;
display: flex;
align-items: center;
transition: color var(--transition-fast);
}
.db-recent-item:hover .db-recent-arrow { color: var(--gray-500); }
.db-recent-info {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
gap: 2px;
}
.stat-value {
font-size: 1.375rem;
font-weight: 700;
color: var(--text-primary);
line-height: 1.2;
letter-spacing: -0.02em;
}
.stat-label {
font-size: 0.78rem;
color: var(--text-secondary);
margin-top: 0.1rem;
}
/* Dashboard grid */
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 1rem;
}
.dashboard-card {
background: var(--card-bg);
border-radius: var(--radius-md);
padding: 1.25rem;
border: 1px solid var(--border-color);
box-shadow: var(--shadow-xs);
}
.dashboard-card h3 {
margin: 0 0 1rem;
font-size: 0.875rem;
.db-recent-num {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.db-recent-client {
font-size: 12px;
color: var(--text-secondary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.db-recent-right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 2px;
flex-shrink: 0;
}
.db-recent-amount {
font-size: 13px;
font-weight: 600;
color: var(--text-primary);
}
.db-recent-date {
font-size: 11px;
color: var(--text-secondary);
}
.db-table-card { margin-bottom: var(--space-4); }
.db-table-wrap { overflow-x: auto; }
.db-monitoring-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.db-monitoring-table thead th {
padding: var(--space-3) var(--space-4);
text-align: left;
font-size: 11px;
font-weight: 600;
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.03em;
letter-spacing: 0.05em;
background: var(--gray-50);
border-bottom: 1px solid var(--border-color);
white-space: nowrap;
}
/* Quick actions */
.quick-actions {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.db-th-right { text-align: right !important; }
.db-monitoring-table thead th:first-child { padding-left: var(--space-5); }
.db-monitoring-table thead th:last-child { padding-right: var(--space-5); }
.quick-action-btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.625rem 1rem;
border-radius: var(--radius-sm);
font-size: 0.8125rem;
font-weight: 500;
text-decoration: none;
transition: background-color 0.15s ease;
cursor: pointer;
background: var(--primary);
color: #fff;
}
.db-table-row { cursor: pointer; }
.quick-action-btn:hover {
background: var(--primary-hover);
color: #fff;
}
.quick-action-btn.secondary {
background: var(--primary-subtle);
color: var(--primary);
}
.quick-action-btn.secondary:hover {
background: rgba(37, 99, 235, 0.12);
color: var(--primary);
}
/* Dashboard info */
.dashboard-info-list {
display: flex;
flex-direction: column;
}
.dashboard-info-row {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.55rem 0;
border-bottom: 1px solid var(--border-subtle);
font-size: 0.8125rem;
}
.dashboard-info-row:last-child {
border-bottom: none;
}
.dashboard-info-row span:first-child {
color: var(--text-secondary);
}
.dashboard-info-value {
.db-table-row td {
padding: var(--space-3) var(--space-4);
border-bottom: 1px solid var(--border-color);
color: var(--text-primary);
font-weight: 600;
vertical-align: middle;
transition: background var(--transition-fast);
}
/* Stat skeleton */
.stat-card .skeleton-value {
width: 50px;
height: 24px;
.db-table-row:last-child td { border-bottom: none; }
.db-table-row:hover td { background: var(--gray-50); }
.db-table-row td:first-child { padding-left: var(--space-5); }
.db-table-row td:last-child { padding-right: var(--space-5); }
.db-cell-num {
font-variant-numeric: tabular-nums;
font-weight: 500;
white-space: nowrap;
}
.db-cell-client {
max-width: 180px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.db-cell-right { text-align: right; }
.db-remain-due { color: var(--red-600); font-weight: 600; }
.db-remain-ok { color: var(--green-600); }
.db-table-loading {
text-align: center;
padding: var(--space-8) !important;
color: var(--text-secondary);
font-size: 13px;
}
.db-loading-inner,
.db-no-data,
.db-error {
text-align: center;
padding: var(--space-6);
color: var(--text-secondary);
font-size: 13px;
margin: 0;
}
.db-error { color: var(--red-600); }
.db-td-center { text-align: center; }
.db-recent-skeleton { pointer-events: none; }
.db-skel-icon,
.db-skel-lines span,
.db-skel-right span {
background: linear-gradient(90deg, var(--gray-100) 25%, var(--gray-200) 50%, var(--gray-100) 75%);
background-size: 200% 100%;
animation: db-shimmer 1.4s infinite;
}
.db-skel-icon {
width: 30px;
height: 30px;
border-radius: var(--radius-md);
flex-shrink: 0;
}
.db-skel-lines {
flex: 1;
display: flex;
flex-direction: column;
gap: 5px;
}
.db-skel-lines span {
display: block;
height: 10px;
border-radius: var(--radius-sm);
}
.db-skel-lines span:first-child { width: 70%; }
.db-skel-lines span:last-child { width: 50%; }
.db-skel-right {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 5px;
}
.db-skel-right span {
display: block;
height: 10px;
border-radius: var(--radius-sm);
}
.db-skel-right span:first-child { width: 60px; }
.db-skel-right span:last-child { width: 44px; }
@media (max-width: 1100px) {
.db-kpi-row { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 900px) {
.db-mid-row { grid-template-columns: 1fr; }
}
@media (max-width: 580px) {
.db-kpi-row { grid-template-columns: 1fr; }
.db-greeting { font-size: 1.1rem; }
}
.dashboard-subtitle {
color: var(--text-secondary);
font-size: 13px;
margin: var(--space-1) 0 0 0;
}
/* Dark mode UX tune */
:root[data-theme='dark'] .db-kpi-card,
:root[data-theme='dark'] .db-chart-card,
:root[data-theme='dark'] .db-recent-card,
:root[data-theme='dark'] .db-table-card {
background: #081226;
border-color: #1e2f4d;
}
:root[data-theme='dark'] .db-card-year,
:root[data-theme='dark'] .db-card-badge,
:root[data-theme='dark'] .db-kpi-icon {
background: rgba(30, 41, 59, 0.8);
}
:root[data-theme='dark'] .db-recent-item,
:root[data-theme='dark'] .db-table-row td {
background: transparent;
}
:root[data-theme='dark'] .db-recent-item:hover,
:root[data-theme='dark'] .db-table-row:hover td {
background: rgba(30, 58, 110, 0.24);
}
:root[data-theme='dark'] .db-recent-dot.status-paid {
background: rgba(34, 197, 94, 0.18);
color: #86efac;
}
:root[data-theme='dark'] .db-recent-dot.status-unpaid {
background: rgba(245, 158, 11, 0.2);
color: #fcd34d;
}
:root[data-theme='dark'] .db-recent-dot.status-draft {
background: rgba(148, 163, 184, 0.18);
color: #cbd5e1;
}

View File

@ -0,0 +1,270 @@
/* ============================================
Facturas + Tablas + Paginacion
============================================ */
.facturas-page {
padding: var(--space-6);
max-width: 1400px;
margin: 0 auto;
}
.facturas-header { margin-bottom: var(--space-5); }
.facturas-header h1 { font-size: 1.25rem; font-weight: 600; }
.btn-new-invoice {
background-color: var(--primary);
color: white;
padding: var(--space-2) var(--space-4);
border: none;
border-radius: var(--radius-md);
cursor: pointer;
font-weight: 500;
font-size: 14px;
transition: background-color var(--transition-fast);
height: 36px;
}
.btn-new-invoice:hover { background-color: var(--primary-hover); }
.facturas-filters {
display: flex;
gap: var(--space-3);
margin-bottom: var(--space-4);
align-items: center;
}
.search-input,
.filter-status {
padding: var(--space-2) var(--space-3);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
font-size: 14px;
font-family: inherit;
background: var(--card-bg);
color: var(--text-primary);
height: 36px;
transition: border-color var(--transition-fast);
}
.search-input:focus,
.filter-status:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.12);
}
.search-input { flex: 1; max-width: 320px; }
.filter-status { min-width: 180px; cursor: pointer; }
.invoices-table-container,
.clients-table-container {
background: var(--card-bg);
border-radius: var(--radius-lg);
border: 1px solid var(--border-color);
overflow: hidden;
box-shadow: var(--shadow-xs);
}
.invoices-table,
.clients-table {
width: 100%;
border-collapse: collapse;
}
.invoices-table thead,
.clients-table thead {
background: var(--gray-50);
border-bottom: 1px solid var(--border-color);
}
.invoices-table th,
.clients-table th {
font-weight: 500;
color: var(--text-secondary);
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.04em;
padding: var(--space-3) var(--space-4);
text-align: left;
}
.invoices-table tbody tr,
.clients-table tbody tr {
border-bottom: 1px solid var(--border-color);
transition: background-color var(--transition-fast);
}
.invoices-table tbody tr:hover,
.clients-table tbody tr:hover { background-color: var(--gray-50); }
.invoices-table tbody tr:last-child,
.clients-table tbody tr:last-child { border-bottom: none; }
.invoices-table td,
.clients-table td { padding: var(--space-3) var(--space-4); font-size: 14px; }
.invoice-number { font-weight: 500; color: var(--text-primary); }
.invoice-client {
display: flex;
align-items: center;
gap: var(--space-2);
color: var(--text-secondary);
}
.client-icon { display: flex; align-items: center; color: var(--gray-400); }
.invoice-date { color: var(--text-primary); }
.invoice-total,
.invoice-remain {
font-weight: 500;
font-variant-numeric: tabular-nums;
color: var(--text-primary);
}
.invoice-actions {
display: flex;
gap: var(--space-1);
justify-content: center;
}
.btn-view,
.btn-action {
background: transparent;
border: none;
padding: var(--space-1);
border-radius: var(--radius-sm);
cursor: pointer;
color: var(--gray-400);
transition: color var(--transition-fast), background-color var(--transition-fast);
display: inline-flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
}
.btn-view:hover { color: var(--primary); background-color: var(--blue-50); }
.btn-action:hover { background-color: var(--gray-100); color: var(--text-primary); }
.btn-delete:hover { background-color: var(--red-50); color: var(--danger); }
.loading,
.error,
.no-invoices,
.no-clients {
padding: var(--space-10);
text-align: center;
color: var(--text-secondary);
font-size: 14px;
}
.error { color: var(--danger); }
.pagination {
display: flex;
justify-content: center;
align-items: center;
gap: var(--space-4);
margin-top: var(--space-4);
padding: var(--space-3) var(--space-4);
background: var(--card-bg);
border-radius: var(--radius-lg);
border: 1px solid var(--border-color);
}
.pagination-info {
font-weight: 500;
color: var(--text-secondary);
font-size: 13px;
display: flex;
gap: var(--space-1);
align-items: center;
font-variant-numeric: tabular-nums;
}
.pagination-info .current-page,
.pagination-info .total-pages {
font-weight: 600;
color: var(--text-primary);
min-width: 20px;
text-align: center;
}
.btn-pagination {
padding: var(--space-2) var(--space-3);
background-color: var(--card-bg);
color: var(--text-primary);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
cursor: pointer;
font-weight: 500;
font-size: 13px;
transition: background-color var(--transition-fast), border-color var(--transition-fast);
display: flex;
align-items: center;
gap: var(--space-1);
}
.btn-pagination:hover:not(:disabled) {
background-color: var(--gray-50);
border-color: var(--gray-300);
}
.btn-pagination:disabled {
background-color: var(--gray-50);
color: var(--gray-300);
cursor: not-allowed;
border-color: var(--gray-200);
}
/* Dark tune */
:root[data-theme='dark'] .invoices-table-container,
:root[data-theme='dark'] .clients-table-container,
:root[data-theme='dark'] .pagination {
background: #081226;
border-color: #1e2f4d;
}
:root[data-theme='dark'] .invoices-table thead,
:root[data-theme='dark'] .clients-table thead {
background: rgba(15, 23, 42, 0.95);
}
:root[data-theme='dark'] .invoices-table tbody tr:hover,
:root[data-theme='dark'] .clients-table tbody tr:hover,
:root[data-theme='dark'] .btn-pagination:hover:not(:disabled) {
background-color: rgba(30, 58, 110, 0.24);
}
@media (max-width: 768px) {
.facturas-filters {
flex-direction: column;
}
.search-input {
max-width: 100% !important;
}
.pagination {
flex-wrap: wrap;
gap: var(--space-3);
}
.btn-pagination {
flex: 1;
min-width: 100px;
justify-content: center;
}
.pagination-info {
order: 3;
width: 100%;
justify-content: center;
}
}
@media (max-width: 1024px) {
.facturas-page {
padding: var(--space-4);
}
}

View File

@ -0,0 +1,179 @@
/* ============================================
Login + Session Notices
============================================ */
.login-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
width: 100%;
background: var(--bg-page);
}
.login-card {
background: var(--card-bg);
padding: var(--space-8);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-sm);
width: 100%;
max-width: 380px;
border: 1px solid var(--border-color);
}
.login-title {
color: var(--text-primary);
margin: 0 0 var(--space-6) 0;
font-size: 1.25rem;
text-align: center;
font-weight: 600;
}
.form-group {
margin-bottom: var(--space-4);
}
.form-group label {
display: block;
color: var(--text-secondary);
margin-bottom: var(--space-1);
font-weight: 500;
font-size: 13px;
}
.form-group input {
width: 100%;
padding: var(--space-2) var(--space-3);
border: 1px solid var(--border-color);
border-radius: var(--radius-md);
background: var(--card-bg);
color: var(--text-primary);
font-size: 14px;
font-family: inherit;
box-sizing: border-box;
transition: border-color var(--transition-fast);
height: 36px;
}
.form-group input:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.12);
}
.login-button {
width: 100%;
padding: var(--space-2) var(--space-4);
background-color: var(--primary);
color: white;
border: none;
border-radius: var(--radius-md);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: background-color var(--transition-fast);
height: 36px;
margin-top: var(--space-2);
}
.login-button:hover {
background-color: var(--primary-hover);
border-color: transparent;
}
.login-error {
color: var(--danger);
text-align: center;
margin-top: var(--space-3);
font-size: 13px;
}
.session-warning-overlay,
.connection-lost-overlay {
position: fixed;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
padding: var(--space-4);
}
.session-warning-overlay {
background: rgba(17, 24, 39, 0.45);
z-index: 3000;
}
.connection-lost-overlay {
background: rgba(17, 24, 39, 0.55);
z-index: 4000;
}
.session-warning-modal,
.connection-lost-modal {
width: 100%;
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
box-shadow: var(--shadow-lg);
padding: var(--space-6);
}
.session-warning-modal { max-width: 430px; }
.connection-lost-modal { max-width: 460px; }
.session-warning-modal h3 {
margin: 0 0 var(--space-3) 0;
font-size: 1.15rem;
}
.connection-lost-modal h3 {
margin: 0 0 var(--space-2) 0;
color: var(--danger);
}
.session-warning-modal p,
.connection-lost-modal p {
margin: 0;
color: var(--text-secondary);
}
.session-warning-actions {
margin-top: var(--space-5);
display: flex;
justify-content: flex-end;
gap: var(--space-2);
}
.session-warning-logout {
background: transparent;
border: 1px solid var(--border-color);
color: var(--text-secondary);
}
.session-warning-logout:hover {
background: var(--gray-100);
}
.session-warning-continue {
background: var(--primary);
}
.connection-lost-sub {
margin-top: var(--space-3) !important;
font-size: 0.92rem;
}
.connection-lost-login-btn {
margin-top: var(--space-5);
width: 100%;
}
@media (max-width: 640px) {
.session-warning-actions {
flex-direction: column;
}
.session-warning-actions button {
width: 100%;
}
}

View File

@ -87,7 +87,7 @@
}
.btn-close:hover {
background-color: #f1f5f9;
background-color: var(--bg-subtle);
color: var(--text-primary);
}
@ -168,7 +168,7 @@
font-size: 0.9375rem;
transition: all 0.2s;
font-family: inherit;
background-color: #ffffff;
background-color: var(--card-bg);
color: var(--text-primary);
}
@ -183,19 +183,19 @@
.form-group input:disabled,
.form-group textarea:disabled,
.form-group select:disabled {
background-color: #f8fafc;
background-color: var(--bg-subtle);
color: var(--text-secondary);
cursor: not-allowed;
}
.form-group input[readonly] {
background-color: #f8fafc;
background-color: var(--bg-subtle);
color: var(--text-secondary);
}
/* Add Line Form */
.add-line-form {
background-color: #f8fafc;
background-color: var(--bg-subtle);
padding: 1.5rem;
border-radius: 8px;
margin-bottom: 1rem;
@ -219,7 +219,7 @@
}
.lines-table thead {
background-color: #f8fafc;
background-color: var(--bg-subtle);
}
.lines-table th {
@ -236,7 +236,7 @@
}
.lines-table tbody tr:hover {
background-color: #f8fafc;
background-color: var(--bg-subtle);
}
.no-lines {
@ -281,7 +281,7 @@
.invoice-totals {
margin-top: 1.5rem;
padding: 1rem;
background-color: #f8fafc;
background-color: var(--bg-subtle);
border-radius: 8px;
display: flex;
flex-direction: column;
@ -493,7 +493,7 @@
}
.btn-cancel:hover {
background-color: #f8fafc;
background-color: var(--bg-subtle);
color: var(--text-primary);
}
@ -519,7 +519,7 @@
}
.btn-action:hover {
background-color: #f1f5f9;
background-color: var(--bg-subtle);
transform: scale(1.1);
}
@ -720,14 +720,14 @@
}
.confirm-exit-actions .btn-exit {
background-color: #f1f5f9;
background-color: var(--bg-subtle);
color: var(--text-primary, #1e293b);
border: 1px solid var(--border-color, #e2e8f0);
}
.confirm-exit-actions .btn-exit:hover {
background-color: #e2e8f0;
border-color: #cbd5e1;
background-color: rgba(148, 163, 184, 0.18);
border-color: rgba(148, 163, 184, 0.28);
}
/* Responsive Confirm Modal */

85
src/styles/settings.css Normal file
View File

@ -0,0 +1,85 @@
/* ============================================
Settings Page
============================================ */
.settings-page {
max-width: 980px;
margin: 0 auto;
padding: var(--space-6);
}
.settings-header {
margin-bottom: var(--space-6);
}
.settings-header p {
margin-top: var(--space-2);
color: var(--text-secondary);
}
.settings-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: var(--space-4);
}
.settings-card {
background: var(--card-bg);
border: 1px solid var(--border-color);
border-radius: 12px;
padding: var(--space-5);
box-shadow: var(--shadow-xs);
}
.settings-card h2 {
margin-bottom: var(--space-4);
font-size: 1.05rem;
}
.settings-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: var(--space-4);
}
.settings-label {
margin: 0;
font-size: 0.95rem;
font-weight: 600;
color: var(--text-primary);
}
.settings-help {
margin: var(--space-1) 0 0;
font-size: 0.85rem;
color: var(--text-secondary);
}
.settings-theme-toggle {
min-width: 116px;
}
.settings-session-list {
display: flex;
flex-direction: column;
gap: var(--space-4);
}
.settings-session-item {
display: flex;
justify-content: space-between;
align-items: center;
gap: var(--space-3);
padding-bottom: var(--space-3);
border-bottom: 1px dashed var(--border-color);
}
.settings-session-item:last-child {
border-bottom: none;
padding-bottom: 0;
}
.settings-token-expired {
color: var(--danger);
}

View File

@ -0,0 +1,185 @@
/* ============================================
App Layout + Sidebar
============================================ */
.app-layout {
display: flex;
min-height: 100vh;
width: 100%;
}
.sidebar {
width: 220px;
background-color: var(--card-bg);
border-right: 1px solid var(--border-color);
display: flex;
flex-direction: column;
transition: width var(--transition-base);
position: relative;
z-index: 100;
flex-shrink: 0;
}
.sidebar-header {
padding: var(--space-4);
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
height: 52px;
}
.sidebar-header h2 {
margin: 0;
font-size: 14px;
font-weight: 600;
color: var(--text-primary);
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
letter-spacing: -0.01em;
}
.sidebar-toggle {
background: transparent;
border: none;
cursor: pointer;
padding: var(--space-1);
color: var(--text-secondary);
font-size: 14px;
border-radius: var(--radius-sm);
transition: background-color var(--transition-fast);
display: flex;
align-items: center;
justify-content: center;
width: 28px;
height: 28px;
}
.sidebar-toggle:hover {
background-color: var(--gray-100);
border-color: transparent;
}
.sidebar-nav {
flex: 1;
overflow-y: auto;
padding: var(--space-2) 0;
}
.sidebar-menu {
list-style: none;
margin: 0;
padding: 0;
}
.sidebar-menu li {
margin: 0;
padding: 0 var(--space-2);
}
.sidebar-link {
display: flex;
align-items: center;
gap: var(--space-3);
padding: var(--space-2) var(--space-3);
color: var(--text-secondary);
text-decoration: none;
transition: color var(--transition-fast), background-color var(--transition-fast);
cursor: pointer;
border-radius: var(--radius-md);
font-size: 14px;
font-weight: 400;
margin-bottom: 1px;
}
.sidebar-link:hover {
background-color: var(--gray-100);
color: var(--text-primary);
}
.sidebar-link.active {
background-color: var(--blue-50);
color: var(--primary);
font-weight: 500;
}
:root[data-theme='dark'] .sidebar-link:hover {
background-color: rgba(30, 58, 110, 0.18);
color: #dbeafe;
}
:root[data-theme='dark'] .sidebar-link.active {
background-color: rgba(30, 58, 110, 0.32);
color: #bfdbfe;
box-shadow: inset 0 0 0 1px rgba(96, 165, 250, 0.25);
}
.sidebar-icon {
display: flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
flex-shrink: 0;
}
.sidebar-icon svg {
width: 16px;
height: 16px;
}
.sidebar-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.sidebar.collapsed { width: 56px; }
.sidebar.collapsed .sidebar-header h2,
.sidebar.collapsed .sidebar-text {
opacity: 0;
width: 0;
overflow: hidden;
}
.sidebar.collapsed .sidebar-icon { margin-right: 0; }
.sidebar.collapsed .sidebar-link {
justify-content: center;
padding: var(--space-2);
}
.main-content {
flex: 1;
overflow-y: auto;
background-color: var(--bg-page);
min-width: 0;
}
.main-content > div {
max-width: 1280px;
margin: 0 auto;
padding: var(--space-6);
}
@media (max-width: 768px) {
.sidebar {
position: fixed;
left: 0;
top: 0;
bottom: 0;
z-index: 1000;
transform: translateX(0);
}
.sidebar.collapsed {
transform: translateX(-100%);
width: 220px;
}
.main-content {
margin-left: 0;
}
}