feat(dashboard): make sales chart period buttons work

- Replace placeholder setPeriod() with functional period switcher
- loadChart() supports Hoy (1 day), Semana (7 days), Mes (4 weeks), Año (12 months)
- Includes both normal POS sales and imported historical sales
- Updates chart title, total label and legend dynamically
- Bump dashboard.js to ?v=6
This commit is contained in:
2026-06-12 08:33:29 +00:00
parent c1e93ed52a
commit a9052e63c2
2 changed files with 151 additions and 31 deletions

View File

@@ -84,13 +84,15 @@ const Dashboard = (() => {
}); });
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// Period selector (placeholder for future use) // Period selector
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
function setPeriod(btn) { function setPeriod(btn) {
btn.closest('.period-selector').querySelectorAll('.period-btn').forEach(function(b) { btn.closest('.period-selector').querySelectorAll('.period-btn').forEach(function(b) {
b.classList.remove('active'); b.classList.remove('active');
}); });
btn.classList.add('active'); btn.classList.add('active');
const period = btn.textContent.trim().toLowerCase();
loadChart(period);
} }
window.setPeriod = setPeriod; window.setPeriod = setPeriod;
@@ -440,50 +442,168 @@ const Dashboard = (() => {
} }
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
// 5. Weekly bar chart (last 7 days) // Helpers for chart grouping
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
async function loadWeeklyChart() { function isoWeek(date) {
const tmp = new Date(date.valueOf());
const dayNum = (date.getDay() + 6) % 7;
tmp.setDate(tmp.getDate() - dayNum + 3);
const firstThursday = tmp.valueOf();
tmp.setMonth(0, 1);
if (tmp.getDay() !== 4) {
tmp.setMonth(0, 1 + ((4 - tmp.getDay()) + 7) % 7);
}
return 1 + Math.ceil((firstThursday - tmp) / 604800000);
}
function weekLabel(date) {
return `Sem ${isoWeek(date)}`;
}
function monthLabel(date) {
return MONTH_NAMES[date.getMonth()].slice(0, 3);
}
// -------------------------------------------------------------------------
// 5. Sales chart (today / week / month / year)
// -------------------------------------------------------------------------
async function loadChart(period) {
const chartEl = document.getElementById('bar-chart'); const chartEl = document.getElementById('bar-chart');
const totalEl = document.getElementById('chart-week-total'); const totalEl = document.getElementById('chart-week-total');
const legendEl = document.getElementById('chart-legend');
const titleEl = document.querySelector('.chart-header .section-title');
if (!chartEl) return; if (!chartEl) return;
// Fetch daily summary for each of last 7 days period = period || 'semana';
const days = [];
for (let i = 6; i >= 0; i--) { let dateFrom, dateTo, labels = [], buckets = {}, labelOrder = [];
days.push(daysAgo(i)); const now = new Date();
if (period === 'hoy') {
dateFrom = dateTo = todayStr();
labelOrder = ['Hoy'];
buckets['Hoy'] = 0;
} else if (period === 'semana') {
const days = [];
for (let i = 6; i >= 0; i--) { days.push(daysAgo(i)); }
dateFrom = days[0];
dateTo = days[6];
days.forEach(d => {
const date = new Date(d + 'T12:00:00');
const label = DAY_NAMES_SHORT[date.getDay()];
labelOrder.push(label);
buckets[label] = { total: 0, date: d };
});
} else if (period === 'mes') {
const start = new Date();
start.setDate(start.getDate() - 27);
dateFrom = start.toISOString().slice(0, 10);
dateTo = todayStr();
for (let i = 3; i >= 0; i--) {
const d = new Date();
d.setDate(d.getDate() - (i * 7 + 6));
const label = weekLabel(d);
labelOrder.push(label);
buckets[label] = 0;
}
} else if (period === 'año') {
for (let i = 11; i >= 0; i--) {
const d = new Date(now.getFullYear(), now.getMonth() - i, 1);
const label = monthLabel(d);
labelOrder.push(label);
buckets[label] = 0;
}
dateFrom = new Date(now.getFullYear(), now.getMonth() - 11, 1).toISOString().slice(0, 10);
dateTo = todayStr();
} }
const [summaries, histData] = await Promise.all([ // Fetch normal sales for short periods
Promise.all(days.map(d => apiFetch(`/pos/api/register/daily-summary?date=${d}`))), let normalByKey = {};
apiFetch(`/pos/api/historical-sales?date_from=${days[0]}&date_to=${days[6]}&per_page=200`).catch(() => ({ data: [] })) if (period === 'hoy' || period === 'semana') {
]); const days = period === 'hoy' ? [todayStr()] : (function() {
const arr = [];
for (let i = 6; i >= 0; i--) arr.push(daysAgo(i));
return arr;
})();
const summaries = await Promise.all(
days.map(d => apiFetch(`/pos/api/register/daily-summary?date=${d}`))
);
days.forEach((d, i) => {
const date = new Date(d + 'T12:00:00');
const key = period === 'hoy' ? 'Hoy' : DAY_NAMES_SHORT[date.getDay()];
normalByKey[key] = summaries[i] ? (summaries[i].total_sales || 0) : 0;
});
}
// Group historical sales by day // Fetch historical sales for the range
const histRows = histData.data || []; let histRows = [];
const histByDay = {}; try {
const perPage = period === 'año' ? 2000 : 1000;
const histData = await apiFetch(`/pos/api/historical-sales?date_from=${dateFrom}&date_to=${dateTo}&per_page=${perPage}`);
histRows = histData.data || [];
const totalPages = histData.pagination ? histData.pagination.total_pages : 1;
for (let p = 2; p <= totalPages && p <= 20; p++) {
const more = await apiFetch(`/pos/api/historical-sales?date_from=${dateFrom}&date_to=${dateTo}&per_page=${perPage}&page=${p}`);
histRows = histRows.concat(more.data || []);
}
} catch (e) {
histRows = [];
}
// Group historical sales
histRows.forEach(r => { histRows.forEach(r => {
if (!r.sale_date) return; if (!r.sale_date) return;
histByDay[r.sale_date] = (histByDay[r.sale_date] || 0) + (r.total || 0); const date = new Date(r.sale_date + 'T12:00:00');
let key;
if (period === 'hoy') key = 'Hoy';
else if (period === 'semana') key = DAY_NAMES_SHORT[date.getDay()];
else if (period === 'mes') {
// Find which week bucket this date belongs to
for (let i = 0; i < 4; i++) {
const bucketStart = new Date();
bucketStart.setDate(bucketStart.getDate() - ((3 - i) * 7 + 6));
const bucketEnd = new Date(bucketStart);
bucketEnd.setDate(bucketEnd.getDate() + 6);
if (date >= bucketStart && date <= bucketEnd) {
key = labelOrder[i];
break;
}
}
} else {
key = monthLabel(date);
}
if (key) {
if (typeof buckets[key] === 'object') buckets[key].total += (r.total || 0);
else buckets[key] = (buckets[key] || 0) + (r.total || 0);
}
}); });
let weekTotal = 0; // Build chart data
const dayData = days.map((dateStr, i) => { let chartTotal = 0;
const s = summaries[i]; const dayData = labelOrder.map(label => {
const normalTotal = s ? (s.total_sales || 0) : 0; let normalTotal = normalByKey[label] || 0;
const histTotal = histByDay[dateStr] || 0; let histTotal = 0;
if (typeof buckets[label] === 'object') {
histTotal = buckets[label].total;
} else {
histTotal = buckets[label] || 0;
}
const total = normalTotal + histTotal; const total = normalTotal + histTotal;
weekTotal += total; chartTotal += total;
const d = new Date(dateStr + 'T12:00:00'); const isToday = period === 'hoy' || (typeof buckets[label] === 'object' && buckets[label].date === todayStr());
return { return { label, total, isToday };
label: DAY_NAMES_SHORT[d.getDay()],
total: total,
isToday: dateStr === todayStr(),
};
}); });
// Update week total // Update labels
const titles = { hoy: 'Ventas de Hoy', semana: 'Ventas Semanales', mes: 'Ventas del Mes', año: 'Ventas del Año' };
const legends = { hoy: 'Total del día', semana: 'Ventas brutas (7 días)', mes: 'Ventas brutas (4 semanas)', año: 'Ventas brutas (12 meses)' };
if (titleEl) titleEl.textContent = titles[period] || 'Ventas';
if (totalEl) { if (totalEl) {
totalEl.innerHTML = `Total semana: <strong style="color:var(--color-primary);font-family:var(--font-mono);">${fmt(weekTotal)}</strong>`; const periodLabel = period === 'hoy' ? 'Total día' : period === 'semana' ? 'Total semana' : period === 'mes' ? 'Total mes' : 'Total año';
totalEl.innerHTML = `${periodLabel}: <strong style="color:var(--color-primary);font-family:var(--font-mono);">${fmt(chartTotal)}</strong>`;
}
if (legendEl) {
legendEl.innerHTML = `<div class="legend-item"><div class="legend-dot"></div>${legends[period]}</div>`;
} }
const maxVal = Math.max(...dayData.map(d => d.total), 1); const maxVal = Math.max(...dayData.map(d => d.total), 1);
@@ -591,7 +711,7 @@ const Dashboard = (() => {
loadHistoricalSummary(); loadHistoricalSummary();
loadAlerts(); loadAlerts();
loadTopProducts(); loadTopProducts();
loadWeeklyChart(); loadChart('semana');
loadRecentSales(); loadRecentSales();
// Auto-refresh every 2 minutes // Auto-refresh every 2 minutes

View File

@@ -568,7 +568,7 @@
<script src="/pos/static/js/pos-utils.js?v=2" defer></script> <script src="/pos/static/js/pos-utils.js?v=2" defer></script>
<script src="/pos/static/js/sidebar.js" defer></script> <script src="/pos/static/js/sidebar.js" defer></script>
<script src="/pos/static/js/dashboard-stats.js?v=3" defer></script> <script src="/pos/static/js/dashboard-stats.js?v=3" defer></script>
<script src="/pos/static/js/dashboard.js?v=5" defer></script> <script src="/pos/static/js/dashboard.js?v=6" defer></script>
<script src="/pos/static/js/sync-engine.js" defer></script> <script src="/pos/static/js/sync-engine.js" defer></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script> <script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
<script src="/pos/static/js/pwa-install.js" defer></script> <script src="/pos/static/js/pwa-install.js" defer></script>