// /home/Autopartes/pos/static/js/dashboard.js // Dashboard module: fetches real data from POS APIs const Dashboard = (() => { function token() { return localStorage.getItem('pos_token') || ''; } function checkAuth() { if (!token()) { window.location.href = '/pos/login'; return false; } return true; } function headers() { return { 'Authorization': `Bearer ${token()}`, 'Content-Type': 'application/json' }; } function fmt(n) { return '$' + parseFloat(n || 0).toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } function fmtInt(n) { return parseInt(n || 0).toLocaleString('es-MX'); } /** Today as YYYY-MM-DD */ function todayStr() { return new Date().toISOString().slice(0, 10); } /** Return a date N days ago as YYYY-MM-DD */ function daysAgo(n) { const d = new Date(); d.setDate(d.getDate() - n); return d.toISOString().slice(0, 10); } const DAY_NAMES_SHORT = ['Dom', 'Lun', 'Mar', 'Mie', 'Jue', 'Vie', 'Sab']; const MONTH_NAMES = ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre']; const DAY_NAMES_LONG = ['Domingo', 'Lunes', 'Martes', 'Miercoles', 'Jueves', 'Viernes', 'Sabado']; // ------------------------------------------------------------------------- // Theme switcher // ------------------------------------------------------------------------- function setTheme(theme) { document.documentElement.setAttribute('data-theme', theme); try { localStorage.setItem('nexus-theme', theme); } catch(e) {} const btnInd = document.getElementById('btn-industrial'); const btnMod = document.getElementById('btn-modern'); if (btnInd) btnInd.classList.toggle('active', theme === 'industrial'); if (btnMod) btnMod.classList.toggle('active', theme === 'modern'); } window.setTheme = setTheme; // ------------------------------------------------------------------------- // Sidebar toggle (mobile) // ------------------------------------------------------------------------- function toggleSidebar() { const sidebar = document.getElementById('sidebar'); const overlay = document.getElementById('sidebar-overlay'); if (!sidebar) return; const isOpen = sidebar.classList.contains('open'); sidebar.classList.toggle('open', !isOpen); if (overlay) overlay.classList.toggle('open', !isOpen); document.body.style.overflow = isOpen ? '' : 'hidden'; } window.toggleSidebar = toggleSidebar; function closeSidebar() { const sidebar = document.getElementById('sidebar'); const overlay = document.getElementById('sidebar-overlay'); if (sidebar) sidebar.classList.remove('open'); if (overlay) overlay.classList.remove('open'); document.body.style.overflow = ''; } window.closeSidebar = closeSidebar; window.addEventListener('resize', function() { if (window.innerWidth >= 768) closeSidebar(); }); // ------------------------------------------------------------------------- // Period selector (placeholder for future use) // ------------------------------------------------------------------------- function setPeriod(btn) { btn.closest('.period-selector').querySelectorAll('.period-btn').forEach(function(b) { b.classList.remove('active'); }); btn.classList.add('active'); } window.setPeriod = setPeriod; // ------------------------------------------------------------------------- // Generic fetch helper // ------------------------------------------------------------------------- async function apiFetch(url) { try { const resp = await fetch(url, { headers: headers() }); if (resp.status === 401) { window.location.href = '/pos/login'; return null; } if (!resp.ok) return null; return await resp.json(); } catch (err) { console.error('Dashboard fetch error:', url, err); return null; } } // ------------------------------------------------------------------------- // Set header greeting with real date and user info // ------------------------------------------------------------------------- function setGreeting() { const now = new Date(); const h = now.getHours(); let saludo = 'Buenos dias'; if (h >= 12 && h < 19) saludo = 'Buenas tardes'; else if (h >= 19) saludo = 'Buenas noches'; // Try to get user name from JWT payload let userName = ''; try { const payload = JSON.parse(atob(token().split('.')[1])); userName = payload.name || payload.sub || ''; } catch(e) {} const greetEl = document.getElementById('header-greeting'); if (greetEl) greetEl.textContent = userName ? `${saludo}, ${userName}` : saludo; const subEl = document.getElementById('header-subtitle'); if (subEl) { const dayName = DAY_NAMES_LONG[now.getDay()]; const day = now.getDate(); const month = MONTH_NAMES[now.getMonth()]; const year = now.getFullYear(); subEl.textContent = `${dayName}, ${day} de ${month} de ${year}`; } } // ------------------------------------------------------------------------- // 1. Ventas de hoy KPIs (daily summary) // ------------------------------------------------------------------------- async function loadDailySummary() { const today = todayStr(); const data = await apiFetch(`/pos/api/register/daily-summary?date=${today}`); if (!data) { setKpiError('kpi-ventas-total', 'kpi-ventas-meta'); setKpiError('kpi-tickets-count', 'kpi-tickets-meta'); setKpiError('kpi-promedio-value', 'kpi-promedio-meta'); return null; } // Ventas Hoy const totalEl = document.getElementById('kpi-ventas-total'); const metaEl = document.getElementById('kpi-ventas-meta'); if (totalEl) totalEl.textContent = fmt(data.total_sales); if (metaEl) { const methods = Object.entries(data.sales_by_method || {}) .map(([m, v]) => `${capitalize(m)}: ${fmt(v.amount)}`) .join(' | '); metaEl.innerHTML = methods ? `` : ''; } // Tickets Hoy const ticketsEl = document.getElementById('kpi-tickets-count'); const ticketsMetaEl = document.getElementById('kpi-tickets-meta'); if (ticketsEl) ticketsEl.textContent = fmtInt(data.total_sales_count); if (ticketsMetaEl) { const cancelled = data.cancelled_count || 0; ticketsMetaEl.innerHTML = cancelled > 0 ? `${cancelled} cancelada${cancelled > 1 ? 's' : ''}` : ``; } // Ticket Promedio const promedioEl = document.getElementById('kpi-promedio-value'); const promedioMetaEl = document.getElementById('kpi-promedio-meta'); const avg = data.total_sales_count > 0 ? data.total_sales / data.total_sales_count : 0; if (promedioEl) promedioEl.textContent = fmt(avg); if (promedioMetaEl) { promedioMetaEl.innerHTML = ``; } // Cajas del Día KPI const cajasCountEl = document.getElementById('kpi-cajas-count'); const cajasMetaEl = document.getElementById('kpi-cajas-meta'); const regs = data.registers || []; const activeRegs = regs.filter(r => r.status === 'open'); const closedRegs = regs.filter(r => r.status === 'closed'); if (cajasCountEl) cajasCountEl.textContent = regs.length; if (cajasMetaEl) { cajasMetaEl.innerHTML = `${activeRegs.length} abierta${activeRegs.length !== 1 ? 's' : ''}`; } // Also populate registers list renderRegisters(regs); return data; } // ------------------------------------------------------------------------- // 1b. Historical sales KPIs (imported data) // ------------------------------------------------------------------------- async function loadHistoricalSummary() { try { const now = new Date(); const firstDay = new Date(now.getFullYear(), now.getMonth(), 1).toISOString().slice(0, 10); const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0).toISOString().slice(0, 10); // All historical sales const all = await apiFetch('/pos/api/historical-sales?per_page=1'); const totalRecords = all.pagination ? all.pagination.total : 0; // Current month historical sales const month = await apiFetch(`/pos/api/historical-sales?date_from=${firstDay}&date_to=${lastDay}&per_page=200`); const monthRows = month.data || []; const monthTotal = monthRows.reduce((a, r) => a + (r.total || 0), 0); const totalEl = document.getElementById('kpi-historico-total-value'); const totalMetaEl = document.getElementById('kpi-historico-total-meta'); if (totalEl) totalEl.textContent = fmt(monthTotal); if (totalMetaEl) totalMetaEl.innerHTML = ``; const mesEl = document.getElementById('kpi-historico-mes-value'); const mesMetaEl = document.getElementById('kpi-historico-mes-meta'); if (mesEl) mesEl.textContent = fmt(monthTotal); if (mesMetaEl) mesMetaEl.innerHTML = ``; const countEl = document.getElementById('kpi-historico-count-value'); const countMetaEl = document.getElementById('kpi-historico-count-meta'); if (countEl) countEl.textContent = fmtInt(totalRecords); if (countMetaEl) countMetaEl.innerHTML = ``; } catch (err) { console.error('Error loading historical summary:', err); const ids = [ ['kpi-historico-total-value', 'kpi-historico-total-meta'], ['kpi-historico-mes-value', 'kpi-historico-mes-meta'], ['kpi-historico-count-value', 'kpi-historico-count-meta'], ]; ids.forEach(([v, m]) => setKpiError(v, m)); } } function setKpiError(valueId, metaId) { const v = document.getElementById(valueId); const m = document.getElementById(metaId); if (v) v.innerHTML = '--'; if (m) m.innerHTML = ''; } function capitalize(s) { if (!s) return ''; return s.charAt(0).toUpperCase() + s.slice(1); } // ------------------------------------------------------------------------- // 2. Registers list // ------------------------------------------------------------------------- function renderRegisters(registers) { const container = document.getElementById('registers-list'); if (!container) return; if (!registers || registers.length === 0) { container.innerHTML = renderEmptyState({ icon: '', title: 'Sin cajas hoy', subtitle: 'Ninguna caja ha sido abierta el día de hoy.', action: 'Abrir POS' }); return; } const maxSale = Math.max(...registers.map(r => r.sale_total || 0), 1); container.innerHTML = registers.map((r, i) => { const statusClass = r.status === 'open' ? 'kpi-tag--up' : 'kpi-tag--neutral'; const statusLabel = r.status === 'open' ? 'Abierta' : 'Cerrada'; const pct = maxSale > 0 ? Math.round((r.sale_total / maxSale) * 100) : 0; return `