Unify medication card next/last labels across both variants

The GLP-1 card overrode the shared next/last slot with appointment-phrased
labels (Next / Last, "Nächster Termin" / "Letzter Termin") while the generic
card used Next intake / Last intake. Drop the override so both cards name the
concept identically; the GLP-1 relative-day value phrasing (in N days / today)
is unchanged. Remove the now-dead label override props from the shared slot
and the orphaned glp1NextLabel / glp1LastLabel strings from all locales.
This commit is contained in:
Marc-André Bombeck committed 2026-06-07 01:06:05 +02:00
1 parent 781995e382
commit a3410efb5c
9 files changed
+60 -74

No files matched your search

+1 -2
View File
@@ -700,6 +700,7 @@
"compliance7d": "7-Tage-Compliance",
"compliance30d": "30-Tage-Compliance",
"complianceWindow": "{days}-Tage-Compliance",
"complianceDoses": "{count} Dosen",
"lastIntake": "Letzte Einnahme:",
"nextIntake": "Nächste Einnahme:",
"dayStreak": "Tage Serie",
@@ -745,8 +746,6 @@
"glp1DosesPerUnit": "Dosen pro Pen / Ampulle",
"glp1DosesPerUnitPlaceholder": "z. B. 4 für einen Mounjaro KwikPen",
"glp1DosesPerUnitHelp": "Optional — Mounjaro KwikPen = 4, Ozempic 0,25/0,5 mg = 4, Wegovy Einmal-Pen = 1. Leer lassen, wenn keine Dosis-pro-Pen-Verfolgung gewünscht ist.",
"glp1NextLabel": "Nächster Termin:",
"glp1LastLabel": "Letzter Termin:",
"glp1LastInjection": "{label}",
"glp1LastInjectionWithSite": "{label} · {site}",
"glp1NextInjectionDays": "{label} (in {days} Tagen)",
+1 -2
View File
@@ -700,6 +700,7 @@
"compliance7d": "7-day compliance",
"compliance30d": "30-day compliance",
"complianceWindow": "{days}-day compliance",
"complianceDoses": "{count} doses",
"lastIntake": "Last intake:",
"nextIntake": "Next intake:",
"dayStreak": "day streak",
@@ -745,8 +746,6 @@
"glp1DosesPerUnit": "Doses per pen / vial",
"glp1DosesPerUnitPlaceholder": "e.g. 4 for a Mounjaro KwikPen",
"glp1DosesPerUnitHelp": "Optional — Mounjaro KwikPen = 4, Ozempic 0.25/0.5 mg = 4, Wegovy single-dose pen = 1. Leave blank if you don't track per-pen dosing.",
"glp1NextLabel": "Next:",
"glp1LastLabel": "Last:",
"glp1LastInjection": "{label}",
"glp1LastInjectionWithSite": "{label} · {site}",
"glp1NextInjectionDays": "{label} (in {days} days)",
+1 -2
View File
@@ -700,6 +700,7 @@
"compliance7d": "Cumplimiento 7 días",
"compliance30d": "Cumplimiento 30 días",
"complianceWindow": "Cumplimiento {days} días",
"complianceDoses": "{count} dosis",
"lastIntake": "Última toma:",
"nextIntake": "Próxima toma:",
"dayStreak": "días de racha",
@@ -745,8 +746,6 @@
"glp1DosesPerUnit": "Dosis por pluma / vial",
"glp1DosesPerUnitPlaceholder": "p. ej. 4 para una pluma Mounjaro KwikPen",
"glp1DosesPerUnitHelp": "Opcional — Mounjaro KwikPen = 4, Ozempic 0,25/0,5 mg = 4, Wegovy pluma de un solo uso = 1. Déjalo vacío si no quieres llevar el conteo de dosis por pluma.",
"glp1NextLabel": "Próxima cita:",
"glp1LastLabel": "Última cita:",
"glp1LastInjection": "{label}",
"glp1LastInjectionWithSite": "{label} · {site}",
"glp1NextInjectionDays": "{label} (en {days} días)",
+1 -2
View File
@@ -700,6 +700,7 @@
"compliance7d": "Observance 7 jours",
"compliance30d": "Observance 30 jours",
"complianceWindow": "Observance {days} jours",
"complianceDoses": "{count} doses",
"lastIntake": "Dernière prise :",
"nextIntake": "Prochaine prise :",
"dayStreak": "jours de série",
@@ -745,8 +746,6 @@
"glp1DosesPerUnit": "Doses par stylo / flacon",
"glp1DosesPerUnitPlaceholder": "p. ex. 4 pour un stylo Mounjaro KwikPen",
"glp1DosesPerUnitHelp": "Optionnel — Mounjaro KwikPen = 4, Ozempic 0,25/0,5 mg = 4, Wegovy stylo à usage unique = 1. Laisse vide si tu ne veux pas suivre la dose par stylo.",
"glp1NextLabel": "Prochain rendez-vous :",
"glp1LastLabel": "Dernier rendez-vous :",
"glp1LastInjection": "{label}",
"glp1LastInjectionWithSite": "{label} · {site}",
"glp1NextInjectionDays": "{label} (dans {days} jours)",
+1 -2
View File
@@ -700,6 +700,7 @@
"compliance7d": "Aderenza 7 giorni",
"compliance30d": "Aderenza 30 giorni",
"complianceWindow": "Aderenza {days} giorni",
"complianceDoses": "{count} dosi",
"lastIntake": "Ultima assunzione:",
"nextIntake": "Prossima assunzione:",
"dayStreak": "giorni di serie",
@@ -745,8 +746,6 @@
"glp1DosesPerUnit": "Dosi per penna / fiala",
"glp1DosesPerUnitPlaceholder": "es. 4 per una penna Mounjaro KwikPen",
"glp1DosesPerUnitHelp": "Opzionale — Mounjaro KwikPen = 4, Ozempic 0,25/0,5 mg = 4, Wegovy penna monouso = 1. Lascia vuoto se non vuoi monitorare le dosi per penna.",
"glp1NextLabel": "Prossimo appuntamento:",
"glp1LastLabel": "Ultimo appuntamento:",
"glp1LastInjection": "{label}",
"glp1LastInjectionWithSite": "{label} · {site}",
"glp1NextInjectionDays": "{label} (tra {days} giorni)",
+1 -2
View File
@@ -700,6 +700,7 @@
"compliance7d": "Przestrzeganie 7 dni",
"compliance30d": "Przestrzeganie 30 dni",
"complianceWindow": "Przestrzeganie {days} dni",
"complianceDoses": "{count} dawek",
"lastIntake": "Ostatnie przyjęcie:",
"nextIntake": "Następne przyjęcie:",
"dayStreak": "dni serii",
@@ -745,8 +746,6 @@
"glp1DosesPerUnit": "Dawki na pen / fiolkę",
"glp1DosesPerUnitPlaceholder": "np. 4 dla pena Mounjaro KwikPen",
"glp1DosesPerUnitHelp": "Opcjonalne — Mounjaro KwikPen = 4, Ozempic 0,25/0,5 mg = 4, Wegovy pen jednorazowy = 1. Zostaw puste, jeśli nie chcesz śledzić dawek na pen.",
"glp1NextLabel": "Następny termin:",
"glp1LastLabel": "Ostatni termin:",
"glp1LastInjection": "{label}",
"glp1LastInjectionWithSite": "{label} · {site}",
"glp1NextInjectionDays": "{label} (za {days} dni)",
@@ -320,14 +320,16 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
client,
);
// The last-line renders the "Last:" label in its own left column and
// v1.15.8 — the GLP-1 card unified onto the generic "Next intake" /
// "Last intake" labels (the appointment-phrased override is gone). The
// last-line renders the "Last intake:" label in its own left column and
// the value (label · site) flush right; both halves show in the SSR.
expect(html).toContain("Last:");
expect(html).toContain("Last intake:");
expect(html).toContain("Abdomen, lower left");
// The next-line carries the "Next:" label in its left column,
// The next-line carries the "Next intake:" label in its left column,
// independent of which of the three value variants (today / tomorrow /
// "in N days") the helper produced.
expect(html).toMatch(/Next:/);
expect(html).toMatch(/Next intake:/);
});
it("no longer renders the inline dose-history disclosure (retired v1.4.28)", () => {
@@ -606,7 +608,9 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
// visually symmetric with the generic card on the medications list.
expect(html).toContain("Sonstiges");
expect(html).not.toContain("GLP-1-Injektion");
expect(html).toContain("Letzter Termin:");
// v1.15.8 — unified onto the generic "Letzte Einnahme:" label (the
// appointment-phrased "Letzter Termin:" override is gone).
expect(html).toContain("Letzte Einnahme:");
expect(html).toContain("Bauch, unten links");
// v1.4.28 retired the "Dosis-Historie" disclosure on the GLP-1 card.
expect(html).not.toContain("Dosis-Historie");
@@ -22,16 +22,18 @@ import { useTranslations } from "@/lib/i18n/context";
* that renders both lines, so the dose rows line up across the grid.
*
* Each card still builds the value *content* itself (the generic card emits a
* day-label + window-range; the GLP-1 card emits a weekly-cadence string),
* because the two cadences read differently — but the wrapper, order, colour,
* label/value layout, and gating are now guaranteed identical. The GLP-1 card
* overrides the left-column label with its appointment-phrased wording.
* day-label + window-range; the GLP-1 card emits a relative-day cadence
* string), because the two cadences read differently — but the wrapper,
* order, colour, label/value layout, and gating are guaranteed identical.
* v1.15.8 dropped the GLP-1-specific appointment-phrased label override so
* both cards name the concept identically ("Next intake" / "Last intake");
* only the value content stays per-card.
*
* Layout: when `labelled`, each line is a `flex justify-between` row with the
* bold label pinned left and the value flush right (`text-right`), so the
* label and the time read as two distinct columns rather than crammed onto
* one run of text. The label truncates first on a narrow viewport; the value
* (the time / cadence) never truncates.
* Layout: each line is a `flex justify-between` row with the bold label
* pinned left and the value flush right (`text-right`), so the label and the
* time read as two distinct columns rather than crammed onto one run of text.
* The label truncates first on a narrow viewport; the value (the time /
* cadence) never truncates.
*/
interface MedicationNextLastSlotProps {
/**
@@ -42,57 +44,32 @@ interface MedicationNextLastSlotProps {
next: React.ReactNode | null;
/** The last-intake line content, or null when the med has never been taken. */
last: React.ReactNode | null;
/**
* When true the part renders each line as a two-column row: a bold label
* pinned left and the value flush right. The generic card uses the default
* oral "Next intake" / "Last intake" labels; the GLP-1 card overrides them
* with its appointment-phrased labels (see `nextLabel` / `lastLabel`).
* When false the line renders as a single full-width run of text.
*/
labelled?: boolean;
/**
* Override for the left-column label on the next-line. Defaults to the
* oral `medications.nextIntake` string. The GLP-1 card passes its own
* "next appointment" wording so the value column carries only the time.
*/
nextLabel?: React.ReactNode;
/** Override for the left-column label on the last-line. */
lastLabel?: React.ReactNode;
}
export function MedicationNextLastSlot({
next,
last,
labelled = true,
nextLabel,
lastLabel,
}: MedicationNextLastSlotProps) {
const { t } = useTranslations();
return (
<div className="min-h-[2.75rem] space-y-3.5 text-sm">
{next &&
(labelled ? (
<div className="text-muted-foreground flex items-baseline justify-between gap-3">
<span className="min-w-0 flex-shrink truncate font-medium">
{nextLabel ?? t("medications.nextIntake")}
</span>
<span className="text-right">{next}</span>
</div>
) : (
<p className="text-muted-foreground">{next}</p>
))}
{last &&
(labelled ? (
<div className="text-muted-foreground flex items-baseline justify-between gap-3">
<span className="min-w-0 flex-shrink truncate font-medium">
{lastLabel ?? t("medications.lastIntake")}
</span>
<span className="text-right">{last}</span>
</div>
) : (
<p className="text-muted-foreground">{last}</p>
))}
{next && (
<div className="text-muted-foreground flex items-baseline justify-between gap-3">
<span className="min-w-0 flex-shrink truncate font-medium">
{t("medications.nextIntake")}
</span>
<span className="text-right">{next}</span>
</div>
)}
{last && (
<div className="text-muted-foreground flex items-baseline justify-between gap-3">
<span className="min-w-0 flex-shrink truncate font-medium">
{t("medications.lastIntake")}
</span>
<span className="text-right">{last}</span>
</div>
)}
</div>
);
}
@@ -285,6 +285,13 @@ export function Glp1MedicationCard({
const rate7 = display?.short.rate ?? compliance?.compliance7?.rate ?? 0;
const rate30 = display?.long.rate ?? compliance?.compliance30?.rate ?? 0;
const streak = display?.short.streak ?? compliance?.compliance7?.streak ?? 0;
// v1.15.8 — taken-of-expected counts, shown after each percentage so two
// identical rates stay distinguishable (a weekly GLP-1 reading 100% on
// both windows shows e.g. `100% · 4 / 4` vs `100% · 13 / 13`).
const takenShort = display?.short.taken;
const expectedShort = display?.expectedShort;
const takenLong = display?.long.taken;
const expectedLong = display?.expectedLong;
// v1.4.37 W4b — symmetric take-now / overdue pill with the generic
// card. Sort schedules by `windowStart` so the most-actionable
@@ -403,14 +410,14 @@ export function Glp1MedicationCard({
{/* Injection state — next + last, rendered through the SAME shared
<MedicationNextLastSlot> the generic card uses, so the order
(next-then-last), colour, spacing, gating, reserved min-height
and the label-left / value-right two-column layout are identical
across the two card types. The GLP-1 card overrides only the
left-column label with its appointment-phrased wording; the value
column carries the time (plus the optional rotation nudge). */}
(next-then-last), labels ("Next intake" / "Last intake"), colour,
spacing, gating, reserved min-height and the label-left /
value-right two-column layout are identical across the two card
types. v1.15.8 dropped the GLP-1-specific appointment-phrased
label override so both cards name the concept identically; the
relative-day value phrasing ("in N days" / "today") stays the
GLP-1 card's own and is preserved. */}
<MedicationNextLastSlot
nextLabel={t("medications.glp1NextLabel")}
lastLabel={t("medications.glp1LastLabel")}
next={
next && currentWindowStatus.status !== "in_window" ? (
<>
@@ -462,6 +469,10 @@ export function Glp1MedicationCard({
streak={streak}
shortDays={shortDays}
longDays={longDays}
takenShort={takenShort}
expectedShort={expectedShort}
takenLong={takenLong}
expectedLong={expectedLong}
/>
) : (
<MedicationComplianceSkeleton />