From f34c2c95781db63448cb9e3c0573c20ded843537 Mon Sep 17 00:00:00 2001 From: Daniel Volz Date: Fri, 26 Dec 2025 23:54:15 +0100 Subject: [PATCH] feat(schedule): enhance dose button styles for better user experience and add visual cues for future doses --- frontend/src/App.tsx | 115 +++++++++++++++++++++++----------------- frontend/src/styles.css | 14 ++++- 2 files changed, 78 insertions(+), 51 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index fb01725..5eae974 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -362,9 +362,9 @@ function AppContent() { const coverageByMed = useMemo(() => Object.fromEntries(coverage.all.map((c) => [c.name, c])), [coverage.all]); const groupedSchedule = useMemo(() => { type DoseInfo = { id: string; timeStr: string; when: number; usage: number }; - const days = new Map }>(); + const days = new Map }>(); schedule.events.slice(0, 2000).forEach((event) => { - const day = days.get(event.dateStr) ?? { dateStr: event.dateStr, meds: new Map() }; + const day = days.get(event.dateStr) ?? { dateStr: event.dateStr, date: new Date(event.when), meds: new Map() }; const medEntry = day.meds.get(event.medName) ?? { medName: event.medName, total: 0, doses: [], lastWhen: event.when }; medEntry.total += event.usage; medEntry.doses.push({ id: event.id, timeStr: event.timeStr, when: event.when, usage: event.usage }); @@ -372,7 +372,7 @@ function AppContent() { day.meds.set(event.medName, medEntry); days.set(event.dateStr, day); }); - return Array.from(days.values()).map((d) => ({ dateStr: d.dateStr, meds: Array.from(d.meds.values()) })).slice(0, scheduleDays); + return Array.from(days.values()).map((d) => ({ dateStr: d.dateStr, date: d.date, meds: Array.from(d.meds.values()) })).slice(0, scheduleDays); }, [schedule.events, scheduleDays]); useEffect(() => { @@ -1008,8 +1008,15 @@ function AppContent() { const allDayTaken = allDoseIds.length > 0 && allDoseIds.every((id) => takenDoses.has(id)); const takenCount = allDoseIds.filter((id) => takenDoses.has(id)).length; - // Determine if day should be collapsed - const isAutoCollapsed = allDayTaken; + // Check if this is today, past, or future + const today = new Date(); + today.setHours(0, 0, 0, 0); + const dayDate = new Date(day.date); + dayDate.setHours(0, 0, 0, 0); + const isToday = dayDate.getTime() === today.getTime(); + + // Determine if day should be collapsed: only today is expanded by default + const isAutoCollapsed = allDayTaken || !isToday; const isManuallyExpanded = manuallyExpandedDays.has(day.dateStr); const isManuallyCollapsed = manuallyCollapsedDays.has(day.dateStr); const isCollapsed = isAutoCollapsed ? !isManuallyExpanded : isManuallyCollapsed; @@ -1032,49 +1039,49 @@ function AppContent() { {!isCollapsed && day.meds.map((item) => { - const medCoverage = coverageByMed[item.medName]; - const med = meds.find(m => m.name === item.medName); - const depletionTime = depletionByMed[item.medName]; - // Check if this dose is scheduled after medication runs out - const willBeOutOfStock = typeof depletionTime === "number" && item.lastWhen > depletionTime; - const status = willBeOutOfStock - ? { className: "danger", label: "status.outOfStock" } - : medCoverage ? getStockStatus(medCoverage.daysLeft, medCoverage.medsLeft, settings) : null; - const allTaken = item.doses.every((d) => takenDoses.has(d.id)); - const takenCount = item.doses.filter((d) => takenDoses.has(d.id)).length; - return ( -
-
-
{item.medName}{med?.intakeRemindersEnabled && 🔔}
-
- {item.total} {t('common.pills')} {t('common.total')} - {status && - {t(status.label)} - } + const medCoverage = coverageByMed[item.medName]; + const med = meds.find(m => m.name === item.medName); + const depletionTime = depletionByMed[item.medName]; + // Check if this dose is scheduled after medication runs out + const willBeOutOfStock = typeof depletionTime === "number" && item.lastWhen > depletionTime; + const status = willBeOutOfStock + ? { className: "danger", label: "status.outOfStock" } + : medCoverage ? getStockStatus(medCoverage.daysLeft, medCoverage.medsLeft, settings) : null; + const allTaken = item.doses.every((d) => takenDoses.has(d.id)); + return ( +
+
+
{item.medName}{med?.intakeRemindersEnabled && 🔔}
+
+ {item.total} {t('common.pills')} {t('common.total')} + {status && + {t(status.label)} + } +
+
+
+ {item.doses.map((dose) => { + const isTaken = takenDoses.has(dose.id); + const isOverdue = dose.when < Date.now(); + const isFutureDose = dose.when > Date.now(); + return ( +
+ {dose.timeStr} + {dose.usage} {dose.usage !== 1 ? t('common.pills') : t('common.pill')}{med?.pillWeightMg && ` (${dose.usage * med.pillWeightMg} mg)`}{med?.takenBy && {t('dose.takenBy')} setSelectedUser(med.takenBy!)}>{med.takenBy}} + {isTaken ? ( + + ) : ( + + )} +
+ ); + })}
-
- {item.doses.map((dose) => { - const isTaken = takenDoses.has(dose.id); - const isOverdue = dose.when < Date.now(); - return ( -
- {dose.timeStr} - {dose.usage} {dose.usage !== 1 ? t('common.pills') : t('common.pill')}{med?.pillWeightMg && ` (${dose.usage * med.pillWeightMg} mg)`}{med?.takenBy && {t('dose.takenBy')} setSelectedUser(med.takenBy!)}>{med.takenBy}} - {isTaken ? ( - - ) : ( - - )} -
- ); - })} -
-
- ); - })} -
- ); + ); + })} +
+ ); })} @@ -2681,8 +2688,15 @@ function SharedSchedule() { const allDayTaken = allDoseIds.length > 0 && allDoseIds.every((id) => takenDoses.has(id)); const takenCount = allDoseIds.filter((id) => takenDoses.has(id)).length; - // Determine if day should be collapsed - const isAutoCollapsed = allDayTaken; + // Check if this is today, past, or future + const today = new Date(); + today.setHours(0, 0, 0, 0); + const dayDate = new Date(day.date); + dayDate.setHours(0, 0, 0, 0); + const isToday = dayDate.getTime() === today.getTime(); + + // Determine if day should be collapsed: only today is expanded by default + const isAutoCollapsed = allDayTaken || !isToday; const isManuallyExpanded = manuallyExpandedDays.has(day.dateStr); const isManuallyCollapsed = manuallyCollapsedDays.has(day.dateStr); const isCollapsed = isAutoCollapsed ? !isManuallyExpanded : isManuallyCollapsed; @@ -2728,8 +2742,9 @@ function SharedSchedule() { {item.doses.map((dose) => { const isTaken = takenDoses.has(dose.id); const isOverdue = dose.when < Date.now() && !isTaken; + const isFutureDose = dose.when > Date.now(); return ( -
+
{dose.timeStr} {dose.usage} {dose.usage !== 1 ? t('common.pills') : t('common.pill')} @@ -2738,7 +2753,7 @@ function SharedSchedule() { {isTaken ? ( ) : ( - + )}
); diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 96f09cd..9f4e5ca 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -543,12 +543,24 @@ textarea { color: var(--success); } -.dose-btn.take:hover { +.dose-btn.take:hover:not(:disabled) { background: var(--success); color: white; transform: scale(1.1); } +.dose-btn.take:disabled { + opacity: 0.3; + cursor: not-allowed; + background: var(--bg-tertiary); + border-color: var(--border-primary); + color: var(--text-secondary); +} + +.dose-item.future { + opacity: 0.5; +} + .dose-btn.undo { background: rgba(255, 255, 255, 0.05); border: 1px solid var(--border-secondary);