/** * dashboard-stats.js — In-app real-time charts using Chart.js * Fetches /pos/api/dashboard/stats and renders hourly + top-products charts. */ (function () { 'use strict'; const token = localStorage.getItem('pos_token') || ''; if (!token) return; let hourlyChart = null; let topProductsChart = null; function headers() { return { 'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json' }; } function fmt(n) { return '$' + parseFloat(n || 0).toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } async function loadStats() { try { const res = await fetch('/pos/api/dashboard/stats', { headers: headers() }); if (!res.ok) return; const data = await res.json(); renderHourlyChart(data.hourly_sales || []); renderTopProductsChart(data.top_products || []); } catch (e) { console.error('[dashboard-stats] failed to load', e); } } function renderHourlyChart(hourly) { const ctx = document.getElementById('hourlySalesChart'); if (!ctx) return; if (hourlyChart) { hourlyChart.destroy(); hourlyChart = null; } const labels = hourly.map(function (h) { return h.hour + ':00'; }); const totals = hourly.map(function (h) { return h.total; }); hourlyChart = new Chart(ctx, { type: 'bar', data: { labels: labels, datasets: [{ label: 'Ventas ($)', data: totals, backgroundColor: 'rgba(245, 166, 35, 0.7)', borderColor: 'rgba(245, 166, 35, 1)', borderWidth: 1, borderRadius: 4, }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { x: { ticks: { color: '#888', font: { size: 10 } }, grid: { display: false } }, y: { ticks: { color: '#888', font: { size: 10 }, callback: function (v) { return '$' + (v / 1000).toFixed(0) + 'k'; } }, grid: { color: '#333' } } } } }); } function renderTopProductsChart(topProducts) { const ctx = document.getElementById('topProductsChart'); if (!ctx) return; if (topProductsChart) { topProductsChart.destroy(); topProductsChart = null; } if (!topProducts || topProducts.length === 0) { // No sales today — render a friendly empty-state mini chart so the canvas // doesn't collapse or leave a blank hole. topProductsChart = new Chart(ctx, { type: 'doughnut', data: { labels: ['Sin ventas hoy'], datasets: [{ data: [1], backgroundColor: ['rgba(136, 136, 136, 0.25)'], borderWidth: 0, }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right', labels: { color: '#888', font: { size: 10 }, boxWidth: 10 } } } } }); return; } const labels = topProducts.map(function (p) { return p.name.substring(0, 20); }); const revenues = topProducts.map(function (p) { return p.revenue; }); topProductsChart = new Chart(ctx, { type: 'doughnut', data: { labels: labels, datasets: [{ data: revenues, backgroundColor: [ '#F5A623', '#E85D75', '#4ECDC4', '#556270', '#C7F464', '#FF6B6B', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD' ], borderWidth: 0, }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'right', labels: { color: '#ccc', font: { size: 10 }, boxWidth: 10 } } } } }); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', loadStats); } else { loadStats(); } })();