diff --git a/dashboard.html b/dashboard.html index 345bb9c..c003053 100644 --- a/dashboard.html +++ b/dashboard.html @@ -46,6 +46,12 @@ .today-table td { padding: 8px 6px; border-bottom: 1px solid var(--border); } .today-table tr:last-child td { border: none; } .today-table .num { text-align: right; font-variant-numeric: tabular-nums; } + .day-nav { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; } + .day-nav button { background: var(--card); color: var(--text); border: 1px solid var(--border); border-radius: 6px; width: 32px; height: 32px; cursor: pointer; font-size: 1rem; display: flex; align-items: center; justify-content: center; } + .day-nav button:hover:not(:disabled) { background: var(--border); } + .day-nav button:disabled { opacity: 0.3; cursor: default; } + .day-nav .day-label { font-size: 0.9rem; font-weight: 600; min-width: 120px; text-align: center; } + .day-nav .day-detail { font-size: 0.75rem; color: var(--muted); } .donut-container { display: flex; align-items: center; justify-content: center; } .donut-container canvas { max-height: 220px; } @media (max-width: 640px) { @@ -78,7 +84,18 @@

Chomp Dashboard

Loading data...
@@ -123,7 +140,7 @@

Chomp Dashboard

const rows = csvText.trim().split('\n').slice(1).map(line => { const [date, food, amount, protein, fat, carbs, calories] = line.split(','); - return { date, food: food ? food.trim().toLowerCase() : '', protein: +protein, fat: +fat, carbs: +carbs, calories: +calories }; + return { date, food: food ? food.trim().toLowerCase() : '', amount: amount ? amount.trim() : '', protein: +protein, fat: +fat, carbs: +carbs, calories: +calories }; }).filter(r => r.date && !isNaN(r.calories)); if (rows.length === 0) { @@ -282,32 +299,72 @@

Chomp Dashboard

options: { responsive: true, plugins: { legend: { display: false } }, scales: { y: { grid: { color: 'rgba(255,255,255,0.05)' } } } } }); - // Load today's entries - loadToday(); + // Store rows globally for day navigation + window._allRows = rows; + window._allDates = dates; + window._viewingDate = null; // null = today + showDayEntries(null); } -async function loadToday() { - try { - const resp = await fetch('/api/today'); - const data = await resp.json(); - const entries = data.entries || []; - - if (entries.length === 0) { - document.getElementById('todayTable').innerHTML = 'No entries today'; - } else { - let html = 'FoodAmountProteinFatCarbsCal'; - for (const e of entries) { - html += `${e.food_name}${e.amount}${e.protein.toFixed(0)}g${e.fat.toFixed(0)}g${e.carbs.toFixed(0)}g${e.calories.toFixed(0)}`; - } - const totals = data.totals; - html += `Total${totals.protein.toFixed(0)}g${totals.fat.toFixed(0)}g${totals.carbs.toFixed(0)}g${totals.calories.toFixed(0)}`; - html += ''; - document.getElementById('todayTable').innerHTML = html; +function showDayEntries(date) { + const rows = window._allRows || []; + const dates = window._allDates || []; + + const today = new Date().toISOString().slice(0, 10); + const viewDate = date || today; + window._viewingDate = date; + + // Build list of unique dates with data, plus today if not present + const datesWithData = [...new Set([...dates, today])].sort(); + const currentIdx = datesWithData.indexOf(viewDate); + + // Update nav buttons + document.getElementById('prevDay').disabled = currentIdx <= 0; + document.getElementById('nextDay').disabled = !date || viewDate >= today; + document.getElementById('todayBtn').style.display = date ? '' : 'none'; + + // Update label + const d = new Date(viewDate + 'T12:00:00'); + const dayNames = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']; + const monthNames = ['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']; + const isToday = viewDate === today; + const isYesterday = (() => { const y = new Date(); y.setDate(y.getDate()-1); return viewDate === y.toISOString().slice(0,10); })(); + + let label = isToday ? 'Today' : isYesterday ? 'Yesterday' : `${dayNames[d.getDay()]}, ${monthNames[d.getMonth()]} ${d.getDate()}`; + document.getElementById('dayLabel').textContent = label; + document.getElementById('dayDetail').textContent = isToday ? viewDate : (isYesterday ? viewDate : ''); + + // Filter entries for this date + const entries = rows.filter(r => r.date === viewDate); + + if (entries.length === 0) { + document.getElementById('todayTable').innerHTML = `No entries${isToday ? ' today' : ''}`; + } else { + let html = 'FoodAmountProteinFatCarbsCal'; + for (const e of entries) { + html += `${e.food}${e.amount || ''}${e.protein.toFixed(0)}g${e.fat.toFixed(0)}g${e.carbs.toFixed(0)}g${e.calories.toFixed(0)}`; } - document.getElementById('today-section').style.display = ''; - } catch (e) { - // silently skip if today endpoint fails + const totP = entries.reduce((s,e) => s+e.protein, 0); + const totF = entries.reduce((s,e) => s+e.fat, 0); + const totC = entries.reduce((s,e) => s+e.carbs, 0); + const totCal = entries.reduce((s,e) => s+e.calories, 0); + html += `Total${totP.toFixed(0)}g${totF.toFixed(0)}g${totC.toFixed(0)}g${totCal.toFixed(0)}`; + html += ''; + document.getElementById('todayTable').innerHTML = html; } + document.getElementById('today-section').style.display = ''; + + // Wire up nav + document.getElementById('prevDay').onclick = () => { + if (currentIdx > 0) showDayEntries(datesWithData[currentIdx - 1]); + }; + document.getElementById('nextDay').onclick = () => { + if (currentIdx < datesWithData.length - 1) { + const next = datesWithData[currentIdx + 1]; + showDayEntries(next === today ? null : next); + } + }; + document.getElementById('todayBtn').onclick = () => showDayEntries(null); } async function logout() { @@ -315,6 +372,11 @@

Chomp Dashboard

window.location.href = '/login'; } +document.addEventListener('keydown', (e) => { + if (e.key === 'ArrowLeft') { document.getElementById('prevDay').click(); } + else if (e.key === 'ArrowRight') { document.getElementById('nextDay').click(); } +}); + document.getElementById('dayRange').addEventListener('change', () => { loadDashboard().catch(err => { document.getElementById('summary-cards').innerHTML = `

Error

Failed to load
${err.message}
`;