feat(symptoms): log a symptom in one step and show it on the illness pages

- A new capture kind "symptom" (illness module, illness write grant) in
  the bottom bar's picker and the dashboard Add menu. The sheet follows
  the measurement and mood forms: the person's symptoms as chips with the
  cycle sheet's dashed "new symptom" chip, the 0-10 slider with its
  anchors, the time prefilled with now, an optional note, and a "during"
  selector only while an illness episode is open.
- "Your symptoms" on the illness page: one line per symptom (how often in
  30 days, how strong at worst, when last), the last 30 days grouped by
  day, edit and delete per entry, rename, icon, hide and delete per
  symptom. Renders nothing until a symptom exists.
- "Symptoms logged" on an episode's page: the entries filed against it or
  logged while it ran, the filed ones marked. This is the reader of the
  episode link.
- Query keys live under the illness root, so either side's writes
  refresh both.
This commit is contained in:
Marc-André Bombeck committed 2026-10-03 21:24:12 +02:00
1 parent 9d9e1f153c
commit 9272daa59c
17 files changed
+1940 -19

No files matched your search

@@ -14,6 +14,7 @@ import Link from "next/link";
import { EpisodeVisitsCard } from "@/components/illness/episode-visits-card";
import { EpisodeDocumentsCard } from "@/components/documents/episode-documents-card";
import { EpisodeSymptomsCard } from "@/components/symptoms/episode-symptoms-card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
@@ -176,6 +177,10 @@ export function IllnessEpisodeDetail({ episodeId }: { episodeId: string }) {
/>
) : null}
{/* v1.40 — the person's own symptoms filed against this episode or
logged while it ran. Renders nothing when there are none. */}
{episode ? <EpisodeSymptomsCard episode={episode} /> : null}
{isLoading ? (
<Skeleton className="h-40 w-full" />
) : (
+22 -14
View File
@@ -41,6 +41,7 @@ import { LogDaySheet } from "./log-day-sheet";
import { NewEpisodeSheet } from "./new-episode-sheet";
import { EpisodeMenu } from "./episode-menu";
import { IllnessInsightsCard } from "./illness-insights-card";
import { SymptomsSection } from "@/components/symptoms/symptoms-section";
import { useIllnessEpisodes, useResolveEpisode } from "./use-illness";
import type { IllnessEpisodeDTO } from "./types";
@@ -448,6 +449,10 @@ export function IllnessView() {
never buries it. */}
<IllnessInsightsCard />
{/* v1.40 — the person's own symptoms; renders nothing until one is
defined. */}
<SymptomsSection />
<EpisodeGroup
title={t("illness.section.active")}
parents={grouped.activeParents}
@@ -471,20 +476,23 @@ export function IllnessView() {
/>
</>
) : (
<EmptyState
icon={<Stethoscope className="size-6" />}
title={t("illness.empty.title")}
description={t("illness.empty.body")}
ctaSize="lg"
action={
canAddEpisode ? (
<Button onClick={() => setNewOpen(true)}>
<Plus className="h-4 w-4" />
{t("illness.newEpisode")}
</Button>
) : undefined
}
/>
<>
<SymptomsSection />
<EmptyState
icon={<Stethoscope className="size-6" />}
title={t("illness.empty.title")}
description={t("illness.empty.body")}
ctaSize="lg"
action={
canAddEpisode ? (
<Button onClick={() => setNewOpen(true)}>
<Plus className="h-4 w-4" />
{t("illness.newEpisode")}
</Button>
) : undefined
}
/>
</>
)}
<NewEpisodeSheet open={newOpen} onOpenChange={setNewOpen} today={today} />