From 57ff8776f52be54f12dd2b7431655d84c776e0a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Thu, 28 May 2026 08:22:33 +0200 Subject: [PATCH] feat(meds): reusable cadence-picker, times-of-day, and course-window components MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three components plus a shared `types.ts` module under `src/components/medications/scheduling/`. The wizard + the form refactor consume them; the picker emits a `CadenceValue` discriminated union that the form maps onto the API request body. ### CadencePicker A grouped flat radio list with 8 cadence kinds: - Every day → `FREQ=DAILY` - Certain weekdays (chip-toggle row) → `FREQ=WEEKLY;BYDAY=...` - Every N weeks on certain days → `FREQ=WEEKLY;INTERVAL=N;BYDAY=...` - Monthly on day → `FREQ=MONTHLY;BYMONTHDAY=N` - Every N months on day → `FREQ=MONTHLY;INTERVAL=N;BYMONTHDAY=M` - Yearly on date → `FREQ=YEARLY;BYMONTH=M;BYMONTHDAY=D` - Rolling N days from last intake → `rollingIntervalDays: N` - One-time dose → `oneShot: true` The "Every N days flexible" option carries an explainer line so the GLP-1 patient understands the rolling semantic. Defaults are chosen so the encoded RRULE is always parseable (weekday set defaults to Monday, yearly clamps to Jan 1) — the wizard / form gates "continue" until the user picks the real day. Sub-controls state is owned by the parent (the picker accepts `subControls` + emits via the second `onChange` arg) so a wizard that re-enters step 3 sees the user's previous picks. ### TimesOfDayChips Multi-chip HH:mm input with morning / noon / evening / night presets. Dedupes + sorts. `maxChips` prop honours the Zod cap of 8; when the parent passes `maxChips: 1` (one-shot context) the component renders a single time picker instead of a chip row. ### CourseWindowRow Two date pickers (`startsOn` + `endsOn`) with a "No end date" toggle. `lockEndsToStart` prop forces the one-shot case where both pickers share a date. Validation message rendered below the row when `endsOn < startsOn`. ### Quality - "use client" on every component file. - shadcn/ui-only — no new deps, no shadcn Calendar primitive in this repo so date pickers fall back to ``. - WCAG 2.5.5 tap targets (44 px) across every interactive element; focus rings, aria-labels, aria-pressed on toggles, role="group" on chip rows. - 49 tests across the three components (encoder mapping, sort / dedupe / preset toggle, range validation, SSR-only render smoke tests). Pure helpers exported so the contract is pinned at the unit-test level; user-event paths land in Playwright e2e per the synthesis quality bar. i18n keys listed at the top of each component file under `medications.scheduling.{cadence,timesOfDay,courseWindow}.*`. The follow-up i18n agent populates DE + EN. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../medications/scheduling/CadencePicker.tsx | 586 ++++++++++++++++++ .../scheduling/CourseWindowRow.tsx | 211 +++++++ .../scheduling/TimesOfDayChips.tsx | 278 +++++++++ .../__tests__/CadencePicker.test.tsx | 234 +++++++ .../__tests__/CourseWindowRow.test.tsx | 142 +++++ .../__tests__/TimesOfDayChips.test.tsx | 146 +++++ .../medications/scheduling/types.ts | 94 +++ 7 files changed, 1691 insertions(+) create mode 100644 src/components/medications/scheduling/CadencePicker.tsx create mode 100644 src/components/medications/scheduling/CourseWindowRow.tsx create mode 100644 src/components/medications/scheduling/TimesOfDayChips.tsx create mode 100644 src/components/medications/scheduling/__tests__/CadencePicker.test.tsx create mode 100644 src/components/medications/scheduling/__tests__/CourseWindowRow.test.tsx create mode 100644 src/components/medications/scheduling/__tests__/TimesOfDayChips.test.tsx create mode 100644 src/components/medications/scheduling/types.ts diff --git a/src/components/medications/scheduling/CadencePicker.tsx b/src/components/medications/scheduling/CadencePicker.tsx new file mode 100644 index 000000000..64b498682 --- /dev/null +++ b/src/components/medications/scheduling/CadencePicker.tsx @@ -0,0 +1,586 @@ +"use client"; + +/** + * v1.5.0 — CadencePicker component. + * + * Single picker exposing all 8 cadence kinds as a flat-grouped radio + * list (per design-synthesis R-1 + R-4). One native radio drives the + * `kind`; conditional sub-controls render only when their parent + * radio is selected (hide-not-grey-out). + * + * Emits a `CadenceValue` (`{ kind, rrule, rollingIntervalDays, + * oneShot }`) via `onChange`. The wizard / form layer maps that to + * the wire shape — `oneShot` to the medication level, `rrule` + + * `rollingIntervalDays` to the schedule. + * + * No new dependencies. Uses native `` for the + * grouped-flat-list semantic (no `RadioGroup` primitive ships in + * `src/components/ui/`); shadcn `