feat: theme dropdown with system preference and comprehensive bottle-type fixes (#138)

- Replace dark/light toggle with Light/Dark/System dropdown menu
- System theme follows OS prefers-color-scheme setting
- Apply theme dropdown to shared schedule page
- Fix 7 packageType (bottle) bugs across stock calc, share, refills, export/import
- Fix planner bottle-type stock calculation and display
- Fix dailyRate double-counting with per-intake takenBy
- Fix About modal update check stale caching
- Fix intake reminder past-intake seeding and push title
- Fix phantom DB path in drizzle.config.ts
- Fix mobile dose field visibility
- Make medication name clickable in dashboard reminder bar
- Improve planner checkbox UX with inline tooltip
- Add 20+ new tests covering all fixes
This commit is contained in:
Daniel Volz
2026-02-08 20:32:40 +01:00
committed by GitHub
parent b19bcf02c2
commit 8c5deed4c2
29 changed files with 1053 additions and 166 deletions
+16 -4
View File
@@ -34,14 +34,18 @@ function formatOpenBlisterAndLoose(
return `${openBlisterPills} ${t("common.of")} ${pillsPerBlister} ${t("common.pills")}`;
}
// Get total pills for a medication
// Get total pills for a medication (packageType-aware)
function getMedTotal(med: {
packCount: number;
blistersPerPack: number;
pillsPerBlister: number;
looseTablets: number;
stockAdjustment?: number | null;
packageType?: string;
}): number {
if (med.packageType === "bottle") {
return med.looseTablets + (med.stockAdjustment ?? 0);
}
return med.packCount * med.blistersPerPack * med.pillsPerBlister + med.looseTablets + (med.stockAdjustment ?? 0);
}
@@ -276,9 +280,17 @@ export function DashboardPage() {
<div className="reminder-status-row">
<span className="reminder-status-label">{t("dashboard.reminders.lastSent")}:</span>
<span className="reminder-status-value">
{reminderData.lastSent.medName && (
<span className="reminder-med-name">{reminderData.lastSent.medName}</span>
)}
{reminderData.lastSent.medName &&
(() => {
const medication = meds.find((m) => m.name === reminderData.lastSent!.medName);
return medication ? (
<span className="med-link clickable" onClick={() => openMedDetail(medication)}>
{reminderData.lastSent!.medName}
</span>
) : (
<span className="reminder-med-name">{reminderData.lastSent!.medName}</span>
);
})()}
{reminderData.lastSent.takenBy && (
<span className="reminder-taken-by"> ({reminderData.lastSent.takenBy})</span>
)}
+1 -1
View File
@@ -526,7 +526,7 @@ export function MedicationsPage() {
</label>
</>
)}
<label>
<label className="full">
{t("form.pillWeight")} ({form.doseUnit})
<div className="dose-input-group">
<input
+14 -3
View File
@@ -178,6 +178,9 @@ export function PlannerPage() {
onChange={(e) => setIncludeUntilStart(e.target.checked)}
/>
{t("planner.includeUntilStart")}
<span className="info-tooltip small" data-tooltip={t("planner.includeUntilStartTooltip")}>
</span>
</label>
<div className="planner-actions">
<button type="button" className="ghost" onClick={resetRange}>
@@ -210,11 +213,19 @@ export function PlannerPage() {
<strong>{row.plannerUsage}</strong>&nbsp;{t("common.pills")}
</span>
<span data-label={t("planner.table.blisters")}>
{row.blistersNeeded} × {row.blisterSize}
{row.packageType === "bottle"
? `${row.plannerUsage} ${t("common.pills")}`
: `${row.blistersNeeded} × ${row.blisterSize}`}
</span>
<span data-label={t("planner.table.available")}>
{row.fullBlisters} {t("common.blisters")}
{row.loosePills > 0 && ` + ${Math.round(row.loosePills * 10) / 10} ${t("common.pills")}`}
{row.packageType === "bottle" ? (
`${Math.round(row.loosePills * 10) / 10} ${t("common.pills")}`
) : (
<>
{row.fullBlisters} {t("common.blisters")}
{row.loosePills > 0 && ` + ${Math.round(row.loosePills * 10) / 10} ${t("common.pills")}`}
</>
)}
</span>
<span
data-label={t("table.status")}