// Dashboard — today at a glance. Integrated view of meds + intake + refills + spending.
const { TODAY_INTAKE, MED, CABINET, REGIMENS, SPENDING, PURCHASES, ACTIVITY, PRICES, STORES } = window.MESHI;
const DashboardPage = ({ onNav }) => {
const [intake, setIntake] = useState(TODAY_INTAKE);
const taken = intake.filter(i => i.status === 'taken').length;
const total = intake.length;
const pct = Math.round((taken / total) * 100);
const lowMeds = CABINET.map(r => ({ r, s: cabinetStatus(r) })).filter(x => x.s.level !== 'ok').sort((a, b) => a.s.days - b.s.days);
const thisMonth = SPENDING[SPENDING.length - 1].amount;
const lastMonth = SPENDING[SPENDING.length - 2].amount;
const spendDelta = ((thisMonth - lastMonth) / lastMonth) * 100;
const toggleTake = (id) => {
setIntake(intake.map(i => i.id === id ? { ...i, status: i.status === 'taken' ? 'upcoming' : 'taken', takenAt: '21:04' } : i));
};
// Days supply across meds for mini sparkline
const sparkMax = 60;
const supplyData = CABINET.map(r => ({ med: MED[r.medId], days: cabinetStatus(r).days }))
.sort((a, b) => a.days - b.days);
return (
Saturday · April 18
Good evening, aeri.
Evening pills are due in 2h 34m. Everything else is on track.
Today's schedule
{total - taken} upcoming · {taken} completed
{['08:00', '21:00'].map(time => {
const items = intake.filter(i => i.scheduled === time);
if (!items.length) return null;
const allTaken = items.every(i => i.status === 'taken');
return (
{time}
{time === '08:00' ? 'Morning' : 'Evening'}
{items.map(i => {
const m = MED[i.medId];
return (
);
})}
);
})}
Running low
{lowMeds.length} need attention
{lowMeds.slice(0, 4).map(({ r, s }) => {
const m = MED[r.medId];
return (
{m.name}
{r.qty} {r.unit}
);
})}
Spending · 6 months
{fmtJPY(thisMonth)} this month {spendDelta < 0 ? '↓' : '↑'} {Math.abs(Math.round(spendDelta))}%
Cabinet — days of supply
At current usage
{supplyData.map(s => {
const pct = Math.min(100, (s.days / sparkMax) * 100);
const tone = s.days <= 7 ? 'critical' : s.days <= 14 ? 'low' : 'ok';
return (
);
})}
Pending orders
{PURCHASES.filter(p => p.status === 'pending').length} awaiting arrival
{PURCHASES.filter(p => p.status === 'pending').map(p => {
const s = STORES.find(s => s.id === p.store);
const total = p.items.reduce((a, i) => a + i.price, 0);
return (
{s?.name}
{p.items.map(i => MED[i.medId].name).join(', ')}
{fmtJPY(total)}
ordered {p.date}
);
})}
{PURCHASES.filter(p => p.status === 'received').slice(0, 2).map(p => {
const s = STORES.find(s => s.id === p.store);
return (
{s?.name} · {p.items.length} items received
{p.date}
);
})}
Recent activity
Cabinet changes
{ACTIVITY.slice(0, 5).map(a => {
const m = MED[a.medId];
return (
{a.type}
{m.name}
{a.delta > 0 ? '+' : ''}{a.delta}
{a.before}→{a.after}
{a.at.slice(5)}
);
})}
);
};
window.DashboardPage = DashboardPage;