@burdenoff/microfe-mybodyshield 2026.706.2 → 2026.713.1
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.
- package/dist/dev/main.d.ts +2 -0
- package/dist/dev/main.d.ts.map +1 -0
- package/dist/generated/global-operations.d.ts +2 -0
- package/dist/generated/global-operations.d.ts.map +1 -0
- package/dist/generated/global-types.d.ts +18199 -0
- package/dist/generated/global-types.d.ts.map +1 -0
- package/dist/generated/wspace-operations.d.ts +2 -0
- package/dist/generated/wspace-operations.d.ts.map +1 -0
- package/dist/generated/wspace-types.d.ts +43046 -0
- package/dist/generated/wspace-types.d.ts.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -1
- package/dist/mybodyshield/MyBodyShieldRoot.d.ts +5 -0
- package/dist/mybodyshield/MyBodyShieldRoot.d.ts.map +1 -0
- package/dist/mybodyshield/MyBodyShieldRoutes.d.ts +4 -0
- package/dist/mybodyshield/MyBodyShieldRoutes.d.ts.map +1 -0
- package/dist/mybodyshield/MyBodyShieldRoutes.js +77 -36
- package/dist/mybodyshield/MyBodyShieldRoutes.js.map +1 -1
- package/dist/mybodyshield/components/DataTable.d.ts +28 -0
- package/dist/mybodyshield/components/DataTable.d.ts.map +1 -0
- package/dist/mybodyshield/components/EmptyState.d.ts +18 -0
- package/dist/mybodyshield/components/EmptyState.d.ts.map +1 -0
- package/dist/mybodyshield/components/EvidenceUpload.d.ts +14 -0
- package/dist/mybodyshield/components/EvidenceUpload.d.ts.map +1 -0
- package/dist/mybodyshield/components/FeatureFlagGuard.d.ts +11 -0
- package/dist/mybodyshield/components/FeatureFlagGuard.d.ts.map +1 -0
- package/dist/mybodyshield/components/FeatureFlagGuard.js +27 -0
- package/dist/mybodyshield/components/FeatureFlagGuard.js.map +1 -0
- package/dist/mybodyshield/components/FileLink.d.ts +15 -0
- package/dist/mybodyshield/components/FileLink.d.ts.map +1 -0
- package/dist/mybodyshield/components/FilterTabs.d.ts +18 -0
- package/dist/mybodyshield/components/FilterTabs.d.ts.map +1 -0
- package/dist/mybodyshield/components/MetricsCard.d.ts +23 -0
- package/dist/mybodyshield/components/MetricsCard.d.ts.map +1 -0
- package/dist/mybodyshield/components/MiniBar.d.ts +16 -0
- package/dist/mybodyshield/components/MiniBar.d.ts.map +1 -0
- package/dist/mybodyshield/components/MybDiscussion.d.ts +18 -0
- package/dist/mybodyshield/components/MybDiscussion.d.ts.map +1 -0
- package/dist/mybodyshield/components/MybDiscussion.js +5 -5
- package/dist/mybodyshield/components/MybTags.d.ts +17 -0
- package/dist/mybodyshield/components/MybTags.d.ts.map +1 -0
- package/dist/mybodyshield/components/PageDisabled.d.ts +9 -0
- package/dist/mybodyshield/components/PageDisabled.d.ts.map +1 -0
- package/dist/mybodyshield/components/PageDisabled.js +17 -0
- package/dist/mybodyshield/components/PageDisabled.js.map +1 -0
- package/dist/mybodyshield/components/PageLayout.d.ts +16 -0
- package/dist/mybodyshield/components/PageLayout.d.ts.map +1 -0
- package/dist/mybodyshield/components/Pagination.d.ts +22 -0
- package/dist/mybodyshield/components/Pagination.d.ts.map +1 -0
- package/dist/mybodyshield/components/StateViews.d.ts +25 -0
- package/dist/mybodyshield/components/StateViews.d.ts.map +1 -0
- package/dist/mybodyshield/components/StateViews.js +2 -1
- package/dist/mybodyshield/components/StateViews.js.map +1 -1
- package/dist/mybodyshield/components/StatusBadge.d.ts +9 -0
- package/dist/mybodyshield/components/StatusBadge.d.ts.map +1 -0
- package/dist/mybodyshield/components/Tabs.d.ts +17 -0
- package/dist/mybodyshield/components/Tabs.d.ts.map +1 -0
- package/dist/mybodyshield/components/UserSearchSelect.d.ts +28 -0
- package/dist/mybodyshield/components/UserSearchSelect.d.ts.map +1 -0
- package/dist/mybodyshield/components/index.d.ts +22 -0
- package/dist/mybodyshield/components/index.d.ts.map +1 -0
- package/dist/mybodyshield/components/index.js +4 -2
- package/dist/mybodyshield/context/MyBodyShieldContext.d.ts +16 -0
- package/dist/mybodyshield/context/MyBodyShieldContext.d.ts.map +1 -0
- package/dist/mybodyshield/context/index.d.ts +6 -0
- package/dist/mybodyshield/context/index.d.ts.map +1 -0
- package/dist/mybodyshield/hooks/index.d.ts +12 -0
- package/dist/mybodyshield/hooks/index.d.ts.map +1 -0
- package/dist/mybodyshield/hooks/index.js +1 -0
- package/dist/mybodyshield/hooks/useMyBodyShieldAnalytics.d.ts +21 -0
- package/dist/mybodyshield/hooks/useMyBodyShieldAnalytics.d.ts.map +1 -0
- package/dist/mybodyshield/hooks/useMyBodyShieldAnalytics.js +35 -0
- package/dist/mybodyshield/hooks/useMyBodyShieldAnalytics.js.map +1 -0
- package/dist/mybodyshield/hooks/useMyBodyShieldApi.d.ts +83 -0
- package/dist/mybodyshield/hooks/useMyBodyShieldApi.d.ts.map +1 -0
- package/dist/mybodyshield/hooks/useMybComments.d.ts +36 -0
- package/dist/mybodyshield/hooks/useMybComments.d.ts.map +1 -0
- package/dist/mybodyshield/hooks/useMybFiles.d.ts +47 -0
- package/dist/mybodyshield/hooks/useMybFiles.d.ts.map +1 -0
- package/dist/mybodyshield/hooks/useMybTags.d.ts +44 -0
- package/dist/mybodyshield/hooks/useMybTags.d.ts.map +1 -0
- package/dist/mybodyshield/hooks/useMybUsers.d.ts +27 -0
- package/dist/mybodyshield/hooks/useMybUsers.d.ts.map +1 -0
- package/dist/mybodyshield/hooks/usePager.d.ts +29 -0
- package/dist/mybodyshield/hooks/usePager.d.ts.map +1 -0
- package/dist/mybodyshield/index.d.ts +9 -0
- package/dist/mybodyshield/index.d.ts.map +1 -0
- package/dist/mybodyshield/pages/DashboardPage.d.ts +10 -0
- package/dist/mybodyshield/pages/DashboardPage.d.ts.map +1 -0
- package/dist/mybodyshield/pages/DashboardPage.js +91 -86
- package/dist/mybodyshield/pages/DashboardPage.js.map +1 -1
- package/dist/mybodyshield/pages/_shared/visuals.d.ts +77 -0
- package/dist/mybodyshield/pages/_shared/visuals.d.ts.map +1 -0
- package/dist/mybodyshield/pages/coaching/CoachingPage.d.ts +11 -0
- package/dist/mybodyshield/pages/coaching/CoachingPage.d.ts.map +1 -0
- package/dist/mybodyshield/pages/coaching/CoachingPage.js +60 -60
- package/dist/mybodyshield/pages/goals/GoalsPage.d.ts +11 -0
- package/dist/mybodyshield/pages/goals/GoalsPage.d.ts.map +1 -0
- package/dist/mybodyshield/pages/goals/GoalsPage.js +125 -125
- package/dist/mybodyshield/pages/goals/GoalsPage.js.map +1 -1
- package/dist/mybodyshield/pages/nutrition/NutritionPage.d.ts +13 -0
- package/dist/mybodyshield/pages/nutrition/NutritionPage.d.ts.map +1 -0
- package/dist/mybodyshield/pages/nutrition/NutritionPage.js +129 -129
- package/dist/mybodyshield/pages/nutrition/NutritionPage.js.map +1 -1
- package/dist/mybodyshield/pages/programs/ProgramDetailPage.d.ts +10 -0
- package/dist/mybodyshield/pages/programs/ProgramDetailPage.d.ts.map +1 -0
- package/dist/mybodyshield/pages/programs/ProgramDetailPage.js +218 -191
- package/dist/mybodyshield/pages/programs/ProgramDetailPage.js.map +1 -1
- package/dist/mybodyshield/pages/programs/ProgramsPage.d.ts +11 -0
- package/dist/mybodyshield/pages/programs/ProgramsPage.d.ts.map +1 -0
- package/dist/mybodyshield/pages/programs/ProgramsPage.js +197 -193
- package/dist/mybodyshield/pages/programs/ProgramsPage.js.map +1 -1
- package/dist/mybodyshield/pages/progress/ProgressPage.d.ts +13 -0
- package/dist/mybodyshield/pages/progress/ProgressPage.d.ts.map +1 -0
- package/dist/mybodyshield/pages/progress/ProgressPage.js +143 -143
- package/dist/mybodyshield/pages/progress/ProgressPage.js.map +1 -1
- package/dist/mybodyshield/pages/recovery/RecoveryPage.d.ts +11 -0
- package/dist/mybodyshield/pages/recovery/RecoveryPage.d.ts.map +1 -0
- package/dist/mybodyshield/pages/recovery/RecoveryPage.js +60 -60
- package/dist/mybodyshield/pages/recovery/RecoveryPage.js.map +1 -1
- package/dist/mybodyshield/pages/train/SessionDetailPage.d.ts +11 -0
- package/dist/mybodyshield/pages/train/SessionDetailPage.d.ts.map +1 -0
- package/dist/mybodyshield/pages/train/SessionDetailPage.js +184 -181
- package/dist/mybodyshield/pages/train/SessionDetailPage.js.map +1 -1
- package/dist/mybodyshield/pages/train/SessionsPage.d.ts +11 -0
- package/dist/mybodyshield/pages/train/SessionsPage.d.ts.map +1 -0
- package/dist/mybodyshield/pages/train/SessionsPage.js +119 -115
- package/dist/mybodyshield/pages/train/SessionsPage.js.map +1 -1
- package/dist/mybodyshield/services/createSandboxAssistantTransport.d.ts +24 -0
- package/dist/mybodyshield/services/createSandboxAssistantTransport.d.ts.map +1 -0
- package/dist/mybodyshield/services/createSandboxAssistantTransport.js +14 -0
- package/dist/mybodyshield/services/createSandboxAssistantTransport.js.map +1 -0
- package/dist/mybodyshield/types/assistant.d.ts +55 -0
- package/dist/mybodyshield/types/assistant.d.ts.map +1 -0
- package/dist/mybodyshield/types/index.d.ts +419 -0
- package/dist/mybodyshield/types/index.d.ts.map +1 -0
- package/dist/mybodyshield/utils/cn.d.ts +4 -0
- package/dist/mybodyshield/utils/cn.d.ts.map +1 -0
- package/dist/mybodyshield/utils/formatters.d.ts +32 -0
- package/dist/mybodyshield/utils/formatters.d.ts.map +1 -0
- package/dist/mybodyshield/utils/i18n.d.ts +6 -0
- package/dist/mybodyshield/utils/i18n.d.ts.map +1 -0
- package/dist/mybodyshield/utils/index.d.ts +9 -0
- package/dist/mybodyshield/utils/index.d.ts.map +1 -0
- package/dist/mybodyshield/utils/navigation.d.ts +14 -0
- package/dist/mybodyshield/utils/navigation.d.ts.map +1 -0
- package/dist/mybodyshield/utils/pageContext.d.ts +28 -0
- package/dist/mybodyshield/utils/pageContext.d.ts.map +1 -0
- package/dist/mybodyshield/utils/pageContext.js +62 -0
- package/dist/mybodyshield/utils/pageContext.js.map +1 -0
- package/package.json +8 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ProgressPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/progress/ProgressPage.tsx"],"sourcesContent":["/**\n * Progress — body metrics over time.\n * @module mybodyshield/pages/progress\n *\n * A single-metric time-series view over the real `mybListBodyMetrics`\n * endpoint: pick a body-metric type (weight, body-fat, measurements…), see the\n * latest value + trend vs the previous reading, a token-styled line chart of the\n * history window, aggregate stats, and an offset-paginated log table. New\n * readings are recorded through `mybRecordBodyMetric`. No mock data — everything\n * derives from the hooks.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport {\n AreaChart,\n Area,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n ReferenceDot,\n ResponsiveContainer,\n} from 'recharts';\nimport { motion, useReducedMotion, type MotionProps } from 'framer-motion';\nimport { Plus, Activity, Sigma, ArrowDownUp } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n GlassCard,\n Select,\n SelectTrigger,\n SelectContent,\n SelectItem,\n SelectValue,\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 Pagination,\n LoadingView,\n ErrorView,\n Section,\n EmptyState,\n EvidenceUpload,\n FileLink,\n type Column,\n type FilterOption,\n} from '../../components';\nimport { IconChip, TrendPill, METRIC_META } from '../_shared/visuals';\nimport {\n useMybBodyMetrics,\n useMybRecordBodyMetric,\n useOffsetPager,\n type UploadedMybFile,\n} from '../../hooks';\nimport type { MybBodyMetric, MybBodyMetricType, MybRecordMetricInput } from '../../types';\nimport {\n formatNumber,\n formatDate,\n formatDateTime,\n formatRelativeTime,\n formatEnumLabel,\n} from '../../utils/formatters';\n\n// Ordered enum surface for the metric-type selector (METRIC_META is a Record).\nconst METRIC_TYPES: MybBodyMetricType[] = [\n 'WEIGHT',\n 'BODY_FAT',\n 'RESTING_HEART_RATE',\n 'WAIST',\n 'CHEST',\n 'HIPS',\n 'ARM',\n 'THIGH',\n];\n\n// Which direction of change is \"good\" per metric (drives TrendPill colouring).\nconst GOOD_DIRECTION: Record<MybBodyMetricType, 'up' | 'down'> = {\n WEIGHT: 'down',\n BODY_FAT: 'down',\n RESTING_HEART_RATE: 'down',\n WAIST: 'down',\n HIPS: 'down',\n CHEST: 'up',\n ARM: 'up',\n THIGH: 'up',\n};\n\n// Window used for the chart / KPIs / aggregates (the log table paginates on top).\nconst CHART_WINDOW = 100;\nconst PAGE_SIZE = 25;\n\n// Brand emerald accent — the dominant color for the area chart, line & endpoint.\nconst CHART_ACCENT = 'var(--color-action-primary-bg)';\n\ninterface RecordForm {\n type: MybBodyMetricType;\n value: string;\n unit: string;\n measuredAt: string;\n notes: string;\n photo: UploadedMybFile | null;\n}\n\n/** A `Date` → `<input type=\"datetime-local\">` value (local wall-clock time). */\nconst toLocalInput = (d: Date): string => {\n const pad = (n: number) => String(n).padStart(2, '0');\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;\n};\n\nconst makeForm = (type: MybBodyMetricType): RecordForm => ({\n type,\n value: '',\n unit: METRIC_META[type].unit,\n measuredAt: toLocalInput(new Date()),\n notes: '',\n photo: null,\n});\n\nexport function ProgressPage() {\n const [selectedType, setSelectedType] = useState<MybBodyMetricType>('WEIGHT');\n const [open, setOpen] = useState(false);\n const [form, setForm] = useState<RecordForm>(() => makeForm('WEIGHT'));\n const [formError, setFormError] = useState<string | null>(null);\n\n const pager = useOffsetPager(PAGE_SIZE);\n\n // Reset the log table to page 1 whenever the metric type changes.\n // Depend on the stable `pager.reset` (NOT the whole pager, whose identity\n // changes with `page`) so advancing pages doesn't snap back to page 1.\n const resetPager = pager.reset;\n useEffect(() => {\n resetPager();\n }, [selectedType, resetPager]);\n\n // Wide window drives the chart, latest/trend KPIs and aggregate stats.\n const history = useMybBodyMetrics({\n type: selectedType,\n pagination: { limit: CHART_WINDOW },\n });\n // Independent offset-paginated slice powers the log table.\n const table = useMybBodyMetrics({\n type: selectedType,\n pagination: { limit: PAGE_SIZE, offset: pager.offset },\n });\n const record = useMybRecordBodyMetric();\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 meta = METRIC_META[selectedType];\n const MetricIcon = meta.icon;\n const goodDir = GOOD_DIRECTION[selectedType];\n\n const historyRows = useMemo<MybBodyMetric[]>(() => history.data?.items ?? [], [history.data]);\n\n // Ascending by measuredAt → chart series + robust latest/prior derivation.\n const ascending = useMemo(\n () =>\n [...historyRows].sort(\n (a, b) => new Date(a.measuredAt).getTime() - new Date(b.measuredAt).getTime()\n ),\n [historyRows]\n );\n\n const latest = ascending[ascending.length - 1];\n const prior = ascending[ascending.length - 2];\n const delta = latest && prior ? latest.value - prior.value : undefined;\n\n const chartData = useMemo(\n () => ascending.map((m) => ({ label: formatDate(m.measuredAt), value: m.value })),\n [ascending]\n );\n const lastPoint = chartData.length > 0 ? chartData[chartData.length - 1] : undefined;\n\n const stats = useMemo(() => {\n if (ascending.length === 0) return null;\n const values = ascending.map((m) => m.value);\n return {\n count: values.length,\n min: Math.min(...values),\n max: Math.max(...values),\n avg: values.reduce((a, b) => a + b, 0) / values.length,\n };\n }, [ascending]);\n\n const metricOptions: FilterOption<MybBodyMetricType>[] = METRIC_TYPES.map((t) => ({\n value: t,\n label: formatEnumLabel(t),\n }));\n\n const openRecord = () => {\n setForm(makeForm(selectedType));\n setFormError(null);\n setOpen(true);\n };\n\n const submit = async () => {\n const numValue = Number(form.value);\n if (form.value.trim() === '' || !Number.isFinite(numValue)) {\n setFormError('Enter a valid measurement value.');\n return;\n }\n setFormError(null);\n const input: MybRecordMetricInput = {\n type: form.type,\n value: numValue,\n unit: form.unit.trim() || METRIC_META[form.type].unit,\n measuredAt: form.measuredAt\n ? new Date(form.measuredAt).toISOString()\n : new Date().toISOString(),\n notes: form.notes.trim() ? form.notes.trim() : null,\n photoFileId: form.photo?.fileId ?? null,\n };\n try {\n await record.mutateAsync(input);\n setOpen(false);\n // Surface the just-recorded type so the new reading is visible immediately.\n setSelectedType(form.type);\n pager.reset();\n } catch (e) {\n setFormError(e instanceof Error ? e.message : 'Failed to record measurement.');\n }\n };\n\n const tableRows = table.data?.items ?? [];\n const tableColumns: Column<MybBodyMetric>[] = [\n {\n key: 'value',\n header: 'Value',\n align: 'right',\n sortValue: (row) => row.value,\n render: (row) => (\n <span className=\"font-medium tabular-nums text-text-primary\">\n {formatNumber(row.value)}\n <span className=\"ml-1 text-xs font-normal text-text-tertiary\">{row.unit}</span>\n </span>\n ),\n },\n {\n key: 'measuredAt',\n header: 'Measured',\n sortValue: (row) => new Date(row.measuredAt).getTime(),\n render: (row) => (\n <span className=\"whitespace-nowrap text-text-secondary\">\n {formatDateTime(row.measuredAt)}\n </span>\n ),\n },\n {\n key: 'notes',\n header: 'Notes',\n hideOnMobile: true,\n render: (row) =>\n row.notes ? (\n <span className=\"text-text-secondary\">{row.notes}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'photo',\n header: 'Photo',\n render: (row) =>\n row.photo?.id ? (\n <FileLink fileId={row.photo.id} />\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n ];\n\n const recordButton = (\n <Button onClick={openRecord}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record measurement\n </Button>\n );\n\n return (\n <PageLayout\n title=\"Progress\"\n description=\"Body metrics over time — weight, composition & measurements.\"\n actions={recordButton}\n >\n {/* Metric-type selector */}\n <div className=\"mb-6 overflow-x-auto\">\n <FilterTabs options={metricOptions} value={selectedType} onChange={setSelectedType} />\n </div>\n\n {history.isLoading && historyRows.length === 0 ? (\n <LoadingView label={`Loading ${formatEnumLabel(selectedType).toLowerCase()} history…`} />\n ) : history.isError ? (\n <ErrorView\n message=\"We couldn't load your body-metric history.\"\n onRetry={() => history.refetch()}\n />\n ) : historyRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<MetricIcon className=\"h-10 w-10\" />}\n title={`No ${formatEnumLabel(selectedType).toLowerCase()} readings yet`}\n description=\"Record a measurement to start tracking this metric over time.\"\n action={\n <Button onClick={openRecord}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record measurement\n </Button>\n }\n />\n ) : (\n <div className=\"space-y-6\">\n {/* Latest + trend + chart */}\n <motion.div {...rise(0)}>\n <GlassCard\n treatment=\"glass\"\n className=\"rounded-2xl p-6 transition-shadow duration-300 hover:shadow-2xl\"\n >\n <div className=\"flex flex-wrap items-start justify-between gap-4\">\n <div className=\"flex items-center gap-3\">\n <IconChip icon={<MetricIcon className=\"h-5 w-5\" />} tint={meta.tint} />\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">\n {formatEnumLabel(selectedType)}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {latest\n ? `Last measured ${formatRelativeTime(latest.measuredAt)}`\n : 'No measurements yet'}\n </p>\n </div>\n </div>\n <div className=\"text-right\">\n <p className=\"text-3xl font-bold leading-none tracking-tight tabular-nums text-text-primary sm:text-4xl\">\n {latest ? formatNumber(latest.value) : '—'}\n {latest && (\n <span className=\"ml-1 text-base font-medium text-text-tertiary\">\n {meta.unit}\n </span>\n )}\n </p>\n {delta != null && (\n <div className=\"mt-2 flex justify-end\">\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2.5 py-1 ring-1 ring-border-seam\">\n <TrendPill delta={delta} unit={` ${meta.unit}`} goodDirection={goodDir} />\n </span>\n </div>\n )}\n </div>\n </div>\n\n <div className=\"mt-6 h-64 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <AreaChart data={chartData} margin={{ top: 8, right: 14, bottom: 4, left: -8 }}>\n <defs>\n <linearGradient id=\"myb-progress-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 stroke=\"var(--color-border-seam)\"\n strokeDasharray=\"4 4\"\n vertical={false}\n />\n <XAxis\n dataKey=\"label\"\n stroke=\"var(--color-border-seam)\"\n tickLine={false}\n axisLine={false}\n tick={{ fill: 'var(--color-text-tertiary)', fontSize: 12 }}\n tickMargin={8}\n minTickGap={24}\n />\n <YAxis\n stroke=\"var(--color-border-seam)\"\n tickLine={false}\n axisLine={false}\n tick={{ fill: 'var(--color-text-tertiary)', fontSize: 12 }}\n width={44}\n domain={['auto', 'auto']}\n />\n <Tooltip\n cursor={{ stroke: 'var(--color-border-seam)', strokeDasharray: '4 4' }}\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 }}\n labelStyle={{ color: 'var(--color-text-tertiary)' }}\n itemStyle={{ color: 'var(--color-text-primary)' }}\n formatter={(value) => [\n `${formatNumber(Number(value))} ${meta.unit}`,\n formatEnumLabel(selectedType),\n ]}\n />\n <Area\n type=\"monotone\"\n dataKey=\"value\"\n name={formatEnumLabel(selectedType)}\n stroke={CHART_ACCENT}\n strokeWidth={2.5}\n fill=\"url(#myb-progress-fill)\"\n fillOpacity={1}\n dot={false}\n activeDot={{\n r: 5,\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 </div>\n </GlassCard>\n </motion.div>\n\n {/* Aggregate stats over the loaded window (not full history) */}\n {stats && (\n <div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <motion.div {...rise(0.08)}>\n <MetricsCard\n className=\"h-full rounded-2xl transition-shadow duration-300 hover:shadow-xl\"\n icon={<IconChip icon={<Activity className=\"h-5 w-5\" />} tint={CHART_ACCENT} />}\n value={formatNumber(stats.count)}\n label=\"Recent readings\"\n />\n </motion.div>\n <motion.div {...rise(0.14)}>\n <MetricsCard\n className=\"h-full rounded-2xl transition-shadow duration-300 hover:shadow-xl\"\n icon={<IconChip icon={<Sigma className=\"h-5 w-5\" />} tint={CHART_ACCENT} />}\n value={`${formatNumber(stats.avg, { maximumFractionDigits: 1 })} ${meta.unit}`}\n label={`Window average ${formatEnumLabel(selectedType).toLowerCase()}`}\n />\n </motion.div>\n <motion.div {...rise(0.2)}>\n <MetricsCard\n className=\"h-full rounded-2xl transition-shadow duration-300 hover:shadow-xl\"\n icon={\n <IconChip icon={<ArrowDownUp className=\"h-5 w-5\" />} tint={CHART_ACCENT} />\n }\n value={`${formatNumber(stats.min)}–${formatNumber(stats.max)}`}\n label={`Window range (${meta.unit})`}\n />\n </motion.div>\n </div>\n <p className=\"mt-2 text-xs text-text-tertiary\">\n Based on your most recent {formatNumber(stats.count)}{' '}\n {formatEnumLabel(selectedType).toLowerCase()} reading\n {stats.count === 1 ? '' : 's'} (up to {formatNumber(CHART_WINDOW)}) — not a\n full-history total.\n </p>\n </div>\n )}\n\n {/* Full measurement log */}\n <motion.div {...rise(0.26, false)}>\n <Section title=\"Measurement log\" description=\"Every recorded reading for this metric.\">\n {table.isError ? (\n <ErrorView\n message=\"We couldn't load this page of measurements.\"\n onRetry={() => table.refetch()}\n />\n ) : table.isLoading && tableRows.length === 0 ? (\n <div className=\"py-8 text-center text-sm text-text-secondary\">\n Loading measurements…\n </div>\n ) : tableRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<MetricIcon className=\"h-10 w-10\" />}\n title=\"No measurements on this page\"\n description=\"Go back a page or record a new measurement.\"\n />\n ) : (\n <>\n <DataTable\n columns={tableColumns}\n rows={tableRows}\n rowKey={(row) => row.id}\n defaultSort={{ key: 'measuredAt', dir: 'desc' }}\n />\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={tableRows.length}\n hasMore={table.data?.pageInfo?.hasMore ?? false}\n onPageChange={(t) => pager.onPageChange(t)}\n />\n </>\n )}\n </Section>\n </motion.div>\n </div>\n )}\n\n {/* Record-measurement dialog */}\n <ResponsiveDialog open={open} onOpenChange={setOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record measurement</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Log a new body-metric reading to track your progress over time.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-metric-type\">Metric</Label>\n <Select\n value={form.type}\n onValueChange={(v) => {\n // `v` is constrained to METRIC_TYPES by the SelectItems below.\n const nextType = v as MybBodyMetricType;\n setForm((prev) => ({\n ...prev,\n type: nextType,\n unit: METRIC_META[nextType].unit,\n }));\n }}\n >\n <SelectTrigger id=\"myb-metric-type\">\n <SelectValue placeholder=\"Select a metric\" />\n </SelectTrigger>\n <SelectContent>\n {METRIC_TYPES.map((t) => (\n <SelectItem key={t} value={t}>\n {formatEnumLabel(t)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-metric-value\">Value</Label>\n <Input\n id=\"myb-metric-value\"\n type=\"number\"\n inputMode=\"decimal\"\n step=\"any\"\n value={form.value}\n onChange={(e) => setForm((prev) => ({ ...prev, value: e.target.value }))}\n placeholder=\"0\"\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-metric-unit\">Unit</Label>\n <Input\n id=\"myb-metric-unit\"\n value={form.unit}\n onChange={(e) => setForm((prev) => ({ ...prev, unit: e.target.value }))}\n placeholder={METRIC_META[form.type].unit}\n />\n </div>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-metric-measured\">Measured at</Label>\n <Input\n id=\"myb-metric-measured\"\n type=\"datetime-local\"\n value={form.measuredAt}\n onChange={(e) => setForm((prev) => ({ ...prev, measuredAt: e.target.value }))}\n />\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-metric-notes\">Notes</Label>\n <Textarea\n id=\"myb-metric-notes\"\n value={form.notes}\n onChange={(e) => setForm((prev) => ({ ...prev, notes: e.target.value }))}\n placeholder=\"Optional context (fasted, post-workout, morning…)\"\n rows={3}\n />\n </div>\n\n {/* Optional progress photo — uploads via useMybFileUpload (byte-progress\n + inline error handled inside EvidenceUpload) and sets photoFileId. */}\n <EvidenceUpload\n id=\"myb-metric-photo\"\n label=\"Progress photo (optional)\"\n value={form.photo}\n onChange={(photo) => setForm((prev) => ({ ...prev, photo }))}\n accept=\"image/*\"\n disabled={record.isPending}\n hint=\"Attach a progress photo for this reading.\"\n />\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setOpen(false)} disabled={record.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={record.isPending}>\n {record.isPending ? 'Recording…' : 'Record'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2EA,IAAM,IAAoC;CACxC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,EAGK,KAA2D;CAC/D,QAAQ;CACR,UAAU;CACV,oBAAoB;CACpB,OAAO;CACP,MAAM;CACN,OAAO;CACP,KAAK;CACL,OAAO;CACR,EAGK,IAAe,KACf,IAAY,IAGZ,IAAe,kCAYf,KAAgB,MAAoB;CACxC,IAAM,KAAO,MAAc,OAAO,EAAE,CAAC,SAAS,GAAG,IAAI;AACrD,QAAO,GAAG,EAAE,aAAa,CAAC,GAAG,EAAI,EAAE,UAAU,GAAG,EAAE,CAAC,GAAG,EAAI,EAAE,SAAS,CAAC,CAAC,GAAG,EAAI,EAAE,UAAU,CAAC,CAAC,GAAG,EAAI,EAAE,YAAY,CAAC;GAG9G,KAAY,OAAyC;CACzD;CACA,OAAO;CACP,MAAM,EAAY,GAAM;CACxB,YAAY,kBAAa,IAAI,MAAM,CAAC;CACpC,OAAO;CACP,OAAO;CACR;AAED,SAAgB,IAAe;CAC7B,IAAM,CAAC,GAAc,KAAmB,EAA4B,SAAS,EACvE,CAAC,IAAM,KAAW,EAAS,GAAM,EACjC,CAAC,GAAM,KAAW,QAA2B,EAAS,SAAS,CAAC,EAChE,CAAC,GAAW,KAAgB,EAAwB,KAAK,EAEzD,IAAQ,GAAe,EAAU,EAKjC,IAAa,EAAM;AACzB,UAAgB;AACd,KAAY;IACX,CAAC,GAAc,EAAW,CAAC;CAG9B,IAAM,IAAU,EAAkB;EAChC,MAAM;EACN,YAAY,EAAE,OAAO,GAAc;EACpC,CAAC,EAEI,IAAQ,EAAkB;EAC9B,MAAM;EACN,YAAY;GAAE,OAAO;GAAW,QAAQ,EAAM;GAAQ;EACvD,CAAC,EACI,IAAS,IAAwB,EAEjC,IAAU,GAAkB,EAC5B,KAAQ,GAAe,IAAQ,OACnC,IACI,EAAE,SAAS,IAAO,GAClB;EACE,SAAS;GAAE,SAAS;GAAG,GAAG;GAAI;EAC9B,SAAS;GAAE,SAAS;GAAG,GAAG;GAAG;EAC7B,YAAY;GAAE,UAAU;GAAM;GAAO,MAAM;GAAW;EACtD,GAAI,IACA,EACE,YAAY;GACV,GAAG;GACH,YAAY;IAAE,MAAM;IAAU,WAAW;IAAK,SAAS;IAAI;GAC5D,EACF,GACD,EAAE;EACP,EAED,IAAO,EAAY,IACnB,IAAa,EAAK,MAClB,KAAU,GAAe,IAEzB,IAAc,QAA+B,EAAQ,MAAM,SAAS,EAAE,EAAE,CAAC,EAAQ,KAAK,CAAC,EAGvF,IAAY,QAEd,CAAC,GAAG,EAAY,CAAC,MACd,GAAG,MAAM,IAAI,KAAK,EAAE,WAAW,CAAC,SAAS,GAAG,IAAI,KAAK,EAAE,WAAW,CAAC,SAAS,CAC9E,EACH,CAAC,EAAY,CACd,EAEK,IAAS,EAAU,EAAU,SAAS,IACtC,IAAQ,EAAU,EAAU,SAAS,IACrC,IAAQ,KAAU,IAAQ,EAAO,QAAQ,EAAM,QAAQ,KAAA,GAEvD,IAAY,QACV,EAAU,KAAK,OAAO;EAAE,OAAO,GAAW,EAAE,WAAW;EAAE,OAAO,EAAE;EAAO,EAAE,EACjF,CAAC,EAAU,CACZ,EACK,IAAY,EAAU,SAAS,IAAI,EAAU,EAAU,SAAS,KAAK,KAAA,GAErE,IAAQ,QAAc;AAC1B,MAAI,EAAU,WAAW,EAAG,QAAO;EACnC,IAAM,IAAS,EAAU,KAAK,MAAM,EAAE,MAAM;AAC5C,SAAO;GACL,OAAO,EAAO;GACd,KAAK,KAAK,IAAI,GAAG,EAAO;GACxB,KAAK,KAAK,IAAI,GAAG,EAAO;GACxB,KAAK,EAAO,QAAQ,GAAG,MAAM,IAAI,GAAG,EAAE,GAAG,EAAO;GACjD;IACA,CAAC,EAAU,CAAC,EAET,KAAmD,EAAa,KAAK,OAAO;EAChF,OAAO;EACP,OAAO,EAAgB,EAAE;EAC1B,EAAE,EAEG,UAAmB;AAGvB,EAFA,EAAQ,EAAS,EAAa,CAAC,EAC/B,EAAa,KAAK,EAClB,EAAQ,GAAK;IAGT,KAAS,YAAY;EACzB,IAAM,IAAW,OAAO,EAAK,MAAM;AACnC,MAAI,EAAK,MAAM,MAAM,KAAK,MAAM,CAAC,OAAO,SAAS,EAAS,EAAE;AAC1D,KAAa,mCAAmC;AAChD;;AAEF,IAAa,KAAK;EAClB,IAAM,IAA8B;GAClC,MAAM,EAAK;GACX,OAAO;GACP,MAAM,EAAK,KAAK,MAAM,IAAI,EAAY,EAAK,MAAM;GACjD,YAAY,EAAK,aACb,IAAI,KAAK,EAAK,WAAW,CAAC,aAAa,oBACvC,IAAI,MAAM,EAAC,aAAa;GAC5B,OAAO,EAAK,MAAM,MAAM,GAAG,EAAK,MAAM,MAAM,GAAG;GAC/C,aAAa,EAAK,OAAO,UAAU;GACpC;AACD,MAAI;AAKF,GAJA,MAAM,EAAO,YAAY,EAAM,EAC/B,EAAQ,GAAM,EAEd,EAAgB,EAAK,KAAK,EAC1B,EAAM,OAAO;WACN,GAAG;AACV,KAAa,aAAa,QAAQ,EAAE,UAAU,gCAAgC;;IAI5E,IAAY,EAAM,MAAM,SAAS,EAAE,EACnC,KAAwC;EAC5C;GACE,KAAK;GACL,QAAQ;GACR,OAAO;GACP,YAAY,MAAQ,EAAI;GACxB,SAAS,MACP,kBAAC,QAAD;IAAM,WAAU;cAAhB,CACG,EAAa,EAAI,MAAM,EACxB,kBAAC,QAAD;KAAM,WAAU;eAA+C,EAAI;KAAY,CAAA,CAC1E;;GAEV;EACD;GACE,KAAK;GACL,QAAQ;GACR,YAAY,MAAQ,IAAI,KAAK,EAAI,WAAW,CAAC,SAAS;GACtD,SAAS,MACP,kBAAC,QAAD;IAAM,WAAU;cACb,EAAe,EAAI,WAAW;IAC1B,CAAA;GAEV;EACD;GACE,KAAK;GACL,QAAQ;GACR,cAAc;GACd,SAAS,MACP,EAAI,QACF,kBAAC,QAAD;IAAM,WAAU;cAAuB,EAAI;IAAa,CAAA,GAExD,kBAAC,QAAD;IAAM,WAAU;cAAqB;IAAQ,CAAA;GAElD;EACD;GACE,KAAK;GACL,QAAQ;GACR,SAAS,MACP,EAAI,OAAO,KACT,kBAAC,IAAD,EAAU,QAAQ,EAAI,MAAM,IAAM,CAAA,GAElC,kBAAC,QAAD;IAAM,WAAU;cAAqB;IAAQ,CAAA;GAElD;EACF;AAQD,QACE,kBAAC,IAAD;EACE,OAAM;EACN,aAAY;EACZ,SATF,kBAAC,GAAD;GAAQ,SAAS;aAAjB,CACE,kBAAC,GAAD,EAAM,WAAU,kBAAmB,CAAA,EAAA,sBAC5B;;YAIT;GAME,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,IAAD;KAAY,SAAS;KAAe,OAAO;KAAc,UAAU;KAAmB,CAAA;IAClF,CAAA;GAEL,EAAQ,aAAa,EAAY,WAAW,IAC3C,kBAAC,IAAD,EAAa,OAAO,WAAW,EAAgB,EAAa,CAAC,aAAa,CAAC,YAAc,CAAA,GACvF,EAAQ,UACV,kBAAC,GAAD;IACE,SAAQ;IACR,eAAe,EAAQ,SAAS;IAChC,CAAA,GACA,EAAY,WAAW,IACzB,kBAAC,GAAD;IACE,cAAa;IACb,MAAM,kBAAC,GAAD,EAAY,WAAU,aAAc,CAAA;IAC1C,OAAO,MAAM,EAAgB,EAAa,CAAC,aAAa,CAAC;IACzD,aAAY;IACZ,QACE,kBAAC,GAAD;KAAQ,SAAS;eAAjB,CACE,kBAAC,GAAD,EAAM,WAAU,kBAAmB,CAAA,EAAA,sBAC5B;;IAEX,CAAA,GAEF,kBAAC,OAAD;IAAK,WAAU;cAAf;KAEE,kBAAC,EAAO,KAAR;MAAY,GAAI,EAAK,EAAE;gBACrB,kBAAC,IAAD;OACE,WAAU;OACV,WAAU;iBAFZ,CAIE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAU,MAAM,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;UAAE,MAAM,EAAK;UAAQ,CAAA,EACvE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAgB,EAAa;UAC3B,CAAA,EACL,kBAAC,KAAD;UAAG,WAAU;oBACV,IACG,iBAAiB,GAAmB,EAAO,WAAW,KACtD;UACF,CAAA,CACA,EAAA,CAAA,CACF;YACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,KAAD;UAAG,WAAU;oBAAb,CACG,IAAS,EAAa,EAAO,MAAM,GAAG,KACtC,KACC,kBAAC,QAAD;WAAM,WAAU;qBACb,EAAK;WACD,CAAA,CAEP;aACH,KAAS,QACR,kBAAC,OAAD;UAAK,WAAU;oBACb,kBAAC,QAAD;WAAM,WAAU;qBACd,kBAAC,IAAD;YAAkB;YAAO,MAAM,IAAI,EAAK;YAAQ,eAAe;YAAW,CAAA;WACrE,CAAA;UACH,CAAA,CAEJ;WACF;WAEN,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,IAAD;SAAqB,OAAM;SAAO,QAAO;mBACvC,kBAAC,IAAD;UAAW,MAAM;UAAW,QAAQ;WAAE,KAAK;WAAG,OAAO;WAAI,QAAQ;WAAG,MAAM;WAAI;oBAA9E;WACE,kBAAC,QAAD,EAAA,UACE,kBAAC,kBAAD;YAAgB,IAAG;YAAoB,IAAG;YAAI,IAAG;YAAI,IAAG;YAAI,IAAG;sBAA/D;aACE,kBAAC,QAAD;cAAM,QAAO;cAAK,WAAW;cAAc,aAAa;cAAQ,CAAA;aAChE,kBAAC,QAAD;cAAM,QAAO;cAAM,WAAW;cAAc,aAAa;cAAQ,CAAA;aACjE,kBAAC,QAAD;cAAM,QAAO;cAAO,WAAW;cAAc,aAAa;cAAK,CAAA;aAChD;eACZ,CAAA;WACP,kBAAC,IAAD;YACE,QAAO;YACP,iBAAgB;YAChB,UAAU;YACV,CAAA;WACF,kBAAC,IAAD;YACE,SAAQ;YACR,QAAO;YACP,UAAU;YACV,UAAU;YACV,MAAM;aAAE,MAAM;aAA8B,UAAU;aAAI;YAC1D,YAAY;YACZ,YAAY;YACZ,CAAA;WACF,kBAAC,IAAD;YACE,QAAO;YACP,UAAU;YACV,UAAU;YACV,MAAM;aAAE,MAAM;aAA8B,UAAU;aAAI;YAC1D,OAAO;YACP,QAAQ,CAAC,QAAQ,OAAO;YACxB,CAAA;WACF,kBAAC,IAAD;YACE,QAAQ;aAAE,QAAQ;aAA4B,iBAAiB;aAAO;YACtE,cAAc;aACZ,YAAY;aACZ,QAAQ;aACR,cAAc;aACd,OAAO;aACR;YACD,YAAY,EAAE,OAAO,8BAA8B;YACnD,WAAW,EAAE,OAAO,6BAA6B;YACjD,YAAY,MAAU,CACpB,GAAG,EAAa,OAAO,EAAM,CAAC,CAAC,GAAG,EAAK,QACvC,EAAgB,EAAa,CAC9B;YACD,CAAA;WACF,kBAAC,IAAD;YACE,MAAK;YACL,SAAQ;YACR,MAAM,EAAgB,EAAa;YACnC,QAAQ;YACR,aAAa;YACb,MAAK;YACL,aAAa;YACb,KAAK;YACL,WAAW;aACT,GAAG;aACH,aAAa;aACb,QAAQ;aACR,MAAM;aACP;YACD,mBAAmB,CAAC;YACpB,mBAAmB;YACnB,CAAA;WACD,KACC,kBAAC,GAAD;YACE,GAAG,EAAU;YACb,GAAG,EAAU;YACb,GAAG;YACH,SAAA;YACA,YAAW;YACX,MAAM;YACN,aAAa;YACb,QAAO;YACP,CAAA;WAEH,KACC,kBAAC,GAAD;YACE,GAAG,EAAU;YACb,GAAG,EAAU;YACb,GAAG;YACH,SAAA;YACA,YAAW;YACX,MAAM;YACN,QAAO;YACP,aAAa;YACb,CAAA;WAEM;;SACQ,CAAA;QAClB,CAAA,CACI;;MACD,CAAA;KAGZ,KACC,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,EAAO,KAAR;QAAY,GAAI,EAAK,IAAK;kBACxB,kBAAC,GAAD;SACE,WAAU;SACV,MAAM,kBAAC,GAAD;UAAU,MAAM,kBAAC,IAAD,EAAU,WAAU,WAAY,CAAA;UAAE,MAAM;UAAgB,CAAA;SAC9E,OAAO,EAAa,EAAM,MAAM;SAChC,OAAM;SACN,CAAA;QACS,CAAA;OACb,kBAAC,EAAO,KAAR;QAAY,GAAI,EAAK,IAAK;kBACxB,kBAAC,GAAD;SACE,WAAU;SACV,MAAM,kBAAC,GAAD;UAAU,MAAM,kBAAC,IAAD,EAAO,WAAU,WAAY,CAAA;UAAE,MAAM;UAAgB,CAAA;SAC3E,OAAO,GAAG,EAAa,EAAM,KAAK,EAAE,uBAAuB,GAAG,CAAC,CAAC,GAAG,EAAK;SACxE,OAAO,kBAAkB,EAAgB,EAAa,CAAC,aAAa;SACpE,CAAA;QACS,CAAA;OACb,kBAAC,EAAO,KAAR;QAAY,GAAI,EAAK,GAAI;kBACvB,kBAAC,GAAD;SACE,WAAU;SACV,MACE,kBAAC,GAAD;UAAU,MAAM,kBAAC,IAAD,EAAa,WAAU,WAAY,CAAA;UAAE,MAAM;UAAgB,CAAA;SAE7E,OAAO,GAAG,EAAa,EAAM,IAAI,CAAC,GAAG,EAAa,EAAM,IAAI;SAC5D,OAAO,iBAAiB,EAAK,KAAK;SAClC,CAAA;QACS,CAAA;OACT;SACN,kBAAC,KAAD;MAAG,WAAU;gBAAb;OAA+C;OAClB,EAAa,EAAM,MAAM;OAAE;OACrD,EAAgB,EAAa,CAAC,aAAa;OAAC;OAC5C,EAAM,UAAU,IAAI,KAAK;OAAI;OAAS,EAAa,EAAa;OAAC;OAEhE;QACA,EAAA,CAAA;KAIR,kBAAC,EAAO,KAAR;MAAY,GAAI,EAAK,KAAM,GAAM;gBAC/B,kBAAC,IAAD;OAAS,OAAM;OAAkB,aAAY;iBAC1C,EAAM,UACL,kBAAC,GAAD;QACE,SAAQ;QACR,eAAe,EAAM,SAAS;QAC9B,CAAA,GACA,EAAM,aAAa,EAAU,WAAW,IAC1C,kBAAC,OAAD;QAAK,WAAU;kBAA+C;QAExD,CAAA,GACJ,EAAU,WAAW,IACvB,kBAAC,GAAD;QACE,cAAa;QACb,MAAM,kBAAC,GAAD,EAAY,WAAU,aAAc,CAAA;QAC1C,OAAM;QACN,aAAY;QACZ,CAAA,GAEF,kBAAA,IAAA,EAAA,UAAA,CACE,kBAAC,IAAD;QACE,SAAS;QACT,MAAM;QACN,SAAS,MAAQ,EAAI;QACrB,aAAa;SAAE,KAAK;SAAc,KAAK;SAAQ;QAC/C,CAAA,EACF,kBAAC,IAAD;QACE,MAAM,EAAM;QACZ,UAAU;QACV,WAAW,EAAU;QACrB,SAAS,EAAM,MAAM,UAAU,WAAW;QAC1C,eAAe,MAAM,EAAM,aAAa,EAAE;QAC1C,CAAA,CACD,EAAA,CAAA;OAEG,CAAA;MACC,CAAA;KACT;;GAIR,kBAAC,IAAD;IAAwB;IAAM,cAAc;cAC1C,kBAAC,IAAD,EAAA,UAAA;KACE,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,IAAD,EAAA,UAAuB,sBAA0C,CAAA,EACjE,kBAAC,IAAD,EAAA,UAA6B,mEAEC,CAAA,CACP,EAAA,CAAA;KAEzB,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAkB;SAAc,CAAA,EAC/C,kBAAC,GAAD;SACE,OAAO,EAAK;SACZ,gBAAgB,MAAM;UAEpB,IAAM,IAAW;AACjB,aAAS,OAAU;WACjB,GAAG;WACH,MAAM;WACN,MAAM,EAAY,GAAU;WAC7B,EAAE;;mBATP,CAYE,kBAAC,IAAD;UAAe,IAAG;oBAChB,kBAAC,IAAD,EAAa,aAAY,mBAAoB,CAAA;UAC/B,CAAA,EAChB,kBAAC,GAAD,EAAA,UACG,EAAa,KAAK,MACjB,kBAAC,GAAD;UAAoB,OAAO;oBACxB,EAAgB,EAAE;UACR,EAFI,EAEJ,CACb,EACY,CAAA,CACT;WACL;;OAEN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAmB;UAAa,CAAA,EAC/C,kBAAC,GAAD;UACE,IAAG;UACH,MAAK;UACL,WAAU;UACV,MAAK;UACL,OAAO,EAAK;UACZ,WAAW,MAAM,GAAS,OAAU;WAAE,GAAG;WAAM,OAAO,EAAE,OAAO;WAAO,EAAE;UACxE,aAAY;UACZ,CAAA,CACE;YACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UAAO,SAAQ;oBAAkB;UAAY,CAAA,EAC7C,kBAAC,GAAD;UACE,IAAG;UACH,OAAO,EAAK;UACZ,WAAW,MAAM,GAAS,OAAU;WAAE,GAAG;WAAM,MAAM,EAAE,OAAO;WAAO,EAAE;UACvE,aAAa,EAAY,EAAK,MAAM;UACpC,CAAA,CACE;WACF;;OAEN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAsB;SAAmB,CAAA,EACxD,kBAAC,GAAD;SACE,IAAG;SACH,MAAK;SACL,OAAO,EAAK;SACZ,WAAW,MAAM,GAAS,OAAU;UAAE,GAAG;UAAM,YAAY,EAAE,OAAO;UAAO,EAAE;SAC7E,CAAA,CACE;;OAEN,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD;SAAO,SAAQ;mBAAmB;SAAa,CAAA,EAC/C,kBAAC,IAAD;SACE,IAAG;SACH,OAAO,EAAK;SACZ,WAAW,MAAM,GAAS,OAAU;UAAE,GAAG;UAAM,OAAO,EAAE,OAAO;UAAO,EAAE;SACxE,aAAY;SACZ,MAAM;SACN,CAAA,CACE;;OAIN,kBAAC,IAAD;QACE,IAAG;QACH,OAAM;QACN,OAAO,EAAK;QACZ,WAAW,MAAU,GAAS,OAAU;SAAE,GAAG;SAAM;SAAO,EAAE;QAC5D,QAAO;QACP,UAAU,EAAO;QACjB,MAAK;QACL,CAAA;OAED,KAAa,kBAAC,KAAD;QAAG,WAAU;kBAAkC;QAAc,CAAA;OACvE;;KAEN,kBAAC,IAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAQ,SAAQ;MAAU,eAAe,EAAQ,GAAM;MAAE,UAAU,EAAO;gBAAW;MAE5E,CAAA,EACT,kBAAC,GAAD;MAAQ,SAAS;MAAQ,UAAU,EAAO;gBACvC,EAAO,YAAY,eAAe;MAC5B,CAAA,CACc,EAAA,CAAA;KACD,EAAA,CAAA;IACT,CAAA;GACR"}
|
|
1
|
+
{"version":3,"file":"ProgressPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/progress/ProgressPage.tsx"],"sourcesContent":["/**\n * Progress — body metrics over time.\n * @module mybodyshield/pages/progress\n *\n * A single-metric time-series view over the real `mybListBodyMetrics`\n * endpoint: pick a body-metric type (weight, body-fat, measurements…), see the\n * latest value + trend vs the previous reading, a token-styled line chart of the\n * history window, aggregate stats, and an offset-paginated log table. New\n * readings are recorded through `mybRecordBodyMetric`. No mock data — everything\n * derives from the hooks.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport {\n AreaChart,\n Area,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n ReferenceDot,\n ResponsiveContainer,\n} from 'recharts';\nimport { motion, useReducedMotion, type MotionProps } from 'framer-motion';\nimport { Plus, Activity, Sigma, ArrowDownUp } from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Textarea,\n GlassCard,\n Select,\n SelectTrigger,\n SelectContent,\n SelectItem,\n SelectValue,\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 Pagination,\n LoadingView,\n ErrorView,\n Section,\n EmptyState,\n EvidenceUpload,\n FileLink,\n type Column,\n type FilterOption,\n} from '../../components';\nimport { IconChip, TrendPill, METRIC_META } from '../_shared/visuals';\nimport {\n useMybBodyMetrics,\n useMybRecordBodyMetric,\n useOffsetPager,\n type UploadedMybFile,\n} from '../../hooks';\nimport type { MybBodyMetric, MybBodyMetricType, MybRecordMetricInput } from '../../types';\nimport {\n formatNumber,\n formatDate,\n formatDateTime,\n formatRelativeTime,\n formatEnumLabel,\n} from '../../utils/formatters';\n\n// Ordered enum surface for the metric-type selector (METRIC_META is a Record).\nconst METRIC_TYPES: MybBodyMetricType[] = [\n 'WEIGHT',\n 'BODY_FAT',\n 'RESTING_HEART_RATE',\n 'WAIST',\n 'CHEST',\n 'HIPS',\n 'ARM',\n 'THIGH',\n];\n\n// Which direction of change is \"good\" per metric (drives TrendPill colouring).\nconst GOOD_DIRECTION: Record<MybBodyMetricType, 'up' | 'down'> = {\n WEIGHT: 'down',\n BODY_FAT: 'down',\n RESTING_HEART_RATE: 'down',\n WAIST: 'down',\n HIPS: 'down',\n CHEST: 'up',\n ARM: 'up',\n THIGH: 'up',\n};\n\n// Window used for the chart / KPIs / aggregates (the log table paginates on top).\nconst CHART_WINDOW = 100;\nconst PAGE_SIZE = 25;\n\n// Brand emerald accent — the dominant color for the area chart, line & endpoint.\nconst CHART_ACCENT = 'var(--color-action-primary-bg)';\n\ninterface RecordForm {\n type: MybBodyMetricType;\n value: string;\n unit: string;\n measuredAt: string;\n notes: string;\n photo: UploadedMybFile | null;\n}\n\n/** A `Date` → `<input type=\"datetime-local\">` value (local wall-clock time). */\nconst toLocalInput = (d: Date): string => {\n const pad = (n: number) => String(n).padStart(2, '0');\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`;\n};\n\nconst makeForm = (type: MybBodyMetricType): RecordForm => ({\n type,\n value: '',\n unit: METRIC_META[type].unit,\n measuredAt: toLocalInput(new Date()),\n notes: '',\n photo: null,\n});\n\nexport function ProgressPage() {\n const [selectedType, setSelectedType] = useState<MybBodyMetricType>('WEIGHT');\n const [open, setOpen] = useState(false);\n const [form, setForm] = useState<RecordForm>(() => makeForm('WEIGHT'));\n const [formError, setFormError] = useState<string | null>(null);\n\n const pager = useOffsetPager(PAGE_SIZE);\n\n // Reset the log table to page 1 whenever the metric type changes.\n // Depend on the stable `pager.reset` (NOT the whole pager, whose identity\n // changes with `page`) so advancing pages doesn't snap back to page 1.\n const resetPager = pager.reset;\n useEffect(() => {\n resetPager();\n }, [selectedType, resetPager]);\n\n // Wide window drives the chart, latest/trend KPIs and aggregate stats.\n const history = useMybBodyMetrics({\n type: selectedType,\n pagination: { limit: CHART_WINDOW },\n });\n // Independent offset-paginated slice powers the log table.\n const table = useMybBodyMetrics({\n type: selectedType,\n pagination: { limit: PAGE_SIZE, offset: pager.offset },\n });\n const record = useMybRecordBodyMetric();\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 meta = METRIC_META[selectedType];\n const MetricIcon = meta.icon;\n const goodDir = GOOD_DIRECTION[selectedType];\n\n const historyRows = useMemo<MybBodyMetric[]>(() => history.data?.items ?? [], [history.data]);\n\n // Ascending by measuredAt → chart series + robust latest/prior derivation.\n const ascending = useMemo(\n () =>\n [...historyRows].sort(\n (a, b) => new Date(a.measuredAt).getTime() - new Date(b.measuredAt).getTime()\n ),\n [historyRows]\n );\n\n const latest = ascending[ascending.length - 1];\n const prior = ascending[ascending.length - 2];\n const delta = latest && prior ? latest.value - prior.value : undefined;\n\n const chartData = useMemo(\n () => ascending.map((m) => ({ label: formatDate(m.measuredAt), value: m.value })),\n [ascending]\n );\n const lastPoint = chartData.length > 0 ? chartData[chartData.length - 1] : undefined;\n\n const stats = useMemo(() => {\n if (ascending.length === 0) return null;\n const values = ascending.map((m) => m.value);\n return {\n count: values.length,\n min: Math.min(...values),\n max: Math.max(...values),\n avg: values.reduce((a, b) => a + b, 0) / values.length,\n };\n }, [ascending]);\n\n const metricOptions: FilterOption<MybBodyMetricType>[] = METRIC_TYPES.map((t) => ({\n value: t,\n label: formatEnumLabel(t),\n }));\n\n const openRecord = () => {\n setForm(makeForm(selectedType));\n setFormError(null);\n setOpen(true);\n };\n\n const submit = async () => {\n const numValue = Number(form.value);\n if (form.value.trim() === '' || !Number.isFinite(numValue)) {\n setFormError('Enter a valid measurement value.');\n return;\n }\n setFormError(null);\n const input: MybRecordMetricInput = {\n type: form.type,\n value: numValue,\n unit: form.unit.trim() || METRIC_META[form.type].unit,\n measuredAt: form.measuredAt\n ? new Date(form.measuredAt).toISOString()\n : new Date().toISOString(),\n notes: form.notes.trim() ? form.notes.trim() : null,\n photoFileId: form.photo?.fileId ?? null,\n };\n try {\n await record.mutateAsync(input);\n setOpen(false);\n // Surface the just-recorded type so the new reading is visible immediately.\n setSelectedType(form.type);\n pager.reset();\n } catch (e) {\n setFormError(e instanceof Error ? e.message : 'Failed to record measurement.');\n }\n };\n\n const tableRows = table.data?.items ?? [];\n const tableColumns: Column<MybBodyMetric>[] = [\n {\n key: 'value',\n header: 'Value',\n align: 'right',\n sortValue: (row) => row.value,\n render: (row) => (\n <span className=\"font-medium tabular-nums text-text-primary\">\n {formatNumber(row.value)}\n <span className=\"ml-1 text-xs font-normal text-text-tertiary\">{row.unit}</span>\n </span>\n ),\n },\n {\n key: 'measuredAt',\n header: 'Measured',\n sortValue: (row) => new Date(row.measuredAt).getTime(),\n render: (row) => (\n <span className=\"whitespace-nowrap text-text-secondary\">\n {formatDateTime(row.measuredAt)}\n </span>\n ),\n },\n {\n key: 'notes',\n header: 'Notes',\n hideOnMobile: true,\n render: (row) =>\n row.notes ? (\n <span className=\"text-text-secondary\">{row.notes}</span>\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n {\n key: 'photo',\n header: 'Photo',\n render: (row) =>\n row.photo?.id ? (\n <FileLink fileId={row.photo.id} />\n ) : (\n <span className=\"text-text-tertiary\">—</span>\n ),\n },\n ];\n\n const recordButton = (\n <Button onClick={openRecord}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record measurement\n </Button>\n );\n\n return (\n <PageLayout\n title=\"Progress\"\n description=\"Body metrics over time — weight, composition & measurements.\"\n actions={recordButton}\n >\n {/* Metric-type selector */}\n <div className=\"mb-6 overflow-x-auto\">\n <FilterTabs options={metricOptions} value={selectedType} onChange={setSelectedType} />\n </div>\n\n {history.isLoading && historyRows.length === 0 ? (\n <LoadingView label={`Loading ${formatEnumLabel(selectedType).toLowerCase()} history…`} />\n ) : history.isError ? (\n <ErrorView\n message=\"We couldn't load your body-metric history.\"\n onRetry={() => history.refetch()}\n />\n ) : historyRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<MetricIcon className=\"h-10 w-10\" />}\n title={`No ${formatEnumLabel(selectedType).toLowerCase()} readings yet`}\n description=\"Record a measurement to start tracking this metric over time.\"\n action={\n <Button onClick={openRecord}>\n <Plus className=\"mr-1.5 h-4 w-4\" /> Record measurement\n </Button>\n }\n />\n ) : (\n <div className=\"space-y-6\">\n {/* Latest + trend + chart */}\n <motion.div {...rise(0)}>\n <GlassCard\n treatment=\"glass\"\n className=\"rounded-2xl p-6 transition-shadow duration-300 hover:shadow-2xl\"\n >\n <div className=\"flex flex-wrap items-start justify-between gap-4\">\n <div className=\"flex items-center gap-3\">\n <IconChip icon={<MetricIcon className=\"h-5 w-5\" />} tint={meta.tint} />\n <div>\n <h2 className=\"text-base font-semibold text-text-primary\">\n {formatEnumLabel(selectedType)}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {latest\n ? `Last measured ${formatRelativeTime(latest.measuredAt)}`\n : 'No measurements yet'}\n </p>\n </div>\n </div>\n <div className=\"text-right\">\n <p className=\"text-3xl font-bold leading-none tracking-tight tabular-nums text-text-primary sm:text-4xl\">\n {latest ? formatNumber(latest.value) : '—'}\n {latest && (\n <span className=\"ml-1 text-base font-medium text-text-tertiary\">\n {meta.unit}\n </span>\n )}\n </p>\n {delta != null && (\n <div className=\"mt-2 flex justify-end\">\n <span className=\"inline-flex items-center rounded-full bg-bg-sunken px-2.5 py-1 ring-1 ring-border-seam\">\n <TrendPill delta={delta} unit={` ${meta.unit}`} goodDirection={goodDir} />\n </span>\n </div>\n )}\n </div>\n </div>\n\n <div className=\"mt-6 h-64 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <AreaChart data={chartData} margin={{ top: 8, right: 14, bottom: 4, left: -8 }}>\n <defs>\n <linearGradient id=\"myb-progress-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 stroke=\"var(--color-border-seam)\"\n strokeDasharray=\"4 4\"\n vertical={false}\n />\n <XAxis\n dataKey=\"label\"\n stroke=\"var(--color-border-seam)\"\n tickLine={false}\n axisLine={false}\n tick={{ fill: 'var(--color-text-tertiary)', fontSize: 12 }}\n tickMargin={8}\n minTickGap={24}\n />\n <YAxis\n stroke=\"var(--color-border-seam)\"\n tickLine={false}\n axisLine={false}\n tick={{ fill: 'var(--color-text-tertiary)', fontSize: 12 }}\n width={44}\n domain={['auto', 'auto']}\n />\n <Tooltip\n cursor={{ stroke: 'var(--color-border-seam)', strokeDasharray: '4 4' }}\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 }}\n labelStyle={{ color: 'var(--color-text-tertiary)' }}\n itemStyle={{ color: 'var(--color-text-primary)' }}\n formatter={(value) => [\n `${formatNumber(Number(value))} ${meta.unit}`,\n formatEnumLabel(selectedType),\n ]}\n />\n <Area\n type=\"monotone\"\n dataKey=\"value\"\n name={formatEnumLabel(selectedType)}\n stroke={CHART_ACCENT}\n strokeWidth={2.5}\n fill=\"url(#myb-progress-fill)\"\n fillOpacity={1}\n dot={false}\n activeDot={{\n r: 5,\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 </div>\n </GlassCard>\n </motion.div>\n\n {/* Aggregate stats over the loaded window (not full history) */}\n {stats && (\n <div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <motion.div {...rise(0.08)}>\n <MetricsCard\n className=\"h-full rounded-2xl transition-shadow duration-300 hover:shadow-xl\"\n icon={<IconChip icon={<Activity className=\"h-5 w-5\" />} tint={CHART_ACCENT} />}\n value={formatNumber(stats.count)}\n label=\"Recent readings\"\n />\n </motion.div>\n <motion.div {...rise(0.14)}>\n <MetricsCard\n className=\"h-full rounded-2xl transition-shadow duration-300 hover:shadow-xl\"\n icon={<IconChip icon={<Sigma className=\"h-5 w-5\" />} tint={CHART_ACCENT} />}\n value={`${formatNumber(stats.avg, { maximumFractionDigits: 1 })} ${meta.unit}`}\n label={`Window average ${formatEnumLabel(selectedType).toLowerCase()}`}\n />\n </motion.div>\n <motion.div {...rise(0.2)}>\n <MetricsCard\n className=\"h-full rounded-2xl transition-shadow duration-300 hover:shadow-xl\"\n icon={\n <IconChip icon={<ArrowDownUp className=\"h-5 w-5\" />} tint={CHART_ACCENT} />\n }\n value={`${formatNumber(stats.min)}–${formatNumber(stats.max)}`}\n label={`Window range (${meta.unit})`}\n />\n </motion.div>\n </div>\n <p className=\"mt-2 text-xs text-text-tertiary\">\n Based on your most recent {formatNumber(stats.count)}{' '}\n {formatEnumLabel(selectedType).toLowerCase()} reading\n {stats.count === 1 ? '' : 's'} (up to {formatNumber(CHART_WINDOW)}) — not a\n full-history total.\n </p>\n </div>\n )}\n\n {/* Full measurement log */}\n <motion.div {...rise(0.26, false)}>\n <Section title=\"Measurement log\" description=\"Every recorded reading for this metric.\">\n {table.isError ? (\n <ErrorView\n message=\"We couldn't load this page of measurements.\"\n onRetry={() => table.refetch()}\n />\n ) : table.isLoading && tableRows.length === 0 ? (\n <div className=\"py-8 text-center text-sm text-text-secondary\">\n Loading measurements…\n </div>\n ) : tableRows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<MetricIcon className=\"h-10 w-10\" />}\n title=\"No measurements on this page\"\n description=\"Go back a page or record a new measurement.\"\n />\n ) : (\n <>\n <DataTable\n columns={tableColumns}\n rows={tableRows}\n rowKey={(row) => row.id}\n defaultSort={{ key: 'measuredAt', dir: 'desc' }}\n />\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={tableRows.length}\n hasMore={table.data?.pageInfo?.hasMore ?? false}\n onPageChange={(t) => pager.onPageChange(t)}\n />\n </>\n )}\n </Section>\n </motion.div>\n </div>\n )}\n\n {/* Record-measurement dialog */}\n <ResponsiveDialog open={open} onOpenChange={setOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Record measurement</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Log a new body-metric reading to track your progress over time.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4 py-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-metric-type\">Metric</Label>\n <Select\n value={form.type}\n onValueChange={(v) => {\n // `v` is constrained to METRIC_TYPES by the SelectItems below.\n const nextType = v as MybBodyMetricType;\n setForm((prev) => ({\n ...prev,\n type: nextType,\n unit: METRIC_META[nextType].unit,\n }));\n }}\n >\n <SelectTrigger id=\"myb-metric-type\">\n <SelectValue placeholder=\"Select a metric\" />\n </SelectTrigger>\n <SelectContent>\n {METRIC_TYPES.map((t) => (\n <SelectItem key={t} value={t}>\n {formatEnumLabel(t)}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-metric-value\">Value</Label>\n <Input\n id=\"myb-metric-value\"\n type=\"number\"\n inputMode=\"decimal\"\n step=\"any\"\n value={form.value}\n onChange={(e) => setForm((prev) => ({ ...prev, value: e.target.value }))}\n placeholder=\"0\"\n />\n </div>\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-metric-unit\">Unit</Label>\n <Input\n id=\"myb-metric-unit\"\n value={form.unit}\n onChange={(e) => setForm((prev) => ({ ...prev, unit: e.target.value }))}\n placeholder={METRIC_META[form.type].unit}\n />\n </div>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-metric-measured\">Measured at</Label>\n <Input\n id=\"myb-metric-measured\"\n type=\"datetime-local\"\n value={form.measuredAt}\n onChange={(e) => setForm((prev) => ({ ...prev, measuredAt: e.target.value }))}\n />\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-metric-notes\">Notes</Label>\n <Textarea\n id=\"myb-metric-notes\"\n value={form.notes}\n onChange={(e) => setForm((prev) => ({ ...prev, notes: e.target.value }))}\n placeholder=\"Optional context (fasted, post-workout, morning…)\"\n rows={3}\n />\n </div>\n\n {/* Optional progress photo — uploads via useMybFileUpload (byte-progress\n + inline error handled inside EvidenceUpload) and sets photoFileId. */}\n <EvidenceUpload\n id=\"myb-metric-photo\"\n label=\"Progress photo (optional)\"\n value={form.photo}\n onChange={(photo) => setForm((prev) => ({ ...prev, photo }))}\n accept=\"image/*\"\n disabled={record.isPending}\n hint=\"Attach a progress photo for this reading.\"\n />\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button variant=\"outline\" onClick={() => setOpen(false)} disabled={record.isPending}>\n Cancel\n </Button>\n <Button onClick={submit} disabled={record.isPending}>\n {record.isPending ? 'Recording…' : 'Record'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recovery — sleep, readiness, soreness, stress & mood tracking.
|
|
3
|
+
* @module mybodyshield/pages/recovery
|
|
4
|
+
*
|
|
5
|
+
* Reads the real `mybListRecoveryLogs` array endpoint (no pagination). KPIs pull
|
|
6
|
+
* the readiness / sleep / mood metrics independently so they stay stable while
|
|
7
|
+
* the FilterTabs drive the charted + tabulated metric. New entries go through
|
|
8
|
+
* `mybLogRecovery`. No mock data — everything is derived from the hooks.
|
|
9
|
+
*/
|
|
10
|
+
export declare function RecoveryPage(): import("react").JSX.Element;
|
|
11
|
+
//# sourceMappingURL=RecoveryPage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"RecoveryPage.d.ts","sourceRoot":"","sources":["../../../../src/mybodyshield/pages/recovery/RecoveryPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA+FH,wBAAgB,YAAY,gCA4hB3B"}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { useMybLogRecovery as re, useMybRecoveryLogs as u } from "../../hooks/useMyBodyShieldApi.js";
|
|
1
|
+
import { PageLayout as e } from "../../components/PageLayout.js";
|
|
2
|
+
import { MetricsCard as t } from "../../components/MetricsCard.js";
|
|
3
|
+
import { formatDate as n, formatNumber as r, formatRelativeTime as i } from "../../utils/formatters.js";
|
|
4
|
+
import { EmptyState as ee } from "../../components/EmptyState.js";
|
|
5
|
+
import { ErrorView as a, LoadingView as te, Section as o } from "../../components/StateViews.js";
|
|
6
|
+
import { DataTable as ne } from "../../components/DataTable.js";
|
|
7
|
+
import { FilterTabs as re } from "../../components/FilterTabs.js";
|
|
8
|
+
import { useMybLogRecovery as ie, useMybRecoveryLogs as s } from "../../hooks/useMyBodyShieldApi.js";
|
|
10
9
|
import "../../hooks/index.js";
|
|
11
|
-
import { DataTable as ie } from "../../components/DataTable.js";
|
|
12
|
-
import { FilterTabs as ae } from "../../components/FilterTabs.js";
|
|
13
10
|
import "../../components/index.js";
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
11
|
+
import { motion as c } from "../../../node_modules/framer-motion/dist/es/render/components/motion/proxy.js";
|
|
12
|
+
import { useReducedMotion as l } from "../../../node_modules/framer-motion/dist/es/utils/reduced-motion/use-reduced-motion.js";
|
|
13
|
+
import { IconChip as u, RECOVERY_META as d } from "../_shared/visuals.js";
|
|
14
|
+
import { Tooltip as f } from "../../../node_modules/recharts/es6/component/Tooltip.js";
|
|
15
|
+
import { ResponsiveContainer as ae } from "../../../node_modules/recharts/es6/component/ResponsiveContainer.js";
|
|
16
|
+
import { ReferenceDot as p } from "../../../node_modules/recharts/es6/cartesian/ReferenceDot.js";
|
|
17
|
+
import { CartesianGrid as oe } from "../../../node_modules/recharts/es6/cartesian/CartesianGrid.js";
|
|
18
18
|
import { Area as se } from "../../../node_modules/recharts/es6/cartesian/Area.js";
|
|
19
19
|
import { XAxis as ce } from "../../../node_modules/recharts/es6/cartesian/XAxis.js";
|
|
20
20
|
import { YAxis as le } from "../../../node_modules/recharts/es6/cartesian/YAxis.js";
|
|
@@ -22,7 +22,7 @@ import { AreaChart as ue } from "../../../node_modules/recharts/es6/chart/AreaCh
|
|
|
22
22
|
import { useMemo as m, useState as h } from "react";
|
|
23
23
|
import { HeartPulse as g, Plus as _ } from "lucide-react";
|
|
24
24
|
import { jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
25
|
-
import { Button as b, GlassCard as de, Input as x, Label as S, ResponsiveDialog as fe, ResponsiveDialogContent as pe, ResponsiveDialogDescription as
|
|
25
|
+
import { Button as b, GlassCard as de, Input as x, Label as S, ResponsiveDialog as fe, ResponsiveDialogContent as pe, ResponsiveDialogDescription as C, ResponsiveDialogFooter as w, ResponsiveDialogHeader as me, ResponsiveDialogTitle as he, Select as ge, SelectContent as _e, SelectItem as ve, SelectTrigger as ye, SelectValue as be, Spinner as xe, Textarea as Se } from "@burdenoff/fe-libs/ui";
|
|
26
26
|
//#region src/mybodyshield/pages/recovery/RecoveryPage.tsx
|
|
27
27
|
var T = [
|
|
28
28
|
"SLEEP",
|
|
@@ -35,7 +35,7 @@ var T = [
|
|
|
35
35
|
label: "All"
|
|
36
36
|
}, ...T.map((e) => ({
|
|
37
37
|
value: e,
|
|
38
|
-
label:
|
|
38
|
+
label: d[e].label
|
|
39
39
|
}))], E = "var(--color-action-primary-bg)", D = (e) => e >= 70 ? "success" : e >= 40 ? "warning" : "error", O = (e) => e >= 7 ? "success" : e >= 6 ? "warning" : "error";
|
|
40
40
|
function k(e) {
|
|
41
41
|
let t = (e) => String(e).padStart(2, "0");
|
|
@@ -49,7 +49,7 @@ var A = (e = "READINESS") => ({
|
|
|
49
49
|
loggedAt: k(/* @__PURE__ */ new Date())
|
|
50
50
|
});
|
|
51
51
|
function j() {
|
|
52
|
-
let [k, j] = h("ALL"), [we, M] = h(!1), [N, P] = h(A), [F, I] = h(null), Te =
|
|
52
|
+
let [k, j] = h("ALL"), [we, M] = h(!1), [N, P] = h(A), [F, I] = h(null), Te = s("READINESS"), Ee = s("SLEEP"), De = s("MOOD"), L = s(k === "ALL" ? void 0 : k), R = ie(), z = l(), B = (e, t = !0) => z ? { initial: !1 } : {
|
|
53
53
|
initial: {
|
|
54
54
|
opacity: 0,
|
|
55
55
|
y: 14
|
|
@@ -71,8 +71,8 @@ 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" :
|
|
75
|
-
label:
|
|
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) => ({
|
|
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") => {
|
|
78
78
|
P(A(e)), I(null), M(!0);
|
|
@@ -114,12 +114,12 @@ function j() {
|
|
|
114
114
|
{
|
|
115
115
|
key: "metric",
|
|
116
116
|
header: "Metric",
|
|
117
|
-
sortValue: (e) =>
|
|
117
|
+
sortValue: (e) => d[e.metric].label,
|
|
118
118
|
render: (e) => {
|
|
119
|
-
let t =
|
|
119
|
+
let t = d[e.metric], n = t.icon;
|
|
120
120
|
return /* @__PURE__ */ y("span", {
|
|
121
121
|
className: "inline-flex items-center gap-2.5",
|
|
122
|
-
children: [/* @__PURE__ */ v(
|
|
122
|
+
children: [/* @__PURE__ */ v(u, {
|
|
123
123
|
icon: /* @__PURE__ */ v(n, { className: "h-4 w-4" }),
|
|
124
124
|
tint: t.tint
|
|
125
125
|
}), /* @__PURE__ */ v("span", {
|
|
@@ -136,10 +136,10 @@ function j() {
|
|
|
136
136
|
sortValue: (e) => e.metric === "SLEEP" ? e.sleepHours : e.score,
|
|
137
137
|
render: (e) => e.metric === "SLEEP" ? /* @__PURE__ */ y("span", {
|
|
138
138
|
className: "tabular-nums text-text-primary",
|
|
139
|
-
children: [
|
|
139
|
+
children: [r(e.sleepHours, { maximumFractionDigits: 1 }), " h"]
|
|
140
140
|
}) : /* @__PURE__ */ v("span", {
|
|
141
141
|
className: "tabular-nums text-text-primary",
|
|
142
|
-
children:
|
|
142
|
+
children: r(e.score)
|
|
143
143
|
})
|
|
144
144
|
},
|
|
145
145
|
{
|
|
@@ -161,11 +161,11 @@ function j() {
|
|
|
161
161
|
sortValue: (e) => new Date(e.loggedAt).getTime(),
|
|
162
162
|
render: (e) => /* @__PURE__ */ v("span", {
|
|
163
163
|
className: "whitespace-nowrap text-text-tertiary",
|
|
164
|
-
children:
|
|
164
|
+
children: i(e.loggedAt)
|
|
165
165
|
})
|
|
166
166
|
}
|
|
167
|
-
], Ae =
|
|
168
|
-
return /* @__PURE__ */ y(
|
|
167
|
+
], Ae = d.READINESS.icon, je = d.SLEEP.icon, Me = d.MOOD.icon;
|
|
168
|
+
return /* @__PURE__ */ y(e, {
|
|
169
169
|
title: "Recovery",
|
|
170
170
|
description: "Sleep, readiness, soreness, stress & mood.",
|
|
171
171
|
actions: /* @__PURE__ */ y(b, {
|
|
@@ -174,21 +174,21 @@ function j() {
|
|
|
174
174
|
children: [/* @__PURE__ */ v(_, { className: "h-4 w-4" }), "Log recovery"]
|
|
175
175
|
}),
|
|
176
176
|
children: [
|
|
177
|
-
/* @__PURE__ */ y(
|
|
177
|
+
/* @__PURE__ */ y(c.div, {
|
|
178
178
|
...B(0),
|
|
179
179
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-3",
|
|
180
180
|
children: [
|
|
181
|
-
/* @__PURE__ */ v(
|
|
182
|
-
icon: /* @__PURE__ */ v(
|
|
181
|
+
/* @__PURE__ */ v(t, {
|
|
182
|
+
icon: /* @__PURE__ */ v(u, {
|
|
183
183
|
icon: /* @__PURE__ */ v(Ae, { className: "h-5 w-5" }),
|
|
184
|
-
tint:
|
|
184
|
+
tint: d.READINESS.tint
|
|
185
185
|
}),
|
|
186
|
-
value: G ?
|
|
186
|
+
value: G ? r(G.score) : "—",
|
|
187
187
|
label: "Latest readiness",
|
|
188
188
|
accent: G ? D(G.score) : void 0,
|
|
189
189
|
footer: G ? /* @__PURE__ */ v("p", {
|
|
190
190
|
className: "text-xs text-text-tertiary",
|
|
191
|
-
children:
|
|
191
|
+
children: i(G.loggedAt)
|
|
192
192
|
}) : /* @__PURE__ */ v("button", {
|
|
193
193
|
type: "button",
|
|
194
194
|
onClick: () => Q("READINESS"),
|
|
@@ -196,12 +196,12 @@ function j() {
|
|
|
196
196
|
children: "Log readiness →"
|
|
197
197
|
})
|
|
198
198
|
}),
|
|
199
|
-
/* @__PURE__ */ v(
|
|
200
|
-
icon: /* @__PURE__ */ v(
|
|
199
|
+
/* @__PURE__ */ v(t, {
|
|
200
|
+
icon: /* @__PURE__ */ v(u, {
|
|
201
201
|
icon: /* @__PURE__ */ v(je, { className: "h-5 w-5" }),
|
|
202
|
-
tint:
|
|
202
|
+
tint: d.SLEEP.tint
|
|
203
203
|
}),
|
|
204
|
-
value: q == null ? "—" : `${
|
|
204
|
+
value: q == null ? "—" : `${r(q, { maximumFractionDigits: 1 })} h`,
|
|
205
205
|
label: "Avg sleep",
|
|
206
206
|
accent: q == null ? void 0 : O(q),
|
|
207
207
|
footer: H.length > 0 ? /* @__PURE__ */ y("p", {
|
|
@@ -222,17 +222,17 @@ function j() {
|
|
|
222
222
|
children: "Log sleep →"
|
|
223
223
|
})
|
|
224
224
|
}),
|
|
225
|
-
/* @__PURE__ */ v(
|
|
226
|
-
icon: /* @__PURE__ */ v(
|
|
225
|
+
/* @__PURE__ */ v(t, {
|
|
226
|
+
icon: /* @__PURE__ */ v(u, {
|
|
227
227
|
icon: /* @__PURE__ */ v(Me, { className: "h-5 w-5" }),
|
|
228
|
-
tint:
|
|
228
|
+
tint: d.MOOD.tint
|
|
229
229
|
}),
|
|
230
|
-
value: K ?
|
|
230
|
+
value: K ? r(K.score) : "—",
|
|
231
231
|
label: "Latest mood",
|
|
232
232
|
accent: K ? D(K.score) : void 0,
|
|
233
233
|
footer: K ? /* @__PURE__ */ v("p", {
|
|
234
234
|
className: "text-xs text-text-tertiary",
|
|
235
|
-
children:
|
|
235
|
+
children: i(K.loggedAt)
|
|
236
236
|
}) : /* @__PURE__ */ v("button", {
|
|
237
237
|
type: "button",
|
|
238
238
|
onClick: () => Q("MOOD"),
|
|
@@ -244,16 +244,16 @@ function j() {
|
|
|
244
244
|
}),
|
|
245
245
|
/* @__PURE__ */ v("div", {
|
|
246
246
|
className: "mt-6 flex flex-wrap items-center gap-3",
|
|
247
|
-
children: /* @__PURE__ */ v(
|
|
247
|
+
children: /* @__PURE__ */ v(re, {
|
|
248
248
|
options: Ce,
|
|
249
249
|
value: k,
|
|
250
250
|
onChange: j
|
|
251
251
|
})
|
|
252
252
|
}),
|
|
253
|
-
/* @__PURE__ */ v(
|
|
253
|
+
/* @__PURE__ */ v(o, {
|
|
254
254
|
title: `${Y} trend`,
|
|
255
255
|
description: J ? "Sleep hours over time." : "Recovery score over time.",
|
|
256
|
-
children: /* @__PURE__ */ v(
|
|
256
|
+
children: /* @__PURE__ */ v(c.div, {
|
|
257
257
|
...B(.08),
|
|
258
258
|
children: /* @__PURE__ */ v(de, {
|
|
259
259
|
treatment: "glass",
|
|
@@ -261,7 +261,7 @@ function j() {
|
|
|
261
261
|
children: L.isLoading ? /* @__PURE__ */ v("div", {
|
|
262
262
|
className: "flex h-[280px] items-center justify-center",
|
|
263
263
|
children: /* @__PURE__ */ v(xe, { size: "lg" })
|
|
264
|
-
}) : L.isError ? /* @__PURE__ */ v(
|
|
264
|
+
}) : L.isError ? /* @__PURE__ */ v(a, {
|
|
265
265
|
message: "We couldn't load this metric's history.",
|
|
266
266
|
onRetry: () => L.refetch()
|
|
267
267
|
}) : X.length === 0 ? /* @__PURE__ */ y("div", {
|
|
@@ -284,7 +284,7 @@ function j() {
|
|
|
284
284
|
]
|
|
285
285
|
})
|
|
286
286
|
]
|
|
287
|
-
}) : /* @__PURE__ */ v(
|
|
287
|
+
}) : /* @__PURE__ */ v(ae, {
|
|
288
288
|
width: "100%",
|
|
289
289
|
height: 280,
|
|
290
290
|
children: /* @__PURE__ */ y(ue, {
|
|
@@ -320,7 +320,7 @@ function j() {
|
|
|
320
320
|
})
|
|
321
321
|
]
|
|
322
322
|
}) }),
|
|
323
|
-
/* @__PURE__ */ v(
|
|
323
|
+
/* @__PURE__ */ v(oe, {
|
|
324
324
|
vertical: !1,
|
|
325
325
|
stroke: "var(--color-border-seam)",
|
|
326
326
|
strokeDasharray: "4 4"
|
|
@@ -345,7 +345,7 @@ function j() {
|
|
|
345
345
|
fontSize: 12
|
|
346
346
|
}
|
|
347
347
|
}),
|
|
348
|
-
/* @__PURE__ */ v(
|
|
348
|
+
/* @__PURE__ */ v(f, {
|
|
349
349
|
contentStyle: {
|
|
350
350
|
background: "var(--color-bg-surface)",
|
|
351
351
|
border: "1px solid var(--color-border-seam)",
|
|
@@ -377,7 +377,7 @@ function j() {
|
|
|
377
377
|
isAnimationActive: !z,
|
|
378
378
|
animationDuration: 900
|
|
379
379
|
}),
|
|
380
|
-
Z && /* @__PURE__ */ v(
|
|
380
|
+
Z && /* @__PURE__ */ v(p, {
|
|
381
381
|
x: Z.label,
|
|
382
382
|
y: Z.value,
|
|
383
383
|
r: 12,
|
|
@@ -387,7 +387,7 @@ function j() {
|
|
|
387
387
|
fillOpacity: .16,
|
|
388
388
|
stroke: "none"
|
|
389
389
|
}),
|
|
390
|
-
Z && /* @__PURE__ */ v(
|
|
390
|
+
Z && /* @__PURE__ */ v(p, {
|
|
391
391
|
x: Z.label,
|
|
392
392
|
y: Z.value,
|
|
393
393
|
r: 5,
|
|
@@ -403,15 +403,15 @@ function j() {
|
|
|
403
403
|
})
|
|
404
404
|
})
|
|
405
405
|
}),
|
|
406
|
-
/* @__PURE__ */ v(
|
|
406
|
+
/* @__PURE__ */ v(c.div, {
|
|
407
407
|
...B(.16, !1),
|
|
408
|
-
children: /* @__PURE__ */ v(
|
|
408
|
+
children: /* @__PURE__ */ v(o, {
|
|
409
409
|
title: "Recovery log",
|
|
410
410
|
description: "Every recovery entry, most recent first.",
|
|
411
|
-
children: L.isLoading ? /* @__PURE__ */ v(te, { label: "Loading recovery logs…" }) : L.isError ? /* @__PURE__ */ v(
|
|
411
|
+
children: L.isLoading ? /* @__PURE__ */ v(te, { label: "Loading recovery logs…" }) : L.isError ? /* @__PURE__ */ v(a, {
|
|
412
412
|
message: "We couldn't load your recovery logs.",
|
|
413
413
|
onRetry: () => L.refetch()
|
|
414
|
-
}) : W.length === 0 ? /* @__PURE__ */ v(
|
|
414
|
+
}) : W.length === 0 ? /* @__PURE__ */ v(ee, {
|
|
415
415
|
illustration: "empty-data",
|
|
416
416
|
icon: /* @__PURE__ */ v(g, { className: "h-10 w-10" }),
|
|
417
417
|
title: "No recovery logs yet",
|
|
@@ -421,7 +421,7 @@ function j() {
|
|
|
421
421
|
onClick: () => Q(),
|
|
422
422
|
children: [/* @__PURE__ */ v(_, { className: "h-4 w-4" }), "Log recovery"]
|
|
423
423
|
})
|
|
424
|
-
}) : /* @__PURE__ */ v(
|
|
424
|
+
}) : /* @__PURE__ */ v(ne, {
|
|
425
425
|
columns: ke,
|
|
426
426
|
rows: W,
|
|
427
427
|
rowKey: (e) => e.id,
|
|
@@ -438,7 +438,7 @@ function j() {
|
|
|
438
438
|
children: /* @__PURE__ */ y(pe, {
|
|
439
439
|
className: "sm:max-w-[480px]",
|
|
440
440
|
children: [
|
|
441
|
-
/* @__PURE__ */ y(
|
|
441
|
+
/* @__PURE__ */ y(me, { children: [/* @__PURE__ */ v(he, { children: "Log recovery" }), /* @__PURE__ */ v(C, { children: "Capture how your body is recovering — one metric per entry." })] }),
|
|
442
442
|
/* @__PURE__ */ y("div", {
|
|
443
443
|
className: "space-y-4 py-2",
|
|
444
444
|
children: [
|
|
@@ -458,7 +458,7 @@ function j() {
|
|
|
458
458
|
children: /* @__PURE__ */ v(be, {})
|
|
459
459
|
}), /* @__PURE__ */ v(_e, { children: T.map((e) => /* @__PURE__ */ v(ve, {
|
|
460
460
|
value: e,
|
|
461
|
-
children:
|
|
461
|
+
children: d[e].label
|
|
462
462
|
}, e)) })]
|
|
463
463
|
})]
|
|
464
464
|
}),
|
|
@@ -551,7 +551,7 @@ function j() {
|
|
|
551
551
|
})
|
|
552
552
|
]
|
|
553
553
|
}),
|
|
554
|
-
/* @__PURE__ */ y(
|
|
554
|
+
/* @__PURE__ */ y(w, { children: [/* @__PURE__ */ v(b, {
|
|
555
555
|
variant: "outline",
|
|
556
556
|
onClick: () => M(!1),
|
|
557
557
|
disabled: R.isPending,
|