diff --git a/messages/de.json b/messages/de.json index 04af2a658..0953795e9 100644 --- a/messages/de.json +++ b/messages/de.json @@ -862,6 +862,8 @@ "emptyStateDescription": "Erfasse mindestens 3 Einträge, um die Trendlinie freizuschalten.", "needMoreDistinctDaysTitle": "Mehr Messtage erforderlich", "needMoreDistinctDaysDescription": "Erfasse an mehreren Tagen, um eine Trendlinie freizuschalten.", + "noDataInRangeTitle": "Keine Daten in diesem Zeitraum", + "noDataInRangeDescription": "Für den gewählten Bereich liegen keine Messungen vor. Wechsle den Zeitraum oder erfasse einen neuen Wert.", "personalBaseline": "Dein Mittel", "deltaVsBaseline": "{delta} vs. dein Mittel", "deltaUnchanged": "entspricht deinem Mittel", diff --git a/messages/en.json b/messages/en.json index 6c7ca9eb0..7cbe112b4 100644 --- a/messages/en.json +++ b/messages/en.json @@ -862,6 +862,8 @@ "emptyStateDescription": "Log at least 3 entries to unlock the trend line.", "needMoreDistinctDaysTitle": "More distinct days needed", "needMoreDistinctDaysDescription": "Log on more days to unlock the trend line.", + "noDataInRangeTitle": "No data in this range", + "noDataInRangeDescription": "No measurements fall inside the selected range. Switch the range or log a new measurement.", "personalBaseline": "Your normal", "deltaVsBaseline": "{delta} vs. your normal", "deltaUnchanged": "matches your normal", diff --git a/messages/es.json b/messages/es.json index ef1e71759..a9ffba9d4 100644 --- a/messages/es.json +++ b/messages/es.json @@ -862,6 +862,8 @@ "emptyStateDescription": "Registra al menos 3 entradas para desbloquear la línea de tendencia.", "needMoreDistinctDaysTitle": "Se necesitan más días distintos", "needMoreDistinctDaysDescription": "Registra en más días para desbloquear la línea de tendencia.", + "noDataInRangeTitle": "Sin datos en este rango", + "noDataInRangeDescription": "No hay mediciones en el rango seleccionado. Cambia el rango o registra una nueva medición.", "personalBaseline": "Tu media", "deltaVsBaseline": "{delta} vs. tu media", "deltaUnchanged": "coincide con tu media", diff --git a/messages/fr.json b/messages/fr.json index 855306be8..7881dd197 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -862,6 +862,8 @@ "emptyStateDescription": "Saisis au moins 3 entrées pour débloquer la courbe de tendance.", "needMoreDistinctDaysTitle": "Plus de jours distincts requis", "needMoreDistinctDaysDescription": "Saisis sur plusieurs jours pour débloquer la courbe de tendance.", + "noDataInRangeTitle": "Aucune donnée sur cette période", + "noDataInRangeDescription": "Aucune mesure ne tombe dans la plage sélectionnée. Change de plage ou saisis une nouvelle mesure.", "personalBaseline": "Ta moyenne", "deltaVsBaseline": "{delta} vs. ta moyenne", "deltaUnchanged": "correspond à ta moyenne", diff --git a/messages/it.json b/messages/it.json index a3ae5e91f..e6cb47e80 100644 --- a/messages/it.json +++ b/messages/it.json @@ -862,6 +862,8 @@ "emptyStateDescription": "Registra almeno 3 voci per sbloccare la linea di tendenza.", "needMoreDistinctDaysTitle": "Servono più giorni distinti", "needMoreDistinctDaysDescription": "Registra in più giorni per sbloccare la linea di tendenza.", + "noDataInRangeTitle": "Nessun dato in questo intervallo", + "noDataInRangeDescription": "Nessuna misurazione rientra nell'intervallo selezionato. Cambia intervallo o registra una nuova misurazione.", "personalBaseline": "La tua media", "deltaVsBaseline": "{delta} vs. la tua media", "deltaUnchanged": "corrisponde alla tua media", diff --git a/messages/pl.json b/messages/pl.json index 70819f8fd..59672d7b9 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -862,6 +862,8 @@ "emptyStateDescription": "Zarejestruj co najmniej 3 wpisy, aby odblokować linię trendu.", "needMoreDistinctDaysTitle": "Potrzeba więcej różnych dni", "needMoreDistinctDaysDescription": "Rejestruj w więcej dni, aby odblokować linię trendu.", + "noDataInRangeTitle": "Brak danych w tym zakresie", + "noDataInRangeDescription": "Żadne pomiary nie mieszczą się w wybranym zakresie. Zmień zakres lub dodaj nowy pomiar.", "personalBaseline": "Twoja średnia", "deltaVsBaseline": "{delta} vs. twoja średnia", "deltaUnchanged": "odpowiada twojej średniej", diff --git a/src/components/charts/__tests__/health-chart-no-data-in-range.test.tsx b/src/components/charts/__tests__/health-chart-no-data-in-range.test.tsx new file mode 100644 index 000000000..af2a6d546 --- /dev/null +++ b/src/components/charts/__tests__/health-chart-no-data-in-range.test.tsx @@ -0,0 +1,77 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { renderToStaticMarkup } from "react-dom/server"; + +/** + * v1.4.43 W11-M6 — chart empty-window state. + * + * Pre-fix the chart returned `null` when the loaded data array was + * empty in the selected range, erasing the entire card (header + + * tabs + chart). The user saw a missing widget without explanation. + * The fix paints a "no data in this range" empty state via + * `` so the dashboard composition stays intact and + * the user knows the chart loaded but the selected window holds no + * measurements. The copy is distinct from the < 3-points "Mehr + * Messtage erforderlich" branch so the two situations don't blur. + */ + +function buildData(rawCount: number): unknown[] { + const points: unknown[] = []; + Object.defineProperty(points, "rawCount", { + value: rawCount, + enumerable: false, + configurable: false, + writable: false, + }); + return points; +} + +vi.mock("@/hooks/use-auth", () => ({ + useAuth: () => ({ + isAuthenticated: true, + user: null, + isLoading: false, + }), +})); + +describe(" — empty-window state (W11-M6)", () => { + beforeEach(() => { + vi.resetModules(); + }); + + it("renders the no-data-in-range copy when data resolves to an empty array", async () => { + const data = buildData(0); + + vi.doMock("@tanstack/react-query", () => ({ + useQuery: () => ({ data, isLoading: false }), + useQueryClient: () => ({ + cancelQueries: () => Promise.resolve(), + getQueryData: () => undefined, + setQueryData: () => undefined, + invalidateQueries: () => Promise.resolve(), + }), + useMutation: () => ({ mutate: () => undefined, isPending: false }), + })); + + const { I18nProvider } = await import("@/lib/i18n/context"); + const { HealthChart } = await import("../health-chart"); + + const html = renderToStaticMarkup( + + + , + ); + + // The new empty state must paint… + expect(html).toContain("Keine Daten in diesem Zeitraum"); + // …and the chart card chrome (title) must still be mounted so the + // dashboard isn't missing a tile. The pre-fix `return null` would + // have stripped this entirely. + expect(html).toContain("Pulse"); + // The < 3-points branch must NOT paint here — empty-window and + // sparse-data are different situations with distinct copy. + expect(html).not.toContain("Mehr Messtage erforderlich"); + expect(html).not.toContain("Erfasse mindestens 3 Einträge"); + + vi.doUnmock("@tanstack/react-query"); + }); +}); diff --git a/src/components/charts/health-chart.tsx b/src/components/charts/health-chart.tsx index 059351ebc..366a03553 100644 --- a/src/components/charts/health-chart.tsx +++ b/src/components/charts/health-chart.tsx @@ -1132,7 +1132,14 @@ export function HealthChart({ return map; }, [chartData, types]); - if (!isLoading && !data?.length) return null; + // v1.4.43 W11-M6 — keep the card shell mounted even when the loaded + // payload is empty. Pre-fix the early `return null` here erased the + // whole widget (header + range tabs + chart) and the user saw an + // unexplained gap on the dashboard. The in-card render branch below + // now paints a "no data in this range" empty state so the user + // understands the chart loaded but the selected window holds no + // measurements — distinct copy from the < 3-points "more days + // needed" hint so the two situations don't blur. const maxPointIndex = Math.max(0, (chartData?.length ?? 1) - 1); @@ -1288,7 +1295,29 @@ export function HealthChart({
- ) : !chartData?.length ? null : (chartData?.length ?? 0) < 3 ? ( + ) : !chartData?.length ? ( + // v1.4.43 W11-M6 — empty-window state. + // + // Pre-fix the chart silently `return null`ed when `chartData` + // resolved empty (after-load, no points in the selected + // range). Paired with the < 3-points hint, the user's + // five visible chart states were: + // + // - skeleton (loading) + // - 1-2 daily points / many raw → "Mehr Messtage erforderlich" + // - 1-2 daily points / < 3 raw → "Erfasse mindestens 3 Einträge" + // - ≥ 3 daily points → line chart + // - 0 daily points → NOTHING + // + // The "nothing" branch read as a broken widget on the + // dashboard. Paint a distinct empty state explaining the + // selected range is empty so the user reaches for the range + // tabs or the quick-add instead of suspecting a bug. + + ) : (chartData?.length ?? 0) < 3 ? ( // v1.4.16 B1a — sparse-data placeholder. <3 daily points is too // few to render a meaningful trend; paint a friendly hint // instead so the dashboard doesn't look broken.