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
-
+
Macro Ratio (avg by calories)
@@ -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 = '| Food | Amount | Protein | Fat | Carbs | Cal |
';
- 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 = '| Food | Amount | Protein | Fat | Carbs | Cal |
';
+ 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}
`;