diff --git a/messages/de.json b/messages/de.json index 0a198ed70..1078385c5 100644 --- a/messages/de.json +++ b/messages/de.json @@ -1201,6 +1201,16 @@ "yesterday": "Gestern", "tomorrow": "Morgen", "asNeededMarker": "Bei Bedarf", + "recordOnlyBadge": "Nur dokumentiert", + "trackIntake": { + "title": "Einnahmen erfassen", + "label": "Einnahmen erfassen", + "helperOn": "Dosen werden nach diesem Plan fällig, mit Erinnerungen und Therapietreue.", + "helperOff": "Nur dokumentiert: nichts wird fällig, keine Erinnerungen, keine Therapietreue.", + "enabledToast": "Einnahmen werden erfasst", + "disabledToast": "Einnahmen werden nicht erfasst", + "toggleFailed": "Einnahme-Erfassung konnte nicht geändert werden." + }, "withoutNotification": "Ohne Benachrichtigung", "pausedSince": "Pausiert seit", "takeNow": "Jetzt einnehmen", @@ -1927,6 +1937,7 @@ "nextDoseLabel": "Nächste Dosis", "nextDoseNone": "Keine anstehende Dosis.", "nextDoseAsNeeded": "Bei Bedarf — keine geplante Dosis.", + "nextDoseNotTracked": "Nicht erfasst, nur dokumentiert.", "pausedHint": "Pausiert — keine Erinnerungen.", "reminderLabel": "Erinnerung", "reminderOn": "Aktiv", diff --git a/messages/en.json b/messages/en.json index 23ef4eadb..ff8d829c9 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1201,6 +1201,16 @@ "yesterday": "Yesterday", "tomorrow": "Tomorrow", "asNeededMarker": "As needed", + "recordOnlyBadge": "Record only", + "trackIntake": { + "title": "Intake tracking", + "label": "Track intake", + "helperOn": "Doses fall due on this schedule, with reminders and adherence.", + "helperOff": "Kept as a record: nothing falls due, no reminders, no adherence.", + "enabledToast": "Intake tracking on", + "disabledToast": "Intake tracking off", + "toggleFailed": "Could not change intake tracking." + }, "withoutNotification": "Without notification", "pausedSince": "Paused since", "takeNow": "Take now", @@ -1927,6 +1937,7 @@ "nextDoseLabel": "Next dose", "nextDoseNone": "No upcoming dose.", "nextDoseAsNeeded": "As needed — no planned dose.", + "nextDoseNotTracked": "Not tracked, kept as a record.", "pausedHint": "Paused — no reminders.", "reminderLabel": "Reminder", "reminderOn": "On", diff --git a/messages/es.json b/messages/es.json index 4b84a3931..d846bcee8 100644 --- a/messages/es.json +++ b/messages/es.json @@ -1201,6 +1201,16 @@ "yesterday": "Ayer", "tomorrow": "Mañana", "asNeededMarker": "Según necesidad", + "recordOnlyBadge": "Solo registro", + "trackIntake": { + "title": "Registro de tomas", + "label": "Registrar tomas", + "helperOn": "Las dosis tocan según este horario, con recordatorios y adherencia.", + "helperOff": "Se guarda como registro: nada queda pendiente, sin recordatorios ni adherencia.", + "enabledToast": "Registro de tomas activado", + "disabledToast": "Registro de tomas desactivado", + "toggleFailed": "No se pudo cambiar el registro de tomas." + }, "withoutNotification": "Sin notificación", "pausedSince": "En pausa desde", "takeNow": "Tomar ahora", @@ -1927,6 +1937,7 @@ "nextDoseLabel": "Próxima dosis", "nextDoseNone": "No hay dosis pendiente.", "nextDoseAsNeeded": "Según necesidad — sin dosis programada.", + "nextDoseNotTracked": "Sin registro de tomas, se guarda como registro.", "pausedHint": "En pausa — sin recordatorios.", "reminderLabel": "Recordatorio", "reminderOn": "Activado", diff --git a/messages/fr.json b/messages/fr.json index 1174510f1..f860447dd 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -1201,6 +1201,16 @@ "yesterday": "Hier", "tomorrow": "Demain", "asNeededMarker": "Au besoin", + "recordOnlyBadge": "Pour mémoire", + "trackIntake": { + "title": "Suivi des prises", + "label": "Suivre les prises", + "helperOn": "Les doses sont dues selon cet horaire, avec rappels et observance.", + "helperOff": "Conservé pour mémoire : rien n’est dû, pas de rappel, pas d’observance.", + "enabledToast": "Suivi des prises activé", + "disabledToast": "Suivi des prises désactivé", + "toggleFailed": "Impossible de modifier le suivi des prises." + }, "withoutNotification": "Sans notification", "pausedSince": "En pause depuis", "takeNow": "Prendre maintenant", @@ -1927,6 +1937,7 @@ "nextDoseLabel": "Prochaine dose", "nextDoseNone": "Aucune dose à venir.", "nextDoseAsNeeded": "Au besoin — aucune dose planifiée.", + "nextDoseNotTracked": "Prises non suivies, conservé pour mémoire.", "pausedHint": "En pause — aucun rappel.", "reminderLabel": "Rappel", "reminderOn": "Activé", diff --git a/messages/it.json b/messages/it.json index c687ebcfb..e377952b9 100644 --- a/messages/it.json +++ b/messages/it.json @@ -1201,6 +1201,16 @@ "yesterday": "Ieri", "tomorrow": "Domani", "asNeededMarker": "Al bisogno", + "recordOnlyBadge": "Solo annotato", + "trackIntake": { + "title": "Registrazione assunzioni", + "label": "Registra le assunzioni", + "helperOn": "Le dosi sono dovute secondo questo programma, con promemoria e aderenza.", + "helperOff": "Solo annotato: nulla è dovuto, niente promemoria, niente aderenza.", + "enabledToast": "Registrazione assunzioni attiva", + "disabledToast": "Registrazione assunzioni disattivata", + "toggleFailed": "Impossibile modificare la registrazione delle assunzioni." + }, "withoutNotification": "Senza notifica", "pausedSince": "In pausa dal", "takeNow": "Assumi ora", @@ -1927,6 +1937,7 @@ "nextDoseLabel": "Prossima dose", "nextDoseNone": "Nessuna dose in programma.", "nextDoseAsNeeded": "Al bisogno — nessuna dose programmata.", + "nextDoseNotTracked": "Assunzioni non registrate, solo annotato.", "pausedHint": "In pausa — nessun promemoria.", "reminderLabel": "Promemoria", "reminderOn": "Attivo", diff --git a/messages/ko.json b/messages/ko.json index cd61a437d..ddcf5ad19 100644 --- a/messages/ko.json +++ b/messages/ko.json @@ -1201,6 +1201,16 @@ "yesterday": "어제", "tomorrow": "내일", "asNeededMarker": "필요시", + "recordOnlyBadge": "기록만", + "trackIntake": { + "title": "복용 기록", + "label": "복용 기록하기", + "helperOn": "이 일정에 따라 복용 시각이 되고, 알림과 복약 순응도가 적용돼요.", + "helperOff": "기록으로만 남겨요: 예정된 복용, 알림, 복약 순응도가 없어요.", + "enabledToast": "복용 기록 켜짐", + "disabledToast": "복용 기록 꺼짐", + "toggleFailed": "복용 기록 설정을 바꾸지 못했어요." + }, "withoutNotification": "알림 없음", "pausedSince": "일시중지 시작", "takeNow": "지금 복용", @@ -1927,6 +1937,7 @@ "nextDoseLabel": "다음 복용", "nextDoseNone": "예정된 복용이 없어요.", "nextDoseAsNeeded": "필요시 복용 — 예정된 복용이 없어요.", + "nextDoseNotTracked": "복용을 기록하지 않아요. 기록으로만 남겨요.", "pausedHint": "일시중지 — 알림이 오지 않아요.", "reminderLabel": "알림", "reminderOn": "켜짐", diff --git a/messages/pl.json b/messages/pl.json index 9d673396e..b2a46fcdf 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -1201,6 +1201,16 @@ "yesterday": "Wczoraj", "tomorrow": "Jutro", "asNeededMarker": "W razie potrzeby", + "recordOnlyBadge": "Tylko zapis", + "trackIntake": { + "title": "Rejestrowanie przyjęć", + "label": "Rejestruj przyjęcia", + "helperOn": "Dawki przypadają według tego harmonogramu, z przypomnieniami i przestrzeganiem.", + "helperOff": "Tylko zapis: nic nie przypada, bez przypomnień i bez przestrzegania.", + "enabledToast": "Rejestrowanie przyjęć włączone", + "disabledToast": "Rejestrowanie przyjęć wyłączone", + "toggleFailed": "Nie udało się zmienić rejestrowania przyjęć." + }, "withoutNotification": "Bez powiadomienia", "pausedSince": "Wstrzymane od", "takeNow": "Przyjmij teraz", @@ -1927,6 +1937,7 @@ "nextDoseLabel": "Następna dawka", "nextDoseNone": "Brak nadchodzącej dawki.", "nextDoseAsNeeded": "W razie potrzeby — brak zaplanowanej dawki.", + "nextDoseNotTracked": "Przyjęcia nie są rejestrowane, tylko zapis.", "pausedHint": "Wstrzymano — brak przypomnień.", "reminderLabel": "Przypomnienie", "reminderOn": "Włączone", diff --git a/src/app/medications/page-client.tsx b/src/app/medications/page-client.tsx index ba7ae67b3..54e39bf6d 100644 --- a/src/app/medications/page-client.tsx +++ b/src/app/medications/page-client.tsx @@ -105,6 +105,12 @@ interface Medication { oneShot?: boolean; /** v1.16.11 (#316) — as-needed (PRN): no schedules, never due. */ asNeeded?: boolean; + /** + * v1.39.1 (#1033) — false keeps the medication as a record: served with + * `schedules: []` (the stored rows ride `recordedSchedules`), never due, + * and left out of the take-all and log-dose flows. + */ + trackIntake?: boolean; /** v1.9.0 — optional WHO ATC classification code for the FHIR export. */ atcCode?: string | null; /** v1.9.0 — optional RxNorm RxCUI (secondary FHIR coding). */ @@ -647,25 +653,27 @@ export default function MedicationsPageClient() { ({ - id: m.id, - name: m.name, - dose: m.dose, - active: m.active, - lastTakenAt: m.lastTakenAt, - todayEventCount: m.todayEventCount ?? 0, - nextDueAt: m.nextDueAt, - nextDueOverdue: m.nextDueOverdue, - schedules: m.schedules.map((s) => ({ - windowStart: s.windowStart, - windowEnd: s.windowEnd, - daysOfWeek: s.daysOfWeek, - label: s.label, - dose: s.dose, - timesOfDay: s.timesOfDay, - doseWindows: s.doseWindows, - })), - }))} + medications={activeMeds + .filter((m) => m.trackIntake !== false) + .map((m) => ({ + id: m.id, + name: m.name, + dose: m.dose, + active: m.active, + lastTakenAt: m.lastTakenAt, + todayEventCount: m.todayEventCount ?? 0, + nextDueAt: m.nextDueAt, + nextDueOverdue: m.nextDueOverdue, + schedules: m.schedules.map((s) => ({ + windowStart: s.windowStart, + windowEnd: s.windowEnd, + daysOfWeek: s.daysOfWeek, + label: s.label, + dose: s.dose, + timesOfDay: s.timesOfDay, + doseWindows: s.doseWindows, + })), + }))} /> )} diff --git a/src/components/dashboard/medication-intake-quick-add.tsx b/src/components/dashboard/medication-intake-quick-add.tsx index dcc22cbd1..1476bfb78 100644 --- a/src/components/dashboard/medication-intake-quick-add.tsx +++ b/src/components/dashboard/medication-intake-quick-add.tsx @@ -80,6 +80,8 @@ interface Schedule extends ScheduleWindowInput { export interface MedicationOption extends DefaultMedicationOption { dose: string; schedules: Schedule[]; + /** v1.39.1 (#1033) — false: kept as a record, not offered for a dose. */ + trackIntake?: boolean; } interface MedicationIntakeQuickAddProps { @@ -138,7 +140,9 @@ export function MedicationIntakeQuickAdd({ const medications = useMemo( () => Array.isArray(medicationsRaw) - ? medicationsRaw.filter((m) => m.active) + ? // v1.39.1 (#1033) — a medication with intake tracking off is + // kept as a record; the dose capture does not offer it. + medicationsRaw.filter((m) => m.active && m.trackIntake !== false) : [], [medicationsRaw], ); diff --git a/src/components/medications/card-parts/medication-card-body.tsx b/src/components/medications/card-parts/medication-card-body.tsx index 8fc154b3d..d17b6078e 100644 --- a/src/components/medications/card-parts/medication-card-body.tsx +++ b/src/components/medications/card-parts/medication-card-body.tsx @@ -133,6 +133,14 @@ export interface MedicationCardBodyProps { */ asNeeded?: boolean; + /** + * v1.39.1 (#1033) — intake tracking off. The medication is shown as a + * record: no compliance block (the batched read excludes it, so a + * skeleton would spin forever), no open-cycle slot, and no take / skip + * actions, because nothing is due and doses are not logged from here. + */ + recordOnly?: boolean; + /** * v1.16.11 — projected supply runway in whole days, set ONLY while it * sits below the user's low-stock threshold (the variants gate it). @@ -182,6 +190,7 @@ export function MedicationCardBody({ onRetryCompliance, currentCycle, asNeeded = false, + recordOnly = false, lowStockRunwayDays = null, intakeLoading, onRecordIntake, @@ -306,6 +315,7 @@ export function MedicationCardBody({ notice now costs zero layout shift. */} {active && !asNeeded && + !recordOnly && (complianceNotApplicable ? ( {currentCycle && } @@ -342,7 +352,7 @@ export function MedicationCardBody({ {/* Quick actions — bottom-pinned so the action rows align across a grid row regardless of how much content sits above. */} - {active && ( + {active && !recordOnly && (
- {!notificationsEnabled && ( + {recordOnly && ( + + {t("medications.recordOnlyBadge")} + + )} + {!notificationsEnabled && !recordOnly && ( {t("medications.withoutNotification")} diff --git a/src/components/medications/detail/medication-detail-tabs.tsx b/src/components/medications/detail/medication-detail-tabs.tsx index fb71596c3..359c09d62 100644 --- a/src/components/medications/detail/medication-detail-tabs.tsx +++ b/src/components/medications/detail/medication-detail-tabs.tsx @@ -91,6 +91,7 @@ import { useTranslations, useFormatters } from "@/lib/i18n/context"; import { formatDose } from "@/lib/medications/format-dose"; import type { MedicationPayload } from "@/components/medications/wizard/wizard-payload"; import type { ComplianceDisplay } from "@/lib/analytics/compliance"; +import { IntakeTrackingBody } from "@/components/medications/sections/intake-tracking-section"; // v1.15.20 — Recharts stays out of the detail page's initial bundle: // both chart bodies load through `next/dynamic` (client-only) with the @@ -163,6 +164,13 @@ export interface MedicationDetailSnapshot { * estimate; the estimate remains only as the stale-payload fallback. */ runwayDays?: number | null; + /** + * v1.39.1 (#1033) — false keeps the medication as a record. The detail + * GET then serves `schedules: []` and the stored rows in + * `recordedSchedules`; the page shows (and edits) the stored rows. + */ + trackIntake?: boolean; + recordedSchedules?: ScheduleSnapshot[]; schedules: ScheduleSnapshot[]; } @@ -234,6 +242,7 @@ function snapshotToWizardPayload( endsOn: med.endsOn ? new Date(med.endsOn) : null, oneShot: med.oneShot ?? false, asNeeded: med.asNeeded ?? false, + trackIntake: med.trackIntake !== false, schedules: med.schedules.map((s) => ({ id: s.id, windowStart: s.windowStart, @@ -250,10 +259,23 @@ function snapshotToWizardPayload( } export function MedicationDetailTabs({ - medication, + medication: served, }: { medication: MedicationDetailSnapshot; }) { + // v1.39.1 (#1033) — a record-only medication arrives with `schedules: []` + // (so clients that derive due doses from it stay silent) and its stored + // schedule in `recordedSchedules`. This page is where the record is read + // and edited, so it works on the stored rows; nothing on it derives a due + // dose from them (next-due and compliance come from the server). + const recordOnly = served.trackIntake === false; + const medication = useMemo( + () => + served.recordedSchedules + ? { ...served, schedules: served.recordedSchedules } + : served, + [served], + ); const { t } = useTranslations(); const fmt = useFormatters(); const { canManageDomain, inSharedRecord } = useRecordCapabilities(); @@ -381,7 +403,8 @@ export function MedicationDetailTabs({ try { return await apiGet<{ applicable: boolean; - notApplicableReason: "NO_LOCAL_SCHEDULE" | null; + notApplicableReason: + "NO_LOCAL_SCHEDULE" | "INTAKE_NOT_TRACKED" | null; compliance7: { rate: number; streak: number }; compliance30: { rate: number }; complianceDisplay: ComplianceDisplay | null; @@ -483,6 +506,7 @@ export function MedicationDetailTabs({ oneShot={oneShot} asNeeded={asNeeded} startsOn={medication.startsOn} + recordOnly={recordOnly} /> {/* v1.16.11 — as-needed never reminds; the row would only mislead (the cron skips schedule-less medications). */} - {!asNeeded && ( + {!asNeeded && !recordOnly && ( - {!asNeeded && ( + {!asNeeded && !recordOnly && ( + {/* v1.39.1 (#1033) — the intake-tracking switch leads the tab: + it decides whether the times below are a dose plan or a + record. */} + + + + {medication.schedules.length > 0 ? ( ) : null} - -
- - ({ - windowStart: s.windowStart, - windowEnd: s.windowEnd, - label: s.label, - dose: s.dose, - daysOfWeek: s.daysOfWeek, - timesOfDay: s.timesOfDay, - rrule: s.rrule, - rollingIntervalDays: s.rollingIntervalDays, - reminderGraceMinutes: s.reminderGraceMinutes, - scheduleType: s.scheduleType, - cyclicOnWeeks: s.cyclicOnWeeks, - cyclicOffWeeks: s.cyclicOffWeeks, - doseWindows: s.doseWindows, - }))} - /> - {/* Quiet cross-link: WHICH channels deliver the reminder + {!recordOnly && ( + +
+ + ({ + windowStart: s.windowStart, + windowEnd: s.windowEnd, + label: s.label, + dose: s.dose, + daysOfWeek: s.daysOfWeek, + timesOfDay: s.timesOfDay, + rrule: s.rrule, + rollingIntervalDays: s.rollingIntervalDays, + reminderGraceMinutes: s.reminderGraceMinutes, + scheduleType: s.scheduleType, + cyclicOnWeeks: s.cyclicOnWeeks, + cyclicOffWeeks: s.cyclicOffWeeks, + doseWindows: s.doseWindows, + }))} + /> + {/* Quiet cross-link: WHICH channels deliver the reminder (push / Telegram / ntfy, quiet hours) is configured globally, not per medication. */} -

- - {t("medications.detail.zeitplan.manageChannels")} - -

-
-
+

+ + {t("medications.detail.zeitplan.manageChannels")} + +

+
+
+ )} {/* PLANHISTORIE — archived schedule eras (v1.16.3 effective dating) as a quiet collapsible timeline, plus the manual diff --git a/src/components/medications/glp1-medication-card.tsx b/src/components/medications/glp1-medication-card.tsx index 1b71e879e..eaf699ee0 100644 --- a/src/components/medications/glp1-medication-card.tsx +++ b/src/components/medications/glp1-medication-card.tsx @@ -128,6 +128,12 @@ export interface Glp1Medication { runwayDays?: number | null; /** v1.17.0 — per-medication reorder lead override (days); null = inherit the user default. */ reorderLeadDays?: number | null; + /** + * v1.39.1 (#1033) — false keeps the medication as a record: the list + * serves `schedules: []` and nothing is due, so the card drops the + * compliance bars and the take / skip actions and shows a badge instead. + */ + trackIntake?: boolean; schedules: ScheduleLite[]; } @@ -438,6 +444,7 @@ export function Glp1MedicationCard({ notificationsEnabled={medication.notificationsEnabled} active={medication.active} pausedAt={medication.pausedAt} + recordOnly={medication.trackIntake === false} /> ); @@ -551,6 +558,7 @@ export function Glp1MedicationCard({ currentCycle={ complianceNotApplicable ? null : (display?.currentCycle ?? null) } + recordOnly={medication.trackIntake === false} lowStockRunwayDays={lowStockRunwayDays} intakeLoading={intakeLoading} onRecordIntake={(skipped) => recordIntake(skipped, displayedSlot)} diff --git a/src/components/medications/medication-card.tsx b/src/components/medications/medication-card.tsx index 1d1aeb3c3..2d37ad872 100644 --- a/src/components/medications/medication-card.tsx +++ b/src/components/medications/medication-card.tsx @@ -113,6 +113,12 @@ interface Medication { * low-stock trigger so the notice lands before the last dose. */ reorderLeadDays?: number | null; + /** + * v1.39.1 (#1033) — false keeps the medication as a record: the list + * serves `schedules: []` and nothing is due, so the card drops the + * compliance bars and the take / skip actions and shows a badge instead. + */ + trackIntake?: boolean; schedules: Schedule[]; } @@ -377,6 +383,7 @@ export function MedicationCard({ notificationsEnabled={medication.notificationsEnabled} active={medication.active} pausedAt={medication.pausedAt} + recordOnly={medication.trackIntake === false} /> ); @@ -516,6 +523,7 @@ export function MedicationCard({ currentCycle={ complianceNotApplicable ? null : (display?.currentCycle ?? null) } + recordOnly={medication.trackIntake === false} lowStockRunwayDays={lowStockRunwayDays} intakeLoading={intakeLoading} onRecordIntake={(skipped) => recordIntake(skipped, displayedSlot)} diff --git a/src/components/medications/medication-detail-summary.tsx b/src/components/medications/medication-detail-summary.tsx index dd9722b8f..4904a1f7c 100644 --- a/src/components/medications/medication-detail-summary.tsx +++ b/src/components/medications/medication-detail-summary.tsx @@ -39,6 +39,8 @@ export interface MedicationDetailSummaryProps { /** v1.16.11 — as-needed (PRN): the cadence line reads "Bei Bedarf". */ asNeeded?: boolean; startsOn?: string | null; + /** v1.39.1 (#1033) — intake tracking off: a "Record only" badge. */ + recordOnly?: boolean; } type Status = "active" | "paused" | "ended"; @@ -62,6 +64,7 @@ export function MedicationDetailSummary({ oneShot, asNeeded = false, startsOn, + recordOnly = false, }: MedicationDetailSummaryProps) { const { t, locale } = useTranslations(); const formatters = useFormatters(); @@ -116,6 +119,14 @@ export function MedicationDetailSummary({ /> {statusLabel} + {recordOnly && ( + + {t("medications.recordOnlyBadge")} + + )}

– ); - if (medication.asNeeded) { + const recordOnly = medication.trackIntake === false; + if (recordOnly) { + // v1.39.1 (#1033) — kept as a record: nothing is ever due. + nextCell = ( + + {t("medications.recordOnlyBadge")} + + ); + } else if (medication.asNeeded) { // v1.16.11 — a calm marker where next-due normally sits; no due // pill, no overdue escalation, ever. nextCell = ( @@ -688,7 +704,7 @@ function MedicationTableRowItem({ {statusCell} {nextCell} - {medication.active && !medication.asNeeded ? ( + {medication.active && !medication.asNeeded && !recordOnly ? ( complianceCell ) : ( – @@ -696,7 +712,7 @@ function MedicationTableRowItem({ {stockCell} - {medication.active && canAddIntake ? ( + {medication.active && canAddIntake && !recordOnly ? (

+ {/* v1.39.1 (#1033) — intake tracking. Off keeps the medication and + its schedule as a record; nothing is due, so the reminders toggle + below goes with it. As-needed is never due either way. */} + {payload.mode !== "asNeeded" && ( +
+
+ +

+ {payload.trackIntake + ? t("medications.trackIntake.helperOn") + : t("medications.trackIntake.helperOff")} +

+
+ + applyPartial({ trackIntake: checked }) + } + data-slot="wizard-track-intake-toggle" + aria-label={t("medications.trackIntake.label")} + /> +
+ )} + {/* v1.16.11 — an as-needed medication never reminds (no slots), so the reminders toggle is hidden alongside the schedules. */} - {payload.mode !== "asNeeded" && ( + {payload.mode !== "asNeeded" && payload.trackIntake && (
)} - {payload.mode !== "asNeeded" && showNoChannelHint && ( -

- {t("medications.wizard.steps.step8.noChannelHint")}{" "} - - {t("medications.wizard.steps.step8.noChannelHintLink")} - -

- )} + {t("medications.wizard.steps.step8.noChannelHint")}{" "} + + {t("medications.wizard.steps.step8.noChannelHintLink")} + +

+ )} {submitError && (

= 1 entry. */ schedules: ScheduleDraft[]; /** Which schedule Steps 5-7 currently edit. */ @@ -322,6 +327,7 @@ export function emptyWizardPayload(): WizardPayload { startsOn: todayUtc(), endsOn: null, notificationsEnabled: true, + trackIntake: true, schedules: [draft], activeScheduleIndex: 0, }; @@ -603,6 +609,12 @@ export interface CreateMedicationBody { * `schedules` array (the route 422s on any entry alongside the flag). */ asNeeded: boolean; + /** + * v1.39.1 (#1033) — intake tracking. Sent on create and edit for a + * scheduled medication (the wizard hydrates it from the stored value); + * omitted for an as-needed one, which is never due either way. + */ + trackIntake?: boolean; schedules: Array<{ id?: string; windowStart: string; @@ -813,6 +825,7 @@ export function buildCreateBody( }), oneShot: isOneShot, asNeeded: isAsNeeded, + ...(!isAsNeeded && { trackIntake: committed.trackIntake }), schedules: draftsToEmit.map((draft) => encodeScheduleDraft(draft, isOneShot), ), @@ -1035,6 +1048,8 @@ export interface MedicationPayload { oneShot: boolean; /** v1.16.11 — as-needed (PRN) flag; hydrates the "Bei Bedarf" mode. */ asNeeded?: boolean; + /** v1.39.1 (#1033) — intake tracking; absent = tracked. */ + trackIntake?: boolean; schedules: Array<{ id?: string; windowStart: string; @@ -1181,6 +1196,7 @@ export function hydrateWizardPayload( startsOn: initial.startsOn ?? base.startsOn, endsOn: initial.endsOn, notificationsEnabled: initial.notificationsEnabled ?? true, + trackIntake: initial.trackIntake !== false, schedules: drafts, activeScheduleIndex: 0, }; diff --git a/src/lib/queries/use-medication-compliance-summary.ts b/src/lib/queries/use-medication-compliance-summary.ts index d58e11641..a3aa4c03b 100644 --- a/src/lib/queries/use-medication-compliance-summary.ts +++ b/src/lib/queries/use-medication-compliance-summary.ts @@ -16,7 +16,7 @@ import type { ComplianceDisplay } from "@/lib/analytics/compliance"; export interface MedicationComplianceSummaryEntry { medicationId: string; applicable: boolean; - notApplicableReason: "NO_LOCAL_SCHEDULE" | null; + notApplicableReason: "NO_LOCAL_SCHEDULE" | "INTAKE_NOT_TRACKED" | null; compliance7: { rate: number; streak: number }; compliance30: { rate: number }; complianceDisplay: ComplianceDisplay | null;