diff --git a/messages/de.json b/messages/de.json index 28604c9b1..e9e1948be 100644 --- a/messages/de.json +++ b/messages/de.json @@ -7680,14 +7680,11 @@ "history": { "title": "Dein Verlauf", "empty": "Noch keine Check-ins. Dein Verlauf erscheint hier, sobald du einen abgeschlossen hast.", - "chartTitle": "Punkteverlauf", "flaggedBadge": "Hilfe angezeigt", - "totalLabel": "Punkte", "tab": { "phq9": "PHQ-9", "gad7": "GAD-7" - }, - "bandLabel": "Einstufung" + } }, "crisis": { "title": "Du musst das nicht allein durchstehen", @@ -7755,8 +7752,7 @@ "review": { "title": "Überblick", "recap": "Du hast {answered} von {total} Fragen beantwortet." - }, - "openDetail": "Verlauf anzeigen" + } }, "records": { "disclaimer": "Das sind deine eigenen Angaben, keine ärztliche Diagnose. HealthLog stellt keine Diagnose, behandelt nicht und deutet nichts.", diff --git a/messages/en.json b/messages/en.json index df45fbd25..486122f95 100644 --- a/messages/en.json +++ b/messages/en.json @@ -7680,14 +7680,11 @@ "history": { "title": "Your history", "empty": "No check-ins yet. Your trend will appear here once you complete one.", - "chartTitle": "Score trend", "flaggedBadge": "Support shown", - "totalLabel": "Score", "tab": { "phq9": "PHQ-9", "gad7": "GAD-7" - }, - "bandLabel": "Category" + } }, "crisis": { "title": "You don't have to face this alone", @@ -7755,8 +7752,7 @@ "review": { "title": "Review", "recap": "You answered {answered} of {total} questions." - }, - "openDetail": "Show trend" + } }, "records": { "disclaimer": "These are your own records, not a medical diagnosis. HealthLog does not diagnose, treat, or interpret them.", diff --git a/messages/es.json b/messages/es.json index 35f95049a..cf87c537c 100644 --- a/messages/es.json +++ b/messages/es.json @@ -7680,14 +7680,11 @@ "history": { "title": "Tu historial", "empty": "Aún no hay revisiones. Tu tendencia aparecerá aquí cuando completes una.", - "chartTitle": "Evolución de la puntuación", "flaggedBadge": "Apoyo mostrado", - "totalLabel": "Puntuación", "tab": { "phq9": "PHQ-9", "gad7": "GAD-7" - }, - "bandLabel": "Categoría" + } }, "crisis": { "title": "No tienes que afrontar esto a solas", @@ -7755,8 +7752,7 @@ "review": { "title": "Resumen", "recap": "Has respondido {answered} de {total} preguntas." - }, - "openDetail": "Ver evolución" + } }, "records": { "disclaimer": "Son tus propios registros, no un diagnóstico médico. HealthLog no diagnostica, no trata ni los interpreta.", diff --git a/messages/fr.json b/messages/fr.json index 92682f7a4..94a427606 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -7680,14 +7680,11 @@ "history": { "title": "Ton historique", "empty": "Aucun bilan pour le moment. Ta tendance apparaîtra ici dès que tu en auras terminé un.", - "chartTitle": "Évolution du score", "flaggedBadge": "Aide affichée", - "totalLabel": "Score", "tab": { "phq9": "PHQ-9", "gad7": "GAD-7" - }, - "bandLabel": "Catégorie" + } }, "crisis": { "title": "Tu n'as pas à affronter cela seul(e)", @@ -7755,8 +7752,7 @@ "review": { "title": "Récapitulatif", "recap": "Vous avez répondu à {answered} questions sur {total}." - }, - "openDetail": "Voir l’évolution" + } }, "records": { "disclaimer": "Ce sont vos propres informations, pas un diagnostic médical. HealthLog ne diagnostique pas, ne traite pas et ne les interprète pas.", diff --git a/messages/it.json b/messages/it.json index 6a20cc358..1575f4e47 100644 --- a/messages/it.json +++ b/messages/it.json @@ -7680,14 +7680,11 @@ "history": { "title": "La tua cronologia", "empty": "Ancora nessun check-in. Il tuo andamento apparirà qui dopo che ne avrai completato uno.", - "chartTitle": "Andamento del punteggio", "flaggedBadge": "Supporto mostrato", - "totalLabel": "Punteggio", "tab": { "phq9": "PHQ-9", "gad7": "GAD-7" - }, - "bandLabel": "Categoria" + } }, "crisis": { "title": "Non devi affrontare tutto questo da solo/a", @@ -7755,8 +7752,7 @@ "review": { "title": "Riepilogo", "recap": "Hai risposto a {answered} domande su {total}." - }, - "openDetail": "Mostra andamento" + } }, "records": { "disclaimer": "Sono i tuoi dati, non una diagnosi medica. HealthLog non diagnostica, non cura e non li interpreta.", diff --git a/messages/pl.json b/messages/pl.json index 179663f05..d36985dbc 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -7680,14 +7680,11 @@ "history": { "title": "Twoja historia", "empty": "Brak sprawdzeń. Twój trend pojawi się tutaj po ukończeniu pierwszego.", - "chartTitle": "Przebieg wyniku", "flaggedBadge": "Pokazano wsparcie", - "totalLabel": "Wynik", "tab": { "phq9": "PHQ-9", "gad7": "GAD-7" - }, - "bandLabel": "Kategoria" + } }, "crisis": { "title": "Nie musisz mierzyć się z tym sam(a)", @@ -7755,8 +7752,7 @@ "review": { "title": "Podsumowanie", "recap": "Odpowiedziano na {answered} z {total} pytań." - }, - "openDetail": "Pokaż przebieg" + } }, "records": { "disclaimer": "To Twoje własne wpisy, a nie diagnoza lekarska. HealthLog nie diagnozuje, nie leczy i ich nie interpretuje.", diff --git a/src/components/mental-health/__tests__/mental-wellbeing.test.tsx b/src/components/mental-health/__tests__/mental-wellbeing.test.tsx index f983ed473..a20c42337 100644 --- a/src/components/mental-health/__tests__/mental-wellbeing.test.tsx +++ b/src/components/mental-health/__tests__/mental-wellbeing.test.tsx @@ -11,8 +11,9 @@ import en from "../../../../messages/en.json"; * means clicks can't be driven here — the wizard's advance/back grammar is * pinned in `check-in-nav.test.ts`. These tests pin the SSR-observable * contracts: the disclaimer renders ONLY on the landing (never while testing), - * a positive item-9 surfaces the crisis card, and the history paints its chart - * shell + dated list with severity + "support shown" badges. + * a positive item-9 surfaces the crisis card, and the history paints its + * dated list with severity + "support shown" badges — and NO trend chart + * (v1.27.6: the score curve lives in Insights / Measurements, not here). */ vi.mock("@/lib/api/api-fetch", () => ({ @@ -174,9 +175,12 @@ describe("history rendering", () => { const html = withProviders(); - it("paints the lazy chart shell + the dated list", () => { - expect(html).toContain('data-slot="skeleton"'); + it("paints the dated list WITHOUT a trend chart shell", () => { expect(html).toContain('data-slot="history-list"'); + // v1.27.6 — no chart on this surface: no lazy-chart skeleton, no + // recharts container. + expect(html).not.toContain('data-slot="skeleton"'); + expect(html).not.toContain("recharts"); }); it("shows the PHQ-9 / GAD-7 toggle and a severity badge", () => { @@ -196,74 +200,14 @@ describe("history rendering", () => { }); }); -describe("instrument card → per-instrument trend (v1.25.12)", () => { - it("exposes the card body as a detail target separate from Start", () => { +describe("instrument card (v1.27.6 — no trend-detail target)", () => { + it("renders the card body as plain content with Start as the single action", () => { const html = withProviders( - {}} - onOpenDetail={() => {}} - />, + {}} />, ); - // The title + last-result block is a button that opens the Verlauf… - expect(html).toContain('data-slot="instrument-card-open"'); - expect(html).toContain(mh.openDetail); - // …and the Start action is still its own button. + // The former trend-detail button is gone… + expect(html).not.toContain('data-slot="instrument-card-open"'); + // …and the Start action remains. expect(html).toContain(mh.start); }); - - it("pins the history to one instrument: no toggle, bare (no titled card)", () => { - const rows: AssessmentRow[] = [ - { - id: "p1", - instrument: "PHQ9", - locale: "en", - totalScore: 8, - severityBand: "mild", - item9Flagged: false, - crisisShownAt: null, - takenAt: "2026-06-20T00:00:00.000Z", - }, - { - id: "g1", - instrument: "GAD7", - locale: "en", - totalScore: 12, - severityBand: "moderate", - item9Flagged: false, - crisisShownAt: null, - takenAt: "2026-06-20T00:00:00.000Z", - }, - ]; - const html = withProviders( - , - ); - // Pinned: the PHQ-9 / GAD-7 toggle + the titled landing card are gone… - expect(html).toContain('data-pinned="PHQ9"'); - expect(html).not.toContain('role="tablist"'); - // …only the chosen instrument's row paints (its band), not the other's. - expect(html).toContain(mh.band.PHQ9.mild); - expect(html).not.toContain(mh.band.GAD7.moderate); - }); - - it("shows the per-instrument empty state when that instrument has no rows", () => { - const onlyGad: AssessmentRow[] = [ - { - id: "g2", - instrument: "GAD7", - locale: "en", - totalScore: 5, - severityBand: "mild", - item9Flagged: false, - crisisShownAt: null, - takenAt: "2026-06-20T00:00:00.000Z", - }, - ]; - const html = withProviders( - , - ); - expect(html).toContain(mh.history.empty); - expect(html).not.toContain('data-slot="history-list"'); - }); }); diff --git a/src/components/mental-health/assessment-history-chart.tsx b/src/components/mental-health/assessment-history-chart.tsx deleted file mode 100644 index 7d8648c66..000000000 --- a/src/components/mental-health/assessment-history-chart.tsx +++ /dev/null @@ -1,161 +0,0 @@ -"use client"; - -/** - * v1.25.3 — per-instrument screener trend chart. - * - * v1.25.5 — aligned to the dashboard's chart language exactly: a single - * `type="monotone"` line over the primary colour with a soft gradient area - * fill beneath (same `fillOpacity` + margins the dashboard line charts use), - * the shared grid / axis tokens, and `RichChartTooltip`. The earlier - * severity-band `ReferenceArea`s — unique to this surface — are gone so it - * reads like every other trend; the band still surfaces in the tooltip. - * - * One instrument at a time: PHQ-9 (0–27) and GAD-7 (0–21) have different ranges - * and bands, so the parent toggles which series this paints. - */ -import { useMemo } from "react"; -import { - Area, - CartesianGrid, - ComposedChart, - Line, - ResponsiveContainer, - Tooltip, - XAxis, - YAxis, -} from "recharts"; - -import { RichChartTooltip, type RichTooltipRow } from "../charts/chart-tooltip"; -import { CHART_HEIGHT_PX } from "@/lib/charts/constants"; -import { prefersReducedMotion } from "@/lib/charts/reduced-motion"; -import { formatDateShort } from "@/lib/format"; -import { useTranslations } from "@/lib/i18n/context"; -import { INSTRUMENTS } from "@/lib/mental-health/instruments"; - -import type { AssessmentRow, InstrumentId } from "./types"; - -interface ChartPoint { - timestamp: number; - value: number; - band: string; -} - -export function AssessmentHistoryChart({ - instrument, - rows, -}: { - instrument: InstrumentId; - /** Rows for THIS instrument, any order (sorted internally, ascending time). */ - rows: AssessmentRow[]; -}) { - const { t } = useTranslations(); - const def = INSTRUMENTS[instrument]; - - const points = useMemo(() => { - return [...rows] - .sort( - (a, b) => new Date(a.takenAt).getTime() - new Date(b.takenAt).getTime(), - ) - .map((r) => ({ - timestamp: new Date(r.takenAt).getTime(), - value: r.totalScore, - band: r.severityBand, - })); - }, [rows]); - - const animate = !prefersReducedMotion(); - const primary = "var(--primary)"; - const gradientId = `mh-trend-${instrument}`; - - if (points.length === 0) { - return ( -

- {t("mentalHealth.history.empty")} -

- ); - } - - return ( - - - - - - - - - - formatDateShort(new Date(ts))} - tick={{ fontSize: 11, fill: "var(--muted-foreground)" }} - stroke="var(--border)" - minTickGap={32} - /> - - { - const active = props.active ?? false; - const payload = props.payload as - ReadonlyArray<{ payload?: ChartPoint }> | undefined; - const point = payload?.[0]?.payload; - if (!active || !point) { - return ; - } - const rowsOut: RichTooltipRow[] = [ - { - name: t("mentalHealth.history.totalLabel"), - value: String(point.value), - color: primary, - }, - { - name: t("mentalHealth.history.bandLabel"), - value: t(`mentalHealth.band.${instrument}.${point.band}`), - color: "var(--muted-foreground)", - }, - ]; - return ( - - ); - }} - /> - - - - - ); -} diff --git a/src/components/mental-health/assessment-history.tsx b/src/components/mental-health/assessment-history.tsx index d570fe7e9..1bda8bfe8 100644 --- a/src/components/mental-health/assessment-history.tsx +++ b/src/components/mental-health/assessment-history.tsx @@ -1,13 +1,17 @@ "use client"; /** - * v1.25.3 — the history card: a per-instrument trend chart over a band-shaded - * plot, then the dated list with severity badges. Mirrors the labs detail spine - * (chart → list) and lazy-loads Recharts through `next/dynamic` so it stays off - * the surface's first-load JS, with a `ChartSkeleton` shell. + * v1.25.3 — the history card: a dated list with severity badges. * - * PHQ-9 / GAD-7 toggle: the two instruments have different score ranges + bands, - * so the chart paints one at a time. + * v1.27.6 — the trend CHART is gone from this surface. A rising score curve + * on the check-in page itself reads as a verdict at the exact moment someone + * is taking stock of how they feel; the page stays a calm list of past + * check-ins. The score trend still exists — the totals ride PHQ9_SCORE / + * GAD7_SCORE measurement rows, so Insights / Measurements carry the curve + * for whoever seeks it out. + * + * PHQ-9 / GAD-7 toggle: the two instruments have different score ranges + + * bands, so the list shows one at a time. * * CRISIS-SAFETY: a flagged PHQ-9 row carries a discreet "support shown" marker; * tapping it re-surfaces the STATIC crisis-resource card derived from the row's @@ -16,16 +20,11 @@ * leaves the safety boundary. */ import { useMemo, useState } from "react"; -import dynamic from "next/dynamic"; -import type { ComponentProps } from "react"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { EmptyState } from "@/components/ui/empty-state"; -import { ChartErrorBoundary } from "@/components/charts/chart-error-state"; -import { ChartSkeleton } from "@/components/charts/chart-skeleton"; -import { importWithRetry } from "@/lib/retry-import"; import { useFormatters, useTranslations } from "@/lib/i18n/context"; import { crisisResourcesForLocale } from "@/lib/mental-health/crisis-resources"; import { Activity } from "lucide-react"; @@ -33,25 +32,6 @@ import { Activity } from "lucide-react"; import { CrisisCard } from "./crisis-card"; import type { AssessmentRow, CrisisSet, InstrumentId } from "./types"; -// Defer the recharts trend chart so recharts stays off the surface's first-load -// JS (the labs detail precedent). The `` shell holds the layout. -const AssessmentHistoryChartLazy = dynamic( - () => - importWithRetry(() => import("./assessment-history-chart")).then((mod) => ({ - default: mod.AssessmentHistoryChart, - })), - { ssr: false, loading: () => }, -); -function AssessmentHistoryChart( - props: ComponentProps, -) { - return ( - - - - ); -} - function lower(id: InstrumentId): "phq9" | "gad7" { return id === "PHQ9" ? "phq9" : "gad7"; } @@ -68,73 +48,57 @@ function crisisFromRow(row: AssessmentRow): CrisisSet { }; } -export function AssessmentHistory({ - rows, - instrument, -}: { - rows: AssessmentRow[]; - /** - * Pin the history to a single instrument. When set, the PHQ-9 / GAD-7 - * toggle + the outer Card chrome are dropped — the surface is a per- - * instrument detail (opened from an instrument card; the chrome + title - * come from the host sheet). Unset, it renders the combined landing card - * with the toggle, exactly as before. - */ - instrument?: InstrumentId; -}) { +export function AssessmentHistory({ rows }: { rows: AssessmentRow[] }) { const { t } = useTranslations(); const { date: formatDate } = useFormatters(); - const [tab, setTab] = useState(instrument ?? "PHQ9"); + const [tab, setTab] = useState("PHQ9"); // Which flagged row's crisis card is currently expanded (re-surfaced). const [openCrisisRowId, setOpenCrisisRowId] = useState(null); - const pinned = instrument != null; - // When pinned, the tab is forced to the host instrument so re-opening the - // detail for the other instrument never paints a stale series. - const activeTab = pinned ? instrument : tab; - const forTab = useMemo( () => rows - .filter((r) => r.instrument === activeTab) + .filter((r) => r.instrument === tab) .sort( (a, b) => new Date(b.takenAt).getTime() - new Date(a.takenAt).getTime(), ), - [rows, activeTab], + [rows, tab], ); - // Pinned: emptiness is per-instrument (the host opened THIS instrument's - // detail). Combined: emptiness spans both instruments, as before. - const isEmpty = pinned ? forTab.length === 0 : rows.length === 0; + const isEmpty = rows.length === 0; - const body = ( - <> - {isEmpty ? ( - } - title={t("mentalHealth.history.title")} - description={t("mentalHealth.history.empty")} - /> - ) : ( - <> - {/* PHQ-9 / GAD-7 toggle — the two ranges + band sets can't share an - axis, so the chart + list paint one instrument at a time. Hidden - when pinned: the host already chose the instrument. */} - {!pinned && ( + return ( + + + + {t("mentalHealth.history.title")} + + + + {isEmpty ? ( + } + title={t("mentalHealth.history.title")} + description={t("mentalHealth.history.empty")} + /> + ) : ( + <> + {/* PHQ-9 / GAD-7 toggle — the two ranges + band sets differ, so + the list shows one instrument at a time. */}
{(["PHQ9", "GAD7"] as InstrumentId[]).map((id) => ( ))}
- )} - - - {forTab.length === 0 ? ( -

- {t("mentalHealth.history.empty")} -

- ) : ( -
    - {forTab.map((row) => { - const flagged = row.item9Flagged; - const open = openCrisisRowId === row.id; - return ( -
  • -
    - - {formatDate(row.takenAt)} - -
    - {flagged && ( - - )} - - {t( - `mentalHealth.band.${row.instrument}.${row.severityBand}`, - )} - - - {row.totalScore} + {forTab.length === 0 ? ( +

    + {t("mentalHealth.history.empty")} +

    + ) : ( +
      + {forTab.map((row) => { + const flagged = row.item9Flagged; + const open = openCrisisRowId === row.id; + return ( +
    • +
      + + {formatDate(row.takenAt)} +
      + {flagged && ( + + )} + + {t( + `mentalHealth.band.${row.instrument}.${row.severityBand}`, + )} + + + {row.totalScore} + +
      -
    - {flagged && open && ( - - )} -
  • - ); - })} -
- )} - - )} - - ); - - // Pinned: render bare (the host sheet owns the chrome + title). Combined: - // the landing's titled history card, unchanged. - if (pinned) { - return ( -
- {body} -
- ); - } - - return ( - - - - {t("mentalHealth.history.title")} - - - {body} + {flagged && open && ( + + )} + + ); + })} + + )} + + )} +
); } diff --git a/src/components/mental-health/instrument-card.tsx b/src/components/mental-health/instrument-card.tsx index 57c9875b6..bb8751403 100644 --- a/src/components/mental-health/instrument-card.tsx +++ b/src/components/mental-health/instrument-card.tsx @@ -24,18 +24,11 @@ export function InstrumentCard({ instrument, last, onStart, - onOpenDetail, }: { instrument: InstrumentId; /** Most-recent assessment for this instrument, or undefined when none yet. */ last: AssessmentRow | undefined; onStart: () => void; - /** - * Open this instrument's trend detail. The card BODY (title + last-result - * line) is the navigation target — mirroring how a medication card opens its - * detail — while the bottom-pinned Start button stays a separate action. - */ - onOpenDetail: () => void; }) { const { t } = useTranslations(); const { date: formatDate } = useFormatters(); @@ -45,16 +38,10 @@ export function InstrumentCard({ return ( - {/* Body is the detail target: clicking the title / last-result block - opens this instrument's Verlauf, like a medication card. The Start - button below is a separate, deliberately non-overlapping action. */} - +