@burdenoff/microfe-mybodyshield 2026.716.1 → 2026.720.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"ProgramsPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/programs/ProgramsPage.tsx"],"sourcesContent":["/**\n * Programs — training program library (Training pillar).\n * @module mybodyshield/pages/programs\n *\n * Lists the member's structured training blocks (programs), filterable by\n * lifecycle status and goal type, with a create dialog. Every program links to\n * its detail page. All data comes from the real `mybListPrograms` /\n * `mybCreateProgram` operations — no mock data.\n */\n\nimport { useMemo, useState, type FC, type ReactNode } from 'react';\nimport { motion, useReducedMotion } from 'framer-motion';\nimport { Plus, Dumbbell, FilePenLine, Layers, Clock } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectTrigger,\n SelectContent,\n SelectItem,\n SelectValue,\n GlassCard,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n MetricsCard,\n StatusBadge,\n EmptyState,\n LoadingView,\n ErrorView,\n FilterTabs,\n Pagination,\n type FilterOption,\n} from '../../components';\nimport { IconChip, GOAL_TYPE_META } from '../_shared/visuals';\nimport { useMyBodyShield } from '../../context';\nimport {\n useMybPrograms,\n useMybCreateProgram,\n useOffsetPager,\n useMyBodyShieldAnalytics,\n} from '../../hooks';\nimport { formatEnumLabel, formatRelativeTime, formatNumber } from '../../utils/formatters';\nimport type {\n MybProgram,\n MybProgramStatus,\n MybGoalType,\n MybFitnessLevel,\n MybProgramPhase,\n MybCreateProgramInput,\n} from '../../types';\n\nconst PAGE_SIZE = 24;\n\nconst STATUS_FILTERS = ['ALL', 'DRAFT', 'ACTIVE', 'COMPLETED', 'ARCHIVED'] as const;\ntype StatusFilter = (typeof STATUS_FILTERS)[number];\n\nconst GOAL_TYPES: readonly MybGoalType[] = [\n 'WEIGHT_LOSS',\n 'MUSCLE_GAIN',\n 'STRENGTH',\n 'ENDURANCE',\n 'MOBILITY',\n 'GENERAL_FITNESS',\n];\nconst LEVELS: readonly MybFitnessLevel[] = ['BEGINNER', 'INTERMEDIATE', 'ADVANCED', 'ELITE'];\nconst PHASES: readonly MybProgramPhase[] = ['BUILD', 'PEAK', 'DELOAD', 'MAINTENANCE'];\n\ntype GoalFilter = MybGoalType | 'ALL';\n\ninterface ProgramForm {\n name: string;\n description: string;\n goalType: MybGoalType;\n level: MybFitnessLevel;\n phase: MybProgramPhase;\n weeks: string;\n daysPerWeek: string;\n startDate: string;\n}\n\nconst EMPTY_FORM: ProgramForm = {\n name: '',\n description: '',\n goalType: 'GENERAL_FITNESS',\n level: 'BEGINNER',\n phase: 'BUILD',\n weeks: '',\n daysPerWeek: '',\n startDate: '',\n};\n\n/** Parse a numeric text field → number | null (null when blank / invalid). */\nconst toNum = (raw: string): number | null => {\n const t = raw.trim();\n if (t === '') return null;\n const n = Number(t);\n return Number.isFinite(n) ? n : null;\n};\n\nconst REVEAL_EASE: [number, number, number, number] = [0.22, 1, 0.36, 1];\n\n/** Staggered card entrance + gentle hover lift (reduced-motion safe). */\nconst Reveal: FC<{\n index?: number;\n hover?: boolean;\n className?: string;\n children: ReactNode;\n}> = ({ index = 0, hover = false, className, children }) => {\n const reduced = useReducedMotion();\n return (\n <motion.div\n className={className}\n initial={reduced ? false : { opacity: 0, y: 12 }}\n animate={{ opacity: 1, y: 0 }}\n transition={\n reduced\n ? { duration: 0 }\n : { duration: 0.35, delay: Math.min(index * 0.05, 0.45), ease: REVEAL_EASE }\n }\n whileHover={reduced || !hover ? undefined : { y: -4 }}\n >\n {children}\n </motion.div>\n );\n};\n\n/** Token-safe generic enum <Select>. */\nfunction EnumSelect<T extends string>({\n id,\n value,\n onChange,\n options,\n}: {\n id?: string;\n value: T;\n onChange: (value: T) => void;\n options: readonly T[];\n}) {\n return (\n <Select value={value} onValueChange={(v) => onChange(v as T)}>\n <SelectTrigger id={id}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {options.map((opt) => (\n <SelectItem key={opt} value={opt}>\n {formatEnumLabel(opt)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n}\n\nfunction ProgramCard({\n program,\n index,\n onOpen,\n}: {\n program: MybProgram;\n index: number;\n onOpen: (id: string) => void;\n}) {\n const goalMeta = GOAL_TYPE_META[program.goalType];\n const GoalIcon = goalMeta.icon;\n const metaLine = [program.phase, program.level, program.goalType]\n .map((value) => formatEnumLabel(value))\n .join(' · ');\n return (\n <Reveal index={index} hover className=\"h-full\">\n <button\n type=\"button\"\n onClick={() => onOpen(program.id)}\n className=\"block h-full w-full text-left\"\n >\n <GlassCard\n treatment=\"glass\"\n className=\"flex h-full flex-col p-5 transition-shadow hover:shadow-[var(--shadow-pop-hover)]\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-start gap-3\">\n <IconChip icon={<GoalIcon className=\"h-4 w-4\" />} tint={goalMeta.tint} />\n <div className=\"min-w-0\">\n <p className=\"truncate font-semibold text-text-primary\">{program.name}</p>\n <p className=\"mt-0.5 truncate text-xs text-text-tertiary\">{metaLine}</p>\n </div>\n </div>\n <StatusBadge status={program.status} />\n </div>\n\n {program.description && (\n <p className=\"mt-3 line-clamp-2 text-sm text-text-secondary\">{program.description}</p>\n )}\n\n <div className=\"mt-3 inline-flex items-center gap-1.5 text-sm text-text-secondary\">\n <Clock className=\"h-4 w-4 shrink-0 text-action-primary-bg\" />\n <span className=\"tabular-nums\">\n {formatNumber(program.weeks)} wk × {formatNumber(program.daysPerWeek)} days\n </span>\n </div>\n\n {program.tags.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {program.tags.slice(0, 4).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium text-text-secondary\"\n >\n #{tag}\n </span>\n ))}\n </div>\n )}\n\n <div className=\"mt-auto pt-4 text-xs text-text-tertiary\">\n Updated {formatRelativeTime(program.updatedAt)}\n </div>\n </GlassCard>\n </button>\n </Reveal>\n );\n}\n\nexport function ProgramsPage() {\n const { navigateTo, workspaceId } = useMyBodyShield();\n const pager = useOffsetPager(PAGE_SIZE);\n const analytics = useMyBodyShieldAnalytics();\n\n const [statusFilter, setStatusFilter] = useState<StatusFilter>('ALL');\n const [goalFilter, setGoalFilter] = useState<GoalFilter>('ALL');\n\n const [createOpen, setCreateOpen] = useState(false);\n const [form, setForm] = useState<ProgramForm>(EMPTY_FORM);\n const [err, setErr] = useState<string | null>(null);\n\n const create = useMybCreateProgram();\n\n // Aggregate summary (unfiltered, capped) for the KPI row + filter counts.\n const summary = useMybPrograms({ pagination: { limit: 100 } });\n const summaryRows = useMemo(() => summary.data?.items ?? [], [summary.data]);\n\n const counts = useMemo(() => {\n const by = (s: MybProgramStatus) => summaryRows.filter((p) => p.status === s).length;\n return {\n ALL: summaryRows.length,\n DRAFT: by('DRAFT'),\n ACTIVE: by('ACTIVE'),\n COMPLETED: by('COMPLETED'),\n ARCHIVED: by('ARCHIVED'),\n };\n }, [summaryRows]);\n\n const list = useMybPrograms({\n status: statusFilter === 'ALL' ? undefined : statusFilter,\n pagination: { limit: PAGE_SIZE, offset: pager.offset },\n });\n\n const rawRows = list.data?.items ?? [];\n const rows = goalFilter === 'ALL' ? rawRows : rawRows.filter((p) => p.goalType === goalFilter);\n\n const changeStatus = (next: StatusFilter) => {\n setStatusFilter(next);\n pager.reset();\n };\n const changeGoal = (next: GoalFilter) => {\n setGoalFilter(next);\n pager.reset();\n };\n\n const openCreate = () => {\n setForm(EMPTY_FORM);\n setErr(null);\n setCreateOpen(true);\n analytics.emitIntent('program', 'create', { workspaceId });\n };\n\n const submit = async () => {\n if (!form.name.trim()) {\n setErr('Program name is required.');\n return;\n }\n const input: MybCreateProgramInput = {\n name: form.name.trim(),\n description: form.description.trim() || null,\n goalType: form.goalType,\n level: form.level,\n phase: form.phase,\n weeks: toNum(form.weeks),\n daysPerWeek: toNum(form.daysPerWeek),\n startDate: form.startDate ? new Date(form.startDate).toISOString() : null,\n };\n try {\n const created = await create.mutateAsync(input);\n analytics.emitFact('program', 'created', { workspaceId, entityId: created.id });\n setCreateOpen(false);\n navigateTo(`programs/${created.id}`);\n } catch (e) {\n setErr(e instanceof Error ? e.message : 'Failed to create program.');\n }\n };\n\n const statusOptions: FilterOption<StatusFilter>[] = STATUS_FILTERS.map((s) => ({\n value: s,\n label: s === 'ALL' ? 'All' : formatEnumLabel(s),\n count: counts[s],\n }));\n\n return (\n <PageLayout\n title=\"Programs\"\n description=\"Structured training blocks — build, peak, deload & maintain.\"\n actions={\n <Button onClick={openCreate}>\n <Plus className=\"mr-1 h-4 w-4\" /> New program\n </Button>\n }\n >\n {/* KPI row */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <Reveal index={0} className=\"h-full\">\n <MetricsCard\n className=\"h-full\"\n icon={<Dumbbell className=\"h-5 w-5\" />}\n value={formatNumber(counts.ACTIVE)}\n label=\"Active programs\"\n accent=\"success\"\n />\n </Reveal>\n <Reveal index={1} className=\"h-full\">\n <MetricsCard\n className=\"h-full\"\n icon={<FilePenLine className=\"h-5 w-5\" />}\n value={formatNumber(counts.DRAFT)}\n label=\"Draft programs\"\n />\n </Reveal>\n <Reveal index={2} className=\"h-full\">\n <MetricsCard\n className=\"h-full\"\n icon={<Layers className=\"h-5 w-5\" />}\n value={formatNumber(counts.ALL)}\n label=\"Total programs\"\n />\n </Reveal>\n </div>\n\n {/* Filters */}\n <div className=\"mt-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <FilterTabs options={statusOptions} value={statusFilter} onChange={changeStatus} />\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-text-tertiary\">Goal</span>\n <div className=\"w-48\">\n <Select value={goalFilter} onValueChange={(v) => changeGoal(v as GoalFilter)}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All goals</SelectItem>\n {GOAL_TYPES.map((g) => (\n <SelectItem key={g} value={g}>\n {formatEnumLabel(g)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n </div>\n\n {/* List */}\n <div className=\"mt-6\">\n {list.isLoading ? (\n <LoadingView label=\"Loading programs…\" />\n ) : list.isError ? (\n <ErrorView\n message=\"We couldn't load your programs.\"\n onRetry={() => void list.refetch()}\n />\n ) : rawRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Dumbbell className=\"h-10 w-10\" />}\n title={statusFilter === 'ALL' ? 'No programs yet' : 'No programs in this status'}\n description=\"Create a structured training program to plan your workouts week by week.\"\n action={\n <Button onClick={openCreate}>\n <Plus className=\"mr-1 h-4 w-4\" /> New program\n </Button>\n }\n />\n ) : rows.length === 0 ? (\n <div className=\"rounded-xl border border-dashed border-border-seam py-10 text-center text-sm text-text-secondary\">\n No programs on this page match{' '}\n <span className=\"font-medium text-text-primary\">{formatEnumLabel(goalFilter)}</span>.\n </div>\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {rows.map((program, i) => (\n <ProgramCard\n key={program.id}\n program={program}\n index={i}\n onOpen={(id) => navigateTo(`programs/${id}`)}\n />\n ))}\n </div>\n )}\n\n {!list.isLoading && !list.isError && rawRows.length > 0 && (\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={rawRows.length}\n hasMore={list.data?.pageInfo?.hasMore ?? false}\n onPageChange={(t) => pager.onPageChange(t)}\n />\n )}\n </div>\n\n {/* Create dialog */}\n <ResponsiveDialog open={createOpen} onOpenChange={setCreateOpen}>\n <ResponsiveDialogContent className=\"sm:max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New program</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Define a structured training block. You can add workouts &amp; exercises after\n creating it.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4 py-2\">\n {err && (\n <p className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {err}\n </p>\n )}\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-prog-name\">Name</Label>\n <Input\n id=\"myb-prog-name\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n placeholder=\"e.g. Hypertrophy Block A\"\n />\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-prog-desc\">Description</Label>\n <Textarea\n id=\"myb-prog-desc\"\n rows={3}\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n placeholder=\"Focus, progression scheme, notes…\"\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-prog-goal\">Goal type</Label>\n <EnumSelect\n id=\"myb-prog-goal\"\n value={form.goalType}\n onChange={(v) => setForm({ ...form, goalType: v })}\n options={GOAL_TYPES}\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-prog-level\">Level</Label>\n <EnumSelect\n id=\"myb-prog-level\"\n value={form.level}\n onChange={(v) => setForm({ ...form, level: v })}\n options={LEVELS}\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-prog-phase\">Phase</Label>\n <EnumSelect\n id=\"myb-prog-phase\"\n value={form.phase}\n onChange={(v) => setForm({ ...form, phase: v })}\n options={PHASES}\n />\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-prog-weeks\">Weeks</Label>\n <Input\n id=\"myb-prog-weeks\"\n type=\"number\"\n min={1}\n value={form.weeks}\n onChange={(e) => setForm({ ...form, weeks: e.target.value })}\n placeholder=\"8\"\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-prog-days\">Days / week</Label>\n <Input\n id=\"myb-prog-days\"\n type=\"number\"\n min={1}\n max={7}\n value={form.daysPerWeek}\n onChange={(e) => setForm({ ...form, daysPerWeek: e.target.value })}\n placeholder=\"4\"\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-prog-start\">Start date</Label>\n <Input\n id=\"myb-prog-start\"\n type=\"date\"\n value={form.startDate}\n onChange={(e) => setForm({ ...form, startDate: e.target.value })}\n />\n </div>\n </div>\n </div>\n\n <ResponsiveDialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => setCreateOpen(false)}\n disabled={create.isPending}\n >\n Cancel\n </Button>\n <Button onClick={() => void submit()} disabled={create.isPending}>\n {create.isPending ? 'Creating…' : 'Create program'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ProgramsPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/programs/ProgramsPage.tsx"],"sourcesContent":["/**\n * Programs — training program library (Training pillar).\n * @module mybodyshield/pages/programs\n *\n * Lists the member's structured training blocks (programs), filterable by\n * lifecycle status and goal type, with a create dialog. Every program links to\n * its detail page. All data comes from the real `mybListPrograms` /\n * `mybCreateProgram` operations — no mock data.\n */\n\nimport { useMemo, useState, type FC, type ReactNode } from 'react';\nimport { motion, useReducedMotion } from 'framer-motion';\nimport { Plus, Dumbbell, FilePenLine, Layers, Clock } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectTrigger,\n SelectContent,\n SelectItem,\n SelectValue,\n GlassCard,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n MetricsCard,\n StatusBadge,\n EmptyState,\n LoadingView,\n ErrorView,\n FilterTabs,\n Pagination,\n type FilterOption,\n} from '../../components';\nimport { IconChip, GOAL_TYPE_META } from '../_shared/visuals';\nimport { useMyBodyShield } from '../../context';\nimport {\n useMybPrograms,\n useMybCreateProgram,\n useMybAddWorkout,\n useOffsetPager,\n useMyBodyShieldAnalytics,\n} from '../../hooks';\nimport { formatEnumLabel, formatRelativeTime, formatNumber } from '../../utils/formatters';\nimport { PROGRAM_TEMPLATES, findProgramTemplate } from '../../data/programTemplates';\nimport type {\n MybProgram,\n MybProgramStatus,\n MybGoalType,\n MybFitnessLevel,\n MybProgramPhase,\n MybCreateProgramInput,\n} from '../../types';\n\nconst BLANK_TEMPLATE_ID = 'blank';\n\nconst PAGE_SIZE = 24;\n\nconst STATUS_FILTERS = ['ALL', 'DRAFT', 'ACTIVE', 'COMPLETED', 'ARCHIVED'] as const;\ntype StatusFilter = (typeof STATUS_FILTERS)[number];\n\nconst GOAL_TYPES: readonly MybGoalType[] = [\n 'WEIGHT_LOSS',\n 'MUSCLE_GAIN',\n 'STRENGTH',\n 'ENDURANCE',\n 'MOBILITY',\n 'GENERAL_FITNESS',\n];\nconst LEVELS: readonly MybFitnessLevel[] = ['BEGINNER', 'INTERMEDIATE', 'ADVANCED', 'ELITE'];\nconst PHASES: readonly MybProgramPhase[] = ['BUILD', 'PEAK', 'DELOAD', 'MAINTENANCE'];\n\ntype GoalFilter = MybGoalType | 'ALL';\n\ninterface ProgramForm {\n name: string;\n description: string;\n goalType: MybGoalType;\n level: MybFitnessLevel;\n phase: MybProgramPhase;\n weeks: string;\n daysPerWeek: string;\n startDate: string;\n}\n\nconst EMPTY_FORM: ProgramForm = {\n name: '',\n description: '',\n goalType: 'GENERAL_FITNESS',\n level: 'BEGINNER',\n phase: 'BUILD',\n weeks: '',\n daysPerWeek: '',\n startDate: '',\n};\n\n/** Parse a numeric text field → number | null (null when blank / invalid). */\nconst toNum = (raw: string): number | null => {\n const t = raw.trim();\n if (t === '') return null;\n const n = Number(t);\n return Number.isFinite(n) ? n : null;\n};\n\nconst REVEAL_EASE: [number, number, number, number] = [0.22, 1, 0.36, 1];\n\n/** Staggered card entrance + gentle hover lift (reduced-motion safe). */\nconst Reveal: FC<{\n index?: number;\n hover?: boolean;\n className?: string;\n children: ReactNode;\n}> = ({ index = 0, hover = false, className, children }) => {\n const reduced = useReducedMotion();\n return (\n <motion.div\n className={className}\n initial={reduced ? false : { opacity: 0, y: 12 }}\n animate={{ opacity: 1, y: 0 }}\n transition={\n reduced\n ? { duration: 0 }\n : { duration: 0.35, delay: Math.min(index * 0.05, 0.45), ease: REVEAL_EASE }\n }\n whileHover={reduced || !hover ? undefined : { y: -4 }}\n >\n {children}\n </motion.div>\n );\n};\n\n/** Token-safe generic enum <Select>. */\nfunction EnumSelect<T extends string>({\n id,\n value,\n onChange,\n options,\n}: {\n id?: string;\n value: T;\n onChange: (value: T) => void;\n options: readonly T[];\n}) {\n return (\n <Select value={value} onValueChange={(v) => onChange(v as T)}>\n <SelectTrigger id={id}>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {options.map((opt) => (\n <SelectItem key={opt} value={opt}>\n {formatEnumLabel(opt)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n}\n\nfunction ProgramCard({\n program,\n index,\n onOpen,\n}: {\n program: MybProgram;\n index: number;\n onOpen: (id: string) => void;\n}) {\n const goalMeta = GOAL_TYPE_META[program.goalType];\n const GoalIcon = goalMeta.icon;\n const metaLine = [program.phase, program.level, program.goalType]\n .map((value) => formatEnumLabel(value))\n .join(' · ');\n return (\n <Reveal index={index} hover className=\"h-full\">\n <button\n type=\"button\"\n onClick={() => onOpen(program.id)}\n className=\"block h-full w-full text-left\"\n >\n <GlassCard\n treatment=\"glass\"\n className=\"flex h-full flex-col p-5 transition-shadow hover:shadow-[var(--shadow-pop-hover)]\"\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-start gap-3\">\n <IconChip icon={<GoalIcon className=\"h-4 w-4\" />} tint={goalMeta.tint} />\n <div className=\"min-w-0\">\n <p className=\"truncate font-semibold text-text-primary\">{program.name}</p>\n <p className=\"mt-0.5 truncate text-xs text-text-tertiary\">{metaLine}</p>\n </div>\n </div>\n <StatusBadge status={program.status} />\n </div>\n\n {program.description && (\n <p className=\"mt-3 line-clamp-2 text-sm text-text-secondary\">{program.description}</p>\n )}\n\n <div className=\"mt-3 inline-flex items-center gap-1.5 text-sm text-text-secondary\">\n <Clock className=\"h-4 w-4 shrink-0 text-action-primary-bg\" />\n <span className=\"tabular-nums\">\n {formatNumber(program.weeks)} wk × {formatNumber(program.daysPerWeek)} days\n </span>\n </div>\n\n {program.tags.length > 0 && (\n <div className=\"mt-3 flex flex-wrap gap-1.5\">\n {program.tags.slice(0, 4).map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium text-text-secondary\"\n >\n #{tag}\n </span>\n ))}\n </div>\n )}\n\n <div className=\"mt-auto pt-4 text-xs text-text-tertiary\">\n Updated {formatRelativeTime(program.updatedAt)}\n </div>\n </GlassCard>\n </button>\n </Reveal>\n );\n}\n\nexport function ProgramsPage() {\n const { navigateTo, workspaceId } = useMyBodyShield();\n const pager = useOffsetPager(PAGE_SIZE);\n const analytics = useMyBodyShieldAnalytics();\n\n const [statusFilter, setStatusFilter] = useState<StatusFilter>('ALL');\n const [goalFilter, setGoalFilter] = useState<GoalFilter>('ALL');\n\n const [createOpen, setCreateOpen] = useState(false);\n const [form, setForm] = useState<ProgramForm>(EMPTY_FORM);\n const [templateId, setTemplateId] = useState<string>(BLANK_TEMPLATE_ID);\n const [err, setErr] = useState<string | null>(null);\n\n const create = useMybCreateProgram();\n const addWorkout = useMybAddWorkout();\n\n // Aggregate summary (unfiltered, capped) for the KPI row + filter counts.\n const summary = useMybPrograms({ pagination: { limit: 100 } });\n const summaryRows = useMemo(() => summary.data?.items ?? [], [summary.data]);\n\n const counts = useMemo(() => {\n const by = (s: MybProgramStatus) => summaryRows.filter((p) => p.status === s).length;\n return {\n ALL: summaryRows.length,\n DRAFT: by('DRAFT'),\n ACTIVE: by('ACTIVE'),\n COMPLETED: by('COMPLETED'),\n ARCHIVED: by('ARCHIVED'),\n };\n }, [summaryRows]);\n\n const list = useMybPrograms({\n status: statusFilter === 'ALL' ? undefined : statusFilter,\n pagination: { limit: PAGE_SIZE, offset: pager.offset },\n });\n\n const rawRows = list.data?.items ?? [];\n const rows = goalFilter === 'ALL' ? rawRows : rawRows.filter((p) => p.goalType === goalFilter);\n\n const changeStatus = (next: StatusFilter) => {\n setStatusFilter(next);\n pager.reset();\n };\n const changeGoal = (next: GoalFilter) => {\n setGoalFilter(next);\n pager.reset();\n };\n\n const openCreate = () => {\n setForm(EMPTY_FORM);\n setTemplateId(BLANK_TEMPLATE_ID);\n setErr(null);\n setCreateOpen(true);\n analytics.emitIntent('program', 'create', { workspaceId });\n };\n\n const applyTemplate = (id: string) => {\n setTemplateId(id);\n if (id === BLANK_TEMPLATE_ID) return;\n const template = findProgramTemplate(id);\n if (!template) return;\n setForm({\n name: template.name,\n description: template.description,\n goalType: template.goalType,\n level: template.level,\n phase: 'BUILD',\n weeks: String(template.weeks),\n daysPerWeek: String(template.daysPerWeek),\n startDate: '',\n });\n };\n\n const submit = async () => {\n if (!form.name.trim()) {\n setErr('Program name is required.');\n return;\n }\n const input: MybCreateProgramInput = {\n name: form.name.trim(),\n description: form.description.trim() || null,\n goalType: form.goalType,\n level: form.level,\n phase: form.phase,\n weeks: toNum(form.weeks),\n daysPerWeek: toNum(form.daysPerWeek),\n startDate: form.startDate ? new Date(form.startDate).toISOString() : null,\n };\n try {\n const created = await create.mutateAsync(input);\n\n // Seed the template's starter week of workouts via the real\n // mybAddWorkout mutation — best-effort: the program itself is already\n // created, so a partial seeding failure must not block navigation.\n const template =\n templateId === BLANK_TEMPLATE_ID ? undefined : findProgramTemplate(templateId);\n if (template) {\n await Promise.allSettled(\n template.workouts.map((w) =>\n addWorkout.mutateAsync({\n programId: created.id,\n name: w.name,\n type: w.type,\n dayOfWeek: w.dayOfWeek,\n weekNumber: w.weekNumber,\n durationMin: w.durationMin,\n })\n )\n );\n }\n\n analytics.emitFact('program', 'created', { workspaceId, entityId: created.id });\n setCreateOpen(false);\n navigateTo(`programs/${created.id}`);\n } catch (e) {\n setErr(e instanceof Error ? e.message : 'Failed to create program.');\n }\n };\n\n const statusOptions: FilterOption<StatusFilter>[] = STATUS_FILTERS.map((s) => ({\n value: s,\n label: s === 'ALL' ? 'All' : formatEnumLabel(s),\n count: counts[s],\n }));\n\n return (\n <PageLayout\n title=\"Programs\"\n description=\"Structured training blocks — build, peak, deload & maintain.\"\n actions={\n <Button onClick={openCreate}>\n <Plus className=\"mr-1 h-4 w-4\" /> New program\n </Button>\n }\n >\n {/* KPI row */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <Reveal index={0} className=\"h-full\">\n <MetricsCard\n className=\"h-full\"\n icon={<Dumbbell className=\"h-5 w-5\" />}\n value={formatNumber(counts.ACTIVE)}\n label=\"Active programs\"\n accent=\"success\"\n />\n </Reveal>\n <Reveal index={1} className=\"h-full\">\n <MetricsCard\n className=\"h-full\"\n icon={<FilePenLine className=\"h-5 w-5\" />}\n value={formatNumber(counts.DRAFT)}\n label=\"Draft programs\"\n />\n </Reveal>\n <Reveal index={2} className=\"h-full\">\n <MetricsCard\n className=\"h-full\"\n icon={<Layers className=\"h-5 w-5\" />}\n value={formatNumber(counts.ALL)}\n label=\"Total programs\"\n />\n </Reveal>\n </div>\n\n {/* Filters */}\n <div className=\"mt-6 flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <FilterTabs options={statusOptions} value={statusFilter} onChange={changeStatus} />\n <div className=\"flex items-center gap-2\">\n <span className=\"text-sm text-text-tertiary\">Goal</span>\n <div className=\"w-48\">\n <Select value={goalFilter} onValueChange={(v) => changeGoal(v as GoalFilter)}>\n <SelectTrigger>\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"ALL\">All goals</SelectItem>\n {GOAL_TYPES.map((g) => (\n <SelectItem key={g} value={g}>\n {formatEnumLabel(g)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </div>\n </div>\n\n {/* List */}\n <div className=\"mt-6\">\n {list.isLoading ? (\n <LoadingView label=\"Loading programs…\" />\n ) : list.isError ? (\n <ErrorView\n message=\"We couldn't load your programs.\"\n onRetry={() => void list.refetch()}\n />\n ) : rawRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Dumbbell className=\"h-10 w-10\" />}\n title={statusFilter === 'ALL' ? 'No programs yet' : 'No programs in this status'}\n description=\"Create a structured training program to plan your workouts week by week.\"\n action={\n <Button onClick={openCreate}>\n <Plus className=\"mr-1 h-4 w-4\" /> New program\n </Button>\n }\n />\n ) : rows.length === 0 ? (\n <div className=\"rounded-xl border border-dashed border-border-seam py-10 text-center text-sm text-text-secondary\">\n No programs on this page match{' '}\n <span className=\"font-medium text-text-primary\">{formatEnumLabel(goalFilter)}</span>.\n </div>\n ) : (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {rows.map((program, i) => (\n <ProgramCard\n key={program.id}\n program={program}\n index={i}\n onOpen={(id) => navigateTo(`programs/${id}`)}\n />\n ))}\n </div>\n )}\n\n {!list.isLoading && !list.isError && rawRows.length > 0 && (\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={rawRows.length}\n hasMore={list.data?.pageInfo?.hasMore ?? false}\n onPageChange={(t) => pager.onPageChange(t)}\n />\n )}\n </div>\n\n {/* Create dialog */}\n <ResponsiveDialog open={createOpen} onOpenChange={setCreateOpen}>\n <ResponsiveDialogContent className=\"sm:max-w-lg\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New program</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Define a structured training block. You can add workouts &amp; exercises after\n creating it.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4 py-2\">\n {err && (\n <p className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {err}\n </p>\n )}\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-prog-template\">Start from a template</Label>\n <Select value={templateId} onValueChange={applyTemplate}>\n <SelectTrigger id=\"myb-prog-template\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={BLANK_TEMPLATE_ID}>Blank program</SelectItem>\n {PROGRAM_TEMPLATES.map((t) => (\n <SelectItem key={t.id} value={t.id}>\n {t.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {templateId !== BLANK_TEMPLATE_ID && (\n <p className=\"text-xs text-text-tertiary\">\n Fields below are pre-filled from the template and seed a starter week of workouts\n — edit anything before creating.\n </p>\n )}\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-prog-name\">Name</Label>\n <Input\n id=\"myb-prog-name\"\n value={form.name}\n onChange={(e) => setForm({ ...form, name: e.target.value })}\n placeholder=\"e.g. Hypertrophy Block A\"\n />\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-prog-desc\">Description</Label>\n <Textarea\n id=\"myb-prog-desc\"\n rows={3}\n value={form.description}\n onChange={(e) => setForm({ ...form, description: e.target.value })}\n placeholder=\"Focus, progression scheme, notes…\"\n />\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-prog-goal\">Goal type</Label>\n <EnumSelect\n id=\"myb-prog-goal\"\n value={form.goalType}\n onChange={(v) => setForm({ ...form, goalType: v })}\n options={GOAL_TYPES}\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-prog-level\">Level</Label>\n <EnumSelect\n id=\"myb-prog-level\"\n value={form.level}\n onChange={(v) => setForm({ ...form, level: v })}\n options={LEVELS}\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-prog-phase\">Phase</Label>\n <EnumSelect\n id=\"myb-prog-phase\"\n value={form.phase}\n onChange={(v) => setForm({ ...form, phase: v })}\n options={PHASES}\n />\n </div>\n </div>\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-prog-weeks\">Weeks</Label>\n <Input\n id=\"myb-prog-weeks\"\n type=\"number\"\n min={1}\n value={form.weeks}\n onChange={(e) => setForm({ ...form, weeks: e.target.value })}\n placeholder=\"8\"\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-prog-days\">Days / week</Label>\n <Input\n id=\"myb-prog-days\"\n type=\"number\"\n min={1}\n max={7}\n value={form.daysPerWeek}\n onChange={(e) => setForm({ ...form, daysPerWeek: e.target.value })}\n placeholder=\"4\"\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-prog-start\">Start date</Label>\n <Input\n id=\"myb-prog-start\"\n type=\"date\"\n value={form.startDate}\n onChange={(e) => setForm({ ...form, startDate: e.target.value })}\n />\n </div>\n </div>\n </div>\n\n <ResponsiveDialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => setCreateOpen(false)}\n disabled={create.isPending || addWorkout.isPending}\n >\n Cancel\n </Button>\n <Button\n onClick={() => void submit()}\n disabled={create.isPending || addWorkout.isPending}\n >\n {create.isPending || addWorkout.isPending ? 'Creating…' : 'Create program'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -71,7 +71,7 @@ function j() {
71
71
  damping: 22
72
72
  }
