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
@@ -3,6 +3,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { AdvancedSettingsSheet } from "@/components/medications/advanced-settings-sheet";
|
||||
import { useAuth } from "@/hooks/use-auth";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { queryKeys } from "@/lib/query-keys";
|
||||
@@ -69,6 +70,11 @@ export default function MedicationsPage() {
|
||||
const shouldOpenFromUrl = searchParams?.get("new") === "1";
|
||||
const [dialogOpen, setDialogOpen] = useState(shouldOpenFromUrl);
|
||||
const [editingMed, setEditingMed] = useState<Medication | null>(null);
|
||||
// v1.7.1 — the medications-card History + Advanced actions. History
|
||||
// navigates to the full intake-history view; Advanced opens one shared
|
||||
// `<AdvancedSettingsSheet>` mounted below, keyed to the selected
|
||||
// medication (reused, not duplicated per card).
|
||||
const [advancedMed, setAdvancedMed] = useState<Medication | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (shouldOpenFromUrl) {
|
||||
@@ -94,6 +100,40 @@ export default function MedicationsPage() {
|
||||
enabled: isAuthenticated,
|
||||
});
|
||||
|
||||
// v1.7.1 — the advanced sheet's danger zone needs `intakeCount` to
|
||||
// gate the purge button. Fetch it lazily once a medication is selected
|
||||
// (the list view never pays for it otherwise), mirroring the
|
||||
// detail-page query shape.
|
||||
const advancedIntakeParams = {
|
||||
sortBy: "takenAt",
|
||||
sortDir: "desc" as const,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
status: "completed",
|
||||
};
|
||||
const { data: advancedIntakeCount } = useQuery({
|
||||
queryKey: advancedMed
|
||||
? queryKeys.medicationIntakeList(advancedMed.id, advancedIntakeParams)
|
||||
: ["medications", "intake-count", "none"],
|
||||
queryFn: async () => {
|
||||
if (!advancedMed) return 0;
|
||||
const params = new URLSearchParams({
|
||||
sortBy: advancedIntakeParams.sortBy,
|
||||
sortDir: advancedIntakeParams.sortDir,
|
||||
limit: String(advancedIntakeParams.limit),
|
||||
offset: String(advancedIntakeParams.offset),
|
||||
status: advancedIntakeParams.status,
|
||||
});
|
||||
const res = await fetch(
|
||||
`/api/medications/${advancedMed.id}/intake?${params.toString()}`,
|
||||
);
|
||||
if (!res.ok) return 0;
|
||||
const json = await res.json();
|
||||
return (json.data?.meta?.total ?? 0) as number;
|
||||
},
|
||||
enabled: isAuthenticated && !!advancedMed,
|
||||
});
|
||||
|
||||
function openCreate() {
|
||||
setEditingMed(null);
|
||||
setDialogOpen(true);
|
||||
@@ -104,6 +144,14 @@ export default function MedicationsPage() {
|
||||
setDialogOpen(true);
|
||||
}
|
||||
|
||||
function openHistory(med: Medication) {
|
||||
router.push(`/medications/${med.id}/history`);
|
||||
}
|
||||
|
||||
function openAdvanced(med: Medication) {
|
||||
setAdvancedMed(med);
|
||||
}
|
||||
|
||||
function closeDialog() {
|
||||
setDialogOpen(false);
|
||||
setEditingMed(null);
|
||||
@@ -207,12 +255,16 @@ export default function MedicationsPage() {
|
||||
key={med.id}
|
||||
medication={med}
|
||||
onEdit={openEdit}
|
||||
onOpenHistory={openHistory}
|
||||
onOpenAdvanced={openAdvanced}
|
||||
/>
|
||||
) : (
|
||||
<MedicationCard
|
||||
key={med.id}
|
||||
medication={med}
|
||||
onEdit={openEdit}
|
||||
onOpenHistory={openHistory}
|
||||
onOpenAdvanced={openAdvanced}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
@@ -233,12 +285,16 @@ export default function MedicationsPage() {
|
||||
key={med.id}
|
||||
medication={med}
|
||||
onEdit={openEdit}
|
||||
onOpenHistory={openHistory}
|
||||
onOpenAdvanced={openAdvanced}
|
||||
/>
|
||||
) : (
|
||||
<MedicationCard
|
||||
key={med.id}
|
||||
medication={med}
|
||||
onEdit={openEdit}
|
||||
onOpenHistory={openHistory}
|
||||
onOpenAdvanced={openAdvanced}
|
||||
/>
|
||||
),
|
||||
)}
|
||||
@@ -269,6 +325,30 @@ export default function MedicationsPage() {
|
||||
initial={editingMed ? medicationToPayload(editingMed) : undefined}
|
||||
onSuccess={closeDialog}
|
||||
/>
|
||||
|
||||
{/* v1.7.1 — one shared advanced-settings sheet for every card.
|
||||
Reuses the exact component the detail page mounts (Data /
|
||||
Reminders / Lifecycle / Danger zone); the card's sliders button
|
||||
selects the medication and opens it. Not duplicated per card. */}
|
||||
{advancedMed && (
|
||||
<AdvancedSettingsSheet
|
||||
open={!!advancedMed}
|
||||
onOpenChange={(open) => {
|
||||
if (!open) setAdvancedMed(null);
|
||||
}}
|
||||
medicationId={advancedMed.id}
|
||||
medicationName={advancedMed.name}
|
||||
treatmentClass={advancedMed.treatmentClass}
|
||||
active={advancedMed.active}
|
||||
startsOn={advancedMed.startsOn}
|
||||
endsOn={advancedMed.endsOn}
|
||||
notificationsEnabled={advancedMed.notificationsEnabled}
|
||||
reminderGraceMinutes={
|
||||
advancedMed.schedules[0]?.reminderGraceMinutes ?? null
|
||||
}
|
||||
intakeCount={advancedIntakeCount ?? 0}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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