mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 20:42:43 +00:00
fix(medications): split the card next/last dose lines into label-left, value-right rows
The next-intake and last-intake lines on the medication cards ran the label and the time together on one run of text, reading as crammed. Lay each line out as a two-column row — bold label pinned left, value flush right — so the two rows read as distinct, aligned columns. The label truncates first on a narrow viewport; the time never truncates. The GLP-1 card routes through the same shared slot and now overrides only the left-column label with its appointment-phrased wording, so the value column carries the time and the optional rotation nudge. Its value strings drop the now-redundant leading label. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
c59290ddc6
commit
83cf4eff2e
10 files changed
+114
-75
No files matched your search
@@ -320,13 +320,13 @@ describe("<Glp1MedicationCard> — GLP-1 variant rendering", () => {
|
||||
client,
|
||||
);
|
||||
|
||||
// The with-site copy reads "Last: <label> · <site>"; both halves
|
||||
// must show up in the SSR string.
|
||||
// The last-line renders the "Last:" 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("Abdomen, lower left");
|
||||
// The next-injection helper produces one of three strings — today,
|
||||
// tomorrow, or "in N days". All three contain the localised "Next:"
|
||||
// prefix from `glp1NextInjection*` keys.
|
||||
// The next-line carries the "Next:" 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:/);
|
||||
});
|
||||
|
||||
|
||||
@@ -448,11 +448,19 @@ describe("medication card symmetry — Ramipril vs Mounjaro", () => {
|
||||
expect(html).not.toContain("text-foreground/85");
|
||||
// No literal em-dash placeholder for an absent next-injection.
|
||||
expect(html).not.toContain(">—<");
|
||||
// Both lines present (next + last), so two muted <p> in the slot.
|
||||
// Both lines present (next + last). Each line is a two-column
|
||||
// label-left / value-right row (`flex … justify-between`) so the
|
||||
// label and the time read as distinct columns rather than crammed
|
||||
// onto one run of text. Two such rows in the slot.
|
||||
const slotStart = html.indexOf("min-h-[2.75rem]");
|
||||
const slot = html.slice(slotStart, slotStart + 800);
|
||||
const lines = slot.match(/<p class="text-muted-foreground">/g) ?? [];
|
||||
expect(lines.length).toBe(2);
|
||||
const slot = html.slice(slotStart, slotStart + 1200);
|
||||
const rows =
|
||||
slot.match(
|
||||
/<div class="text-muted-foreground flex items-baseline justify-between gap-3">/g,
|
||||
) ?? [];
|
||||
expect(rows.length).toBe(2);
|
||||
// The value is pinned flush-right in its own column.
|
||||
expect(slot).toContain('<span class="text-right">');
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -21,10 +21,17 @@ import { useTranslations } from "@/lib/i18n/context";
|
||||
* whose last-dose line is absent the same vertical footprint as a sibling
|
||||
* that renders both lines, so the dose rows line up across the grid.
|
||||
*
|
||||
* Each card still builds the line *content* itself (the generic card emits a
|
||||
* day-label + window-range; the GLP-1 card emits a weekly-cadence prose
|
||||
* string), because the two cadences read differently — but the wrapper,
|
||||
* order, colour, and gating are now guaranteed identical.
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
interface MedicationNextLastSlotProps {
|
||||
/**
|
||||
@@ -36,45 +43,56 @@ interface MedicationNextLastSlotProps {
|
||||
/** The last-intake line content, or null when the med has never been taken. */
|
||||
last: React.ReactNode | null;
|
||||
/**
|
||||
* When true (the generic card) the part prepends the bold
|
||||
* "Next intake" / "Last intake" labels. The GLP-1 card passes its own
|
||||
* site-aware prose (whose i18n strings already read "Next injection …" /
|
||||
* "Last injection …"), so it opts out and supplies the full line content.
|
||||
* Either way the wrapper, order, colour, spacing, gating and reserved
|
||||
* min-height are identical.
|
||||
* 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 && (
|
||||
<p className="text-muted-foreground">
|
||||
{labelled && (
|
||||
<>
|
||||
<span className="font-medium">{t("medications.nextIntake")}</span>{" "}
|
||||
</>
|
||||
)}
|
||||
{next}
|
||||
</p>
|
||||
)}
|
||||
{last && (
|
||||
<p className="text-muted-foreground">
|
||||
{labelled && (
|
||||
<>
|
||||
<span className="font-medium">{t("medications.lastIntake")}</span>{" "}
|
||||
</>
|
||||
)}
|
||||
{last}
|
||||
</p>
|
||||
)}
|
||||
{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>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -410,13 +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 and reserved
|
||||
min-height are identical across the two card types. The GLP-1
|
||||
phrasing carries its own "Next / Last injection" wording (and
|
||||
the rotation nudge), so it opts out of the part's bold label
|
||||
prefix and supplies the full line content. */}
|
||||
(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). */}
|
||||
<MedicationNextLastSlot
|
||||
labelled={false}
|
||||
nextLabel={t("medications.glp1NextLabel")}
|
||||
lastLabel={t("medications.glp1LastLabel")}
|
||||
next={
|
||||
next && currentWindowStatus.status !== "in_window" ? (
|
||||
<>
|
||||
|
||||
Reference in new issue
Block a user