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:
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user