fix: preserve managed target defaults

- Return effective threshold ranges after managed updates\n- Localize closed Coach and Insights display inventories
This commit is contained in:
Marc-André Bombeck committed 2026-08-06 03:52:49 +02:00
1 parent 247ac8494c
commit 6a1d1dd6e8
10 files changed
+397 -18

No files matched your search

+41 -1
View File
@@ -4494,6 +4494,31 @@
"excludedMetrics": "Ausgeschlossene Messwerte",
"dataClusters": "Datenbereiche",
"customClusters": "Eigene Auswahl von Datenbereichen verwenden",
"metrics": {
"bp": "Blutdruck",
"weight": "Gewicht",
"pulse": "Puls",
"mood": "Stimmung",
"compliance": "Medikamenteneinnahme",
"hrv": "Herzfrequenzvariabilität",
"sleep": "Schlaf",
"restingHr": "Ruhepuls",
"steps": "Schritte",
"medications": "Medikamente",
"anthropometrics": "Körpermaße"
},
"clusters": {
"cardio": "Herz-Kreislauf",
"body": "Körperzusammensetzung",
"activity": "Aktivität",
"workouts": "Trainings",
"sleep": "Schlaf",
"mood": "Stimmung",
"glucose": "Glukose",
"medication": "Medikation",
"mobility": "Mobilität",
"environment": "Umgebung"
},
"value": "{value}"
},
"insights": {
@@ -4501,7 +4526,22 @@
"sections": "Übersichtsbereiche",
"tiles": "Navigationskacheln",
"item": "{id}",
"order": "Reihenfolge"
"order": "Reihenfolge",
"unknownItem": "Nicht verfügbare Erkenntnis",
"sectionLabels": {
"wellnessScores": "Wellnesswerte",
"dailyBriefing": "Tagesüberblick",
"vitals": "Vitalwerte",
"trends": "Trends",
"periodReview": "Periodenrückblick",
"cycleSummary": "Zyklusübersicht",
"signals": "Signale",
"rhythmEvents": "Rhythmusereignisse",
"healthStatus": "Gesundheitsstatus",
"breathing": "Atmung",
"labsChanges": "Laborveränderungen",
"ecg": "EKG"
}
}
},
"unavailableTitle": "In diesem geteilten Datensatz nicht verfügbar",
+41 -1
View File
@@ -4494,6 +4494,31 @@
"excludedMetrics": "Excluded metrics",
"dataClusters": "Data clusters",
"customClusters": "Use a custom cluster selection",
"metrics": {
"bp": "Blood pressure",
"weight": "Weight",
"pulse": "Pulse",
"mood": "Mood",
"compliance": "Medication adherence",
"hrv": "Heart rate variability",
"sleep": "Sleep",
"restingHr": "Resting heart rate",
"steps": "Steps",
"medications": "Medications",
"anthropometrics": "Body measurements"
},
"clusters": {
"cardio": "Cardiovascular health",
"body": "Body composition",
"activity": "Activity",
"workouts": "Workouts",
"sleep": "Sleep",
"mood": "Mood",
"glucose": "Glucose",
"medication": "Medication",
"mobility": "Mobility",
"environment": "Environment"
},
"value": "{value}"
},
"insights": {
@@ -4501,7 +4526,22 @@
"sections": "Overview sections",
"tiles": "Navigation tiles",
"item": "{id}",
"order": "Order"
"order": "Order",
"unknownItem": "Unavailable insight",
"sectionLabels": {
"wellnessScores": "Wellness scores",
"dailyBriefing": "Daily briefing",
"vitals": "Vitals",
"trends": "Trends",
"periodReview": "Period review",
"cycleSummary": "Cycle summary",
"signals": "Signals",
"rhythmEvents": "Rhythm events",
"healthStatus": "Health status",
"breathing": "Breathing",
"labsChanges": "Lab changes",
"ecg": "ECG"
}
}
},
"unavailableTitle": "Unavailable in this shared record",
+41 -1
View File
@@ -4494,6 +4494,31 @@
"excludedMetrics": "Métricas excluidas",
"dataClusters": "Grupos de datos",
"customClusters": "Usar una selección personalizada de grupos",
"metrics": {
"bp": "Presión arterial",
"weight": "Peso",
"pulse": "Pulso",
"mood": "Ánimo",
"compliance": "Adherencia a la medicación",
"hrv": "Variabilidad de la frecuencia cardíaca",
"sleep": "Sueño",
"restingHr": "Frecuencia cardíaca en reposo",
"steps": "Pasos",
"medications": "Medicamentos",
"anthropometrics": "Medidas corporales"
},
"clusters": {
"cardio": "Salud cardiovascular",
"body": "Composición corporal",
"activity": "Actividad",
"workouts": "Entrenamientos",
"sleep": "Sueño",
"mood": "Ánimo",
"glucose": "Glucosa",
"medication": "Medicación",
"mobility": "Movilidad",
"environment": "Entorno"
},
"value": "{value}"
},
"insights": {
@@ -4501,7 +4526,22 @@
"sections": "Secciones del resumen",
"tiles": "Mosaicos de navegación",
"item": "{id}",
"order": "Orden"
"order": "Orden",
"unknownItem": "Análisis no disponible",
"sectionLabels": {
"wellnessScores": "Puntuaciones de bienestar",
"dailyBriefing": "Resumen diario",
"vitals": "Signos vitales",
"trends": "Tendencias",
"periodReview": "Revisión del período",
"cycleSummary": "Resumen del ciclo",
"signals": "Señales",
"rhythmEvents": "Eventos de ritmo",
"healthStatus": "Estado de salud",
"breathing": "Respiración",
"labsChanges": "Cambios de laboratorio",
"ecg": "ECG"
}
}
},
"unavailableTitle": "No disponible en este registro compartido",
+41 -1
View File
@@ -4494,6 +4494,31 @@
"excludedMetrics": "Mesures exclues",
"dataClusters": "Groupes de données",
"customClusters": "Utiliser une sélection personnalisée de groupes",
"metrics": {
"bp": "Tension artérielle",
"weight": "Poids",
"pulse": "Pouls",
"mood": "Humeur",
"compliance": "Adhérence aux médicaments",
"hrv": "Variabilité de la fréquence cardiaque",
"sleep": "Sommeil",
"restingHr": "Fréquence cardiaque au repos",
"steps": "Pas",
"medications": "Médicaments",
"anthropometrics": "Mesures corporelles"
},
"clusters": {
"cardio": "Santé cardiovasculaire",
"body": "Composition corporelle",
"activity": "Activité",
"workouts": "Entraînements",
"sleep": "Sommeil",
"mood": "Humeur",
"glucose": "Glucose",
"medication": "Médicaments",
"mobility": "Mobilité",
"environment": "Environnement"
},
"value": "{value}"
},
"insights": {
@@ -4501,7 +4526,22 @@
"sections": "Sections d’aperçu",
"tiles": "Tuiles de navigation",
"item": "{id}",
"order": "Ordre"
"order": "Ordre",
"unknownItem": "Analyse indisponible",
"sectionLabels": {
"wellnessScores": "Scores de bien-être",
"dailyBriefing": "Brief quotidien",
"vitals": "Signes vitaux",
"trends": "Tendances",
"periodReview": "Bilan de période",
"cycleSummary": "Résumé du cycle",
"signals": "Signaux",
"rhythmEvents": "Événements de rythme",
"healthStatus": "État de santé",
"breathing": "Respiration",
"labsChanges": "Évolutions de laboratoire",
"ecg": "ECG"
}
}
},
"unavailableTitle": "Indisponible dans ce dossier partagé",
+41 -1
View File
@@ -4494,6 +4494,31 @@
"excludedMetrics": "Metriche escluse",
"dataClusters": "Gruppi di dati",
"customClusters": "Usa una selezione personalizzata di gruppi",
"metrics": {
"bp": "Pressione arteriosa",
"weight": "Peso",
"pulse": "Polso",
"mood": "Umore",
"compliance": "Aderenza ai farmaci",
"hrv": "Variabilità della frequenza cardiaca",
"sleep": "Sonno",
"restingHr": "Frequenza cardiaca a riposo",
"steps": "Passi",
"medications": "Farmaci",
"anthropometrics": "Misure corporee"
},
"clusters": {
"cardio": "Salute cardiovascolare",
"body": "Composizione corporea",
"activity": "Attività",
"workouts": "Allenamenti",
"sleep": "Sonno",
"mood": "Umore",
"glucose": "Glucosio",
"medication": "Farmaci",
"mobility": "Mobilità",
"environment": "Ambiente"
},
"value": "{value}"
},
"insights": {
@@ -4501,7 +4526,22 @@
"sections": "Sezioni della panoramica",
"tiles": "Riquadri di navigazione",
"item": "{id}",
"order": "Ordine"
"order": "Ordine",
"unknownItem": "Analisi non disponibile",
"sectionLabels": {
"wellnessScores": "Punteggi di benessere",
"dailyBriefing": "Riepilogo giornaliero",
"vitals": "Parametri vitali",
"trends": "Tendenze",
"periodReview": "Riepilogo del periodo",
"cycleSummary": "Riepilogo del ciclo",
"signals": "Segnali",
"rhythmEvents": "Eventi del ritmo",
"healthStatus": "Stato di salute",
"breathing": "Respirazione",
"labsChanges": "Variazioni di laboratorio",
"ecg": "ECG"
}
}
},
"unavailableTitle": "Non disponibile in questo record condiviso",
+41 -1
View File
@@ -4494,6 +4494,31 @@
"excludedMetrics": "Wykluczone metryki",
"dataClusters": "Grupy danych",
"customClusters": "Użyj własnego wyboru grup",
"metrics": {
"bp": "Ciśnienie krwi",
"weight": "Masa ciała",
"pulse": "Tętno",
"mood": "Nastrój",
"compliance": "Przestrzeganie przyjmowania leków",
"hrv": "Zmienność rytmu serca",
"sleep": "Sen",
"restingHr": "Tętno spoczynkowe",
"steps": "Kroki",
"medications": "Leki",
"anthropometrics": "Wymiary ciała"
},
"clusters": {
"cardio": "Zdrowie sercowo-naczyniowe",
"body": "Skład ciała",
"activity": "Aktywność",
"workouts": "Treningi",
"sleep": "Sen",
"mood": "Nastrój",
"glucose": "Glukoza",
"medication": "Leki",
"mobility": "Mobilność",
"environment": "Środowisko"
},
"value": "{value}"
},
"insights": {
@@ -4501,7 +4526,22 @@
"sections": "Sekcje przeglądu",
"tiles": "Kafelki nawigacji",
"item": "{id}",
"order": "Kolejność"
"order": "Kolejność",
"unknownItem": "Niedostępna analiza",
"sectionLabels": {
"wellnessScores": "Wyniki dobrostanu",
"dailyBriefing": "Codzienny przegląd",
"vitals": "Parametry życiowe",
"trends": "Trendy",
"periodReview": "Przegląd okresu",
"cycleSummary": "Podsumowanie cyklu",
"signals": "Sygnały",
"rhythmEvents": "Zdarzenia rytmu",
"healthStatus": "Stan zdrowia",
"breathing": "Oddychanie",
"labsChanges": "Zmiany wyników laboratoryjnych",
"ecg": "EKG"
}
}
},
"unavailableTitle": "Niedostępne w tym udostępnionym rekordzie",
@@ -297,11 +297,16 @@ export const PATCH = apiHandler(
...((current?.thresholdsJson ?? {}) as ThresholdOverridesJson),
...patch.overrides,
};
await prisma.user.update({
const record = await prisma.user.update({
where: { id: access.recordId },
data: { thresholdsJson: toJson(overrides) },
select: { heightCm: true, dateOfBirth: true, gender: true },
});
settings = { overrides };
invalidateUserProfile(access.recordId);
settings = {
overrides,
effective: getAllEffectiveRanges(record, overrides),
};
changed = Object.keys(patch.overrides);
break;
}
@@ -7,12 +7,15 @@ import { ALL_METRICS } from "@/lib/validations/thresholds";
import { ManagedRecordSettingsForm } from "../managed-record-settings-section";
const LOCALES = ["de", "en", "es", "fr", "it", "pl"] as const;
const render = (
family: Parameters<typeof ManagedRecordSettingsForm>[0]["family"],
settings: Record<string, unknown>,
locale: (typeof LOCALES)[number] = "en",
) =>
renderToStaticMarkup(
<I18nProvider initialLocale="en">
<I18nProvider initialLocale={locale}>
<ManagedRecordSettingsForm
disabled={false}
family={family}
@@ -90,4 +93,36 @@ describe("managed record settings forms", () => {
expect(thresholds).toContain('value="10"');
expect(thresholds).toContain('value="20"');
});
it("uses localized display inventories instead of raw Coach and Insight IDs", () => {
for (const locale of LOCALES) {
const coach = render(
"coach",
{
preferences: {
excludeMetrics: ["bp"],
dataClusters: ["cardio"],
},
},
locale,
);
const insights = render(
"insights",
{
layout: {
version: 2,
sections: [{ id: "unknown-section", visible: true, order: 0 }],
tiles: [{ id: "unknown-tile", visible: true, order: 0 }],
},
},
locale,
);
expect(coach).not.toContain(">bp<");
expect(coach).not.toContain(">cardio<");
expect(insights).not.toContain(">unknown-section<");
expect(insights).not.toContain(">unknown-tile<");
expect(insights).not.toContain("settings.sharedRecord");
}
});
});
@@ -7,6 +7,10 @@ import { Settings } from "lucide-react";
import { useAuth } from "@/hooks/use-auth";
import { apiGet, apiPatch } from "@/lib/api/api-fetch";
import { useTranslations } from "@/lib/i18n/context";
import {
normalizeInsightsTileId,
type InsightsSectionId,
} from "@/lib/insights-layout";
import { MODULE_REGISTRY } from "@/lib/modules/registry";
import { queryKeys } from "@/lib/query-keys";
import {
@@ -20,6 +24,7 @@ import {
type ThresholdMetric,
} from "@/lib/analytics/effective-range";
import { ALL_METRICS } from "@/lib/validations/thresholds";
import { SUB_PAGE_TABS } from "@/components/insights/insights-tab-strip";
import { Button } from "@/components/ui/button";
import { QueryErrorCard } from "@/components/ui/query-error-card";
import { SettingsCardHeader } from "./_card-header";
@@ -67,6 +72,63 @@ const COACH_DATA_CLUSTERS = [
"mobility",
"environment",
] as const;
const COACH_EXCLUDE_METRIC_LABEL_KEYS: Record<
(typeof COACH_EXCLUDE_METRICS)[number],
string
> = {
bp: "settings.sharedRecord.managedSettings.coach.metrics.bp",
weight: "settings.sharedRecord.managedSettings.coach.metrics.weight",
pulse: "settings.sharedRecord.managedSettings.coach.metrics.pulse",
mood: "settings.sharedRecord.managedSettings.coach.metrics.mood",
compliance: "settings.sharedRecord.managedSettings.coach.metrics.compliance",
hrv: "settings.sharedRecord.managedSettings.coach.metrics.hrv",
sleep: "settings.sharedRecord.managedSettings.coach.metrics.sleep",
resting_hr: "settings.sharedRecord.managedSettings.coach.metrics.restingHr",
steps: "settings.sharedRecord.managedSettings.coach.metrics.steps",
medications:
"settings.sharedRecord.managedSettings.coach.metrics.medications",
anthropometrics:
"settings.sharedRecord.managedSettings.coach.metrics.anthropometrics",
};
const COACH_DATA_CLUSTER_LABEL_KEYS: Record<
(typeof COACH_DATA_CLUSTERS)[number],
string
> = {
cardio: "settings.sharedRecord.managedSettings.coach.clusters.cardio",
body: "settings.sharedRecord.managedSettings.coach.clusters.body",
activity: "settings.sharedRecord.managedSettings.coach.clusters.activity",
workouts: "settings.sharedRecord.managedSettings.coach.clusters.workouts",
sleep: "settings.sharedRecord.managedSettings.coach.clusters.sleep",
mood: "settings.sharedRecord.managedSettings.coach.clusters.mood",
glucose: "settings.sharedRecord.managedSettings.coach.clusters.glucose",
medication: "settings.sharedRecord.managedSettings.coach.clusters.medication",
mobility: "settings.sharedRecord.managedSettings.coach.clusters.mobility",
environment:
"settings.sharedRecord.managedSettings.coach.clusters.environment",
};
const INSIGHTS_SECTION_LABEL_KEYS: Record<InsightsSectionId, string> = {
"wellness-scores":
"settings.sharedRecord.managedSettings.insights.sectionLabels.wellnessScores",
"daily-briefing":
"settings.sharedRecord.managedSettings.insights.sectionLabels.dailyBriefing",
vitals: "settings.sharedRecord.managedSettings.insights.sectionLabels.vitals",
trends: "settings.sharedRecord.managedSettings.insights.sectionLabels.trends",
"period-review":
"settings.sharedRecord.managedSettings.insights.sectionLabels.periodReview",
"cycle-summary":
"settings.sharedRecord.managedSettings.insights.sectionLabels.cycleSummary",
signals:
"settings.sharedRecord.managedSettings.insights.sectionLabels.signals",
"rhythm-events":
"settings.sharedRecord.managedSettings.insights.sectionLabels.rhythmEvents",
"health-status":
"settings.sharedRecord.managedSettings.insights.sectionLabels.healthStatus",
breathing:
"settings.sharedRecord.managedSettings.insights.sectionLabels.breathing",
"labs-changes":
"settings.sharedRecord.managedSettings.insights.sectionLabels.labsChanges",
ecg: "settings.sharedRecord.managedSettings.insights.sectionLabels.ecg",
};
const METRIC_LABEL_KEYS: Record<ThresholdMetric, string> = {
WEIGHT: "thresholds.metricWeight",
BLOOD_PRESSURE_SYS: "thresholds.metricBpSys",
@@ -109,6 +171,40 @@ function formNumber(form: FormData, name: string, fallback: number): number {
return Number.isFinite(value) ? value : fallback;
}
function coachMetricLabel(
t: ReturnType<typeof useTranslations>["t"],
metric: (typeof COACH_EXCLUDE_METRICS)[number],
): string {
return t(COACH_EXCLUDE_METRIC_LABEL_KEYS[metric]);
}
function coachClusterLabel(
t: ReturnType<typeof useTranslations>["t"],
cluster: (typeof COACH_DATA_CLUSTERS)[number],
): string {
return t(COACH_DATA_CLUSTER_LABEL_KEYS[cluster]);
}
function insightItemLabel(
t: ReturnType<typeof useTranslations>["t"],
kind: "section" | "tile",
id: string,
): string {
if (kind === "section") {
const labelKey = INSIGHTS_SECTION_LABEL_KEYS[id as InsightsSectionId];
return labelKey
? t(labelKey)
: t("settings.sharedRecord.managedSettings.insights.unknownItem");
}
const normalizedId = normalizeInsightsTileId(id);
if (normalizedId === "overview") return t("insights.navOverview");
const tab = SUB_PAGE_TABS[normalizedId as keyof typeof SUB_PAGE_TABS];
return tab
? t(tab.labelKey)
: t("settings.sharedRecord.managedSettings.insights.unknownItem");
}
function SaveButton({ disabled, label }: { disabled: boolean; label: string }) {
return (
<Button disabled={disabled} type="submit">
@@ -661,9 +757,7 @@ function CoachSettingsForm({
name={`exclude-${metric}`}
type="checkbox"
/>
{t("settings.sharedRecord.managedSettings.coach.value", {
value: metric,
})}
{coachMetricLabel(t, metric)}
</label>
))}
</fieldset>
@@ -691,9 +785,7 @@ function CoachSettingsForm({
name={`cluster-${cluster}`}
type="checkbox"
/>
{t("settings.sharedRecord.managedSettings.coach.value", {
value: cluster,
})}
{coachClusterLabel(t, cluster)}
</label>
))}
</fieldset>
@@ -765,9 +857,7 @@ function InsightsSettingsForm({
name={`${kind}-visible-${item.id}`}
type="checkbox"
/>
{t("settings.sharedRecord.managedSettings.insights.item", {
id: item.id,
})}
{insightItemLabel(t, kind, item.id)}
</label>
<label
className="flex items-center gap-2"
@@ -81,5 +81,14 @@ describe("record settings authorization", () => {
expect(route).toContain("invalidateUserMedications(access.recordId, {");
expect(route).toContain("invalidateUserHealthScore(access.recordId)");
expect(route).toContain("getAllEffectiveRanges(record, overrides)");
expect(route).toContain(
"effective: getAllEffectiveRanges(record, overrides)",
);
expect(route).toContain("invalidateUserProfile(access.recordId)");
expect(
route.lastIndexOf("invalidateUserProfile(access.recordId)"),
).toBeGreaterThan(
route.indexOf("data: { thresholdsJson: toJson(overrides) }"),
);
});
});