feat: Add package type support and per-intake takenBy (#89)

## Package Type Feature
- Add 'blister' and 'bottle' package types for medications
- Bottle type uses totalPills for capacity and looseTablets for current stock
- Blister type continues to use packCount/blistersPerPack/pillsPerBlister
- Add doseUnit field for flexible dosing (mg, ml, IU, etc.)
- Full UI support in medication form and detail modal

## Per-Intake TakenBy
- Move takenBy from medication level to individual intakes
- Each intake schedule can now be assigned to a different person
- Update scheduler-utils to handle per-intake takenBy
- Update SharedSchedule to filter by per-intake takenBy
- Backward compatible with existing medication data

## UI Improvements
- Add PasswordInput component with show/hide toggle
- Centralize stockThresholds in AppContext for consistent status display
- Fix SharedSchedule sync issues with per-intake takenBy
- Improve mobile editing experience

## Technical
- Add migrations 0004 and 0005 for schema changes
- Update all relevant tests (1064 tests passing)
- Maintain backward compatibility with ALTER migrations
This commit is contained in:
Daniel Volz
2026-01-31 23:49:11 +01:00
committed by GitHub
parent ac4b8151e4
commit 571d94bf7e
37 changed files with 2896 additions and 990 deletions
+22 -1
View File
@@ -3,7 +3,7 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useState }
import { useTranslation } from "react-i18next";
import { useAuth } from "../components/Auth";
import { useCollapsedDays, useDoses, useMedications, useRefill, useSettings, useShare } from "../hooks";
import type { Coverage, Medication, ScheduleEvent } from "../types";
import type { Coverage, Medication, ScheduleEvent, StockThresholds } from "../types";
import { getSystemLocale } from "../utils/formatters";
import { buildSchedulePreview, calculateCoverage } from "../utils/schedule";
@@ -134,6 +134,7 @@ export interface AppContextValue {
coverage: { all: Coverage[]; low: Coverage[] };
coverageByMed: Record<string, Coverage>;
depletionByMed: Record<string, number | null>;
stockThresholds: StockThresholds;
existingPeople: string[];
groupedSchedule: GroupedDay[];
pastDays: GroupedDay[];
@@ -296,6 +297,24 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
const coverageByMed = useMemo(() => Object.fromEntries(coverage.all.map((c) => [c.name, c])), [coverage.all]);
// Centralized stock thresholds for consistent status display across all components
const stockThresholds: StockThresholds = useMemo(
() => ({
lowStockDays: settingsHook.settings.lowStockDays,
normalStockDays: settingsHook.settings.normalStockDays,
highStockDays: settingsHook.settings.highStockDays,
criticalStockDays: settingsHook.settings.reminderDaysBefore, // Critical uses the reminder threshold
expiryWarningDays: settingsHook.settings.expiryWarningDays,
}),
[
settingsHook.settings.lowStockDays,
settingsHook.settings.normalStockDays,
settingsHook.settings.highStockDays,
settingsHook.settings.reminderDaysBefore,
settingsHook.settings.expiryWarningDays,
]
);
const existingPeople = useMemo(() => {
const allPeople = medications.meds.flatMap((m) => m.takenBy || []);
return [...new Set(allPeople)].filter(Boolean).sort();
@@ -798,6 +817,7 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
coverage,
coverageByMed,
depletionByMed,
stockThresholds,
existingPeople,
groupedSchedule,
pastDays,
@@ -861,6 +881,7 @@ export function AppProvider({ children }: { children: React.ReactNode }) {
coverage,
coverageByMed,
depletionByMed,
stockThresholds,
existingPeople,
groupedSchedule,
pastDays,