fix(checkups): show the Coach and switched-off badges in the list view

The card view marks a checkup the Coach created and one that is
switched off; the list view drew neither, so a disabled checkup looked
active there. Both views now take the badges from one helper, and the
list row shows them beside the title.
This commit is contained in:
Marc-André Bombeck committed 2026-11-11 22:31:26 +01:00
1 parent d77fe9aa7d
commit 71d4516808
2 files changed
+104 -15

No files matched your search

@@ -0,0 +1,74 @@
import { describe, expect, it, vi } from "vitest";
import { renderToStaticMarkup } from "react-dom/server";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { I18nProvider } from "@/lib/i18n/context";
import type { MeasurementReminder } from "@/hooks/use-measurement-reminders";
/**
* A checkup that is switched off, or that the Coach created, says so in the
* card view. The list view used to drop both badges, so a disabled checkup
* looked active there; which view a person sees is a remembered preference,
* so the same checkup read differently depending on a toggle.
*/
vi.mock("next/navigation", () => ({
useRouter: () => ({ replace: vi.fn(), push: vi.fn() }),
}));
import { VorsorgeCard } from "../checkups/checkup-card";
const reminder = {
id: "coach-off",
label: "Check blood pressure",
measurementType: null,
intervalDays: 30,
rrule: null,
anchorDate: null,
endsOn: null,
origin: "COACH",
nextDueAt: null,
notifyHour: 9,
location: null,
lastSatisfiedAt: null,
enabled: false,
createdAt: "2030-01-01T00:00:00.000Z",
updatedAt: "2030-01-01T00:00:00.000Z",
} as MeasurementReminder;
function render(view: "cards" | "list"): string {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
const noop = () => {};
return renderToStaticMarkup(
<QueryClientProvider client={queryClient}>
<I18nProvider initialLocale="en">
<VorsorgeCard
reminder={reminder}
now={Date.parse("2030-02-01T12:00:00.000Z")}
view={view}
onPrimaryAction={noop}
onFileVisit={noop}
onRemove={noop}
onEdit={noop}
onSkip={noop}
onSnooze={noop}
busy={false}
/>
</I18nProvider>
</QueryClientProvider>,
);
}
describe("checkup state badges", () => {
it.each(["cards", "list"] as const)(
"show the Coach and switched-off badges in the %s view",
(view) => {
const html = render(view);
expect(html).toContain("Check blood pressure");
expect(html).toContain(">Coach<");
expect(html).toContain(">Off<");
},
);
});
@@ -244,6 +244,30 @@ function NextLastSlot({
);
}
/**
* The Coach-origin and switched-off badges, shared by the card and the list
* view; `undefined` when neither applies, so no empty row is drawn.
*/
function checkupStateBadges(
reminder: MeasurementReminder,
t: (key: string) => string,
): React.ReactNode {
const isCoach = reminder.origin === "COACH";
if (!isCoach && reminder.enabled) return undefined;
return (
<>
{isCoach && (
<Badge variant="outline">{t("measurementReminders.originCoach")}</Badge>
)}
{!reminder.enabled && (
<Badge variant="outline">
{t("measurementReminders.disabledBadge")}
</Badge>
)}
</>
);
}
/** The dense list variant of a checkup. */
function CheckupListRow({
reminder,
@@ -251,6 +275,7 @@ function CheckupListRow({
categoryLabel,
cadenceText,
meta,
stateBadges,
isLinked,
isScreening,
canManageMeasurements,
@@ -265,6 +290,8 @@ function CheckupListRow({
categoryLabel: string;
cadenceText: string | null;
meta: React.ReactNode;
/** The same Coach and disabled badges the card view shows. */
stateBadges: React.ReactNode;
isLinked: boolean;
isScreening: boolean;
canManageMeasurements: boolean;
@@ -283,6 +310,7 @@ function CheckupListRow({
<span className="truncate font-medium">
{resolveReminderLabel(reminder, t)}
</span>
{stateBadges}
</div>
{/* Cadence rides on the metric line, styled like the
metric text (no shaded pill). */}
@@ -537,7 +565,6 @@ export function VorsorgeCard({
: reminder.rrule
? t("measurementReminders.cadence.custom")
: "";
const isCoach = reminder.origin === "COACH";
const isLinked = reminder.measurementType != null;
// A screening reminder's action starts the check-in, never numeric entry.
const isScreening = isScreeningReminderType(reminder.measurementType);
@@ -558,20 +585,7 @@ export function VorsorgeCard({
? `/measurements?type=${encodeURIComponent(reminder.measurementType as string)}`
: undefined;
// Provenance and disabled badges get a row only when one applies.
const hasStateBadges = isCoach || !reminder.enabled;
const stateBadges = hasStateBadges ? (
<>
{isCoach && (
<Badge variant="outline">{t("measurementReminders.originCoach")}</Badge>
)}
{!reminder.enabled && (
<Badge variant="outline">
{t("measurementReminders.disabledBadge")}
</Badge>
)}
</>
) : undefined;
const stateBadges = checkupStateBadges(reminder, t);
const headerActions = canManageMeasurements ? (
<CheckupMenu
@@ -643,6 +657,7 @@ export function VorsorgeCard({
categoryLabel={categoryLabel}
cadenceText={cadenceText}
meta={skipSnoozeMeta}
stateBadges={stateBadges}
isLinked={isLinked}
isScreening={isScreening}
canManageMeasurements={canManageMeasurements}