feat(medications): once-weekly cadence preset for GLP1 category

Phase 3 of W4d GLP-1 full integration. The form gains a small
"Treatment class" selector (GENERIC | GLP-1 injection) sitting under
the existing clinical-category dropdown. Picking GLP-1 reveals two
helpers that stay hidden for every other medication:

- "Once weekly on …" preset — a single-tap weekday row that writes
  daysOfWeek=[N] + intervalWeeks=1 into the first schedule and reveals
  the advanced block so the user can still tune the window. The last
  weekday picked persists in localStorage so a follow-up med starts on
  the same day.
- "Doses per pen / vial" optional input that powers the inventory math
  (Mounjaro KwikPen = 4, Ozempic 0.25/0.5 mg = 4, Wegovy single-dose
  pen = 1). Leaving it blank keeps inventory tracking off.

The wire schema gains optional `treatmentClass` and `dosesPerUnit`
fields. Both default sensibly (GENERIC + null) so the v1.4.24 client
keeps working untouched.

Marc directive 2026-05-14: GLP-1 must integrate "harmonisch", not
crowd the form. The new affordances live behind the treatment-class
toggle — for every other med the form is byte-identical to v1.4.24.
This commit is contained in:
Marc-André Bombeck committed 2026-05-14 13:33:18 +02:00
1 parent c7eb226969
commit 94cc7f571d
7 files changed
+279 -3

No files matched your search

+12 -2
View File
@@ -71,8 +71,16 @@ export const PUT = apiHandler(
return apiError(parsed.error.issues[0].message, 422);
}
const { name, dose, category, active, notificationsEnabled, schedules } =
parsed.data;
const {
name,
dose,
category,
treatmentClass,
dosesPerUnit,
active,
notificationsEnabled,
schedules,
} = parsed.data;
const pausedAtPatch =
active === undefined
@@ -93,6 +101,8 @@ export const PUT = apiHandler(
const baseUpdateData = {
...(name !== undefined && { name }),
...(dose !== undefined && { dose }),
...(treatmentClass !== undefined && { treatmentClass }),
...(dosesPerUnit !== undefined && { dosesPerUnit }),
...(active !== undefined && { active }),
...(notificationsEnabled !== undefined && { notificationsEnabled }),
...(schedules && {
+11 -1
View File
@@ -83,6 +83,11 @@ export const GET = apiHandler(async () => {
return apiSuccess(
medications.map((m) => ({
...m,
// v1.4.25 W4d — `category` is the existing clinical taxonomy
// (BLOOD_PRESSURE / VITAMIN / ...) from the side-table; the
// Prisma model has its own `treatmentClass` (GENERIC | GLP1) that
// spreads through `...m` and unlocks the GLP-1 surfaces. Two
// orthogonal concepts, both exposed.
category: categoryMap[m.id] ?? "OTHER",
lastTakenAt: lastTakenAtByMedicationId[m.id] ?? null,
todayEventCount: todayEventCountByMedId[m.id] ?? 0,
@@ -101,13 +106,18 @@ export const POST = apiHandler(async (request: NextRequest) => {
return apiError(parsed.error.issues[0].message, 422);
}
const { name, dose, category, schedules } = parsed.data;
const { name, dose, category, treatmentClass, dosesPerUnit, schedules } =
parsed.data;
const medication = await prisma.medication.create({
data: {
userId: user.id,
name,
dose,
// v1.4.25 W4d — treatmentClass + dosesPerUnit are optional in the
// wire schema; Prisma fills the default GENERIC when omitted.
...(treatmentClass !== undefined && { treatmentClass }),
...(dosesPerUnit !== undefined && { dosesPerUnit }),
schedules: {
create: schedules.map((s) => ({
windowStart: s.windowStart,