// Medium-fi placeholder pages for the remaining medicine + purchasing sections. const { MED: M2, MEDICINES: MEDS2, CABINET: C2, REGIMENS: REG2, STORES: ST2, PRICES: PR2, PURCHASES: PU2, ACTIVITY: AC2 } = window.MESHI; // ---- Library ---- const LibraryPage = () => { const [q, setQ] = useState(''); const rows = MEDS2.filter(m => m.name.toLowerCase().includes(q.toLowerCase())); return (
setQ(e.target.value)} style={{width:'100%'}}/>
{rows.map((m, i) => (
{m.name}
{m.strength} · {m.form}
{m.category}
))}
); }; // ---- Regimens ---- const RegimensPage = () => (
{REG2.map(r => (
Aeri's {r.name} Active
{r.items.length} medication{r.items.length>1?'s':''} · {r.time} · {r.freq}
{r.items.map(it => { const m = M2[it.medId]; return ( {m.name} {it.qty} {it.unit} ); })}
))}
); // ---- Organizer ---- const OrganizerPage = () => { const [regimenId, setRegimenId] = useState(REG2[1].id); const [days, setDays] = useState(7); const regimen = REG2.find(r => r.id === regimenId); // Compute inventory impact: for each item in regimen, how many units will be consumed? const impact = regimen.items.map(it => { const m = M2[it.medId]; const cabEntry = C2.find(c => c.medId === it.medId); const onHand = cabEntry ? cabEntry.qty : 0; const needed = it.qty * days; const after = onHand - needed; const perDay = cabEntry ? (cabEntry.perDose || 1) / (cabEntry.daysPerUnit || 1) : 1; const daysAfter = perDay > 0 ? Math.floor(Math.max(0, after) / perDay) : 0; const status = after < 0 ? 'short' : after < needed * 0.5 ? 'low' : 'ok'; return { m, it, onHand, needed, after, daysAfter, status, cabEntry }; }); const anyShort = impact.some(i => i.status === 'short'); const anyLow = impact.some(i => i.status === 'low'); return (
Fill from regimen
{regimen.time} · {regimen.items.length} medicines · {regimen.freq}
Days to fill
{days}
{[3, 7, 14, 30].map(d => ( ))}
This fill will dispense
{impact.reduce((a,b) => a + b.needed, 0)} units across {impact.length} medicines
{anyShort ? (
Not enough stock. {impact.filter(i => i.status === 'short').length} medicine{impact.filter(i => i.status === 'short').length > 1 ? 's are' : ' is'} short for this fill.
) : anyLow ? (
Low after fill. Some meds will drop below 50% headroom.
) : (
Ready to dispense. Cabinet has enough stock with comfortable headroom.
)}
{/* Inventory impact table */}
Inventory impact
What this fill will take from the cabinet
Medicine
On hand
Needed
After fill
Days left
{impact.map(row => { const m = row.m; const pct = row.onHand > 0 ? Math.max(0, Math.min(100, (row.after / row.onHand) * 100)) : 0; return (
{m.name}
{row.it.qty} × {days} days = {row.needed} {m.form.toLowerCase()}
{row.onHand}
−{row.needed}
{row.after < 0 ? row.after : row.after}
{row.status === 'short' ? Short : {row.daysAfter}d}
); })}
); }; // ---- Activity ---- const ActivityPage = () => { const { SPENDING } = window.MESHI; return (
Spending summary
Last 6 months
{SPENDING.map((s, i) => { const max = Math.max(...SPENDING.map(x=>x.amount)); const h = (s.amount / max) * 140; const x = 20 + i * 56; return ( {s.month.slice(5)} ); })}
Total spent
{fmtJPY(SPENDING.reduce((a,b)=>a+b.amount,0))}
Monthly average
{fmtJPY(Math.round(SPENDING.reduce((a,b)=>a+b.amount,0)/SPENDING.length))}
Top medicine
Semaglutide {fmtJPY(56062)}
Cabinet activity
{AC2.length} events
{AC2.map(a => { const m = M2[a.medId]; return (
{a.type} {m.name} {a.delta > 0?'+':''}{a.delta} {a.before}→{a.after} {a.at}
); })}
); }; // ---- Stores ---- const StoresPage = () => (
{ST2.map(s => (
{s.name}
{s.url}
{s.tags.map(t => {t})}
{PR2.filter(p=>p.store===s.id).length} prices tracked {PU2.filter(p=>p.store===s.id).length} orders
))}
); // ---- Prices ---- const PricesPage = () => (
Store comparison
Best price highlighted
Store
Price
Per unit
Date
{PR2.filter(p=>p.medId==='semaglutide').map(p => { const s = ST2.find(s=>s.id===p.store); const isBest = p.price/p.pkg === Math.min(...PR2.filter(x=>x.medId===p.medId).map(x=>x.price/x.pkg)); return (
{s?.name} {isBest && Cheapest}
{fmtJPY(p.price)}
{fmtJPY(Math.round(p.price/p.pkg))}
{p.date}
); })}
All price records
{PR2.length} entries
Medicine
Store
Qty
Price
Per unit
Date
{PR2.map(p => { const s = ST2.find(s=>s.id===p.store); const m = M2[p.medId]; return (
{m.name}
{s?.name}
{p.pkg} {p.unit}
{fmtJPY(p.price)}
{fmtJPY(Math.round(p.price/p.pkg))}
{p.date}
); })}
); // ---- Refills ---- const RefillsPage = () => { const low = C2.map(r => ({r, s: cabinetStatus(r)})).filter(x => x.s.level !== 'ok'); return (
Refill alerts
Threshold: 14 days
{low.map(({r, s}) => { const m = M2[r.medId]; const bestPrice = PR2.filter(p=>p.medId===r.medId).sort((a,b)=> a.price/a.pkg - b.price/b.pkg)[0]; const bestStore = bestPrice && ST2.find(st=>st.id===bestPrice.store); return (
{m.name} {m.strength}
{s.days} days left · {r.qty} {r.unit} in cabinet
Suggested
{bestPrice?.pkg || 30} units
Best price
{bestStore?.name} · {bestPrice ? fmtJPY(bestPrice.price) : '—'}
); })}
Refill lists
Create from alerts or start blank
No refill lists yet. Create one above or generate from alerts.
); }; // ---- Purchases ---- const PurchasesPage = () => (
{['pending', 'received'].map(status => { const items = PU2.filter(p => p.status === status); if (!items.length) return null; return (
{status === 'pending' ? 'Pending arrival' : 'Received'}
{items.map(p => { const s = ST2.find(s => s.id === p.store); const total = p.items.reduce((a,i) => a + i.price, 0); return (
{s?.name}
{p.date}
{status}
{p.items.map(i => { const m = M2[i.medId]; return (
{m.name} {i.qty} {i.unit} · {fmtJPY(i.price)}
); })}
{fmtJPY(total)}
{status === 'pending' &&
}
); })}
); })}
); // ---- Settings ---- const SettingsPage = () => (
Household
NameRed Panda Den
Invite code2496F-7CF
Members1
Account

Account settings are managed through Keycloak.

); Object.assign(window, { LibraryPage, RegimensPage, OrganizerPage, ActivityPage, StoresPage, PricesPage, RefillsPage, PurchasesPage, SettingsPage });