Merge w10/vorsorgedash into release/v1.18.10

Move the Vorsorge cadence into the metric row, link card headings to filtered
results, tighten the daily-briefing card rhythm, and invalidate the dashboard
snapshot on briefing regenerate and layout save so the Startseite refreshes.
This commit is contained in:
Marc-André Bombeck committed 2026-06-20 16:51:59 +02:00
commit d08e462136
5 files changed
+70 -10

No files matched your search

+7 -2
View File
@@ -302,7 +302,7 @@ function BriefingSkeleton() {
return (
<div
data-slot="daily-briefing-skeleton"
className="space-y-4 motion-reduce:animate-none"
className="space-y-3 motion-reduce:animate-none"
aria-hidden="true"
>
<div className="space-y-2">
@@ -367,7 +367,12 @@ export function DailyBriefing({
<BriefingSkeleton />
</>
) : briefing ? (
<div className="space-y-4">
// v1.18.10 — body block rhythm matches the sibling insights cards
// (`correlation-card` uses `space-y-3` between its chart /
// interpretation / source blocks). The briefing card carried a
// looser `space-y-4`, which read as taller than its neighbours on
// the overview. One token, no new spacing scale.
<div className="space-y-3">
{/* v1.4.27 B1 — the leading narrative paragraph dropped.
The hero strip subtitle on `/insights` already renders
the same `briefing.paragraph` text directly above this
@@ -330,6 +330,15 @@ export function useInsightsAdvisorQuery(
// their query subtree so the per-section text below the advisor card
// re-fetches.
queryClient.invalidateQueries({ queryKey: queryKeys.insightsRoot() });
// v1.18.10 — the dashboard hero surfaces the briefing headline from the
// SNAPSHOT (server-lifted `User.insightsCachedText`), not from this
// advisor cache. A regenerate rewrites that server cache, so invalidate
// the snapshot too — otherwise returning to the Startseite shows the
// previous briefing under the greeting until the snapshot's own stale
// window lapses. Closes "the briefing doesn't update" on the dashboard.
queryClient.invalidateQueries({
queryKey: queryKeys.dashboardSnapshot(),
});
},
});
@@ -770,11 +770,19 @@ function VorsorgeCard({
? t(`measurementReminders.types.${reminder.measurementType}`)
: t("measurementReminders.selfPlanned");
// v1.18.6.1 — the cadence renders as a chip DIRECTLY to the right of the
// metric name (the med-card convention: cadence sits next to the name).
const cadenceChip = cadence ? (
<Badge variant="secondary">{cadence}</Badge>
) : null;
// v1.18.10 — the cadence reads as part of the METRIC ROW (the category
// line), styled like the metric label itself — same muted weight/colour,
// no shaded pill. The card heading stays clean (nothing beside it); the
// cadence rides next to "Blutdruck" et al. via `metricMeta` below.
const cadenceText = cadence || null;
// v1.18.10 — a measurement-linked reminder's heading links to the
// measurements history pre-filtered to its type (the same target the
// "Messergebnisse anzeigen" kebab item uses). A free-text reminder has no
// readings to show, so it stays a plain (non-linking) heading.
const measurementsHref = isLinked
? `/measurements?type=${encodeURIComponent(reminder.measurementType as string)}`
: undefined;
// Provenance / disabled state badges keep their own row below the category.
// Omit the row entirely when neither applies (don't render an empty band).
@@ -900,13 +908,15 @@ function VorsorgeCard({
<span className="truncate font-medium">
{resolveReminderLabel(reminder, t)}
</span>
{cadenceChip}
</div>
{/* v1.18.10 — cadence rides on the metric line, styled like the
metric text (no shaded pill). */}
<div className="text-muted-foreground flex flex-wrap items-center gap-x-2 text-xs">
<span className="text-foreground">
{t(`measurementReminders.${due.key}`, { days: due.days })}
</span>
<span>{categoryLabel}</span>
{cadenceText ? <span>{cadenceText}</span> : null}
</div>
</div>
<div className="flex shrink-0 items-center gap-1">
@@ -991,7 +1001,13 @@ function VorsorgeCard({
name={resolveReminderLabel(reminder, t)}
dose=""
categoryLabel={categoryLabel}
nameChip={cadenceChip}
metricMeta={cadenceText}
href={measurementsHref}
linkLabel={
measurementsHref
? t("measurementReminders.showMeasurements")
: undefined
}
stateBadges={stateBadges}
actions={headerActions}
/>
@@ -43,6 +43,15 @@ export interface MedicationCardHeaderProps {
* from `stateBadges` (which ride their own row below the category).
*/
nameChip?: ReactNode;
/**
* v1.18.10 — inline text rendered DIRECTLY beside the category label on
* line 2 (e.g. the Vorsorge cadence "alle 7 Tage"). Unlike `nameChip`
* (a chip on the title line) this reads as part of the metric row itself,
* inheriting the muted line's weight/colour rather than a shaded pill, so
* the cadence sits next to the metric name without an alarming grey
* background.
*/
metricMeta?: ReactNode;
stateBadges?: ReactNode;
actions?: ReactNode;
/**
@@ -60,6 +69,7 @@ export function MedicationCardHeader({
dose,
categoryLabel,
nameChip,
metricMeta,
stateBadges,
actions,
href,
@@ -81,10 +91,16 @@ export function MedicationCardHeader({
State badges now ride their own row below the category
badge so the canonical row stays two lines on narrow
viewports. */}
<div className="text-muted-foreground flex items-center gap-2 text-sm">
<div className="text-muted-foreground flex flex-wrap items-center gap-x-2 gap-y-1 text-sm">
<Badge variant="outline" className="text-xs">
{categoryLabel}
</Badge>
{/* v1.18.10 — cadence (and any future metric meta) reads as part of
the metric line itself: same muted weight/colour as the row, no
shaded pill. */}
{metricMeta ? (
<span className="text-muted-foreground text-xs">{metricMeta}</span>
) : null}
</div>
{stateBadges ? (
<div
@@ -204,6 +204,15 @@ export function DashboardLayoutSection({ id }: { id: string }) {
},
onSuccess: (saved) => {
queryClient.setQueryData(queryKeys.dashboardWidgets(), saved);
// v1.18.10 — the Startseite reads its layout (tile/chart visibility,
// order, comparison baseline, hero toggle) from the dashboard
// SNAPSHOT, not from this `dashboardWidgets` key. Without an
// invalidation the snapshot keeps its cached layout and the dashboard
// looks stale until a manual reload. Invalidate the snapshot so the
// next visit (or open tab) re-reads the saved layout immediately.
queryClient.invalidateQueries({
queryKey: queryKeys.dashboardSnapshot(),
});
setDraft(null);
toast.success(t("dashboard.layoutSaveSuccess"));
},
@@ -216,6 +225,11 @@ export function DashboardLayoutSection({ id }: { id: string }) {
},
onSuccess: (saved) => {
queryClient.setQueryData(queryKeys.dashboardWidgets(), saved);
// v1.18.10 — same snapshot invalidation as the save path so a reset
// to defaults reflects on the Startseite without a manual reload.
queryClient.invalidateQueries({
queryKey: queryKeys.dashboardSnapshot(),
});
setDraft(null);
toast.success(t("dashboard.layoutResetSuccess"));
},