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