mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
fix(meds): cadence picker accepts an allowedKinds filter
Lets the wizard and the edit form constrain the radio list so the one-shot kind never appears alongside a medication-level switch that already encodes it. The unfiltered shape is preserved for callers that don't pass the prop. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
a2de3c7a30
commit
17ea23a1cc
2 files changed
+46
-1
No files matched your search
@@ -17,6 +17,13 @@
|
||||
* grouped-flat-list semantic (no `RadioGroup` primitive ships in
|
||||
* `src/components/ui/`); shadcn `<Label>`, `<Input>` for sub-controls.
|
||||
*
|
||||
* Props:
|
||||
* - `allowedKinds` (optional) — filters the radio list to a subset
|
||||
* of `CadenceKind`. The wizard passes the recurring-only list so
|
||||
* `"oneShot"` cannot land alongside the mode picker's canonical
|
||||
* value. When omitted, every kind is rendered (the edit-form path
|
||||
* preserves the full picker for legacy schedules).
|
||||
*
|
||||
* i18n keys consumed (namespace `medications.scheduling.cadence.*`):
|
||||
*
|
||||
* .label — picker group ariaLabel
|
||||
@@ -61,6 +68,15 @@ export interface CadencePickerProps {
|
||||
* stateless. Optional — if omitted, `DEFAULT_SUB_CONTROLS` is used
|
||||
* and the picker simply re-emits with the chosen defaults. */
|
||||
subControls?: CadenceSubControls;
|
||||
/**
|
||||
* Optional filter over the rendered radio list. When defined, only
|
||||
* the listed kinds appear (in the file-level `ALL_KINDS` order);
|
||||
* when omitted, every kind renders so the edit-form path stays
|
||||
* fully expressive. Used by the wizard's Step 3 to drop the
|
||||
* `"oneShot"` radio once the mode picker has already taken that
|
||||
* decision.
|
||||
*/
|
||||
allowedKinds?: readonly CadenceKind[];
|
||||
onChange: (next: CadenceValue, subControls: CadenceSubControls) => void;
|
||||
/** Disables the entire picker (edit-locked-while-active medications). */
|
||||
disabled?: boolean;
|
||||
@@ -194,6 +210,7 @@ function parseIsoDate(s: string): { year: number; month: number; day: number } |
|
||||
export function CadencePicker({
|
||||
value,
|
||||
subControls,
|
||||
allowedKinds,
|
||||
onChange,
|
||||
disabled = false,
|
||||
i18nPrefix = "medications.scheduling.cadence",
|
||||
@@ -201,6 +218,11 @@ export function CadencePicker({
|
||||
const { t } = useTranslations();
|
||||
const groupName = useId();
|
||||
const sub = subControls ?? DEFAULT_SUB_CONTROLS;
|
||||
const renderedKinds = useMemo(() => {
|
||||
if (!allowedKinds) return ALL_KINDS;
|
||||
const allowed = new Set(allowedKinds);
|
||||
return ALL_KINDS.filter((k) => allowed.has(k));
|
||||
}, [allowedKinds]);
|
||||
|
||||
const emit = useCallback(
|
||||
(kind: CadenceKind, nextSub: CadenceSubControls) => {
|
||||
@@ -230,7 +252,7 @@ export function CadencePicker({
|
||||
aria-label={t(`${i18nPrefix}.label`)}
|
||||
data-slot="cadence-picker"
|
||||
>
|
||||
{ALL_KINDS.map((kind) => {
|
||||
{renderedKinds.map((kind) => {
|
||||
const selected = value.kind === kind;
|
||||
return (
|
||||
<CadenceOption
|
||||
|
||||
@@ -231,4 +231,27 @@ describe("<CadencePicker> — SSR render", () => {
|
||||
);
|
||||
expect(yearlyHtml).toContain('data-slot="cadence-yearly-date"');
|
||||
});
|
||||
|
||||
it("filters the rendered radio list via the allowedKinds prop", () => {
|
||||
const html = render(
|
||||
<CadencePicker
|
||||
value={makeValue("daily")}
|
||||
onChange={NOOP}
|
||||
allowedKinds={["daily", "weekdays"]}
|
||||
/>,
|
||||
);
|
||||
expect(html.match(/data-slot="cadence-option"/g)?.length).toBe(2);
|
||||
expect(html).toContain('data-kind="daily"');
|
||||
expect(html).toContain('data-kind="weekdays"');
|
||||
expect(html).not.toContain('data-kind="oneShot"');
|
||||
expect(html).not.toContain('data-kind="monthly"');
|
||||
expect(html).not.toContain('data-kind="rolling"');
|
||||
});
|
||||
|
||||
it("renders every kind when allowedKinds is omitted (edit-form path)", () => {
|
||||
const html = render(
|
||||
<CadencePicker value={makeValue("daily")} onChange={NOOP} />,
|
||||
);
|
||||
expect(html.match(/data-slot="cadence-option"/g)?.length).toBe(8);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user