Files
HealthLog/src/components/cycle/symptom-catalog.ts
T
Marc-André Bombeck 985e197db4 feat(cycle): user-defined custom symptoms
Mirror the v1.13 custom-mood-tag CRUD for cycle symptoms. A user can mint
their own symptoms beyond the 15 seeded ones, picked from a label + icon
popover via a dashed ghost-chip in the log-day sheet's symptom grid.

- POST/GET /api/cycle/symptoms/custom and PATCH/DELETE on /[key]: mint a
  custom:<uuid> key, encrypt the free-text label at rest (AES-256-GCM),
  per-user cap, owner-scoped, gated behind requireCycleEnabled. Soft-hide by
  default, hard-purge on ?purge=true.
- The day-log write already resolves a symptom key against the catalogue plus
  the caller's own rows, so a custom: key links by construction.
- The log-day sheet fetches the user's customs and merges them under a new
  custom category in the chip grid; the ghost-chip + popover reuse the chip
  styling and the Lucide icon set.
- Seed the global custom symptom category (migration 0134).
- The purge route (DELETE /api/cycle/all) now drops the user's custom symptom
  rows too, so no intent-revealing label survives a purge.
- The label is never logged: only the icon + counts reach annotate/auditLog.
- i18n: cycle.symptom.custom.* across all six locales.
2026-06-06 16:54:43 +02:00

113 lines
3.2 KiB
TypeScript

/**
* v1.15.0 — the seeded cycle-symptom catalog, mirrored client-side.
*
* The keys are fixed at the migration seed (0129). The `key` is the
* snake_case DB key the day-log write resolves against; the `labelKey` is
* the camelCase i18n leaf under `cycle.symptom.*`; the `icon` is the
* Lucide name from the seed. Keep this in lockstep with the seed in
* `prisma/migrations/0129_v1150_cycle_symptom_taxonomy/migration.sql` and
* the `cycle.symptom.*` / `cycle.symptomCategory.*` i18n leaves.
*
* v1.15.1 layers per-user custom symptoms on top (the `custom` category,
* seeded by migration 0134): the log-day sheet fetches them from
* `/api/cycle/symptoms/custom` and merges them into this seeded grid. They
* are NOT in this static catalog — their labels are user free text held
* encrypted at rest, resolved server-side, never an i18n key.
*/
import {
Activity,
BatteryLow,
Brain,
CircleDot,
CircleSlash,
Cookie,
Drama,
Droplet,
Flame,
Frown,
Heart,
HeartPulse,
MoonStar,
PersonStanding,
Snowflake,
Soup,
Toilet,
Zap,
type LucideIcon,
} from "lucide-react";
export interface CycleSymptom {
/** snake_case DB key — the value sent in `symptoms[].key`. */
key: string;
/** i18n leaf under `cycle.symptom.*`. */
labelKey: string;
icon: LucideIcon;
}
export interface CycleSymptomCategory {
key: string;
/** i18n leaf under `cycle.symptomCategory.*`. */
labelKey: string;
icon: LucideIcon;
symptoms: CycleSymptom[];
}
export const CYCLE_SYMPTOM_CATALOG: CycleSymptomCategory[] = [
{
key: "physical",
labelKey: "cycle.symptomCategory.physical",
icon: Activity,
symptoms: [
{ key: "cramps", labelKey: "cycle.symptom.cramps", icon: Zap },
{ key: "headache", labelKey: "cycle.symptom.headache", icon: Brain },
{ key: "bloating", labelKey: "cycle.symptom.bloating", icon: CircleDot },
{ key: "acne", labelKey: "cycle.symptom.acne", icon: Droplet },
{
key: "breast_tenderness",
labelKey: "cycle.symptom.breastTenderness",
icon: HeartPulse,
},
{ key: "fatigue", labelKey: "cycle.symptom.fatigue", icon: BatteryLow },
{
key: "back_pain",
labelKey: "cycle.symptom.backPain",
icon: PersonStanding,
},
{ key: "insomnia", labelKey: "cycle.symptom.insomnia", icon: MoonStar },
],
},
{
key: "emotional",
labelKey: "cycle.symptomCategory.emotional",
icon: Heart,
symptoms: [
{ key: "libido_high", labelKey: "cycle.symptom.libidoHigh", icon: Flame },
{
key: "libido_low",
labelKey: "cycle.symptom.libidoLow",
icon: Snowflake,
},
{ key: "mood_swings", labelKey: "cycle.symptom.moodSwings", icon: Drama },
],
},
{
key: "digestive",
labelKey: "cycle.symptomCategory.digestive",
icon: Soup,
symptoms: [
{
key: "food_cravings",
labelKey: "cycle.symptom.foodCravings",
icon: Cookie,
},
{ key: "nausea", labelKey: "cycle.symptom.nausea", icon: Frown },
{ key: "diarrhea", labelKey: "cycle.symptom.diarrhea", icon: Toilet },
{
key: "constipation",
labelKey: "cycle.symptom.constipation",
icon: CircleSlash,
},
],
},
];