"use client"; import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { Activity, ChevronLeft, HeartPulse, Info } from "lucide-react"; import { useAuth } from "@/hooks/use-auth"; import { queryKeys } from "@/lib/query-keys"; import { apiGet } from "@/lib/api/api-fetch"; import { useTranslations, useFormatters } from "@/lib/i18n/context"; import { cn } from "@/lib/utils"; import { SectionHeading } from "@/components/insights/section-heading"; import { EcgWaveform } from "@/components/insights/ecg-waveform"; import { InsightSectionCard } from "./insight-section-card"; /** * v1.28.50 — ECG recording surface (list → detail). * * Renders the user's ECG recordings synced from a single-lead device * (Withings ScanWatch today): a list of strips, each opening a detail view * with the `EcgWaveform` trace, the recording metadata, and the DEVICE's * own classification. * * NON-DIAGNOSTIC (load-bearing, do not soften — mirrors `RhythmEventsCard`): * the surface shows only the waveform (raw data), the metadata, and the * recording device's own result, attributed unambiguously to the device * ("Recorded result: …, as reported by the recording device"). HealthLog * generates NO interpretation of the waveform — no measured intervals, no * beat/P/QRS/T annotation, no risk score, no verdict of its own. A * permanent, non-dismissible disclaimer states this, and any non-normal * device result additionally carries a "discuss with a clinician" note. * All copy renders as plain React text children (no markdown library — the * standing XSS rule). * * Data-availability-gated: the section un-mounts entirely (`return null`) * when the user has no recordings — never an empty / alarming card. */ type EcgClassification = "IRREGULAR" | "NOT_DETECTED" | "INCONCLUSIVE" | null; interface EcgRecordingListItem { id: string; recordedAt: string; durationSeconds: number | null; samplingFrequency: number; sampleCount: number; averageHeartRate: number | null; lead: string | null; classification: EcgClassification; source: string; hasWaveform: boolean; } interface EcgListResponse { recordings: EcgRecordingListItem[]; hasRecordings: boolean; } interface EcgDetailResponse { recordedAt: string; durationSeconds: number | null; samplingFrequency: number; averageHeartRate: number | null; lead: string | null; classification: EcgClassification; source: string; samples: number[]; decimated: boolean; } /** The device verdict, surfaced verbatim and attributed to the device. */ const RESULT_LABEL_KEYS: Record = { IRREGULAR: "insights.ecg.result.irregular", NOT_DETECTED: "insights.ecg.result.notDetected", INCONCLUSIVE: "insights.ecg.result.inconclusive", }; /** Non-normal device results trigger the "discuss with a clinician" note. */ function isNonNormal(classification: EcgClassification): boolean { return classification === "IRREGULAR" || classification === "INCONCLUSIVE"; } interface EcgSectionProps { enabled?: boolean; className?: string; /** * v1.30 — suppress the internal `` when the section is * hosted on the routed `/insights/ecg` sub-page, whose `` * already renders the page `

`. Defaults to `false` so the overview * teaser keeps its own heading unchanged. Purely presentational — the * non-diagnostic disclaimer + waveform logic are untouched. */ hideHeading?: boolean; } export function EcgSection({ enabled = true, className, hideHeading = false, }: EcgSectionProps) { const { isAuthenticated } = useAuth(); const { t } = useTranslations(); const fmt = useFormatters(); const [selectedId, setSelectedId] = useState(null); const { data, isLoading } = useQuery({ queryKey: queryKeys.insightsEcgList(), queryFn: async () => { try { return await apiGet("/api/insights/ecg"); } catch { throw new Error(t("insights.ecg.loadError")); } }, enabled: enabled && isAuthenticated, }); // Data-availability gate — never paint an empty card. if (isLoading || !data || !data.hasRecordings) return null; const selected = selectedId != null ? (data.recordings.find((r) => r.id === selectedId) ?? null) : null; function resultLabel(classification: EcgClassification): string | null { if (!classification) return null; const key = RESULT_LABEL_KEYS[classification]; return key ? t(key) : null; } return (
{!hideHeading && ( )} {selected ? ( setSelectedId(null)} /> ) : ( <>

{t("insights.ecg.sectionIntro")}

    {data.recordings.map((rec) => { const label = resultLabel(rec.classification); const rowInner = ( <>

    {fmt.dateTime(new Date(rec.recordedAt))}

    {label && (

    {label}

    )} {rec.averageHeartRate != null && (

    {t("insights.ecg.meta.bpmValue", { bpm: rec.averageHeartRate, })}

    )}
    ); return (
  1. {rec.hasWaveform ? ( ) : (
    {rowInner}
    )}
  2. ); })}
{/* Load-bearing regulatory disclaimer — permanent, non-dismissible. Plain React text children (no markdown). */} )}
); } /** * The detail view: waveform + metadata + device-attributed result + * permanent disclaimer + clinician note on a non-normal device result. * Exported so the non-diagnostic framing can be unit-tested directly. */ export function EcgDetail({ recording, resultLabel, onBack, }: { recording: EcgRecordingListItem; resultLabel: string | null; onBack: () => void; }) { const { t } = useTranslations(); const fmt = useFormatters(); const { data: detail, isLoading } = useQuery({ queryKey: queryKeys.insightsEcgDetail(recording.id, false), queryFn: async () => { try { return await apiGet( `/api/insights/ecg/${recording.id}`, ); } catch { throw new Error(t("insights.ecg.loadError")); } }, }); const nonNormal = isNonNormal(recording.classification); const leadLabel = recording.lead ?? t("insights.ecg.meta.leadSingle"); const durationLabel = recording.durationSeconds != null ? t("insights.ecg.meta.durationValue", { seconds: Math.round(recording.durationSeconds), }) : t("insights.ecg.meta.unknown"); return (
{/* The DEVICE's result, attributed to the device. This is the ONLY verdict shown — HealthLog produces none. */} {resultLabel && (

{t("insights.ecg.resultAttribution", { result: resultLabel })}

)} {isLoading || !detail ? (
) : ( )}
0 ? t("insights.ecg.meta.hzValue", { hz: recording.samplingFrequency, }) : t("insights.ecg.meta.unknown") } />
{/* "Discuss with a clinician" — only on a non-normal device result. */} {nonNormal && (

{t("insights.ecg.clinicianNote")}

)}
); } function MetaItem({ label, value }: { label: string; value: string }) { return (
{label}
{value}
); } /** * The permanent, non-dismissible non-diagnostic disclaimer. States that the * displayed result is the device's and that HealthLog does not read, * interpret, or diagnose ECG recordings. */ function EcgDisclaimer() { const { t } = useTranslations(); return (
); }