{t("measurementReminders.nextDueLabel")}
-
+
{t(`measurementReminders.${due.key}`, { days: due.days })}
@@ -972,12 +989,6 @@ function VorsorgeCard({
{nextLastSlot}
- {/* v1.18.7 (Wave E) — discreet 7-day strip of the metric's last
- readings, sitting under the metric context like the dashboard
- tiles. Renders nothing for a free-text reminder or a too-thin
- window, so the card height stays stable when absent. */}
-
-
{/* v1.18.6 (MOD-06) — light gamification: progress through the
current interval toward next-due, reusing the medication card's
Progress building block. Honest "on track / due" signal without
diff --git a/src/components/measurement-reminders/vorsorge-trend-strip.tsx b/src/components/measurement-reminders/vorsorge-trend-strip.tsx
deleted file mode 100644
index 8250e273d..000000000
--- a/src/components/measurement-reminders/vorsorge-trend-strip.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-"use client";
-
-/**
- * v1.18.7 (Wave E) — a small, DISCREET 7-day trend strip for a Vorsorge
- * card. Renders the reminder metric's last seven readings as seven quiet
- * bars (newest → rightmost), heights normalised across the window. The
- * colour is intentionally muted (`bg-muted-foreground/30`) so the strip
- * reads as calm context sitting under the metric — never an alarming
- * status signal, in keeping with the no-loud-colour card ethos.
- *
- * Pulls the bounded last-7 readings for one `MeasurementType` through the
- * existing measurements list endpoint (a single `limit=7` read per card,
- * cached by type) — no new heavy aggregate query. A free-text reminder
- * (no `measurementType`) renders nothing; a metric with fewer than two
- * readings in the window renders nothing (a single dot is no trend).
- */
-import { useQuery } from "@tanstack/react-query";
-
-import { apiGet } from "@/lib/api/api-fetch";
-import { useTranslations } from "@/lib/i18n/context";
-import { queryKeys } from "@/lib/query-keys";
-
-interface RecentMeasurement {
- value: number;
- measuredAt: string;
-}
-
-/**
- * Bounded last-7 read for one measurement type, sorted newest-first by the
- * server. Re-sorted oldest → newest here so the bars read left (older) to
- * right (newest). Shares the `measurements` invalidation prefix so a fresh
- * reading (e.g. completing this very reminder) repaints the strip.
- */
-function useRecentValues(measurementType: string | null, enabled: boolean) {
- return useQuery({
- queryKey: queryKeys.measurementRecentValues(measurementType ?? ""),
- queryFn: async () => {
- const params = new URLSearchParams();
- params.set("type", measurementType as string);
- params.set("limit", "7");
- params.set("sortBy", "measuredAt");
- params.set("sortDir", "desc");
- const res = await apiGet<{ measurements: RecentMeasurement[] }>(
- `/api/measurements?${params}`,
- );
- return res.measurements.slice().reverse();
- },
- enabled: enabled && measurementType != null,
- });
-}
-
-export function VorsorgeTrendStrip({
- measurementType,
- enabled = true,
-}: {
- measurementType: string | null;
- enabled?: boolean;
-}) {
- const { t } = useTranslations();
- const { data } = useRecentValues(measurementType, enabled);
-
- // Nothing to show for a free-text reminder or a too-thin window.
- if (measurementType == null) return null;
- const readings = data ?? [];
- if (readings.length < 2) return null;
-
- const values = readings.map((r) => r.value);
- const min = Math.min(...values);
- const max = Math.max(...values);
- const span = max - min || 1;
-
- // 7-day direction from first → last reading in the window. Latest value
- // and direction feed the sr-only summary so a screen reader gets the
- // trend, not just the generic "Last 7 days" label.
- const latest = values[values.length - 1];
- const delta = latest - values[0];
- const direction =
- delta > 0
- ? t("measurementReminders.trendStrip.rising")
- : delta < 0
- ? t("measurementReminders.trendStrip.falling")
- : t("measurementReminders.trendStrip.steady");
-
- return (
-
-
- {t("measurementReminders.trendStrip.summary", {
- value: latest,
- direction,
- })}
-
- {readings.map((r, i) => {
- // Floor at ~18% so a flat series still reads as bars, not a line.
- const pct = 0.18 + ((r.value - min) / span) * 0.82;
- return (
-
- );
- })}
-
- );
-}
diff --git a/src/lib/i18n/relative-time.ts b/src/lib/i18n/relative-time.ts
index d6a901396..3560ed1f9 100644
--- a/src/lib/i18n/relative-time.ts
+++ b/src/lib/i18n/relative-time.ts
@@ -47,3 +47,42 @@ export function formatRelativeTime(
{ count: days },
);
}
+
+/**
+ * v1.18.9 — date-only relative label, shared with the medication card grammar.
+ *
+ * Buckets a calendar date against "now": same day → `medications.today`
+ * ("Heute"), the day before → `medications.yesterday` ("Gestern"), otherwise
+ * the absolute date through the caller's locale formatter. The today /
+ * yesterday i18n keys are the SAME ones the medication card renders for its
+ * last-taken line, so a Vorsorge "zuletzt erledigt" reads identically.
+ *
+ * Day-bucketing uses an `en-CA` (YYYY-MM-DD) day key — locale-independent and
+ * string-comparable — formatted in the supplied IANA `timeZone` so the
+ * boundary follows the user's zone, not the server's. `formatDate` renders the
+ * absolute fallback (pass the locale `fmt.date`).
+ */
+export function relativeCalendarDate(
+ iso: string,
+ t: (key: string, params?: Record) => string,
+ formatDate: (value: Date) => string,
+ timeZone?: string,
+): string {
+ const target = new Date(iso);
+ if (Number.isNaN(target.getTime())) return "";
+ const dayFormatter = new Intl.DateTimeFormat("en-CA", {
+ timeZone,
+ year: "numeric",
+ month: "2-digit",
+ day: "2-digit",
+ });
+ const now = new Date();
+ const yesterday = new Date(now);
+ yesterday.setDate(yesterday.getDate() - 1);
+
+ const targetDay = dayFormatter.format(target);
+ if (targetDay === dayFormatter.format(now)) return t("medications.today");
+ if (targetDay === dayFormatter.format(yesterday))
+ return t("medications.yesterday");
+ return formatDate(target);
+}
diff --git a/src/lib/query-keys/measurements.ts b/src/lib/query-keys/measurements.ts
index 7daefb474..767df552d 100644
--- a/src/lib/query-keys/measurements.ts
+++ b/src/lib/query-keys/measurements.ts
@@ -145,16 +145,6 @@ export const measurementKeys = {
personalRecordsByMetric: (metric: string) =>
["personal-records", "by-metric", metric] as const,
- /**
- * v1.18.7 (Wave E) — bounded last-7 readings for one `MeasurementType`,
- * powering the discreet 7-day trend strip on a Vorsorge card. Rides under
- * the `["measurements"]` prefix so it lands in `measurementDependentKeys`
- * and a fresh reading (e.g. satisfying the very reminder) repaints the
- * strip. Keyed by type so each card caches its own thin window.
- */
- measurementRecentValues: (type: string) =>
- ["measurements", "recent-values", type] as const,
-
/**
* v1.18.6 — batched dashboard daily series
* (`GET /api/measurements/series-batch`). Shares the `chart-data`