let eggChart = null; function buildChart(eggs) { const tz = (typeof Auth !== 'undefined' && Auth.getUser()?.timezone) || 'UTC'; const labels = []; const data = []; // Build a lookup map from date string → egg count const eggMap = {}; eggs.forEach(e => { eggMap[e.date] = e.eggs; }); // Generate the last 30 days in the user's configured timezone for (let i = 29; i >= 0; i--) { const d = new Date(); d.setDate(d.getDate() - i); const dateStr = d.toLocaleDateString('en-CA', { timeZone: tz }); const [, mo, dy] = dateStr.split('-'); labels.push(`${mo}/${dy}`); data.push(eggMap[dateStr] ?? 0); } const ctx = document.getElementById('eggs-chart').getContext('2d'); const chartWrap = document.getElementById('chart-wrap'); const noDataMsg = document.getElementById('chart-no-data'); const hasData = data.some(v => v > 0); if (!hasData) { if (chartWrap) chartWrap.style.display = 'none'; if (noDataMsg) noDataMsg.style.display = 'block'; return; } if (chartWrap) chartWrap.style.display = 'block'; if (noDataMsg) noDataMsg.style.display = 'none'; if (eggChart) eggChart.destroy(); // prevent duplicate charts on re-render const green = getComputedStyle(document.documentElement).getPropertyValue('--green').trim(); eggChart = new Chart(ctx, { type: 'line', data: { labels, datasets: [{ data, borderColor: green, backgroundColor: 'rgba(61,107,79,0.08)', borderWidth: 2, pointRadius: 3, pointHoverRadius: 5, fill: true, tension: 0.3, }], }, options: { responsive: true, plugins: { legend: { display: false }, tooltip: { callbacks: { label: ctx => ` ${ctx.parsed.y} eggs`, }, }, }, scales: { y: { beginAtZero: true, suggestedMax: 5, ticks: { stepSize: 1, precision: 0 }, }, x: { ticks: { maxTicksLimit: 10 }, }, }, }, }); } async function loadDashboard() { const msg = document.getElementById('msg'); try { // Fetch stats and recent eggs in parallel; eggs limited to last 30 days for chart + recent list const tz = (typeof Auth !== 'undefined' && Auth.getUser()?.timezone) || 'UTC'; const start30 = new Date(); start30.setDate(start30.getDate() - 30); const start30str = start30.toLocaleDateString('en-CA', { timeZone: tz }); const [stats, budget, eggs] = await Promise.all([ API.get('/api/stats/dashboard'), API.get('/api/stats/budget'), API.get(`/api/eggs?start=${start30str}`), ]); // Populate stat cards document.getElementById('s-flock').textContent = stats.current_flock ?? '—'; document.getElementById('s-total').textContent = stats.total_eggs_alltime; document.getElementById('s-7d').textContent = stats.total_eggs_7d; document.getElementById('s-30d').textContent = stats.total_eggs_30d; document.getElementById('s-avg-day').textContent = stats.avg_eggs_per_day_30d ?? '—'; document.getElementById('s-avg-hen').textContent = stats.avg_eggs_per_hen_day_30d ?? '—'; document.getElementById('s-cpe').textContent = fmtMoney(budget.cost_per_egg); document.getElementById('s-cpe-30d').textContent = fmtMoney(budget.cost_per_egg_30d); document.getElementById('s-cpd').textContent = fmtMoney(budget.cost_per_dozen); document.getElementById('s-cpd-30d').textContent = fmtMoney(budget.cost_per_dozen_30d); // Trend chart — uses all fetched eggs, filtered to last 30 days inside buildChart buildChart(eggs); // Recent 10 collections const tbody = document.getElementById('recent-body'); const recent = eggs.slice(0, 10); if (recent.length === 0) { tbody.innerHTML = 'No eggs logged yet. Log your first collection →'; return; } tbody.innerHTML = recent.map(e => ` ${fmtDate(e.date)} ${e.eggs} ${escHtml(e.notes)} `).join(''); } catch (err) { showMessage(msg, `Failed to load dashboard: ${err.message}`, 'error'); } } document.addEventListener('DOMContentLoaded', loadDashboard);