mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-14 14:40:52 +00:00
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:
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 /
|
||||
|
||||
@@ -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>
|
||||
</>
|
||||
);
|
||||
|
||||
|
||||
Reference in new issue
Block a user