73
73
  } } : {}
74
- }, V = Te.data ?? [], H = Ee.data ?? [], U = De.data ?? [], W = L.data ?? [], G = m(() => [...V].sort((e, t) => new Date(t.loggedAt).getTime() - new Date(e.loggedAt).getTime())[0], [V]), K = m(() => [...U].sort((e, t) => new Date(t.loggedAt).getTime() - new Date(e.loggedAt).getTime())[0], [U]), q = m(() => H.length === 0 ? null : H.reduce((e, t) => e + t.sleepHours, 0) / H.length, [H]), J = k === "SLEEP", Y = k === "ALL" ? "All metrics" : d[k].label, X = m(() => [...W].sort((e, t) => new Date(e.loggedAt).getTime() - new Date(t.loggedAt).getTime()).map((e) => ({
74
+ }, V = Te.data?.items ?? [], H = Ee.data?.items ?? [], U = De.data?.items ?? [], W = L.data?.items ?? [], G = m(() => [...V].sort((e, t) => new Date(t.loggedAt).getTime() - new Date(e.loggedAt).getTime())[0], [V]), K = m(() => [...U].sort((e, t) => new Date(t.loggedAt).getTime() - new Date(e.loggedAt).getTime())[0], [U]), q = m(() => H.length === 0 ? null : H.reduce((e, t) => e + t.sleepHours, 0) / H.length, [H]), J = k === "SLEEP", Y = k === "ALL" ? "All metrics" : d[k].label, X = m(() => [...W].sort((e, t) => new Date(e.loggedAt).getTime() - new Date(t.loggedAt).getTime()).map((e) => ({
75
75
  label: n(e.loggedAt),
76
76
  value: J ? e.sleepHours : e.score
77
77
  })), [W, J]), Z = X.length > 0 ? X[X.length - 1] : void 0, Q = (e = "READINESS") => {
@@ -1 +1 @@
1
- {"version":3,"file":"RecoveryPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/recovery/RecoveryPage.tsx"],"sourcesContent":["/**\n * Recovery — sleep, readiness, soreness, stress & mood tracking.\n * @module mybodyshield/pages/recovery\n *\n * Reads the real `mybListRecoveryLogs` array endpoint (no pagination). KPIs pull\n * the readiness / sleep / mood metrics independently so they stay stable while\n * the FilterTabs drive the charted + tabulated metric. New entries go through\n * `mybLogRecovery`. No mock data — everything is derived from the hooks.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, HeartPulse } from 'lucide-react';\nimport {\n ResponsiveContainer,\n AreaChart,\n Area,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n ReferenceDot,\n} from 'recharts';\nimport { motion, useReducedMotion, type MotionProps } from 'framer-motion';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectTrigger,\n SelectContent,\n SelectItem,\n SelectValue,\n GlassCard,\n Spinner,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n MetricsCard,\n FilterTabs,\n DataTable,\n LoadingView,\n ErrorView,\n Section,\n EmptyState,\n type Column,\n type FilterOption,\n} from '../../components';\nimport { useMybRecoveryLogs, useMybLogRecovery } from '../../hooks';\nimport type { MybRecoveryMetric, MybRecoveryLog, MybLogRecoveryInput } from '../../types';\nimport { formatNumber, formatDate, formatRelativeTime } from '../../utils/formatters';\nimport { RECOVERY_META, IconChip } from '../_shared/visuals';\n\ntype RecoveryFilter = 'ALL' | MybRecoveryMetric;\n\ntype Accent = 'success' | 'warning' | 'error';\n\nconst RECOVERY_METRICS: MybRecoveryMetric[] = ['SLEEP', 'READINESS', 'SORENESS', 'STRESS', 'MOOD'];\n\nconst FILTER_OPTIONS: FilterOption<RecoveryFilter>[] = [\n { value: 'ALL', label: 'All' },\n ...RECOVERY_METRICS.map((m) => ({ value: m, label: RECOVERY_META[m].label })),\n];\n\n// Brand emerald accent — dominant color for the recovery area chart & endpoint.\nconst CHART_ACCENT = 'var(--color-action-primary-bg)';\n\nconst scoreAccent = (score: number): Accent =>\n score >= 70 ? 'success' : score >= 40 ? 'warning' : 'error';\n\nconst sleepAccent = (hours: number): Accent =>\n hours >= 7 ? 'success' : hours >= 6 ? 'warning' : 'error';\n\n/** Format a Date into a `datetime-local` input value (local time, minute precision). */\nfunction toDatetimeLocalValue(date: Date): string {\n const pad = (n: number): string => String(n).padStart(2, '0');\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(\n date.getHours()\n )}:${pad(date.getMinutes())}`;\n}\n\ninterface RecoveryForm {\n metric: MybRecoveryMetric;\n score: string;\n sleepHours: string;\n notes: string;\n loggedAt: string;\n}\n\nconst makeEmptyForm = (metric: MybRecoveryMetric = 'READINESS'): RecoveryForm => ({\n metric,\n score: '',\n sleepHours: '',\n notes: '',\n loggedAt: toDatetimeLocalValue(new Date()),\n});\n\nexport function RecoveryPage() {\n const [filter, setFilter] = useState<RecoveryFilter>('ALL');\n const [open, setOpen] = useState(false);\n const [form, setForm] = useState<RecoveryForm>(makeEmptyForm);\n const [err, setErr] = useState<string | null>(null);\n\n // KPI feeds — independent of the active filter so the header stays stable.\n const readiness = useMybRecoveryLogs('READINESS');\n const sleep = useMybRecoveryLogs('SLEEP');\n const mood = useMybRecoveryLogs('MOOD');\n\n // Charted + tabulated metric (undefined → all metrics).\n const selectedMetric = filter === 'ALL' ? undefined : filter;\n const logs = useMybRecoveryLogs(selectedMetric);\n\n const logRecovery = useMybLogRecovery();\n\n const reduced = useReducedMotion();\n const rise = (delay: number, hover = true): MotionProps =>\n reduced\n ? { initial: false }\n : {\n initial: { opacity: 0, y: 14 },\n animate: { opacity: 1, y: 0 },\n transition: { duration: 0.45, delay, ease: 'easeOut' },\n ...(hover\n ? {\n whileHover: {\n y: -3,\n transition: { type: 'spring', stiffness: 300, damping: 22 },\n },\n }\n : {}),\n };\n\n const readinessRows = readiness.data ?? [];\n const sleepRows = sleep.data ?? [];\n const moodRows = mood.data ?? [];\n const logRows = logs.data ?? [];\n\n const latestReadiness = useMemo(\n () =>\n [...readinessRows].sort(\n (a, b) => new Date(b.loggedAt).getTime() - new Date(a.loggedAt).getTime()\n )[0],\n [readinessRows]\n );\n\n const latestMood = useMemo(\n () =>\n [...moodRows].sort(\n (a, b) => new Date(b.loggedAt).getTime() - new Date(a.loggedAt).getTime()\n )[0],\n [moodRows]\n );\n\n const avgSleep = useMemo(() => {\n if (sleepRows.length === 0) return null;\n const sum = sleepRows.reduce((acc, l) => acc + l.sleepHours, 0);\n return sum / sleepRows.length;\n }, [sleepRows]);\n\n const isSleepSelected = filter === 'SLEEP';\n const selectedLabel = filter === 'ALL' ? 'All metrics' : RECOVERY_META[filter].label;\n\n const chartData = useMemo(\n () =>\n [...logRows]\n .sort((a, b) => new Date(a.loggedAt).getTime() - new Date(b.loggedAt).getTime())\n .map((l) => ({\n label: formatDate(l.loggedAt),\n value: isSleepSelected ? l.sleepHours : l.score,\n })),\n [logRows, isSleepSelected]\n );\n const lastPoint = chartData.length > 0 ? chartData[chartData.length - 1] : undefined;\n\n const openLog = (metric: MybRecoveryMetric = 'READINESS') => {\n setForm(makeEmptyForm(metric));\n setErr(null);\n setOpen(true);\n };\n\n const isSleepForm = form.metric === 'SLEEP';\n\n const submit = async () => {\n setErr(null);\n if (isSleepForm) {\n if (form.sleepHours.trim() === '') {\n setErr('Sleep hours is required for sleep entries.');\n return;\n }\n const h = Number(form.sleepHours);\n if (!Number.isFinite(h) || h < 0 || h > 24) {\n setErr('Enter sleep hours between 0 and 24.');\n return;\n }\n } else {\n if (form.score.trim() === '') {\n setErr('A score is required.');\n return;\n }\n }\n if (form.score.trim() !== '') {\n const s = Number(form.score);\n if (!Number.isFinite(s) || s < 0 || s > 100) {\n setErr('Enter a score between 0 and 100.');\n return;\n }\n }\n\n const input: MybLogRecoveryInput = {\n metric: form.metric,\n score: form.score.trim() !== '' ? Number(form.score) : undefined,\n sleepHours: form.sleepHours.trim() !== '' ? Number(form.sleepHours) : undefined,\n notes: form.notes.trim() || undefined,\n loggedAt: form.loggedAt ? new Date(form.loggedAt).toISOString() : undefined,\n };\n\n try {\n await logRecovery.mutateAsync(input);\n setOpen(false);\n } catch (e) {\n setErr(e instanceof Error ? e.message : 'Failed to log recovery.');\n }\n };\n\n const columns: Column<MybRecoveryLog>[] = [\n {\n key: 'metric',\n header: 'Metric',\n sortValue: (r) => RECOVERY_META[r.metric].label,\n render: (r) => {\n const meta = RECOVERY_META[r.metric];\n const Icon = meta.icon;\n return (\n <span className=\"inline-flex items-center gap-2.5\">\n <IconChip icon={<Icon className=\"h-4 w-4\" />} tint={meta.tint} />\n <span className=\"font-medium text-text-primary\">{meta.label}</span>\n </span>\n );\n },\n },\n {\n key: 'value',\n header: 'Value',\n align: 'right',\n sortValue: (r) => (r.metric === 'SLEEP' ? r.sleepHours : r.score),\n render: (r) =>\n r.metric === 'SLEEP' ? (\n <span className=\"tabular-nums text-text-primary\">\n {formatNumber(r.sleepHours, { maximumFractionDigits: 1 })} h\n </span>\n ) : (\n <span className=\"tabular-nums text-text-primary\">{formatNumber(r.score)}</span>\n ),\n },\n {\n key: 'notes',\n header: 'Notes',\n hideOnMobile: true,\n render: (r) =>\n r.notes && r.notes.trim() ? (\n <span className=\"text-text-secondary\">{r.notes}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'loggedAt',\n header: 'Logged',\n align: 'right',\n sortValue: (r) => new Date(r.loggedAt).getTime(),\n render: (r) => (\n <span className=\"whitespace-nowrap text-text-tertiary\">\n {formatRelativeTime(r.loggedAt)}\n </span>\n ),\n },\n ];\n\n const ReadinessIcon = RECOVERY_META.READINESS.icon;\n const SleepIcon = RECOVERY_META.SLEEP.icon;\n const MoodIcon = RECOVERY_META.MOOD.icon;\n\n return (\n <PageLayout\n title=\"Recovery\"\n description=\"Sleep, readiness, soreness, stress & mood.\"\n actions={\n <Button className=\"gap-2\" onClick={() => openLog()}>\n <Plus className=\"h-4 w-4\" />\n Log recovery\n </Button>\n }\n >\n {/* KPI grid */}\n <motion.div {...rise(0)} className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <MetricsCard\n icon={\n <IconChip\n icon={<ReadinessIcon className=\"h-5 w-5\" />}\n tint={RECOVERY_META.READINESS.tint}\n />\n }\n value={latestReadiness ? formatNumber(latestReadiness.score) : '—'}\n label=\"Latest readiness\"\n accent={latestReadiness ? scoreAccent(latestReadiness.score) : undefined}\n footer={\n latestReadiness ? (\n <p className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(latestReadiness.loggedAt)}\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => openLog('READINESS')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Log readiness →\n </button>\n )\n }\n />\n <MetricsCard\n icon={\n <IconChip icon={<SleepIcon className=\"h-5 w-5\" />} tint={RECOVERY_META.SLEEP.tint} />\n }\n value={\n avgSleep != null ? `${formatNumber(avgSleep, { maximumFractionDigits: 1 })} h` : '—'\n }\n label=\"Avg sleep\"\n accent={avgSleep != null ? sleepAccent(avgSleep) : undefined}\n footer={\n sleepRows.length > 0 ? (\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{sleepRows.length}</span>{' '}\n {sleepRows.length === 1 ? 'night' : 'nights'} logged\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => openLog('SLEEP')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Log sleep →\n </button>\n )\n }\n />\n <MetricsCard\n icon={<IconChip icon={<MoodIcon className=\"h-5 w-5\" />} tint={RECOVERY_META.MOOD.tint} />}\n value={latestMood ? formatNumber(latestMood.score) : '—'}\n label=\"Latest mood\"\n accent={latestMood ? scoreAccent(latestMood.score) : undefined}\n footer={\n latestMood ? (\n <p className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(latestMood.loggedAt)}\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => openLog('MOOD')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Log mood →\n </button>\n )\n }\n />\n </motion.div>\n\n {/* Metric filter */}\n <div className=\"mt-6 flex flex-wrap items-center gap-3\">\n <FilterTabs options={FILTER_OPTIONS} value={filter} onChange={setFilter} />\n </div>\n\n {/* Trend chart */}\n <Section\n title={`${selectedLabel} trend`}\n description={isSleepSelected ? 'Sleep hours over time.' : 'Recovery score over time.'}\n >\n <motion.div {...rise(0.08)}>\n <GlassCard\n treatment=\"glass\"\n className=\"rounded-2xl p-6 transition-shadow duration-300 hover:shadow-2xl\"\n >\n {logs.isLoading ? (\n <div className=\"flex h-[280px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n ) : logs.isError ? (\n <ErrorView\n message=\"We couldn't load this metric's history.\"\n onRetry={() => logs.refetch()}\n />\n ) : chartData.length === 0 ? (\n <div className=\"flex h-[280px] flex-col items-center justify-center text-center\">\n <span className=\"flex size-11 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <HeartPulse className=\"h-5 w-5 text-accent\" />\n </span>\n <p className=\"mt-3 text-sm font-medium text-text-primary\">Nothing to chart yet</p>\n <p className=\"mt-1 max-w-xs text-xs text-text-tertiary\">\n Log a few {selectedLabel.toLowerCase()} entries to see your trend here.\n </p>\n </div>\n ) : (\n <ResponsiveContainer width=\"100%\" height={280}>\n <AreaChart data={chartData} margin={{ top: 12, right: 16, bottom: 4, left: 4 }}>\n <defs>\n <linearGradient id=\"myb-recovery-fill\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor={CHART_ACCENT} stopOpacity={0.38} />\n <stop offset=\"55%\" stopColor={CHART_ACCENT} stopOpacity={0.12} />\n <stop offset=\"100%\" stopColor={CHART_ACCENT} stopOpacity={0} />\n </linearGradient>\n </defs>\n <CartesianGrid\n vertical={false}\n stroke=\"var(--color-border-seam)\"\n strokeDasharray=\"4 4\"\n />\n <XAxis\n dataKey=\"label\"\n tickLine={false}\n axisLine={false}\n tickMargin={8}\n minTickGap={20}\n tick={{ fill: 'var(--color-text-tertiary)', fontSize: 12 }}\n />\n <YAxis\n width={44}\n tickLine={false}\n axisLine={false}\n tick={{ fill: 'var(--color-text-tertiary)', fontSize: 12 }}\n />\n <Tooltip\n contentStyle={{\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-seam)',\n borderRadius: 12,\n color: 'var(--color-text-primary)',\n fontSize: 12,\n }}\n labelStyle={{ color: 'var(--color-text-tertiary)' }}\n cursor={{ stroke: 'var(--color-border-seam)', strokeDasharray: '4 4' }}\n />\n <Area\n type=\"monotone\"\n dataKey=\"value\"\n name={isSleepSelected ? 'Sleep hours' : 'Score'}\n stroke={CHART_ACCENT}\n strokeWidth={2.5}\n fill=\"url(#myb-recovery-fill)\"\n fillOpacity={1}\n dot={false}\n activeDot={{\n r: 4,\n strokeWidth: 2,\n stroke: 'var(--color-bg-surface)',\n fill: CHART_ACCENT,\n }}\n isAnimationActive={!reduced}\n animationDuration={900}\n />\n {lastPoint && (\n <ReferenceDot\n x={lastPoint.label}\n y={lastPoint.value}\n r={12}\n isFront\n ifOverflow=\"visible\"\n fill={CHART_ACCENT}\n fillOpacity={0.16}\n stroke=\"none\"\n />\n )}\n {lastPoint && (\n <ReferenceDot\n x={lastPoint.label}\n y={lastPoint.value}\n r={5}\n isFront\n ifOverflow=\"visible\"\n fill={CHART_ACCENT}\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={2}\n />\n )}\n </AreaChart>\n </ResponsiveContainer>\n )}\n </GlassCard>\n </motion.div>\n </Section>\n\n {/* Log table */}\n <motion.div {...rise(0.16, false)}>\n <Section title=\"Recovery log\" description=\"Every recovery entry, most recent first.\">\n {logs.isLoading ? (\n <LoadingView label=\"Loading recovery logs…\" />\n ) : logs.isError ? (\n <ErrorView\n message=\"We couldn't load your recovery logs.\"\n onRetry={() => logs.refetch()}\n />\n ) : logRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<HeartPulse className=\"h-10 w-10\" />}\n title=\"No recovery logs yet\"\n description=\"Track your sleep, readiness, soreness, stress or mood to see how your body is recovering between sessions.\"\n action={\n <Button className=\"gap-2\" onClick={() => openLog()}>\n <Plus className=\"h-4 w-4\" />\n Log recovery\n </Button>\n }\n />\n ) : (\n <DataTable\n columns={columns}\n rows={logRows}\n rowKey={(r) => r.id}\n defaultSort={{ key: 'loggedAt', dir: 'desc' }}\n />\n )}\n </Section>\n </motion.div>\n\n {/* Log recovery dialog */}\n <ResponsiveDialog open={open} onOpenChange={setOpen}>\n <ResponsiveDialogContent className=\"sm:max-w-[480px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Log recovery</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Capture how your body is recovering — one metric per entry.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-metric\">Metric</Label>\n <Select\n value={form.metric}\n onValueChange={(v) =>\n // Select only emits values from RECOVERY_METRICS, so `v` is a\n // MybRecoveryMetric — narrow the Radix `string` payload.\n setForm((f) => ({ ...f, metric: v as MybRecoveryMetric }))\n }\n >\n <SelectTrigger id=\"myb-recovery-metric\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {RECOVERY_METRICS.map((m) => (\n <SelectItem key={m} value={m}>\n {RECOVERY_META[m].label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isSleepForm && (\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-sleep\">Sleep hours</Label>\n <Input\n id=\"myb-recovery-sleep\"\n type=\"number\"\n min=\"0\"\n max=\"24\"\n step=\"0.1\"\n value={form.sleepHours}\n onChange={(e) => setForm((f) => ({ ...f, sleepHours: e.target.value }))}\n placeholder=\"e.g. 7.5\"\n />\n <p className=\"text-xs text-text-tertiary\">Primary measure for sleep entries.</p>\n </div>\n )}\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-score\">Score{isSleepForm ? ' (optional)' : ''}</Label>\n <Input\n id=\"myb-recovery-score\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n step=\"1\"\n value={form.score}\n onChange={(e) => setForm((f) => ({ ...f, score: e.target.value }))}\n placeholder=\"0–100\"\n />\n <p className=\"text-xs text-text-tertiary\">\n {isSleepForm\n ? 'Optional 0–100 sleep-quality score.'\n : 'A 0–100 rating for this metric.'}\n </p>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-at\">Logged at</Label>\n <Input\n id=\"myb-recovery-at\"\n type=\"datetime-local\"\n value={form.loggedAt}\n onChange={(e) => setForm((f) => ({ ...f, loggedAt: e.target.value }))}\n />\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-notes\">Notes</Label>\n <Textarea\n id=\"myb-recovery-notes\"\n rows={3}\n value={form.notes}\n onChange={(e) => setForm((f) => ({ ...f, notes: e.target.value }))}\n placeholder=\"How are you feeling? (optional)\"\n />\n </div>\n\n {err && <p className=\"text-sm text-status-error-text\">{err}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => setOpen(false)}\n disabled={logRecovery.isPending}\n >\n Cancel\n </Button>\n <Button onClick={submit} disabled={logRecovery.isPending}>\n {logRecovery.isPending ? 'Saving…' : 'Log recovery'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"RecoveryPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/recovery/RecoveryPage.tsx"],"sourcesContent":["/**\n * Recovery — sleep, readiness, soreness, stress & mood tracking.\n * @module mybodyshield/pages/recovery\n *\n * Reads the real `mybListRecoveryLogs` array endpoint (no pagination). KPIs pull\n * the readiness / sleep / mood metrics independently so they stay stable while\n * the FilterTabs drive the charted + tabulated metric. New entries go through\n * `mybLogRecovery`. No mock data — everything is derived from the hooks.\n */\n\nimport { useMemo, useState } from 'react';\nimport { Plus, HeartPulse } from 'lucide-react';\nimport {\n ResponsiveContainer,\n AreaChart,\n Area,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n ReferenceDot,\n} from 'recharts';\nimport { motion, useReducedMotion, type MotionProps } from 'framer-motion';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n Select,\n SelectTrigger,\n SelectContent,\n SelectItem,\n SelectValue,\n GlassCard,\n Spinner,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n MetricsCard,\n FilterTabs,\n DataTable,\n LoadingView,\n ErrorView,\n Section,\n EmptyState,\n type Column,\n type FilterOption,\n} from '../../components';\nimport { useMybRecoveryLogs, useMybLogRecovery } from '../../hooks';\nimport type { MybRecoveryMetric, MybRecoveryLog, MybLogRecoveryInput } from '../../types';\nimport { formatNumber, formatDate, formatRelativeTime } from '../../utils/formatters';\nimport { RECOVERY_META, IconChip } from '../_shared/visuals';\n\ntype RecoveryFilter = 'ALL' | MybRecoveryMetric;\n\ntype Accent = 'success' | 'warning' | 'error';\n\nconst RECOVERY_METRICS: MybRecoveryMetric[] = ['SLEEP', 'READINESS', 'SORENESS', 'STRESS', 'MOOD'];\n\nconst FILTER_OPTIONS: FilterOption<RecoveryFilter>[] = [\n { value: 'ALL', label: 'All' },\n ...RECOVERY_METRICS.map((m) => ({ value: m, label: RECOVERY_META[m].label })),\n];\n\n// Brand emerald accent — dominant color for the recovery area chart & endpoint.\nconst CHART_ACCENT = 'var(--color-action-primary-bg)';\n\nconst scoreAccent = (score: number): Accent =>\n score >= 70 ? 'success' : score >= 40 ? 'warning' : 'error';\n\nconst sleepAccent = (hours: number): Accent =>\n hours >= 7 ? 'success' : hours >= 6 ? 'warning' : 'error';\n\n/** Format a Date into a `datetime-local` input value (local time, minute precision). */\nfunction toDatetimeLocalValue(date: Date): string {\n const pad = (n: number): string => String(n).padStart(2, '0');\n return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}T${pad(\n date.getHours()\n )}:${pad(date.getMinutes())}`;\n}\n\ninterface RecoveryForm {\n metric: MybRecoveryMetric;\n score: string;\n sleepHours: string;\n notes: string;\n loggedAt: string;\n}\n\nconst makeEmptyForm = (metric: MybRecoveryMetric = 'READINESS'): RecoveryForm => ({\n metric,\n score: '',\n sleepHours: '',\n notes: '',\n loggedAt: toDatetimeLocalValue(new Date()),\n});\n\nexport function RecoveryPage() {\n const [filter, setFilter] = useState<RecoveryFilter>('ALL');\n const [open, setOpen] = useState(false);\n const [form, setForm] = useState<RecoveryForm>(makeEmptyForm);\n const [err, setErr] = useState<string | null>(null);\n\n // KPI feeds — independent of the active filter so the header stays stable.\n const readiness = useMybRecoveryLogs('READINESS');\n const sleep = useMybRecoveryLogs('SLEEP');\n const mood = useMybRecoveryLogs('MOOD');\n\n // Charted + tabulated metric (undefined → all metrics).\n const selectedMetric = filter === 'ALL' ? undefined : filter;\n const logs = useMybRecoveryLogs(selectedMetric);\n\n const logRecovery = useMybLogRecovery();\n\n const reduced = useReducedMotion();\n const rise = (delay: number, hover = true): MotionProps =>\n reduced\n ? { initial: false }\n : {\n initial: { opacity: 0, y: 14 },\n animate: { opacity: 1, y: 0 },\n transition: { duration: 0.45, delay, ease: 'easeOut' },\n ...(hover\n ? {\n whileHover: {\n y: -3,\n transition: { type: 'spring', stiffness: 300, damping: 22 },\n },\n }\n : {}),\n };\n\n const readinessRows = readiness.data?.items ?? [];\n const sleepRows = sleep.data?.items ?? [];\n const moodRows = mood.data?.items ?? [];\n const logRows = logs.data?.items ?? [];\n\n const latestReadiness = useMemo(\n () =>\n [...readinessRows].sort(\n (a, b) => new Date(b.loggedAt).getTime() - new Date(a.loggedAt).getTime()\n )[0],\n [readinessRows]\n );\n\n const latestMood = useMemo(\n () =>\n [...moodRows].sort(\n (a, b) => new Date(b.loggedAt).getTime() - new Date(a.loggedAt).getTime()\n )[0],\n [moodRows]\n );\n\n const avgSleep = useMemo(() => {\n if (sleepRows.length === 0) return null;\n const sum = sleepRows.reduce((acc, l) => acc + l.sleepHours, 0);\n return sum / sleepRows.length;\n }, [sleepRows]);\n\n const isSleepSelected = filter === 'SLEEP';\n const selectedLabel = filter === 'ALL' ? 'All metrics' : RECOVERY_META[filter].label;\n\n const chartData = useMemo(\n () =>\n [...logRows]\n .sort((a, b) => new Date(a.loggedAt).getTime() - new Date(b.loggedAt).getTime())\n .map((l) => ({\n label: formatDate(l.loggedAt),\n value: isSleepSelected ? l.sleepHours : l.score,\n })),\n [logRows, isSleepSelected]\n );\n const lastPoint = chartData.length > 0 ? chartData[chartData.length - 1] : undefined;\n\n const openLog = (metric: MybRecoveryMetric = 'READINESS') => {\n setForm(makeEmptyForm(metric));\n setErr(null);\n setOpen(true);\n };\n\n const isSleepForm = form.metric === 'SLEEP';\n\n const submit = async () => {\n setErr(null);\n if (isSleepForm) {\n if (form.sleepHours.trim() === '') {\n setErr('Sleep hours is required for sleep entries.');\n return;\n }\n const h = Number(form.sleepHours);\n if (!Number.isFinite(h) || h < 0 || h > 24) {\n setErr('Enter sleep hours between 0 and 24.');\n return;\n }\n } else {\n if (form.score.trim() === '') {\n setErr('A score is required.');\n return;\n }\n }\n if (form.score.trim() !== '') {\n const s = Number(form.score);\n if (!Number.isFinite(s) || s < 0 || s > 100) {\n setErr('Enter a score between 0 and 100.');\n return;\n }\n }\n\n const input: MybLogRecoveryInput = {\n metric: form.metric,\n score: form.score.trim() !== '' ? Number(form.score) : undefined,\n sleepHours: form.sleepHours.trim() !== '' ? Number(form.sleepHours) : undefined,\n notes: form.notes.trim() || undefined,\n loggedAt: form.loggedAt ? new Date(form.loggedAt).toISOString() : undefined,\n };\n\n try {\n await logRecovery.mutateAsync(input);\n setOpen(false);\n } catch (e) {\n setErr(e instanceof Error ? e.message : 'Failed to log recovery.');\n }\n };\n\n const columns: Column<MybRecoveryLog>[] = [\n {\n key: 'metric',\n header: 'Metric',\n sortValue: (r) => RECOVERY_META[r.metric].label,\n render: (r) => {\n const meta = RECOVERY_META[r.metric];\n const Icon = meta.icon;\n return (\n <span className=\"inline-flex items-center gap-2.5\">\n <IconChip icon={<Icon className=\"h-4 w-4\" />} tint={meta.tint} />\n <span className=\"font-medium text-text-primary\">{meta.label}</span>\n </span>\n );\n },\n },\n {\n key: 'value',\n header: 'Value',\n align: 'right',\n sortValue: (r) => (r.metric === 'SLEEP' ? r.sleepHours : r.score),\n render: (r) =>\n r.metric === 'SLEEP' ? (\n <span className=\"tabular-nums text-text-primary\">\n {formatNumber(r.sleepHours, { maximumFractionDigits: 1 })} h\n </span>\n ) : (\n <span className=\"tabular-nums text-text-primary\">{formatNumber(r.score)}</span>\n ),\n },\n {\n key: 'notes',\n header: 'Notes',\n hideOnMobile: true,\n render: (r) =>\n r.notes && r.notes.trim() ? (\n <span className=\"text-text-secondary\">{r.notes}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'loggedAt',\n header: 'Logged',\n align: 'right',\n sortValue: (r) => new Date(r.loggedAt).getTime(),\n render: (r) => (\n <span className=\"whitespace-nowrap text-text-tertiary\">\n {formatRelativeTime(r.loggedAt)}\n </span>\n ),\n },\n ];\n\n const ReadinessIcon = RECOVERY_META.READINESS.icon;\n const SleepIcon = RECOVERY_META.SLEEP.icon;\n const MoodIcon = RECOVERY_META.MOOD.icon;\n\n return (\n <PageLayout\n title=\"Recovery\"\n description=\"Sleep, readiness, soreness, stress & mood.\"\n actions={\n <Button className=\"gap-2\" onClick={() => openLog()}>\n <Plus className=\"h-4 w-4\" />\n Log recovery\n </Button>\n }\n >\n {/* KPI grid */}\n <motion.div {...rise(0)} className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <MetricsCard\n icon={\n <IconChip\n icon={<ReadinessIcon className=\"h-5 w-5\" />}\n tint={RECOVERY_META.READINESS.tint}\n />\n }\n value={latestReadiness ? formatNumber(latestReadiness.score) : '—'}\n label=\"Latest readiness\"\n accent={latestReadiness ? scoreAccent(latestReadiness.score) : undefined}\n footer={\n latestReadiness ? (\n <p className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(latestReadiness.loggedAt)}\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => openLog('READINESS')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Log readiness →\n </button>\n )\n }\n />\n <MetricsCard\n icon={\n <IconChip icon={<SleepIcon className=\"h-5 w-5\" />} tint={RECOVERY_META.SLEEP.tint} />\n }\n value={\n avgSleep != null ? `${formatNumber(avgSleep, { maximumFractionDigits: 1 })} h` : '—'\n }\n label=\"Avg sleep\"\n accent={avgSleep != null ? sleepAccent(avgSleep) : undefined}\n footer={\n sleepRows.length > 0 ? (\n <p className=\"text-xs text-text-tertiary\">\n <span className=\"tabular-nums text-text-secondary\">{sleepRows.length}</span>{' '}\n {sleepRows.length === 1 ? 'night' : 'nights'} logged\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => openLog('SLEEP')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Log sleep →\n </button>\n )\n }\n />\n <MetricsCard\n icon={<IconChip icon={<MoodIcon className=\"h-5 w-5\" />} tint={RECOVERY_META.MOOD.tint} />}\n value={latestMood ? formatNumber(latestMood.score) : '—'}\n label=\"Latest mood\"\n accent={latestMood ? scoreAccent(latestMood.score) : undefined}\n footer={\n latestMood ? (\n <p className=\"text-xs text-text-tertiary\">\n {formatRelativeTime(latestMood.loggedAt)}\n </p>\n ) : (\n <button\n type=\"button\"\n onClick={() => openLog('MOOD')}\n className=\"text-xs font-medium text-accent hover:underline\"\n >\n Log mood →\n </button>\n )\n }\n />\n </motion.div>\n\n {/* Metric filter */}\n <div className=\"mt-6 flex flex-wrap items-center gap-3\">\n <FilterTabs options={FILTER_OPTIONS} value={filter} onChange={setFilter} />\n </div>\n\n {/* Trend chart */}\n <Section\n title={`${selectedLabel} trend`}\n description={isSleepSelected ? 'Sleep hours over time.' : 'Recovery score over time.'}\n >\n <motion.div {...rise(0.08)}>\n <GlassCard\n treatment=\"glass\"\n className=\"rounded-2xl p-6 transition-shadow duration-300 hover:shadow-2xl\"\n >\n {logs.isLoading ? (\n <div className=\"flex h-[280px] items-center justify-center\">\n <Spinner size=\"lg\" />\n </div>\n ) : logs.isError ? (\n <ErrorView\n message=\"We couldn't load this metric's history.\"\n onRetry={() => logs.refetch()}\n />\n ) : chartData.length === 0 ? (\n <div className=\"flex h-[280px] flex-col items-center justify-center text-center\">\n <span className=\"flex size-11 items-center justify-center rounded-full bg-[var(--color-accent-soft)]\">\n <HeartPulse className=\"h-5 w-5 text-accent\" />\n </span>\n <p className=\"mt-3 text-sm font-medium text-text-primary\">Nothing to chart yet</p>\n <p className=\"mt-1 max-w-xs text-xs text-text-tertiary\">\n Log a few {selectedLabel.toLowerCase()} entries to see your trend here.\n </p>\n </div>\n ) : (\n <ResponsiveContainer width=\"100%\" height={280}>\n <AreaChart data={chartData} margin={{ top: 12, right: 16, bottom: 4, left: 4 }}>\n <defs>\n <linearGradient id=\"myb-recovery-fill\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stopColor={CHART_ACCENT} stopOpacity={0.38} />\n <stop offset=\"55%\" stopColor={CHART_ACCENT} stopOpacity={0.12} />\n <stop offset=\"100%\" stopColor={CHART_ACCENT} stopOpacity={0} />\n </linearGradient>\n </defs>\n <CartesianGrid\n vertical={false}\n stroke=\"var(--color-border-seam)\"\n strokeDasharray=\"4 4\"\n />\n <XAxis\n dataKey=\"label\"\n tickLine={false}\n axisLine={false}\n tickMargin={8}\n minTickGap={20}\n tick={{ fill: 'var(--color-text-tertiary)', fontSize: 12 }}\n />\n <YAxis\n width={44}\n tickLine={false}\n axisLine={false}\n tick={{ fill: 'var(--color-text-tertiary)', fontSize: 12 }}\n />\n <Tooltip\n contentStyle={{\n background: 'var(--color-bg-surface)',\n border: '1px solid var(--color-border-seam)',\n borderRadius: 12,\n color: 'var(--color-text-primary)',\n fontSize: 12,\n }}\n labelStyle={{ color: 'var(--color-text-tertiary)' }}\n cursor={{ stroke: 'var(--color-border-seam)', strokeDasharray: '4 4' }}\n />\n <Area\n type=\"monotone\"\n dataKey=\"value\"\n name={isSleepSelected ? 'Sleep hours' : 'Score'}\n stroke={CHART_ACCENT}\n strokeWidth={2.5}\n fill=\"url(#myb-recovery-fill)\"\n fillOpacity={1}\n dot={false}\n activeDot={{\n r: 4,\n strokeWidth: 2,\n stroke: 'var(--color-bg-surface)',\n fill: CHART_ACCENT,\n }}\n isAnimationActive={!reduced}\n animationDuration={900}\n />\n {lastPoint && (\n <ReferenceDot\n x={lastPoint.label}\n y={lastPoint.value}\n r={12}\n isFront\n ifOverflow=\"visible\"\n fill={CHART_ACCENT}\n fillOpacity={0.16}\n stroke=\"none\"\n />\n )}\n {lastPoint && (\n <ReferenceDot\n x={lastPoint.label}\n y={lastPoint.value}\n r={5}\n isFront\n ifOverflow=\"visible\"\n fill={CHART_ACCENT}\n stroke=\"var(--color-bg-surface)\"\n strokeWidth={2}\n />\n )}\n </AreaChart>\n </ResponsiveContainer>\n )}\n </GlassCard>\n </motion.div>\n </Section>\n\n {/* Log table */}\n <motion.div {...rise(0.16, false)}>\n <Section title=\"Recovery log\" description=\"Every recovery entry, most recent first.\">\n {logs.isLoading ? (\n <LoadingView label=\"Loading recovery logs…\" />\n ) : logs.isError ? (\n <ErrorView\n message=\"We couldn't load your recovery logs.\"\n onRetry={() => logs.refetch()}\n />\n ) : logRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<HeartPulse className=\"h-10 w-10\" />}\n title=\"No recovery logs yet\"\n description=\"Track your sleep, readiness, soreness, stress or mood to see how your body is recovering between sessions.\"\n action={\n <Button className=\"gap-2\" onClick={() => openLog()}>\n <Plus className=\"h-4 w-4\" />\n Log recovery\n </Button>\n }\n />\n ) : (\n <DataTable\n columns={columns}\n rows={logRows}\n rowKey={(r) => r.id}\n defaultSort={{ key: 'loggedAt', dir: 'desc' }}\n />\n )}\n </Section>\n </motion.div>\n\n {/* Log recovery dialog */}\n <ResponsiveDialog open={open} onOpenChange={setOpen}>\n <ResponsiveDialogContent className=\"sm:max-w-[480px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Log recovery</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Capture how your body is recovering — one metric per entry.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-metric\">Metric</Label>\n <Select\n value={form.metric}\n onValueChange={(v) =>\n // Select only emits values from RECOVERY_METRICS, so `v` is a\n // MybRecoveryMetric — narrow the Radix `string` payload.\n setForm((f) => ({ ...f, metric: v as MybRecoveryMetric }))\n }\n >\n <SelectTrigger id=\"myb-recovery-metric\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {RECOVERY_METRICS.map((m) => (\n <SelectItem key={m} value={m}>\n {RECOVERY_META[m].label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {isSleepForm && (\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-sleep\">Sleep hours</Label>\n <Input\n id=\"myb-recovery-sleep\"\n type=\"number\"\n min=\"0\"\n max=\"24\"\n step=\"0.1\"\n value={form.sleepHours}\n onChange={(e) => setForm((f) => ({ ...f, sleepHours: e.target.value }))}\n placeholder=\"e.g. 7.5\"\n />\n <p className=\"text-xs text-text-tertiary\">Primary measure for sleep entries.</p>\n </div>\n )}\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-score\">Score{isSleepForm ? ' (optional)' : ''}</Label>\n <Input\n id=\"myb-recovery-score\"\n type=\"number\"\n min=\"0\"\n max=\"100\"\n step=\"1\"\n value={form.score}\n onChange={(e) => setForm((f) => ({ ...f, score: e.target.value }))}\n placeholder=\"0–100\"\n />\n <p className=\"text-xs text-text-tertiary\">\n {isSleepForm\n ? 'Optional 0–100 sleep-quality score.'\n : 'A 0–100 rating for this metric.'}\n </p>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-at\">Logged at</Label>\n <Input\n id=\"myb-recovery-at\"\n type=\"datetime-local\"\n value={form.loggedAt}\n onChange={(e) => setForm((f) => ({ ...f, loggedAt: e.target.value }))}\n />\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-recovery-notes\">Notes</Label>\n <Textarea\n id=\"myb-recovery-notes\"\n rows={3}\n value={form.notes}\n onChange={(e) => setForm((f) => ({ ...f, notes: e.target.value }))}\n placeholder=\"How are you feeling? (optional)\"\n />\n </div>\n\n {err && <p className=\"text-sm text-status-error-text\">{err}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => setOpen(false)}\n disabled={logRecovery.isPending}\n >\n Cancel\n </Button>\n <Button onClick={submit} disabled={logRecovery.isPending}>\n {logRecovery.isPending ? 'Saving…' : 'Log recovery'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"SessionDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/mybodyshield/pages/train/SessionDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA2GH,wBAAgB,iBAAiB,gCA+lBhC"}
1
+ {"version":3,"file":"SessionDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/mybodyshield/pages/train/SessionDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAsIH,wBAAgB,iBAAiB,gCA+0BhC"}