diff --git a/frontend/src/components/SharedSchedule.tsx b/frontend/src/components/SharedSchedule.tsx
index 74de4ab..1f769e1 100644
--- a/frontend/src/components/SharedSchedule.tsx
+++ b/frontend/src/components/SharedSchedule.tsx
@@ -729,54 +729,38 @@ export function SharedSchedule() {
-
0 ? "has-missed" : ""}`}
- onClick={() => setShowPastDays(!showPastDays)}
- >
- {showPastDays ? "▼" : "▶"}
-
- {showPastDays ? t("dashboard.schedules.hidePastDays") : t("dashboard.schedules.showPastDays")}
-
-
- ({t("dashboard.schedules.pastDaysCount", { count: pastDays.length })})
-
- {missedCount > 0 ? (
-
- ⚠️ {missedCount}
-
- ) : totalPastDoses.length > 0 ? (
-
- ✓
-
- ) : null}
-
-
- );
- })()}
- {/* Past days (when expanded) — identical to DashboardPage */}
+ {/* Past days (when expanded) — rendered above toggle */}
{showPastDays &&
pastDays.map((day) => {
+ // Get ALL dose IDs for this day (for total count and yellow styling)
const allDoseIds = day.meds.flatMap((item) => item.doses.map((d) => d.id));
- const allDayTaken =
- allDoseIds.length > 0 && allDoseIds.every((id) => takenDoses.has(id) || dismissedDoses.has(id));
- const takenCount = allDoseIds.filter((id) => takenDoses.has(id) || dismissedDoses.has(id)).length;
+
+ // Really taken = all doses marked as taken by human (for green "All taken")
+ const allReallyTaken = allDoseIds.length > 0 && allDoseIds.every((id) => takenDoses.has(id));
+ const takenCount = allDoseIds.filter((id) => takenDoses.has(id)).length;
+
+ // Count missed doses that are NOT dismissed (for warning icon)
+ const missedNotDismissedCount = day.meds.reduce((count, item) => {
+ const med = data.medications.find((m) => m.name === item.medName);
+ const dismissedUntilDate = med?.dismissedUntil ?? undefined;
+ return (
+ count +
+ item.doses.reduce((doseCount, d) => {
+ if (isDoseDismissed(d.id, dismissedUntilDate)) return doseCount;
+ if (takenDoses.has(d.id) || dismissedDoses.has(d.id)) return doseCount;
+ return doseCount + 1;
+ }, 0)
+ );
+ }, 0);
+ const hasRealMissed = missedNotDismissedCount > 0;
+
const isManuallyExpanded = manuallyExpandedDays.has(day.dateStr);
const isCollapsed = !isManuallyExpanded;
return (
0 ? "past-missed" : ""}`}
+ className={`day-block past ${isCollapsed ? "collapsed" : ""} ${allReallyTaken ? "all-taken" : allDoseIds.length > 0 ? "past-missed" : ""}`}
>
{isCollapsed ? "▶" : "▼"}
{day.dateStr}
- {allDayTaken ? (
+ {allReallyTaken ? (
✓ {t("dashboard.schedules.allTaken")}
) : (
<>
-
- ⚠️
-
+ {hasRealMissed && (
+
+ ⚠️
+
+ )}
{takenCount}/{allDoseIds.length}
@@ -888,6 +874,50 @@ export function SharedSchedule() {
);
})}
+ {/* Past days toggle */}
+ {pastDays.length > 0 &&
+ (() => {
+ const missedCount = missedPastDoseIds.length;
+ const totalPastDoses = pastDays.flatMap((d) => d.meds.flatMap((m) => m.doses.map((dose) => dose.id)));
+ return (
+
+
0 ? "has-missed" : ""}`}
+ onClick={() => {
+ const wasCollapsed = !showPastDays;
+ setShowPastDays(!showPastDays);
+ if (wasCollapsed) {
+ setTimeout(() => {
+ document
+ .querySelector(".day-block.today")
+ ?.scrollIntoView({ behavior: "smooth", block: "center" });
+ }, 50);
+ }
+ }}
+ >
+ {showPastDays ? "▼" : "▶"}
+
+ {showPastDays ? t("dashboard.schedules.hidePastDays") : t("dashboard.schedules.showPastDays")}
+
+
+ ({t("dashboard.schedules.pastDaysCount", { count: pastDays.length })})
+
+ {missedCount > 0 ? (
+
+ ⚠️ {missedCount}
+
+ ) : totalPastDoses.length > 0 ? (
+
+ ✓
+
+ ) : null}
+
+
+ );
+ })()}
{/* Today (always visible) */}
{todayDay &&
(() => {
diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx
index d72e7a1..9c88861 100644
--- a/frontend/src/pages/DashboardPage.tsx
+++ b/frontend/src/pages/DashboardPage.tsx
@@ -5,7 +5,7 @@ import { useAuth } from "../components/Auth";
import { useAppContext } from "../context";
import type { Coverage } from "../types";
import { formatNumber, getExpiryClass, getSystemLocale } from "../utils/formatters";
-import { expandDoseIds, getStockStatus } from "../utils/schedule";
+import { expandDoseIds, getStockStatus, isDoseDismissed } from "../utils/schedule";
// Helper for user-specific localStorage keys
function userStorageKey(userId: number | undefined, key: string): string {
@@ -604,65 +604,37 @@ export function DashboardPage() {
- {/* Past days toggle */}
- {pastDays.length > 0 &&
- (() => {
- const missedCount = missedPastDoseIds.length;
- const totalPastDoses = pastDays.flatMap((d) => d.meds.flatMap((m) => expandDoseIds(m.doses)));
- return (
-
-
0 ? "has-missed" : ""}`}
- onClick={() => setShowPastDays(!showPastDays)}
- >
- {showPastDays ? "▼" : "▶"}
-
- {showPastDays ? t("dashboard.schedules.hidePastDays") : t("dashboard.schedules.showPastDays")}
-
-
- ({t("dashboard.schedules.pastDaysCount", { count: pastDays.length })})
-
- {missedCount > 0 ? (
-
- ⚠️ {missedCount}
-
- ) : totalPastDoses.length > 0 ? (
-
- ✓
-
- ) : null}
-
- {missedCount > 0 && (
-
- )}
-
- );
- })()}
- {/* Past days (when expanded) */}
+ {/* Past days (when expanded) — rendered above toggle */}
{showPastDays &&
pastDays.map((day) => {
+ // Get ALL dose IDs for this day (for total count and yellow styling)
const allDoseIds = day.meds.flatMap((item) =>
item.doses.flatMap((d) => {
const takenByArray = Array.isArray(d.takenBy) ? d.takenBy : [];
return takenByArray.length > 0 ? takenByArray.map((p) => `${d.id}-${p}`) : [d.id];
})
);
- const allDayTaken =
- allDoseIds.length > 0 && allDoseIds.every((id) => takenDoses.has(id) || dismissedDoses.has(id));
- const takenCount = allDoseIds.filter((id) => takenDoses.has(id) || dismissedDoses.has(id)).length;
+
+ // Really taken = all doses marked as taken by human (for green "All taken")
+ const allReallyTaken = allDoseIds.length > 0 && allDoseIds.every((id) => takenDoses.has(id));
+ const takenCount = allDoseIds.filter((id) => takenDoses.has(id)).length;
+
+ // Count missed doses that are NOT dismissed (for warning icon)
+ const missedNotDismissedCount = day.meds.reduce((count, item) => {
+ const med = meds.find((m) => m.name === item.medName);
+ const dismissedUntilDate = med?.dismissedUntil ?? undefined;
+ return (
+ count +
+ item.doses.reduce((doseCount, d) => {
+ if (isDoseDismissed(d.id, dismissedUntilDate)) return doseCount;
+ const takenByArray = Array.isArray(d.takenBy) ? d.takenBy : [];
+ const ids = takenByArray.length > 0 ? takenByArray.map((p) => `${d.id}-${p}`) : [d.id];
+ return doseCount + ids.filter((id) => !takenDoses.has(id) && !dismissedDoses.has(id)).length;
+ }, 0)
+ );
+ }, 0);
+ const hasRealMissed = missedNotDismissedCount > 0;
+
const isAutoCollapsed = true; // Past days are always auto-collapsed
const isManuallyExpanded = manuallyExpandedDays.has(day.dateStr);
const isCollapsed = !isManuallyExpanded;
@@ -671,7 +643,7 @@ export function DashboardPage() {
return (
0 ? "past-missed" : ""}`}
+ className={`day-block past ${isCollapsed ? "collapsed" : ""} ${allReallyTaken ? "all-taken" : allDoseIds.length > 0 ? "past-missed" : ""}`}
>
{isCollapsed ? "▶" : "▼"}
{day.dateStr}
- {allDayTaken ? (
+ {allReallyTaken ? (
✓ {t("dashboard.schedules.allTaken")}
) : (
<>
-
- ⚠️
-
+ {hasRealMissed && (
+
+ ⚠️
+
+ )}
{takenCount}/{allDoseIds.length}
@@ -793,6 +767,63 @@ export function DashboardPage() {
);
})}
+ {/* Past days toggle */}
+ {pastDays.length > 0 &&
+ (() => {
+ const missedCount = missedPastDoseIds.length;
+ const totalPastDoses = pastDays.flatMap((d) => d.meds.flatMap((m) => expandDoseIds(m.doses)));
+ return (
+
+
0 ? "has-missed" : ""}`}
+ onClick={() => {
+ const wasCollapsed = !showPastDays;
+ setShowPastDays(!showPastDays);
+ if (wasCollapsed) {
+ setTimeout(() => {
+ document
+ .querySelector(".day-block.today")
+ ?.scrollIntoView({ behavior: "smooth", block: "center" });
+ }, 50);
+ }
+ }}
+ >
+ {showPastDays ? "▼" : "▶"}
+
+ {showPastDays ? t("dashboard.schedules.hidePastDays") : t("dashboard.schedules.showPastDays")}
+
+
+ ({t("dashboard.schedules.pastDaysCount", { count: pastDays.length })})
+
+ {missedCount > 0 ? (
+
+ ⚠️ {missedCount}
+
+ ) : totalPastDoses.length > 0 ? (
+
+ ✓
+
+ ) : null}
+
+ {missedCount > 0 && (
+
+ )}
+
+ );
+ })()}
{/* Today - always visible */}
{todayDay &&
(() => {
diff --git a/frontend/src/pages/SchedulePage.tsx b/frontend/src/pages/SchedulePage.tsx
index 37f194a..ae3b574 100644
--- a/frontend/src/pages/SchedulePage.tsx
+++ b/frontend/src/pages/SchedulePage.tsx
@@ -3,7 +3,7 @@ import { MedicationAvatar } from "../components";
import { useAuth } from "../components/Auth";
import { useAppContext } from "../context";
import type { Coverage } from "../types";
-import { expandDoseIds } from "../utils/schedule";
+import { expandDoseIds, isDoseDismissed } from "../utils/schedule";
// Helper for user-specific localStorage keys
function userStorageKey(userId: number | undefined, key: string): string {
@@ -65,6 +65,7 @@ export function SchedulePage() {
pastDays,
futureDays,
takenDoses,
+ dismissedDoses,
markDoseTaken,
undoDoseTaken,
coverageByMed,
@@ -95,40 +96,37 @@ export function SchedulePage() {
- {/* Past days toggle */}
- {pastDays.length > 0 &&
- (() => {
- // Use context's missedPastDoseIds which handles dismissed doses and previous schedule detection
- const missedCount = missedPastDoseIds.length;
- return (
-
0 ? "has-missed" : ""}`}
- onClick={() => setShowPastDays(!showPastDays)}
- >
- {showPastDays ? "▼" : "▶"}
-
- {showPastDays ? t("dashboard.schedules.hidePastDays") : t("dashboard.schedules.showPastDays")}
-
-
- ({t("dashboard.schedules.pastDaysCount", { count: pastDays.length })})
-
- {missedCount > 0 && (
-
- ⚠️ {missedCount}
-
- )}
-
- );
- })()}
- {/* Past days (when expanded) */}
+ {/* Past days (when expanded) — rendered above toggle */}
{showPastDays &&
pastDays.map((day) => {
- const allDoseIds = day.meds.flatMap((item) => expandDoseIds(item.doses));
- const allDayTaken = allDoseIds.length > 0 && allDoseIds.every((id) => takenDoses.has(id));
+ // Get ALL dose IDs for this day (for total count and yellow styling)
+ const allDoseIds = day.meds.flatMap((item) =>
+ item.doses.flatMap((d) => {
+ const takenByArray = Array.isArray(d.takenBy) ? d.takenBy : [];
+ return takenByArray.length > 0 ? takenByArray.map((p) => `${d.id}-${p}`) : [d.id];
+ })
+ );
+
+ // Really taken = all doses marked as taken by human (for green "All taken")
+ const allReallyTaken = allDoseIds.length > 0 && allDoseIds.every((id) => takenDoses.has(id));
const takenCount = allDoseIds.filter((id) => takenDoses.has(id)).length;
+
+ // Count missed doses that are NOT dismissed (for warning icon)
+ const missedNotDismissedCount = day.meds.reduce((count, item) => {
+ const med = meds.find((m) => m.name === item.medName);
+ const dismissedUntilDate = med?.dismissedUntil ?? undefined;
+ return (
+ count +
+ item.doses.reduce((doseCount, d) => {
+ if (isDoseDismissed(d.id, dismissedUntilDate)) return doseCount;
+ const takenByArray = Array.isArray(d.takenBy) ? d.takenBy : [];
+ const ids = takenByArray.length > 0 ? takenByArray.map((p) => `${d.id}-${p}`) : [d.id];
+ return doseCount + ids.filter((id) => !takenDoses.has(id) && !dismissedDoses.has(id)).length;
+ }, 0)
+ );
+ }, 0);
+ const hasRealMissed = missedNotDismissedCount > 0;
+
const isManuallyExpanded = manuallyExpandedDays.has(day.dateStr);
const isCollapsed = !isManuallyExpanded;
const worstStatus = getDayStockStatus(day.meds, coverageByMed, settings);
@@ -136,7 +134,7 @@ export function SchedulePage() {
return (
0 ? "past-missed" : ""}`}
+ className={`day-block past ${isCollapsed ? "collapsed" : ""} ${allReallyTaken ? "all-taken" : allDoseIds.length > 0 ? "past-missed" : ""}`}
>
{isCollapsed ? "▶" : "▼"}
{day.dateStr}
- {allDayTaken ? (
+ {allReallyTaken ? (
✓ {t("dashboard.schedules.allTaken")}
) : (
<>
-
- ⚠️
-
+ {hasRealMissed && (
+
+ ⚠️
+
+ )}
{takenCount}/{allDoseIds.length}
@@ -246,6 +246,43 @@ export function SchedulePage() {
);
})}
+ {/* Past days toggle */}
+ {pastDays.length > 0 &&
+ (() => {
+ const missedCount = missedPastDoseIds.length;
+ return (
+
0 ? "has-missed" : ""}`}
+ onClick={() => {
+ const wasCollapsed = !showPastDays;
+ setShowPastDays(!showPastDays);
+ if (wasCollapsed) {
+ setTimeout(() => {
+ document
+ .querySelector(".day-block.today")
+ ?.scrollIntoView({ behavior: "smooth", block: "center" });
+ }, 50);
+ }
+ }}
+ >
+ {showPastDays ? "▼" : "▶"}
+
+ {showPastDays ? t("dashboard.schedules.hidePastDays") : t("dashboard.schedules.showPastDays")}
+
+
+ ({t("dashboard.schedules.pastDaysCount", { count: pastDays.length })})
+
+ {missedCount > 0 && (
+
+ ⚠️ {missedCount}
+
+ )}
+
+ );
+ })()}
{/* Current and future days */}
{futureDays.map((day) => {
const today = new Date();
diff --git a/frontend/src/styles.css b/frontend/src/styles.css
index 2b57e90..79f9d01 100644
--- a/frontend/src/styles.css
+++ b/frontend/src/styles.css
@@ -1500,7 +1500,7 @@ textarea.auto-resize {
user-select: none;
}
.day-divider.clickable:hover {
- color: var(--accent);
+ opacity: 0.8;
}
/* Keep warning/danger colors on hover */
.day-block.stock-warning .day-divider.clickable:hover {
diff --git a/frontend/src/test/pages/SchedulePage.test.tsx b/frontend/src/test/pages/SchedulePage.test.tsx
index dcdecd5..c480e82 100644
--- a/frontend/src/test/pages/SchedulePage.test.tsx
+++ b/frontend/src/test/pages/SchedulePage.test.tsx
@@ -94,6 +94,7 @@ const createMockContext = (overrides = {}) => ({
pastDays: [],
futureDays: [],
takenDoses: new Set(),
+ dismissedDoses: new Set(),
markDoseTaken: vi.fn(),
undoDoseTaken: vi.fn(),
coverageByMed: {},