@burdenoff/microfe-mybodyshield 2026.705.1 → 2026.705.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.
- package/dist/mybodyshield/components/DataTable.js +8 -4
- package/dist/mybodyshield/components/DataTable.js.map +1 -1
- package/dist/mybodyshield/components/EvidenceUpload.js +92 -4
- package/dist/mybodyshield/components/EvidenceUpload.js.map +1 -0
- package/dist/mybodyshield/components/FileLink.js +35 -2
- package/dist/mybodyshield/components/FileLink.js.map +1 -0
- package/dist/mybodyshield/components/MybDiscussion.js +107 -6
- package/dist/mybodyshield/components/MybDiscussion.js.map +1 -0
- package/dist/mybodyshield/components/MybTags.js +148 -4
- package/dist/mybodyshield/components/MybTags.js.map +1 -0
- package/dist/mybodyshield/components/Pagination.js.map +1 -1
- package/dist/mybodyshield/components/UserSearchSelect.js +148 -0
- package/dist/mybodyshield/components/UserSearchSelect.js.map +1 -0
- package/dist/mybodyshield/components/index.js +1 -0
- package/dist/mybodyshield/hooks/index.js +1 -0
- package/dist/mybodyshield/hooks/useMyBodyShieldApi.js +47 -27
- package/dist/mybodyshield/hooks/useMyBodyShieldApi.js.map +1 -1
- package/dist/mybodyshield/hooks/useMybComments.js +44 -2
- package/dist/mybodyshield/hooks/useMybComments.js.map +1 -0
- package/dist/mybodyshield/hooks/useMybFiles.js +80 -3
- package/dist/mybodyshield/hooks/useMybFiles.js.map +1 -0
- package/dist/mybodyshield/hooks/useMybTags.js +88 -2
- package/dist/mybodyshield/hooks/useMybTags.js.map +1 -0
- package/dist/mybodyshield/hooks/useMybUsers.js +36 -0
- package/dist/mybodyshield/hooks/useMybUsers.js.map +1 -0
- package/dist/mybodyshield/pages/DashboardPage.js +41 -36
- package/dist/mybodyshield/pages/DashboardPage.js.map +1 -1
- package/dist/mybodyshield/pages/_shared/visuals.js +15 -9
- package/dist/mybodyshield/pages/_shared/visuals.js.map +1 -1
- package/dist/mybodyshield/pages/coaching/CoachingPage.js +171 -147
- package/dist/mybodyshield/pages/coaching/CoachingPage.js.map +1 -1
- package/dist/mybodyshield/pages/goals/GoalsPage.js +0 -8
- package/dist/mybodyshield/pages/goals/GoalsPage.js.map +1 -1
- package/dist/mybodyshield/pages/nutrition/NutritionPage.js +352 -278
- package/dist/mybodyshield/pages/nutrition/NutritionPage.js.map +1 -1
- package/dist/mybodyshield/pages/programs/ProgramDetailPage.js +600 -296
- package/dist/mybodyshield/pages/programs/ProgramDetailPage.js.map +1 -1
- package/dist/mybodyshield/pages/progress/ProgressPage.js +88 -51
- package/dist/mybodyshield/pages/progress/ProgressPage.js.map +1 -1
- package/dist/mybodyshield/pages/train/SessionDetailPage.js +146 -126
- package/dist/mybodyshield/pages/train/SessionDetailPage.js.map +1 -1
- package/dist/mybodyshield/pages/train/SessionsPage.js +1 -4
- package/dist/mybodyshield/pages/train/SessionsPage.js.map +1 -1
- package/package.json +2 -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 LineChart,\n Line,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n ResponsiveContainer,\n} from 'recharts';\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 type Column,\n type FilterOption,\n} from '../../components';\nimport { IconChip, TrendPill, METRIC_META } from '../_shared/visuals';\nimport { useMybBodyMetrics, useMybRecordBodyMetric, useOffsetPager } 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\ninterface RecordForm {\n type: MybBodyMetricType;\n value: string;\n unit: string;\n measuredAt: string;\n notes: string;\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});\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 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\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 };\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\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 <GlassCard treatment=\"glass\" className=\"p-6\">\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-2xl font-bold tabular-nums text-text-primary\">\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-1 flex justify-end\">\n <TrendPill delta={delta} unit={` ${meta.unit}`} goodDirection={goodDir} />\n </div>\n )}\n </div>\n </div>\n\n <div className=\"mt-6 h-64 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <LineChart data={chartData} margin={{ top: 8, right: 12, bottom: 4, left: -8 }}>\n <CartesianGrid\n stroke=\"var(--color-border-seam)\"\n strokeDasharray=\"3 3\"\n vertical={false}\n />\n <XAxis\n dataKey=\"label\"\n stroke=\"var(--color-border-seam)\"\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 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)' }}\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 <Line\n type=\"monotone\"\n dataKey=\"value\"\n name={formatEnumLabel(selectedType)}\n stroke={meta.tint}\n strokeWidth={2}\n dot={{ r: 3, fill: meta.tint, strokeWidth: 0 }}\n activeDot={{ r: 5 }}\n />\n </LineChart>\n </ResponsiveContainer>\n </div>\n </GlassCard>\n\n {/* Aggregate stats over the loaded window */}\n {stats && (\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <MetricsCard\n icon={<Activity className=\"h-5 w-5\" />}\n value={formatNumber(stats.count)}\n label=\"Measurements logged\"\n />\n <MetricsCard\n icon={<Sigma className=\"h-5 w-5\" />}\n value={`${formatNumber(stats.avg, { maximumFractionDigits: 1 })} ${meta.unit}`}\n label={`Average ${formatEnumLabel(selectedType).toLowerCase()}`}\n />\n <MetricsCard\n icon={<ArrowDownUp className=\"h-5 w-5\" />}\n value={`${formatNumber(stats.min)}–${formatNumber(stats.max)}`}\n label={`Range (${meta.unit})`}\n />\n </div>\n )}\n\n {/* Full measurement log */}\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 </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 {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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"}
|
|
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 LineChart,\n Line,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n ResponsiveContainer,\n} from 'recharts';\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\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 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\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 <GlassCard treatment=\"glass\" className=\"p-6\">\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-2xl font-bold tabular-nums text-text-primary\">\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-1 flex justify-end\">\n <TrendPill delta={delta} unit={` ${meta.unit}`} goodDirection={goodDir} />\n </div>\n )}\n </div>\n </div>\n\n <div className=\"mt-6 h-64 w-full\">\n <ResponsiveContainer width=\"100%\" height=\"100%\">\n <LineChart data={chartData} margin={{ top: 8, right: 12, bottom: 4, left: -8 }}>\n <CartesianGrid\n stroke=\"var(--color-border-seam)\"\n strokeDasharray=\"3 3\"\n vertical={false}\n />\n <XAxis\n dataKey=\"label\"\n stroke=\"var(--color-border-seam)\"\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 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)' }}\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 <Line\n type=\"monotone\"\n dataKey=\"value\"\n name={formatEnumLabel(selectedType)}\n stroke={meta.tint}\n strokeWidth={2}\n dot={{ r: 3, fill: meta.tint, strokeWidth: 0 }}\n activeDot={{ r: 5 }}\n />\n </LineChart>\n </ResponsiveContainer>\n </div>\n </GlassCard>\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 <MetricsCard\n icon={<Activity className=\"h-5 w-5\" />}\n value={formatNumber(stats.count)}\n label=\"Recent readings\"\n />\n <MetricsCard\n icon={<Sigma className=\"h-5 w-5\" />}\n value={`${formatNumber(stats.avg, { maximumFractionDigits: 1 })} ${meta.unit}`}\n label={`Window average ${formatEnumLabel(selectedType).toLowerCase()}`}\n />\n <MetricsCard\n icon={<ArrowDownUp className=\"h-5 w-5\" />}\n value={`${formatNumber(stats.min)}–${formatNumber(stats.max)}`}\n label={`Window range (${meta.unit})`}\n />\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 <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 </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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"}
|