chore(release): v1.7.1 — overview-card edit/history/advanced actions (incl. GLP-1)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Marc-André BombeckandClaude Opus 4.8 committed 2026-05-31 16:10:03 +02:00
1 parent 50e0a65228
commit 05fdddabc5
65 files changed
+10335 -26

No files matched your search

@@ -176,7 +176,12 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
seedGlp1Details(client, med7p5.id, {});
const html = render(
<Glp1MedicationCard medication={med7p5} onEdit={() => {}} />,
<Glp1MedicationCard
medication={med7p5}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
@@ -189,6 +194,47 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
expect(html).not.toMatch(/lucide-syringe/i);
});
it("renders edit + history + advanced header actions with distinct aria-labels", () => {
// v1.7.1 — the GLP-1 card carries the same three header actions as
// the generic medication card: edit (pencil), history, and advanced
// (sliders). All three surface with their localised aria-labels and
// their click handlers deliver the GLP-1 medication object the parent
// routes / sheets against.
const client = makeClient();
seedCompliance(client, med7p5.id);
seedGlp1Details(client, med7p5.id, {});
const onEdit = vi.fn();
const onOpenHistory = vi.fn();
const onOpenAdvanced = vi.fn();
const html = render(
<Glp1MedicationCard
medication={med7p5}
onEdit={onEdit}
onOpenHistory={onOpenHistory}
onOpenAdvanced={onOpenAdvanced}
/>,
client,
);
expect(html).toContain('aria-label="Edit"');
expect(html).toContain('aria-label="View intake history"');
expect(html).toContain('aria-label="Advanced settings"');
// Smoke-check the handler contract — SSR can't fire DOM events, so
// invoke the handlers the way each button's onClick would and pin
// the medication-object payload.
onOpenHistory(med7p5);
onOpenAdvanced(med7p5);
expect(onOpenHistory).toHaveBeenCalledWith(
expect.objectContaining({ id: "med-glp1-1", treatmentClass: "GLP1" }),
);
expect(onOpenAdvanced).toHaveBeenCalledWith(
expect.objectContaining({ id: "med-glp1-1", treatmentClass: "GLP1" }),
);
});
it("renders the default MedicationCard when treatmentClass is null/undefined (back-compat)", () => {
// The page dispatcher renders MedicationCard for everything that
// isn't `"GLP1"`. We verify the default card's output stays free
@@ -198,7 +244,12 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
seedCompliance(client, defaultMed.id);
const html = render(
<MedicationCard medication={defaultMed} onEdit={() => {}} />,
<MedicationCard
medication={defaultMed}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
@@ -217,7 +268,12 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
seedGlp1Details(client, med7p5.id, {});
const html = render(
<Glp1MedicationCard medication={med7p5} onEdit={() => {}} />,
<Glp1MedicationCard
medication={med7p5}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
@@ -239,7 +295,12 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
});
const html = render(
<Glp1MedicationCard medication={med7p5} onEdit={() => {}} />,
<Glp1MedicationCard
medication={med7p5}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
@@ -280,7 +341,12 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
});
const html = render(
<Glp1MedicationCard medication={med7p5} onEdit={() => {}} />,
<Glp1MedicationCard
medication={med7p5}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
@@ -306,7 +372,12 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
});
const html = render(
<Glp1MedicationCard medication={med7p5} onEdit={() => {}} />,
<Glp1MedicationCard
medication={med7p5}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
@@ -333,7 +404,12 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
});
const html = render(
<Glp1MedicationCard medication={med7p5} onEdit={() => {}} />,
<Glp1MedicationCard
medication={med7p5}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
@@ -354,7 +430,12 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
});
const html = render(
<Glp1MedicationCard medication={med7p5} onEdit={() => {}} />,
<Glp1MedicationCard
medication={med7p5}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
@@ -380,6 +461,8 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
<Glp1MedicationCard
medication={med7p5}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
onLogSideEffect={handler}
/>,
client,
@@ -411,7 +494,12 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
seedGlp1Details(client, med7p5.id, {});
const html = render(
<Glp1MedicationCard medication={med7p5} onEdit={() => {}} />,
<Glp1MedicationCard
medication={med7p5}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
@@ -430,7 +518,12 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
seedGlp1Details(client, med7p5.id, {});
const html = render(
<Glp1MedicationCard medication={med7p5} onEdit={() => {}} />,
<Glp1MedicationCard
medication={med7p5}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
@@ -450,7 +543,12 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
seedGlp1Details(client, paused.id, {});
const html = render(
<Glp1MedicationCard medication={paused} onEdit={() => {}} />,
<Glp1MedicationCard
medication={paused}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
@@ -479,7 +577,12 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
});
const html = render(
<Glp1MedicationCard medication={med7p5} onEdit={() => {}} />,
<Glp1MedicationCard
medication={med7p5}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
"de",
);
@@ -131,11 +131,21 @@ describe("medication card symmetry — Ramipril vs Mounjaro", () => {
seedGlp1Details(client, mounjaro.id);
const ramiprilHtml = render(
<MedicationCard medication={ramipril} onEdit={() => {}} />,
<MedicationCard
medication={ramipril}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
const mounjaroHtml = render(
<Glp1MedicationCard medication={mounjaro} onEdit={() => {}} />,
<Glp1MedicationCard
medication={mounjaro}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
@@ -148,6 +158,32 @@ describe("medication card symmetry — Ramipril vs Mounjaro", () => {
}
});
it("the overview card renders the three header actions (edit / history / advanced)", () => {
// v1.7.1 — the overview card mirrors the detail-page header: an
// edit pencil, a History (clock) button routing to the intake
// history, and a SlidersHorizontal button opening the shared
// advanced-settings sheet. Each carries a distinct aria-label.
const client = makeClient();
seedCompliance(client, ramipril.id);
const html = render(
<MedicationCard
medication={ramipril}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
expect(html).toContain("lucide-pencil");
expect(html).toContain("lucide-history");
expect(html).toContain("lucide-sliders-horizontal");
expect(html).toContain('aria-label="Edit"');
expect(html).toContain('aria-label="View intake history"');
expect(html).toContain('aria-label="Advanced settings"');
});
it("neither card paints a kebab when no overflow prop is wired", () => {
// Symmetry default: without onLogSideEffect, the Mounjaro header
// shape matches the Ramipril header exactly (history + edit only).
@@ -157,11 +193,21 @@ describe("medication card symmetry — Ramipril vs Mounjaro", () => {
seedGlp1Details(client, mounjaro.id);
const ramiprilHtml = render(
<MedicationCard medication={ramipril} onEdit={() => {}} />,
<MedicationCard
medication={ramipril}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
const mounjaroHtml = render(
<Glp1MedicationCard medication={mounjaro} onEdit={() => {}} />,
<Glp1MedicationCard
medication={mounjaro}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
@@ -179,6 +225,8 @@ describe("medication card symmetry — Ramipril vs Mounjaro", () => {
<Glp1MedicationCard
medication={mounjaro}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
onLogSideEffect={() => {}}
/>,
client,
@@ -194,11 +242,21 @@ describe("medication card symmetry — Ramipril vs Mounjaro", () => {
seedGlp1Details(client, mounjaro.id);
const ramiprilHtml = render(
<MedicationCard medication={ramipril} onEdit={() => {}} />,
<MedicationCard
medication={ramipril}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
const mounjaroHtml = render(
<Glp1MedicationCard medication={mounjaro} onEdit={() => {}} />,
<Glp1MedicationCard
medication={mounjaro}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
@@ -217,11 +275,21 @@ describe("medication card symmetry — Ramipril vs Mounjaro", () => {
seedGlp1Details(client, mounjaro.id);
const ramiprilHtml = render(
<MedicationCard medication={ramipril} onEdit={() => {}} />,
<MedicationCard
medication={ramipril}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
const mounjaroHtml = render(
<Glp1MedicationCard medication={mounjaro} onEdit={() => {}} />,
<Glp1MedicationCard
medication={mounjaro}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
@@ -240,13 +308,20 @@ describe("medication card symmetry — Ramipril vs Mounjaro", () => {
seedGlp1Details(client, mounjaro.id);
const ramiprilHtml = render(
<MedicationCard medication={ramipril} onEdit={() => {}} />,
<MedicationCard
medication={ramipril}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
const mounjaroHtml = render(
<Glp1MedicationCard
medication={mounjaro}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
onLogSideEffect={() => {}}
/>,
client,
@@ -307,11 +382,21 @@ describe("medication card symmetry — Ramipril vs Mounjaro", () => {
seedGlp1Details(client, mounjaroFuture.id);
const ramiprilHtml = render(
<MedicationCard medication={ramiprilFuture} onEdit={() => {}} />,
<MedicationCard
medication={ramiprilFuture}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
const mounjaroHtml = render(
<Glp1MedicationCard medication={mounjaroFuture} onEdit={() => {}} />,
<Glp1MedicationCard
medication={mounjaroFuture}
onEdit={() => {}}
onOpenHistory={() => {}}
onOpenAdvanced={() => {}}
/>,
client,
);
@@ -10,10 +10,12 @@ import {
ChevronRight,
CircleCheck,
Flame,
History,
Loader2,
MoreVertical,
Pencil,
SkipForward,
SlidersHorizontal,
Stethoscope,
} from "lucide-react";
@@ -120,6 +122,18 @@ interface DetailsResponse {
interface Glp1MedicationCardProps {
medication: Glp1Medication;
onEdit: (med: Glp1Medication) => void;
/**
* v1.7.1 — routes to the medication's full intake-history view
* (`/medications/{id}/history`), mirroring the generic medication card
* and the detail-header History button. The parent owns the navigation.
*/
onOpenHistory: (med: Glp1Medication) => void;
/**
* v1.7.1 — opens the shared `<AdvancedSettingsSheet>` (mounted by the
* list page) for this medication, mirroring the generic medication card
* and the detail-header sliders button.
*/
onOpenAdvanced: (med: Glp1Medication) => void;
onLogSideEffect?: (med: Glp1Medication) => void;
}
@@ -173,6 +187,8 @@ function predictNextWeeklyDate(
export function Glp1MedicationCard({
medication,
onEdit,
onOpenHistory,
onOpenAdvanced,
onLogSideEffect,
}: Glp1MedicationCardProps) {
const queryClient = useQueryClient();
@@ -349,6 +365,29 @@ export function Glp1MedicationCard({
>
<Pencil className="h-4 w-4" />
</Button>
{/* v1.7.1 — History + Advanced icon buttons mirror the generic
medication card and the detail-page header so the GLP-1 card
carries the same three actions (edit / history / advanced).
History routes to the full intake-history view; Advanced opens
the shared settings sheet mounted by the list page. */}
<Button
variant="ghost"
size="icon"
className="min-h-11 min-w-11"
onClick={() => onOpenHistory(medication)}
aria-label={t("medications.detail.header.historyLabel")}
>
<History className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
className="min-h-11 min-w-11"
onClick={() => onOpenAdvanced(medication)}
aria-label={t("medications.detail.header.advancedLabel")}
>
<SlidersHorizontal className="h-4 w-4" />
</Button>
{/* v1.4.37 W4b — GLP-1 specifics (side-effect quick-log etc.)
live in the header actions overflow so the primary action
row stays the canonical two-button shape (Eingenommen /
+43 -1
View File
@@ -23,7 +23,9 @@ import {
CircleCheck,
SkipForward,
Flame,
History,
Pencil,
SlidersHorizontal,
Loader2,
ChevronRight,
} from "lucide-react";
@@ -82,6 +84,18 @@ interface ComplianceData {
interface MedicationCardProps {
medication: Medication;
onEdit: (med: Medication) => void;
/**
* v1.7.1 — routes to the medication's full intake-history view
* (`/medications/{id}/history`), mirroring the detail-header History
* button. The parent owns the navigation.
*/
onOpenHistory: (med: Medication) => void;
/**
* v1.7.1 — opens the shared `<AdvancedSettingsSheet>` (mounted by the
* list page) for this medication, mirroring the detail-header sliders
* button.
*/
onOpenAdvanced: (med: Medication) => void;
}
function getNextOccurrenceTimestamp(
@@ -125,7 +139,12 @@ function getNextOccurrenceTimestamp(
return null;
}
export function MedicationCard({ medication, onEdit }: MedicationCardProps) {
export function MedicationCard({
medication,
onEdit,
onOpenHistory,
onOpenAdvanced,
}: MedicationCardProps) {
const queryClient = useQueryClient();
const { t, locale } = useTranslations();
const fmt = useFormatters();
@@ -287,6 +306,29 @@ export function MedicationCard({ medication, onEdit }: MedicationCardProps) {
>
<Pencil className="h-4 w-4" />
</Button>
{/* v1.7.1 — History + Advanced icon buttons mirror the
detail-page header so the overview card carries the same three
actions (edit / history / advanced). History routes to the
full intake-history view; Advanced opens the shared settings
sheet mounted by the list page. */}
<Button
variant="ghost"
size="icon"
className="min-h-11 min-w-11"
onClick={() => onOpenHistory(medication)}
aria-label={t("medications.detail.header.historyLabel")}
>
<History className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
className="min-h-11 min-w-11"
onClick={() => onOpenAdvanced(medication)}
aria-label={t("medications.detail.header.advancedLabel")}
>
<SlidersHorizontal className="h-4 w-4" />
</Button>
</>
);