feat(documents): labs and health-record entry points into the vault

/labs gains a quiet link to the LAB_RESULT-filtered vault view (the
original report PDFs behind the values); the health-record export panel
links to the vault filtered to the current year next to its date-range
picker (navigation only — attaching documents to the export is out of
scope here). Both render only when the documents module is enabled for
the account, in all six locales.

Refs: document-vault plan W3-T5
This commit is contained in:
Marc-André Bombeck committed 2026-07-07 13:30:46 +02:00
1 parent ed2ac8b61d
commit 0ade02e163
8 files changed
+72 -15

No files matched your search

+4 -2
View File
@@ -5077,7 +5077,8 @@
"error": "Export fehlgeschlagen ({code}).",
"labs": "Laborwerte",
"allergies": "Allergien & Unverträglichkeiten",
"familyHistory": "Familienanamnese"
"familyHistory": "Familienanamnese",
"documentsLink": "Dokumente zum Zeitraum"
},
"globalExcludedInjectionSitesLabel": "Global ausgeschlossene Injektionsstellen",
"globalExcludedInjectionSitesHint": "Hier gelistete Stellen werden für keine Medikation angeboten und beim Erfassen einer Dosis abgelehnt — auch wenn eine Medikation sie bevorzugt.",
@@ -7338,7 +7339,8 @@
"extractFailed": "Der Befund konnte nicht gelesen werden. Versuche ein schärferes Foto.",
"pdfNeedsAnthropic": "PDF-Scannen benötigt einen Claude-Bildanbieter; lade stattdessen ein Foto hoch."
},
"loadMoreReadings": "Weitere Werte laden"
"loadMoreReadings": "Weitere Werte laden",
"documentsLink": "Befund-Dokumente"
},
"modules": {
"cycle": {
+4 -2
View File
@@ -5077,7 +5077,8 @@
"error": "Export failed ({code}).",
"labs": "Lab results",
"allergies": "Allergies & intolerances",
"familyHistory": "Family history"
"familyHistory": "Family history",
"documentsLink": "Documents for this period"
},
"globalExcludedInjectionSitesLabel": "Globally excluded injection sites",
"globalExcludedInjectionSitesHint": "Sites listed here are never offered for any medication and rejected when logging a dose, even if a medication prefers them.",
@@ -7338,7 +7339,8 @@
"extractFailed": "Couldn't read the report. Try a clearer photo.",
"pdfNeedsAnthropic": "PDF scanning needs a Claude vision provider; upload a photo instead."
},
"loadMoreReadings": "Load more readings"
"loadMoreReadings": "Load more readings",
"documentsLink": "Lab report documents"
},
"modules": {
"cycle": {
+4 -2
View File
@@ -5077,7 +5077,8 @@
"error": "Error en la exportación ({code}).",
"labs": "Resultados de análisis",
"allergies": "Alergias e intolerancias",
"familyHistory": "Antecedentes familiares"
"familyHistory": "Antecedentes familiares",
"documentsLink": "Documentos del período"
},
"globalExcludedInjectionSitesLabel": "Sitios de inyección excluidos globalmente",
"globalExcludedInjectionSitesHint": "Los sitios listados aquí no se ofrecen para ningún medicamento y se rechazan al registrar una dosis, aunque un medicamento los prefiera.",
@@ -7338,7 +7339,8 @@
"extractFailed": "No se pudo leer el informe. Prueba con una foto más nítida.",
"pdfNeedsAnthropic": "El escaneo de PDF necesita un proveedor de visión de Claude; sube una foto en su lugar."
},
"loadMoreReadings": "Cargar más mediciones"
"loadMoreReadings": "Cargar más mediciones",
"documentsLink": "Documentos de resultados"
},
"modules": {
"cycle": {
+4 -2
View File
@@ -5077,7 +5077,8 @@
"error": "Échec de l’export ({code}).",
"labs": "Résultats d'analyses",
"allergies": "Allergies et intolérances",
"familyHistory": "Antécédents familiaux"
"familyHistory": "Antécédents familiaux",
"documentsLink": "Documents de la période"
},
"globalExcludedInjectionSitesLabel": "Sites d'injection exclus globalement",
"globalExcludedInjectionSitesHint": "Les sites listés ici ne sont proposés pour aucun médicament et sont refusés lors de l'enregistrement d'une dose, même si un médicament les privilégie.",
@@ -7338,7 +7339,8 @@
"extractFailed": "Impossible de lire le compte rendu. Essayez une photo plus nette.",
"pdfNeedsAnthropic": "Le scan PDF nécessite un fournisseur de vision Claude ; importez plutôt une photo."
},
"loadMoreReadings": "Charger plus de mesures"
"loadMoreReadings": "Charger plus de mesures",
"documentsLink": "Documents de résultats"
},
"modules": {
"cycle": {
+4 -2
View File
@@ -5077,7 +5077,8 @@
"error": "Esportazione non riuscita ({code}).",
"labs": "Risultati degli esami",
"allergies": "Allergie e intolleranze",
"familyHistory": "Anamnesi familiare"
"familyHistory": "Anamnesi familiare",
"documentsLink": "Documenti del periodo"
},
"globalExcludedInjectionSitesLabel": "Siti di iniezione esclusi globalmente",
"globalExcludedInjectionSitesHint": "I siti elencati qui non vengono proposti per alcun farmaco e vengono rifiutati al momento della registrazione di una dose, anche se un farmaco li preferisce.",
@@ -7338,7 +7339,8 @@
"extractFailed": "Impossibile leggere il referto. Prova con una foto più nitida.",
"pdfNeedsAnthropic": "La scansione PDF richiede un provider di visione Claude; carica invece una foto."
},
"loadMoreReadings": "Carica altre misurazioni"
"loadMoreReadings": "Carica altre misurazioni",
"documentsLink": "Documenti dei referti"
},
"modules": {
"cycle": {
+4 -2
View File
@@ -5077,7 +5077,8 @@
"error": "Eksport nie powiódł się ({code}).",
"labs": "Wyniki badań",
"allergies": "Alergie i nietolerancje",
"familyHistory": "Wywiad rodzinny"
"familyHistory": "Wywiad rodzinny",
"documentsLink": "Dokumenty z tego okresu"
},
"globalExcludedInjectionSitesLabel": "Globalnie wykluczone miejsca iniekcji",
"globalExcludedInjectionSitesHint": "Miejsca wymienione tutaj nie są proponowane dla żadnego leku i są odrzucane przy rejestrowaniu dawki, nawet jeśli lek je preferuje.",
@@ -7338,7 +7339,8 @@
"extractFailed": "Nie udało się odczytać wyniku. Spróbuj wyraźniejszego zdjęcia.",
"pdfNeedsAnthropic": "Skanowanie PDF wymaga dostawcy wizji Claude; prześlij zamiast tego zdjęcie."
},
"loadMoreReadings": "Wczytaj więcej pomiarów"
"loadMoreReadings": "Wczytaj więcej pomiarów",
"documentsLink": "Dokumenty wyników badań"
},
"modules": {
"cycle": {
+24 -2
View File
@@ -4,7 +4,15 @@ import { useCallback, useEffect, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useQueryClient } from "@tanstack/react-query";
import { Loader2, Pencil, Plus, ScanLine, Wrench } from "lucide-react";
import {
ArrowRight,
FolderOpen,
Loader2,
Pencil,
Plus,
ScanLine,
Wrench,
} from "lucide-react";
import { LabForm } from "@/components/labs/lab-form";
import { LabList } from "@/components/labs/lab-list";
@@ -27,7 +35,7 @@ import { useTranslations } from "@/lib/i18n/context";
import { queryKeys } from "@/lib/query-keys";
export default function LabsPage() {
const { isAuthenticated, isLoading } = useAuth();
const { user, isAuthenticated, isLoading } = useAuth();
const mounted = useMounted();
const router = useRouter();
const queryClient = useQueryClient();
@@ -128,6 +136,20 @@ export default function LabsPage() {
}
/>
{/* Entry point into the document vault, pre-filtered to lab-result
documents (the original report PDFs behind these values). Only
rendered when the documents module is enabled for this account. */}
{user?.modules?.inboundDocuments ? (
<Link
href="/documents?kind=LAB_RESULT"
className="text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 inline-flex items-center gap-1.5 rounded-md text-sm transition-colors focus-visible:ring-[3px] focus-visible:outline-none"
>
<FolderOpen className="size-4" aria-hidden />
{t("labs.documentsLink")}
<ArrowRight className="size-3.5" aria-hidden />
</Link>
) : null}
<ResponsiveSheet
open={dialogOpen}
onOpenChange={setDialogOpen}
@@ -20,8 +20,15 @@
* compact instead of as a long always-expanded checklist.
*/
import Link from "next/link";
import { useId, useState } from "react";
import { ChevronDown, Download, FileText, Loader2 } from "lucide-react";
import {
ChevronDown,
Download,
FileText,
FolderOpen,
Loader2,
} from "lucide-react";
import { SettingsCard } from "@/components/settings/settings-card";
import { SettingsCardHeader } from "@/components/settings/_card-header";
@@ -32,6 +39,7 @@ import { NativeSelect } from "@/components/ui/native-select";
import { Switch } from "@/components/ui/switch";
import { useRovingRadioGroup } from "@/hooks/use-roving-radio-group";
import { apiFetchRaw } from "@/lib/api/api-fetch";
import { useAuth } from "@/hooks/use-auth";
import { useTranslations } from "@/lib/i18n/context";
import { cn } from "@/lib/utils";
@@ -117,6 +125,7 @@ function buildSelectionSections(s: SectionState) {
export function HealthRecordExportPanel() {
const { t, locale } = useTranslations();
const { user } = useAuth();
const [format, setFormat] = useState<ExportFormat>("pdf");
const [days, setDays] = useState<number>(90);
const [practiceName, setPracticeName] = useState("");
@@ -250,6 +259,20 @@ export function HealthRecordExportPanel() {
<option value="180">{t("settings.healthRecord.range180")}</option>
<option value="365">{t("settings.healthRecord.range365")}</option>
</NativeSelect>
{/* Entry point into the document vault for the report period
(navigation only — attaching documents to the PDF is a later
phase). Year granularity is what the vault's URL filters
offer; the current year covers the bulk of any range. Only
rendered when the documents module is enabled. */}
{user?.modules?.inboundDocuments ? (
<Link
href={`/documents?year=${new Date().getFullYear()}`}
className="text-muted-foreground hover:text-foreground focus-visible:ring-ring/50 inline-flex items-center gap-1.5 rounded-md text-xs transition-colors focus-visible:ring-[3px] focus-visible:outline-none"
>
<FolderOpen className="size-3.5" aria-hidden />
{t("settings.healthRecord.documentsLink")}
</Link>
) : null}
</div>
</div>