/* biome-ignore-all lint/style/noNestedTernary: schedule timeline branches are intentionally explicit */ import { Bell } from "lucide-react"; import { useTranslation } from "react-i18next"; import { MedicationAvatar } from "../components"; import { useAuth } from "../components/Auth"; import { useAppContext } from "../context"; import type { Coverage } from "../types"; import { getMedDisplayName } from "../types"; import { formatNumber } from "../utils/formatters"; import { expandDoseIds, isDoseDismissed } from "../utils/schedule"; // Helper for user-specific localStorage keys function userStorageKey(userId: number | undefined, key: string): string { return userId ? `user_${userId}_${key}` : key; } // Helper function to get stock status based on thresholds function getStockStatus( daysLeft: number | null, medsLeft: number, settings: { lowStockDays: number; normalStockDays: number; highStockDays: number; reminderDaysBefore: number }, packageType?: string ) { if (packageType === "tube") return { className: "success", label: "status.noSchedule" }; // Out of stock or completely depleted = danger (red) if (medsLeft <= 0 || daysLeft === 0) return { className: "danger", label: "status.outOfStock" }; // No schedule, but has stock = normal if (daysLeft === null) return { className: "success", label: "status.noSchedule" }; if (packageType === "liquid_container") { const lowDays = Math.max(1, Math.floor(settings.reminderDaysBefore)); const criticalDays = Math.max(1, Math.ceil(lowDays / 2)); if (daysLeft <= criticalDays) return { className: "danger", label: "status.criticalStock" }; if (daysLeft <= lowDays) return { className: "warning", label: "status.lowStock" }; return { className: "success", label: "status.normal" }; } // Critical: at or below reminder threshold = danger (red) if (daysLeft <= settings.reminderDaysBefore) return { className: "danger", label: "status.criticalStock" }; // Low: below low stock threshold = warning (yellow) if (daysLeft < settings.lowStockDays) return { className: "warning", label: "status.lowStock" }; // High stock if (daysLeft >= settings.highStockDays) return { className: "high", label: "status.highStock" }; // Normal stock return { className: "success", label: "status.normal" }; } // Helper function to get worst stock status for a day function getDayStockStatus( dayMeds: Array<{ medName: string }>, coverageByMed: Record, settings: { lowStockDays: number; normalStockDays: number; highStockDays: number; reminderDaysBefore: number }, meds: Array<{ name: string; genericName?: string | null; packageType?: string }> ): string { let worstLevel = 3; // 3=success, 2=warning, 1=danger for (const item of dayMeds) { const cov = coverageByMed[item.medName]; if (!cov) continue; const med = meds.find((m) => getMedDisplayName(m) === item.medName); const status = getStockStatus(cov.daysLeft, cov.medsLeft, settings, med?.packageType); if (status.className === "danger") worstLevel = Math.min(worstLevel, 1); else if (status.className === "warning") worstLevel = Math.min(worstLevel, 2); } return worstLevel === 1 ? "danger" : worstLevel === 2 ? "warning" : "success"; } // Helper to get dose ID (with or without person) function getDoseId(baseId: string, person: string | null): string { return person ? `${baseId}-${person}` : baseId; } export function SchedulePage() { const { t } = useTranslation(); const { user } = useAuth(); const { meds, settings, scheduleDays, setScheduleDays, showPastDays, setShowPastDays, pastDays, futureDays, takenDoses, isDoseTakenAutomatically, dismissedDoses, markDoseTaken, undoDoseTaken, coverageByMed, depletionByMed, manuallyExpandedDays, toggleDayCollapse, openUserFilter, missedPastDoseIds, } = useAppContext(); const shouldHideNoScheduleStatusForTube = ( med: (typeof meds)[number] | undefined, status: { className: string; label: string } | null ) => med?.packageType === "tube" && status?.label === "status.noSchedule"; const getTubeUnitLabel = (med: (typeof meds)[number] | undefined, value: number) => med?.packageType === "liquid_container" || med?.medicationForm === "liquid" ? t("form.packageAmountUnitMl") : t("form.blisters.applications", { count: Math.abs(value) }); const convertLiquidUsageToMl = (usage: number, unit: "ml" | "tsp" | "tbsp" | null | undefined): number => { if (unit === "tsp") return usage * 5; if (unit === "tbsp") return usage * 15; return usage; }; const getLiquidCountUnitLabel = (unit: "ml" | "tsp" | "tbsp" | null | undefined, usage: number): string => { if (unit === "tsp") return t("form.blisters.teaspoons", { count: Math.abs(usage) }); if (unit === "tbsp") return t("form.blisters.tablespoons", { count: Math.abs(usage) }); return t("form.packageAmountUnitMl"); }; const formatLiquidUsageLabel = (usage: number, unit: "ml" | "tsp" | "tbsp" | null | undefined): string => { const normalizedUsage = Number(usage); if (!Number.isFinite(normalizedUsage) || normalizedUsage <= 0) { return `0 ${t("form.packageAmountUnitMl")}`; } if (unit === "ml" || unit == null) { return `${formatNumber(normalizedUsage)} ${t("form.packageAmountUnitMl")}`; } const mlTotal = convertLiquidUsageToMl(normalizedUsage, unit); return `${formatNumber(normalizedUsage)} ${getLiquidCountUnitLabel(unit, normalizedUsage)} ${formatNumber(mlTotal)} ${t("form.packageAmountUnitMl")}`; }; const formatDoseUsageLabel = ( med: (typeof meds)[number] | undefined, usage: number, intakeUnit?: "ml" | "tsp" | "tbsp" | null ) => { if (med?.packageType === "liquid_container") { return formatLiquidUsageLabel(usage, intakeUnit); } if (med?.packageType === "tube") { return `${usage} ${getTubeUnitLabel(med, usage)}`; } return `${usage} ${usage !== 1 ? t("common.pills") : t("common.pill")}`; }; const formatTotalUsageLabel = ( med: (typeof meds)[number] | undefined, total: number, doses?: Array<{ usage: number; intakeUnit?: "ml" | "tsp" | "tbsp" | null }> ) => { if (med?.packageType === "liquid_container") { if (doses && doses.length > 0) { const normalizedDoses = doses.filter((dose) => Number.isFinite(Number(dose.usage)) && Number(dose.usage) > 0); if (normalizedDoses.length > 0) { const allUnits = new Set(normalizedDoses.map((dose) => dose.intakeUnit ?? "ml")); if (allUnits.size === 1) { const onlyUnit = normalizedDoses[0]?.intakeUnit ?? "ml"; const totalUsageInUnit = normalizedDoses.reduce((sum, dose) => sum + Number(dose.usage), 0); return formatLiquidUsageLabel(totalUsageInUnit, onlyUnit); } const totalMl = normalizedDoses.reduce( (sum, dose) => sum + convertLiquidUsageToMl(Number(dose.usage), dose.intakeUnit ?? "ml"), 0 ); return `${formatNumber(totalMl)} ${t("form.packageAmountUnitMl")}`; } } return `${formatNumber(total)} ${t("form.packageAmountUnitMl")}`; } if (med?.packageType === "tube") { return `${total} ${getTubeUnitLabel(med, total)}`; } return t("common.pillsTotal", { count: total }); }; return (

