diff --git a/backend/src/routes/export.ts b/backend/src/routes/export.ts index 6f97f22..ea0f217 100644 --- a/backend/src/routes/export.ts +++ b/backend/src/routes/export.ts @@ -9,6 +9,7 @@ import { doseTracking, medications, shareTokens, userSettings } from "../db/sche import { getAnonymousUserId, requireAuth } from "../plugins/auth.js"; import { env } from "../plugins/env.js"; import type { AuthUser } from "../types/fastify.js"; +import { parseTakenByJson } from "../utils/scheduler-utils.js"; const IMAGES_DIR = resolve(process.cwd(), "data/images"); @@ -125,17 +126,6 @@ async function getUserId(request: any, reply: any): Promise { return authUser.id; } -// Parse takenByJson safely -function parseTakenByJson(takenByJson: string | null | undefined): string[] { - if (!takenByJson) return []; - try { - const parsed = JSON.parse(takenByJson); - return Array.isArray(parsed) ? parsed.filter((s: unknown) => typeof s === "string" && s.trim()) : []; - } catch { - return []; - } -} - // Parse blisters from DB format to export format function parseBlistersForExport( row: typeof medications.$inferSelect diff --git a/backend/src/routes/medications.ts b/backend/src/routes/medications.ts index 789fb48..baf4105 100644 --- a/backend/src/routes/medications.ts +++ b/backend/src/routes/medications.ts @@ -9,7 +9,7 @@ import { doseTracking, medications } from "../db/schema.js"; import { getAnonymousUserId, requireAuth } from "../plugins/auth.js"; import { env } from "../plugins/env.js"; import type { AuthUser } from "../types/fastify.js"; -import { parseLocalDateTime } from "../utils/scheduler-utils.js"; +import { parseBlisters, parseLocalDateTime, parseTakenByJson } from "../utils/scheduler-utils.js"; const IMAGES_DIR = resolve(process.cwd(), "data/images"); @@ -34,36 +34,6 @@ const medicationSchema = z.object({ blisters: z.array(blisterSchema).min(1).max(12), }); -function zipBlisters(usage: number[], every: number[], start: string[]) { - const len = Math.min(usage.length, every.length, start.length); - const blisters: Array<{ usage: number; every: number; start: string }> = []; - for (let i = 0; i < len; i++) { - blisters.push({ usage: usage[i], every: every[i], start: start[i] }); - } - return blisters; -} - -function parseBlisters(row: typeof medications.$inferSelect) { - try { - const usage = JSON.parse(row.usageJson) as number[]; - const every = JSON.parse(row.everyJson) as number[]; - const start = JSON.parse(row.startJson) as string[]; - return zipBlisters(usage, every, start); - } catch (_err) { - return []; - } -} - -function parseTakenByJson(takenByJson: string | null | undefined): string[] { - if (!takenByJson) return []; - try { - const parsed = JSON.parse(takenByJson); - return Array.isArray(parsed) ? parsed.filter((s: unknown) => typeof s === "string" && s.trim()) : []; - } catch { - return []; - } -} - export async function medicationRoutes(app: FastifyInstance) { // All medication routes require auth app.addHook("preHandler", requireAuth); diff --git a/backend/src/routes/share.ts b/backend/src/routes/share.ts index 238fb7d..438cc38 100644 --- a/backend/src/routes/share.ts +++ b/backend/src/routes/share.ts @@ -7,6 +7,7 @@ import { medications, shareTokens, userSettings, users } from "../db/schema.js"; import { getAnonymousUserId, requireAuth } from "../plugins/auth.js"; import { env } from "../plugins/env.js"; import type { AuthUser } from "../types/fastify.js"; +import { parseTakenByJson } from "../utils/scheduler-utils.js"; // Share token validity: 1 year in milliseconds const SHARE_TOKEN_VALIDITY_MS = 365 * 24 * 60 * 60 * 1000; @@ -35,17 +36,6 @@ async function getUserId(request: FastifyRequest, reply: FastifyReply): Promise< return authUser.id; } -// Helper to parse takenByJson -function parseTakenByJson(takenByJson: string | null | undefined): string[] { - if (!takenByJson) return []; - try { - const parsed = JSON.parse(takenByJson); - return Array.isArray(parsed) ? parsed.filter((s: unknown) => typeof s === "string" && s.trim()) : []; - } catch { - return []; - } -} - // ============================================================================= // Share Routes // ============================================================================= diff --git a/backend/src/services/intake-reminder-scheduler.ts b/backend/src/services/intake-reminder-scheduler.ts index 2d64ced..44e898a 100644 --- a/backend/src/services/intake-reminder-scheduler.ts +++ b/backend/src/services/intake-reminder-scheduler.ts @@ -42,10 +42,6 @@ function saveIntakeReminderState(state: IntakeReminderState): void { writeFileSync(intakeReminderStateFile, JSON.stringify(state, null, 2)); } -function parseBlistersFromRow(row: { usageJson: string; everyJson: string; startJson: string }): Blister[] { - return parseBlisters(row); -} - async function sendIntakeReminderEmail( email: string, intakes: UpcomingIntake[], @@ -295,7 +291,7 @@ async function checkAndSendIntakeRemindersForUser( // Find intakes: upcoming ones in reminder window + past ones for repeat reminders for (const med of medsWithReminders) { - const blisters = parseBlistersFromRow(med); + const blisters = parseBlisters(med); const takenByArray = parseTakenByJson(med.takenByJson); logger.info( diff --git a/frontend/src/components/MobileEditModal.tsx b/frontend/src/components/MobileEditModal.tsx index 1764c4f..7ef4b40 100644 --- a/frontend/src/components/MobileEditModal.tsx +++ b/frontend/src/components/MobileEditModal.tsx @@ -4,6 +4,7 @@ */ import { useTranslation } from "react-i18next"; import type { FieldErrors, FormBlister, FormState, Medication } from "../types"; +import { deriveTotal } from "../utils"; // Field limits for validation const FIELD_LIMITS = { @@ -53,12 +54,13 @@ export interface MobileEditModalProps { onSaveMedication: (e: React.FormEvent) => void; } -function deriveTotal(form: FormState) { +/** Calculate total pills from form state */ +function deriveTotalFromForm(form: FormState) { const packCount = Number(form.packCount) || 0; const blistersPerPack = Number(form.blistersPerPack) || 0; const pillsPerBlister = Number(form.pillsPerBlister) || 1; const looseTablets = Number(form.looseTablets) || 0; - return packCount * blistersPerPack * pillsPerBlister + looseTablets; + return deriveTotal(packCount, blistersPerPack, pillsPerBlister, looseTablets); } export function MobileEditModal({ @@ -216,7 +218,7 @@ export function MobileEditModal({

- {t("form.total")}: {deriveTotal(form)} {t("common.pills")} + {t("form.total")}: {deriveTotalFromForm(form)} {t("common.pills")}