feat(settings): give the health record its own Gesundheitsakte entry

Lift the full health-record export (PDF + FHIR R4 + zip package) out of
Export & Import into a dedicated top-level Gesundheitsakte section. Export
& Import keeps the generic CSV/JSON data-out and import paths only. The
new section is always available (not module-gated).
This commit is contained in:
Marc-André Bombeck committed 2026-06-15 23:14:28 +02:00
1 parent 604778f166
commit 59fea72d8b
15 files changed
+161 -34

No files matched your search

+4
View File
@@ -4121,6 +4121,10 @@
"note": "Kernbereich — immer aktiv"
},
"description": "Lege fest, was HealthLog für dich erfasst. Schalte einzelne Bereiche ein oder aus — die Navigation, Kacheln und Auswertungen passen sich sofort an."
},
"gesundheitsakte": {
"title": "Gesundheitsakte",
"description": "Vollständige Gesundheitsakte als PDF, FHIR R4 oder kombiniertes ZIP-Paket exportieren."
}
},
"profile": "Profil",
+4
View File
@@ -4121,6 +4121,10 @@
"note": "Core area — always on"
},
"description": "Choose what HealthLog tracks for you. Turn individual areas on or off — the navigation, tiles, and insights adapt right away."
},
"gesundheitsakte": {
"title": "Health record",
"description": "Export your full health record as PDF, FHIR R4, or a combined ZIP package."
}
},
"profile": "Profile",
+4
View File
@@ -4121,6 +4121,10 @@
"note": "Área esencial — siempre activa"
},
"description": "Elige qué registra HealthLog por ti. Activa o desactiva áreas concretas: la navegación, los paneles y los análisis se adaptan al instante."
},
"gesundheitsakte": {
"title": "Historial médico",
"description": "Exporta tu historial médico completo como PDF, FHIR R4 o un paquete ZIP combinado."
}
},
"profile": "Perfil",
+4
View File
@@ -4121,6 +4121,10 @@
"note": "Domaine essentiel — toujours actif"
},
"description": "Choisissez ce que HealthLog suit pour vous. Activez ou désactivez chaque domaine : la navigation, les tuiles et les analyses s'adaptent aussitôt."
},
"gesundheitsakte": {
"title": "Dossier médical",
"description": "Exportez votre dossier médical complet au format PDF, FHIR R4 ou en paquet ZIP combiné."
}
},
"profile": "Profil",
+4
View File
@@ -4121,6 +4121,10 @@
"note": "Ambito di base — sempre attivo"
},
"description": "Scegli cosa registra HealthLog per te. Attiva o disattiva i singoli ambiti: navigazione, riquadri e analisi si adattano subito."
},
"gesundheitsakte": {
"title": "Cartella clinica",
"description": "Esporta la tua cartella clinica completa in PDF, FHIR R4 o in un pacchetto ZIP combinato."
}
},
"profile": "Profilo",
+4
View File
@@ -4121,6 +4121,10 @@
"note": "Obszar podstawowy — zawsze aktywny"
},
"description": "Wybierz, co HealthLog ma dla Ciebie śledzić. Włączaj i wyłączaj poszczególne obszary — nawigacja, kafelki i analizy dostosują się od razu."
},
"gesundheitsakte": {
"title": "Dokumentacja zdrowotna",
"description": "Wyeksportuj pełną dokumentację zdrowotną jako PDF, FHIR R4 lub łączony pakiet ZIP."
}
},
"profile": "Profil",
+3 -2
View File
@@ -8,6 +8,7 @@ import { AiSection } from "@/components/settings/ai-section";
import { ApiSection } from "@/components/settings/api-section";
import { DashboardSection } from "@/components/settings/dashboard-section";
import { ExportSection } from "@/components/settings/export-section";
import { GesundheitsakteSection } from "@/components/settings/gesundheitsakte-section";
import { InsightsSection } from "@/components/settings/insights-section";
import { IntegrationsSection } from "@/components/settings/integrations-section";
import { LayoutSection } from "@/components/settings/layout-section";
@@ -29,8 +30,7 @@ import { SettingsShell } from "@/components/settings/settings-shell";
/**
* Dynamic settings section route. Each of the `SETTINGS_SECTION_SLUGS`
* (14 today — 10 nav-visible sections plus the four routable-but-hidden
* Layout child editors) is pre-rendered at build via
* is pre-rendered at build via
* `generateStaticParams()` so the URLs are
* statically known to Next.js, while the `dynamicParams = false` flag below
* tells the router to 404 (instead of attempting on-demand rendering) for any
@@ -61,6 +61,7 @@ const SECTION_COMPONENTS: Record<
mood: MoodSection,
thresholds: ThresholdsSection,
api: ApiSection,
gesundheitsakte: GesundheitsakteSection,
export: ExportSection,
advanced: AdvancedSection,
};
@@ -2,7 +2,6 @@
* v1.4.16 Phase B7 — Settings → Export section.
*
* The consolidated Export page surfaces:
* - Health Record export panel (the page hero, v1.12)
* - Measurements CSV
* - Medications CSV (with optional intake-history toggle)
* - Mood CSV
@@ -12,10 +11,10 @@
* download/generate button. SSR-only smoke test — interaction is
* exercised by the e2e suite.
*
* The health-record export takes the page hero. The doctor-report PDF
* now lives under the health-record export and is no longer offered as
* a separate card here. The four CSV/JSON tiles stay under a "Weitere
* Export-Optionen" / "Other export options" sub-heading. Each component
* v1.18.0 (S5) — the full health-record export moved out to its own
* top-level "Gesundheitsakte" section; this page keeps only the generic
* CSV/JSON data-out paths under a "Weitere Export-Optionen" / "Other
* export options" sub-heading plus the import surface. Each component
* owns its own contract test; this suite pins the page-level shape.
*/
import { describe, expect, it, vi } from "vitest";
@@ -77,12 +76,11 @@ describe("<ExportSection> — SSR smoke", () => {
expect(html).not.toContain('data-testid="export-card-cycle"');
});
it("mounts the health-record export panel as the page hero", () => {
it("no longer mounts the health-record export panel (moved to Gesundheitsakte)", () => {
const html = render(<ExportSection />);
// v1.12 — the health-record export owns the hero treatment.
expect(html).toContain('data-testid="health-record-export-panel"');
expect(html).toContain("hero-gradient");
expect(html).toContain("glow-purple");
// v1.18.0 (S5) — the full health-record export lives in its own
// top-level Gesundheitsakte section now, not on this page.
expect(html).not.toContain('data-testid="health-record-export-panel"');
});
it("no longer mounts a separate doctor-report card", () => {
@@ -130,7 +128,7 @@ describe("<ExportSection> — SSR smoke", () => {
expect(buttonMatches?.length ?? 0).toBe(4);
});
it("German locale renders the DE heading + hero copy", () => {
it("German locale renders the DE heading + sub-heading copy", () => {
const html = render(<ExportSection />, "de");
expect(html).toContain("Export");
expect(html).toContain("Weitere Export-Optionen");
@@ -0,0 +1,54 @@
/**
* v1.18.0 (S5) — Settings → Gesundheitsakte section.
*
* The full health-record export (PDF + FHIR R4 + zip package) lifted out
* of Export & Import into its own top-level home. SSR-only smoke test —
* interaction is exercised by the panel's own contract test + e2e.
*/
import { describe, expect, it, vi } from "vitest";
import { renderToStaticMarkup } from "react-dom/server";
vi.mock("next/navigation", () => ({
useRouter: () => ({ push: vi.fn(), replace: vi.fn() }),
usePathname: () => "/settings/gesundheitsakte",
}));
vi.mock("@tanstack/react-query", () => ({
useQuery: () => ({ data: null, isLoading: false, refetch: vi.fn() }),
useQueryClient: () => ({ invalidateQueries: vi.fn() }),
useMutation: () => ({ mutate: vi.fn(), isPending: false }),
}));
vi.mock("@/hooks/use-auth", () => ({
useAuth: () => ({
user: { id: "u1", role: "USER" },
isAuthenticated: true,
isLoading: false,
refetch: vi.fn(),
}),
}));
import { I18nProvider } from "@/lib/i18n/context";
import { GesundheitsakteSection } from "../gesundheitsakte-section";
function render(node: React.ReactElement, locale: "en" | "de" = "en") {
return renderToStaticMarkup(
<I18nProvider initialLocale={locale}>{node}</I18nProvider>,
);
}
describe("<GesundheitsakteSection> — SSR smoke", () => {
it("renders the section description and the health-record panel", () => {
const html = render(<GesundheitsakteSection />);
expect(html).toContain('id="settings-section-gesundheitsakte-title"');
expect(html).toContain('data-testid="health-record-export-panel"');
// Raw key never leaks past i18n.
expect(html).not.toContain("settings.sections.gesundheitsakte.");
});
it("German locale resolves without leaking the raw key", () => {
const html = render(<GesundheitsakteSection />, "de");
expect(html).toContain('data-testid="health-record-export-panel"');
expect(html).not.toContain("settings.sections.gesundheitsakte.");
});
});
@@ -67,6 +67,8 @@ describe("SETTINGS_SECTION_SLUGS", () => {
// 301-redirects to `/settings/notifications`.
// v1.18.0 — `modules` ("Was du trackst") sits right after `account` as
// the single front door for enabling/disabling secondary domains.
// v1.18.0 (S5) — `gesundheitsakte` (the full health-record export) lifts
// out of Export & Import into its own top-level entry, before `export`.
expect([...SETTINGS_SECTION_SLUGS]).toEqual([
"account",
"modules",
@@ -80,6 +82,7 @@ describe("SETTINGS_SECTION_SLUGS", () => {
"thresholds",
"ai",
"api",
"gesundheitsakte",
"export",
"advanced",
"about",
@@ -204,6 +207,9 @@ describe("<SettingsShell>", () => {
// The ampersand is HTML-escaped by React SSR — assert on the encoded
// form so we don't accidentally match a parser that double-escapes.
expect(html).toContain("API &amp; Tokens");
// v1.18.0 (S5) — the health record is its own top-level entry.
expect(html).toContain('href="/settings/gesundheitsakte"');
expect(html).toContain("Health record");
// v1.4.16 phase B7: the consolidated Export section is a top-level
// entry in the sidebar; the link must be present in both locales.
expect(html).toContain('href="/settings/export"');
+10 -21
View File
@@ -9,18 +9,14 @@
* per export type, each with its own filter inputs and a clear
* "Download" / "Generate" button.
*
* 1. Health Record export — hero panel at the top of the page
* (PDF + FHIR R4 + zip package, see
* `<HealthRecordExportPanel>`)
* 2. Measurements CSV — optional `since`/`until`
* 3. Medications CSV — optional intake-history toggle
* 4. Mood CSV — optional `since`/`until`
* 5. Full JSON Backup — single-file user-scoped dump
* 1. Measurements CSV — optional `since`/`until`
* 2. Medications CSV — optional intake-history toggle
* 3. Mood CSV — optional `since`/`until`
* 4. Full JSON Backup — single-file user-scoped dump
*
* v1.12 — the health-record export is the headline "data out" path; it
* takes the page hero. The doctor-report PDF lives under the
* health-record export now and is no longer offered as a separate card
* here (it used to be a small secondary card at the bottom).
* v1.18.0 (S5) — the full health-record export (PDF + FHIR R4 + zip
* package) moved out to its own top-level "Gesundheitsakte" section. This
* page now keeps only the generic data export/import paths.
*
* Mobile-first: cards stack on `<md`, two-column grid on `>=md`.
*/
@@ -42,7 +38,6 @@ import { Button } from "@/components/ui/button";
import { DateInput } from "@/components/ui/date-input";
import { Label } from "@/components/ui/label";
import { Switch } from "@/components/ui/switch";
import { HealthRecordExportPanel } from "@/components/settings/health-record-export-panel";
import { ImportPanel } from "@/components/settings/import-panel";
import { queryKeys } from "@/lib/query-keys";
import { useTranslations } from "@/lib/i18n/context";
@@ -87,15 +82,9 @@ export function ExportSection() {
</p>
</header>
{/* v1.12 — full health-record export is now the page hero. PDF +
FHIR R4 + a combined zip package, with grouped data-section
toggles (collapsed by default) and a format switch. This is the
headline "data out" path; it carries the primary visual weight. */}
<HealthRecordExportPanel />
{/* v1.12 — secondary export options. The four CSV/JSON
destinations live under a dedicated sub-heading.
Mobile: single column. Desktop (md+): 2-column grid. */}
{/* v1.18.0 (S5) — the full health-record export moved to its own
top-level "Gesundheitsakte" section. This page keeps the generic
CSV/JSON data-out paths and the import surface. */}
<section
aria-labelledby="settings-section-export-other-title"
className="space-y-3"
@@ -0,0 +1,41 @@
"use client";
/**
* v1.18.0 (S5) — Settings → Gesundheitsakte (health record).
*
* The full health-record export was the hero panel at the top of the
* Export & Import section. It is a distinct concept — a complete,
* clinician-ready record (PDF + FHIR R4 + a combined zip package) — and
* earns its own top-level settings home rather than sharing the page with
* the generic CSV / JSON data-out paths. Export & Import now keeps only
* the generic data export/import surfaces.
*
* Not module-gated: the health record is always available.
*/
import { HealthRecordExportPanel } from "@/components/settings/health-record-export-panel";
import { useTranslations } from "@/lib/i18n/context";
export function GesundheitsakteSection() {
const { t } = useTranslations();
return (
<section
aria-labelledby="settings-section-gesundheitsakte-title"
className="space-y-6"
>
<header className="space-y-1">
<h1
id="settings-section-gesundheitsakte-title"
className="sr-only"
>
{t("settings.sections.gesundheitsakte.title")}
</h1>
<p className="text-muted-foreground text-sm">
{t("settings.sections.gesundheitsakte.description")}
</p>
</header>
<HealthRecordExportPanel />
</section>
);
}
@@ -15,6 +15,7 @@ import {
Bell,
Blocks,
Download,
FileHeart,
Info,
KeyRound,
LayoutDashboard,
@@ -46,6 +47,7 @@ const SLUG_ICON: Record<SettingsSectionSlug, LucideIcon> = {
thresholds: SlidersHorizontal,
ai: Sparkles,
api: KeyRound,
gesundheitsakte: FileHeart,
export: Download,
advanced: Settings2,
about: Info,
+4
View File
@@ -31,6 +31,9 @@
// canonical editors; reminder TYPES now live centrally in `notifications`,
// each row gated on its module. `/settings/reminders` 301-redirects to
// `/settings/notifications` (next.config.ts).
// v1.18.0 (S5) — per-submodule settings split. `gesundheitsakte` (the full
// health-record export, always available) lifts out of Export & Import into
// its own top-level entry.
export const SETTINGS_SECTION_SLUGS = [
"account",
// v1.18.0 — `modules` ("Was du trackst") sits right after account: the
@@ -46,6 +49,7 @@ export const SETTINGS_SECTION_SLUGS = [
"thresholds",
"ai",
"api",
"gesundheitsakte",
"export",
"advanced",
"about",
@@ -21,6 +21,7 @@ import {
Bell,
Blocks,
Download,
FileHeart,
Info,
KeyRound,
LayoutDashboard,
@@ -116,6 +117,13 @@ export const SETTINGS_SECTIONS: readonly SettingsSection[] = [
},
{ slug: "ai", titleKey: "settings.sections.ai.title", icon: Sparkles },
{ slug: "api", titleKey: "settings.sections.api.title", icon: KeyRound },
// v1.18.0 (S5) — the full health record (PDF + FHIR R4 + zip package)
// earns its own home, lifted out of Export & Import. Always available.
{
slug: "gesundheitsakte",
titleKey: "settings.sections.gesundheitsakte.title",
icon: FileHeart,
},
{
slug: "export",
titleKey: "settings.sections.export.title",