// 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.
{taken}/{total} DOSES TAKEN
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}
{s.days}d
left
); })}
Spending · 6 months
{fmtJPY(thisMonth)} this month {spendDelta < 0 ? '↓' : '↑'} {Math.abs(Math.round(spendDelta))}%
{SPENDING.map((m, i) => { const max = Math.max(...SPENDING.map(s => s.amount)); const h = (m.amount / max) * 100; const x = 20 + i * 56; const isLast = i === SPENDING.length - 1; return ( {m.month.slice(5)} {(m.amount / 1000).toFixed(0)}k ); })}
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 (
{s.med.name}
{s.days}d
); })}
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;