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) {
btn.closest('.period-selector').querySelectorAll('.period-btn').forEach(function(b) {
b.classList.remove('active');
});
btn.classList.add('active');
const period = btn.textContent.trim().toLowerCase();
loadChart(period);
}
window.setPeriod = setPeriod;
@@ -440,50 +442,168 @@ const Dashboard = (() => {
}
// -------------------------------------------------------------------------
// 5. Weekly bar chart (last 7 days)
// Helpers for chart grouping
// -------------------------------------------------------------------------
async function loadWeeklyChart() {
const chartEl = document.getElementById('bar-chart');
const totalEl = document.getElementById('chart-week-total');
if (!chartEl) return;
// Fetch daily summary for each of last 7 days
const days = [];
for (let i = 6; i >= 0; i--) {
days.push(daysAgo(i));
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);
}
const [summaries, histData] = await Promise.all([
Promise.all(days.map(d => apiFetch(`/pos/api/register/daily-summary?date=${d}`))),
apiFetch(`/pos/api/historical-sales?date_from=${days[0]}&date_to=${days[6]}&per_page=200`).catch(() => ({ data: [] }))
]);
function weekLabel(date) {
return `Sem ${isoWeek(date)}`;
}
// Group historical sales by day
const histRows = histData.data || [];
const histByDay = {};
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 totalEl = document.getElementById('chart-week-total');
const legendEl = document.getElementById('chart-legend');
const titleEl = document.querySelector('.chart-header .section-title');
if (!chartEl) return;
period = period || 'semana';
let dateFrom, dateTo, labels = [], buckets = {}, labelOrder = [];
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();
}
// Fetch normal sales for short periods
let normalByKey = {};
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;
});
}
// Fetch historical sales for the range
let histRows = [];
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 => {
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;
const dayData = days.map((dateStr, i) => {
const s = summaries[i];
const normalTotal = s ? (s.total_sales || 0) : 0;
const histTotal = histByDay[dateStr] || 0;
// Build chart data
let chartTotal = 0;
const dayData = labelOrder.map(label => {
let normalTotal = normalByKey[label] || 0;
let histTotal = 0;
if (typeof buckets[label] === 'object') {
histTotal = buckets[label].total;
} else {
histTotal = buckets[label] || 0;
}
const total = normalTotal + histTotal;
weekTotal += total;
const d = new Date(dateStr + 'T12:00:00');
return {
label: DAY_NAMES_SHORT[d.getDay()],
total: total,
isToday: dateStr === todayStr(),
};
chartTotal += total;
const isToday = period === 'hoy' || (typeof buckets[label] === 'object' && buckets[label].date === todayStr());
return { label, total, isToday };
});
// 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) {
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);
@@ -591,7 +711,7 @@ const Dashboard = (() => {
loadHistoricalSummary();
loadAlerts();
loadTopProducts();
loadWeeklyChart();
loadChart('semana');
loadRecentSales();
// 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/sidebar.js" 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>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
<script src="/pos/static/js/pwa-install.js" defer></script>