{t("dashboard.schedules.title")}

{/* 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]; }) ); // 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) => getMedDisplayName(m) === 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, meds); return (
0 ? "past-missed" : ""}`} >
toggleDayCollapse(day.dateStr, true)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") toggleDayCollapse(day.dateStr, true); }} title={isCollapsed ? t("common.expand") : t("common.collapse")} > {isCollapsed ? "▶" : "▼"} {day.dateStr} {allReallyTaken ? ( ✓ {t("dashboard.schedules.allTaken")} ) : ( <> {hasRealMissed && ( ⚠️ )} {takenCount}/{allDoseIds.length} )}
{!isCollapsed && day.meds.map((item) => { const med = meds.find((m) => getMedDisplayName(m) === item.medName); const medCov = coverageByMed[item.medName]; const isEmpty = medCov ? medCov.medsLeft <= 0 : false; const itemDoseIds = expandDoseIds(item.doses); const allTaken = itemDoseIds.every((id) => takenDoses.has(id)); return (
{item.medName}
{formatTotalUsageLabel(med, item.total, item.doses)}
{item.doses.map((dose) => { // If no takenBy, show single checkbox; otherwise show one per person const people = (dose.takenBy || []).length > 0 ? dose.takenBy : [null]; return (
{dose.timeStr} {formatDoseUsageLabel(med, dose.usage, dose.intakeUnit)} {med?.pillWeightMg && ( {`${dose.usage * med.pillWeightMg} ${med.doseUnit ?? "mg"}`} )} {" "} {dose.intakeRemindersEnabled && ( )}{" "}
{people.map((person) => { const doseId = getDoseId(dose.id, person); const isTaken = takenDoses.has(doseId); const isAutomaticallyTaken = isTaken && isDoseTakenAutomatically(doseId) && dose.when <= Date.now(); return (
{person && ( openUserFilter(person)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") openUserFilter(person); }} > {person} )} {isTaken ? ( ) : ( )}
); })}
); })}
); })}
); })} {/* 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); } }} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { 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(); today.setHours(0, 0, 0, 0); const dayDate = new Date(day.date); dayDate.setHours(0, 0, 0, 0); const isToday = dayDate.getTime() === today.getTime(); return (
{day.dateStr}
{day.meds.map((item) => { const medCoverage = coverageByMed[item.medName]; const isEmpty = medCoverage ? medCoverage.medsLeft <= 0 : false; const med = meds.find((m) => getMedDisplayName(m) === 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, med?.packageType) : null; const visibleStatus = shouldHideNoScheduleStatusForTube(med, status) ? null : status; const itemDoseIds = expandDoseIds(item.doses); const allTaken = itemDoseIds.every((id) => takenDoses.has(id)); return (
{item.medName}
{formatTotalUsageLabel(med, item.total, item.doses)} {visibleStatus && ( {t(visibleStatus.label)} )}
{item.doses.map((dose) => { const people = (dose.takenBy || []).length > 0 ? dose.takenBy : [null]; const now = Date.now(); const dayStart = new Date(day.date).setHours(0, 0, 0, 0); const isPastDay = dayStart < new Date().setHours(0, 0, 0, 0); return (
{dose.timeStr} {formatDoseUsageLabel(med, dose.usage, dose.intakeUnit)} {med?.pillWeightMg && ( {`${dose.usage * med.pillWeightMg} ${med.doseUnit ?? "mg"}`} )} {dose.intakeRemindersEnabled && ( )}
{people.map((person) => { const doseId = getDoseId(dose.id, person); const isTaken = takenDoses.has(doseId); const isAutomaticallyTaken = isTaken && isDoseTakenAutomatically(doseId) && dose.when <= now; const isOverdue = !isTaken && dose.when < now && !isPastDay; return (
{person && ( openUserFilter(person)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") openUserFilter(person); }} > {person} )} {isTaken ? ( ) : ( )}
); })}
); })}
); })}
); })}
); }