@burdenoff/microfe-mybodyshield 2026.1014.1 → 2026.1026.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.
Files changed (32) hide show
  1. package/dist/mybodyshield/MyBodyShieldRoutes.d.ts.map +1 -1
  2. package/dist/mybodyshield/MyBodyShieldRoutes.js +42 -24
  3. package/dist/mybodyshield/MyBodyShieldRoutes.js.map +1 -1
  4. package/dist/mybodyshield/components/FilterTabs.js +1 -1
  5. package/dist/mybodyshield/components/FilterTabs.js.map +1 -1
  6. package/dist/mybodyshield/hooks/useMyBodyShieldApi.d.ts +6 -1
  7. package/dist/mybodyshield/hooks/useMyBodyShieldApi.d.ts.map +1 -1
  8. package/dist/mybodyshield/hooks/useMyBodyShieldApi.js +89 -63
  9. package/dist/mybodyshield/hooks/useMyBodyShieldApi.js.map +1 -1
  10. package/dist/mybodyshield/pages/coaching/CoachClientPage.d.ts +2 -0
  11. package/dist/mybodyshield/pages/coaching/CoachClientPage.d.ts.map +1 -0
  12. package/dist/mybodyshield/pages/coaching/CoachClientPage.js +264 -0
  13. package/dist/mybodyshield/pages/coaching/CoachClientPage.js.map +1 -0
  14. package/dist/mybodyshield/pages/coaching/CoachingPage.d.ts.map +1 -1
  15. package/dist/mybodyshield/pages/coaching/CoachingPage.js +74 -67
  16. package/dist/mybodyshield/pages/coaching/CoachingPage.js.map +1 -1
  17. package/dist/mybodyshield/pages/coaching/coachAnalytics.d.ts +6 -0
  18. package/dist/mybodyshield/pages/coaching/coachAnalytics.d.ts.map +1 -0
  19. package/dist/mybodyshield/pages/coaching/coachAnalytics.js +13 -0
  20. package/dist/mybodyshield/pages/coaching/coachAnalytics.js.map +1 -0
  21. package/dist/mybodyshield/pages/insights/InsightsPage.js +1 -1
  22. package/dist/mybodyshield/pages/insights/InsightsPage.js.map +1 -1
  23. package/dist/mybodyshield/pages/recovery/MindfulnessPage.d.ts +2 -0
  24. package/dist/mybodyshield/pages/recovery/MindfulnessPage.d.ts.map +1 -0
  25. package/dist/mybodyshield/pages/recovery/MindfulnessPage.js +181 -0
  26. package/dist/mybodyshield/pages/recovery/MindfulnessPage.js.map +1 -0
  27. package/dist/mybodyshield/pages/recovery/RecoveryPage.d.ts.map +1 -1
  28. package/dist/mybodyshield/pages/recovery/RecoveryPage.js +210 -201
  29. package/dist/mybodyshield/pages/recovery/RecoveryPage.js.map +1 -1
  30. package/dist/mybodyshield/types/index.d.ts +31 -0
  31. package/dist/mybodyshield/types/index.d.ts.map +1 -1
  32. package/package.json +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"InsightsPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/insights/InsightsPage.tsx"],"sourcesContent":["/**\n * Insights — unified analytics dashboard for MyBodyShield.\n * @module mybodyshield/pages/insights\n *\n * BOFF-4056: surfaces a 30-day rollup of training volume, sessions,\n * readiness, sleep, nutrition, body weight, and active goals using the\n * `mybInsights` federated query. The backend already aggregates and\n * anonymizes the data; this page only renders what the member is allowed\n * to see.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport {\n BarChart3,\n Calendar,\n Dumbbell,\n Flame,\n HeartPulse,\n Moon,\n Scale,\n Target,\n TrendingUp,\n Utensils,\n} from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n MetricsCard,\n LoadingView,\n ErrorView,\n Section,\n DataTable,\n type Column,\n} from '../../components';\nimport { useMybInsights, useMyBodyShieldAnalytics, useTr } from '../../hooks';\nimport { useMyBodyShield } from '../../context';\nimport type { MybInsightsDay, MybInsightsSummary, MybGoal } from '../../types';\nimport {\n formatDate,\n formatNumber,\n formatWeight,\n formatKcal,\n formatGrams,\n formatPercent,\n formatEnumLabel,\n} from '../../utils/formatters';\nimport {\n VolumeChart,\n ReadinessSleepChart,\n MacroChart,\n CalorieChart,\n CorrelationView,\n} from './InsightsCharts';\n\nconst RANGE_OPTIONS = [\n { labelKey: 'last7Days', fallback: 'Last 7 days', days: 7 },\n { labelKey: 'last30Days', fallback: 'Last 30 days', days: 30 },\n { labelKey: 'last90Days', fallback: 'Last 90 days', days: 90 },\n] as const;\n\ntype RangeDays = (typeof RANGE_OPTIONS)[number]['days'];\n\nfunction toISODate(d: Date): string {\n return d.toISOString().split('T')[0];\n}\n\n/**\n * True when this day carries any logged nutrition. `MybInsightsDay` totals are\n * sums over the day's meals, so a day with no meals is all-zero — but a meal\n * logged with zero macros is indistinguishable from that, which is why the\n * label below says \"macros logged\" rather than \"meals logged\".\n */\nfunction hasMacros(day: MybInsightsDay): boolean {\n return day.calories > 0 || day.proteinG > 0 || day.carbsG > 0 || day.fatG > 0;\n}\n\n/** True when the member logged a readiness or a sleep entry that day. */\nfunction hasRecovery(day: MybInsightsDay): boolean {\n return day.readiness != null || day.sleepHours != null;\n}\n\n/** True when the day carries anything at all — used for \"days with data\". */\nfunction hasAnyData(day: MybInsightsDay): boolean {\n return day.sessions > 0 || hasRecovery(day) || hasMacros(day) || day.weight != null;\n}\n\n/**\n * \"What these numbers are built on\" — the evidence behind the charts above.\n *\n * Every figure here is read straight out of the `mybInsights` payload this page\n * already holds. `daily` is zero-filled to one row per calendar day in the\n * range by the service, so a day count is exact; raw entry counts (how many\n * meals, how many measurements) are NOT in the payload and are deliberately not\n * guessed at. Each row deep-links to the page that owns the underlying log.\n */\nfunction InsightsSources({\n daily,\n summary,\n onOpen,\n}: {\n daily: MybInsightsDay[];\n summary: MybInsightsSummary;\n onOpen: (route: string) => void;\n}) {\n const t = useTr();\n const totalDays = daily.length;\n const sources = [\n {\n route: 'train',\n icon: <Dumbbell className=\"h-4 w-4\" />,\n label: t('mybodyshield.pages.insights.training', 'Training'),\n value: summary.totalSessions,\n unit:\n summary.totalSessions === 1\n ? t('mybodyshield.pages.insights.trainingUnitSingular', 'completed session')\n : t('mybodyshield.pages.insights.trainingUnitPlural', 'completed sessions'),\n detail: t('mybodyshield.pages.insights.trainingDetail', 'on {{active}} of {{total}} days', {\n active: formatNumber(daily.filter((d) => d.sessions > 0).length),\n total: formatNumber(totalDays),\n }),\n },\n {\n route: 'recovery',\n icon: <HeartPulse className=\"h-4 w-4\" />,\n label: t('mybodyshield.pages.insights.recovery', 'Recovery'),\n value: daily.filter(hasRecovery).length,\n unit: t('mybodyshield.pages.insights.recoveryUnit', 'days logged'),\n detail: t('mybodyshield.pages.insights.recoveryDetail', 'readiness or sleep entries'),\n },\n {\n route: 'nutrition',\n icon: <Utensils className=\"h-4 w-4\" />,\n label: t('mybodyshield.pages.insights.nutrition', 'Nutrition'),\n value: daily.filter(hasMacros).length,\n unit: t('mybodyshield.pages.insights.nutritionUnit', 'days logged'),\n detail: t('mybodyshield.pages.insights.nutritionDetail', 'days with macros logged'),\n },\n {\n route: 'progress',\n icon: <Scale className=\"h-4 w-4\" />,\n label: t('mybodyshield.pages.insights.bodyWeight', 'Body weight'),\n value: daily.filter((d) => d.weight != null).length,\n unit: t('mybodyshield.pages.insights.bodyWeightUnit', 'days logged'),\n detail: t('mybodyshield.pages.insights.bodyWeightDetail', 'weight readings only'),\n },\n ];\n\n return (\n <Section\n title={t('mybodyshield.pages.insights.sourcesTitle', 'What these numbers are built on')}\n description={t(\n 'mybodyshield.pages.insights.sourcesDescription',\n 'The logs feeding the charts above. Gaps here are why a trend can look flat.'\n )}\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n {sources.map((source) => (\n <GlassCard key={source.route} treatment=\"plain\" className=\"flex flex-col gap-3 p-5\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex min-w-0 items-center gap-2 text-text-secondary\">\n {source.icon}\n <span className=\"truncate text-sm font-medium text-text-primary\">\n {source.label}\n </span>\n </div>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => onOpen(source.route)}>\n {t('mybodyshield.pages.insights.viewButton', 'View')}\n </Button>\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums text-text-primary\">\n {formatNumber(source.value)}\n </p>\n <p className=\"text-xs text-text-secondary\">{source.unit}</p>\n <p className=\"mt-1 text-xs text-text-tertiary\">{source.detail}</p>\n </div>\n </GlassCard>\n ))}\n </div>\n </Section>\n );\n}\n\nfunction useInsightsRange(days: RangeDays) {\n return useMemo(() => {\n const end = new Date();\n const start = new Date();\n start.setDate(end.getDate() - days + 1);\n return {\n dateRangeStart: toISODate(start),\n dateRangeEnd: toISODate(end),\n };\n }, [days]);\n}\n\nexport function InsightsPage() {\n const t = useTr();\n const { workspaceId, navigateTo } = useMyBodyShield();\n const [days, setDays] = useState<RangeDays>(30);\n const range = useInsightsRange(days);\n const insights = useMybInsights(range);\n const analytics = useMyBodyShieldAnalytics();\n\n // Emit a page-view event once on mount.\n useEffect(() => {\n analytics.emitPageView('insights', { workspaceId });\n }, [analytics, workspaceId]);\n\n if (insights.isLoading) {\n return (\n <PageLayout title={t('mybodyshield.pages.insights.title', 'Insights')}>\n <LoadingView label={t('mybodyshield.pages.insights.loading', 'Loading insights…')} />\n </PageLayout>\n );\n }\n\n if (insights.isError) {\n return (\n <PageLayout title={t('mybodyshield.pages.insights.title', 'Insights')}>\n <ErrorView\n title={t('mybodyshield.pages.insights.errorTitle', 'Could not load insights')}\n message={\n insights.error instanceof Error\n ? insights.error.message\n : t('mybodyshield.common.unknownError', 'Unknown error')\n }\n onRetry={() => insights.refetch()}\n />\n </PageLayout>\n );\n }\n\n const data = insights.data;\n if (!data) {\n return (\n <PageLayout title={t('mybodyshield.pages.insights.title', 'Insights')}>\n <ErrorView\n title={t('mybodyshield.pages.insights.errorTitle', 'Could not load insights')}\n message={t('mybodyshield.pages.insights.error', 'No insight data was returned.')}\n onRetry={() => insights.refetch()}\n />\n </PageLayout>\n );\n }\n\n const { summary, daily, goals } = data;\n\n // `daily` carries one row per calendar day in the range, zero-filled by the\n // service for days with nothing logged — so `daily.length` is the range\n // length, not a measure of tracking. Count the days that actually carry data.\n const daysWithData = daily.filter(hasAnyData).length;\n\n const dayColumns: Column<MybInsightsDay>[] = [\n {\n key: 'date',\n header: t('mybodyshield.pages.insights.dateColumn', 'Date'),\n render: (r) => <span className=\"text-text-primary\">{formatDate(r.date)}</span>,\n },\n {\n key: 'sessions',\n header: t('mybodyshield.pages.insights.sessionsColumn', 'Sessions'),\n align: 'right',\n render: (r) => <span className=\"tabular-nums text-text-secondary\">{r.sessions}</span>,\n },\n {\n key: 'volumeKg',\n header: t('mybodyshield.pages.insights.volumeColumn', 'Volume'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">{formatWeight(r.volumeKg)}</span>\n ),\n },\n {\n key: 'readiness',\n header: t('mybodyshield.pages.insights.readinessColumn', 'Readiness'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">\n {r.readiness != null ? formatNumber(r.readiness) : '—'}\n </span>\n ),\n },\n {\n key: 'sleepHours',\n header: t('mybodyshield.pages.insights.sleepColumn', 'Sleep'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">\n {r.sleepHours != null ? `${formatNumber(r.sleepHours)} h` : '—'}\n </span>\n ),\n },\n {\n key: 'calories',\n header: t('mybodyshield.pages.insights.caloriesColumn', 'Calories'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">{formatKcal(r.calories)}</span>\n ),\n },\n {\n key: 'proteinG',\n header: t('mybodyshield.pages.insights.proteinColumn', 'Protein'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">{formatGrams(r.proteinG)}</span>\n ),\n },\n {\n key: 'weight',\n header: t('mybodyshield.pages.insights.weightColumn', 'Weight'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">\n {r.weight != null ? formatWeight(r.weight) : '—'}\n </span>\n ),\n },\n ];\n\n const goalColumns: Column<MybGoal>[] = [\n {\n key: 'title',\n header: t('mybodyshield.pages.insights.goalColumn', 'Goal'),\n render: (r) => (\n <div>\n <span className=\"block text-sm text-text-primary\">{r.title}</span>\n <span className=\"text-xs text-text-tertiary\">{formatEnumLabel(r.type)}</span>\n </div>\n ),\n },\n {\n key: 'status',\n header: t('mybodyshield.pages.insights.statusColumn', 'Status'),\n render: (r) => (\n <span\n className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${\n r.status === 'ACHIEVED'\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-status-warning-bg text-status-warning-text'\n }`}\n >\n {formatEnumLabel(r.status)}\n </span>\n ),\n },\n {\n key: 'progress',\n header: t('mybodyshield.pages.insights.progressColumn', 'Progress'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">\n {formatNumber(r.currentValue)} / {formatNumber(r.targetValue)} {r.unit}\n </span>\n ),\n },\n ];\n\n return (\n <PageLayout\n title={t('mybodyshield.pages.insights.title', 'Insights')}\n description={t('mybodyshield.pages.insights.description', '{{start}} – {{end}}', {\n start: formatDate(data.dateRangeStart),\n end: formatDate(data.dateRangeEnd),\n })}\n >\n {/* Range selector */}\n <div className=\"flex flex-wrap items-center gap-2\">\n {RANGE_OPTIONS.map((opt) => (\n <button\n key={opt.days}\n type=\"button\"\n onClick={() => setDays(opt.days)}\n className={`rounded-lg px-3 py-1.5 text-sm font-medium transition-colors ${\n days === opt.days\n ? 'bg-accent text-white'\n : 'bg-bg-sunken text-text-secondary hover:bg-bg-elevated'\n }`}\n >\n {t(`mybodyshield.pages.insights.${opt.labelKey}`, opt.fallback)}\n </button>\n ))}\n </div>\n\n {/* Summary KPIs */}\n <Section title={t('mybodyshield.pages.insights.summary', 'Summary')}>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <MetricsCard\n icon={<Flame className=\"h-5 w-5\" />}\n value={summary.consistencyStreakDays}\n label={t('mybodyshield.pages.insights.consistencyStreak', 'Consistency streak')}\n />\n <MetricsCard\n icon={<Dumbbell className=\"h-5 w-5\" />}\n value={summary.totalSessions}\n label={t('mybodyshield.pages.insights.totalSessions', 'Total sessions')}\n />\n <MetricsCard\n icon={<TrendingUp className=\"h-5 w-5\" />}\n value={formatWeight(summary.totalVolumeKg)}\n label={t('mybodyshield.pages.insights.totalVolume', 'Total volume')}\n />\n <MetricsCard\n icon={<HeartPulse className=\"h-5 w-5\" />}\n value={formatNumber(summary.averageReadiness)}\n label={t('mybodyshield.pages.insights.averageReadiness', 'Average readiness')}\n />\n <MetricsCard\n icon={<Utensils className=\"h-5 w-5\" />}\n value={formatPercent(summary.macroAdherencePercent)}\n label={t('mybodyshield.pages.insights.macroAdherence', 'Macro adherence')}\n />\n <MetricsCard\n icon={<Calendar className=\"h-5 w-5\" />}\n value={daysWithData}\n label={t('mybodyshield.pages.insights.daysWithData', 'Days with data')}\n footer={\n <p className=\"text-xs text-text-tertiary\">\n {t('mybodyshield.pages.insights.ofDaysInRange', 'of {{count}} days in this range', {\n count: daily.length,\n })}\n </p>\n }\n />\n </div>\n </Section>\n\n {/* Evidence behind the numbers */}\n <InsightsSources daily={daily} summary={summary} onOpen={navigateTo} />\n\n {/* Trends */}\n <Section\n title={t('mybodyshield.pages.insights.trends', 'Trends')}\n description={t(\n 'mybodyshield.pages.insights.trendsDescription',\n 'How training, recovery and nutrition move together.'\n )}\n >\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-2\">\n <VolumeChart daily={daily} />\n <ReadinessSleepChart daily={daily} />\n <MacroChart daily={daily} />\n <CalorieChart daily={daily} />\n </div>\n </Section>\n\n {/* Correlations */}\n <CorrelationView daily={daily} />\n\n {/* Daily breakdown */}\n <Section\n title={t('mybodyshield.pages.insights.dailyBreakdown', 'Daily breakdown')}\n description={t(\n 'mybodyshield.pages.insights.dailyBreakdownDescription',\n 'Per-day training, recovery and nutrition.'\n )}\n >\n <GlassCard treatment=\"plain\" className=\"overflow-hidden p-0\">\n <DataTable\n columns={dayColumns}\n rows={daily}\n rowKey={(r) => r.date}\n defaultSort={{ key: 'date', dir: 'desc' }}\n />\n </GlassCard>\n </Section>\n\n {/* Active goals */}\n <Section\n title={t('mybodyshield.pages.insights.activeGoals', 'Active goals')}\n description={t(\n 'mybodyshield.pages.insights.activeGoalsDescription',\n 'Goals open during this period.'\n )}\n >\n {goals.length === 0 ? (\n <GlassCard treatment=\"plain\" className=\"p-8 text-center\">\n <Target className=\"mx-auto h-8 w-8 text-text-tertiary\" />\n <p className=\"mt-3 text-sm font-medium text-text-primary\">\n {t('mybodyshield.pages.insights.noActiveGoals', 'No active goals')}\n </p>\n <p className=\"mt-1 text-xs text-text-tertiary\">\n {t(\n 'mybodyshield.pages.insights.noActiveGoalsDescription',\n 'Create a goal to see progress here.'\n )}\n </p>\n </GlassCard>\n ) : (\n <GlassCard treatment=\"plain\" className=\"overflow-hidden p-0\">\n <DataTable\n columns={goalColumns}\n rows={goals}\n rowKey={(r) => r.id}\n defaultSort={{ key: 'status', dir: 'asc' }}\n />\n </GlassCard>\n )}\n </Section>\n\n {/* Legend */}\n <div className=\"flex flex-wrap gap-4 text-xs text-text-tertiary\">\n <span className=\"inline-flex items-center gap-1.5\">\n <Dumbbell className=\"h-3.5 w-3.5\" />\n {t(\n 'mybodyshield.pages.insights.legendTraining',\n 'Training data from completed sessions.'\n )}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <Moon className=\"h-3.5 w-3.5\" />\n {t('mybodyshield.pages.insights.legendRecovery', 'Recovery data from recovery logs.')}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <Scale className=\"h-3.5 w-3.5\" />\n {t('mybodyshield.pages.insights.legendBody', 'Body metrics from progress logs.')}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <BarChart3 className=\"h-3.5 w-3.5\" />\n {t('mybodyshield.pages.insights.legendNutrition', 'Nutrition data from logged meals.')}\n </span>\n </div>\n\n <p className=\"mt-4 border-t border-border-seam pt-4 text-xs leading-relaxed text-text-tertiary\">\n {t(\n 'mybodyshield.pages.insights.medicalDisclaimer',\n 'General fitness information, not medical advice. MyBodyShield is not a medical device and does not diagnose, treat, cure or prevent any condition — talk to a qualified healthcare professional before changing how you train or eat.'\n )}\n </p>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"InsightsPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/insights/InsightsPage.tsx"],"sourcesContent":["/**\n * Insights — unified analytics dashboard for MyBodyShield.\n * @module mybodyshield/pages/insights\n *\n * BOFF-4056: surfaces a 30-day rollup of training volume, sessions,\n * readiness, sleep, nutrition, body weight, and active goals using the\n * `mybInsights` federated query. The backend already aggregates and\n * anonymizes the data; this page only renders what the member is allowed\n * to see.\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport {\n BarChart3,\n Calendar,\n Dumbbell,\n Flame,\n HeartPulse,\n Moon,\n Scale,\n Target,\n TrendingUp,\n Utensils,\n} from 'lucide-react';\nimport { Button, GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n MetricsCard,\n LoadingView,\n ErrorView,\n Section,\n DataTable,\n type Column,\n} from '../../components';\nimport { useMybInsights, useMyBodyShieldAnalytics, useTr } from '../../hooks';\nimport { useMyBodyShield } from '../../context';\nimport type { MybInsightsDay, MybInsightsSummary, MybGoal } from '../../types';\nimport {\n formatDate,\n formatNumber,\n formatWeight,\n formatKcal,\n formatGrams,\n formatPercent,\n formatEnumLabel,\n} from '../../utils/formatters';\nimport {\n VolumeChart,\n ReadinessSleepChart,\n MacroChart,\n CalorieChart,\n CorrelationView,\n} from './InsightsCharts';\n\nconst RANGE_OPTIONS = [\n { labelKey: 'last7Days', fallback: 'Last 7 days', days: 7 },\n { labelKey: 'last30Days', fallback: 'Last 30 days', days: 30 },\n { labelKey: 'last90Days', fallback: 'Last 90 days', days: 90 },\n] as const;\n\ntype RangeDays = (typeof RANGE_OPTIONS)[number]['days'];\n\nfunction toISODate(d: Date): string {\n return d.toISOString().split('T')[0];\n}\n\n/**\n * True when this day carries any logged nutrition. `MybInsightsDay` totals are\n * sums over the day's meals, so a day with no meals is all-zero — but a meal\n * logged with zero macros is indistinguishable from that, which is why the\n * label below says \"macros logged\" rather than \"meals logged\".\n */\nfunction hasMacros(day: MybInsightsDay): boolean {\n return day.calories > 0 || day.proteinG > 0 || day.carbsG > 0 || day.fatG > 0;\n}\n\n/** True when the member logged a readiness or a sleep entry that day. */\nfunction hasRecovery(day: MybInsightsDay): boolean {\n return day.readiness != null || day.sleepHours != null;\n}\n\n/** True when the day carries anything at all — used for \"days with data\". */\nfunction hasAnyData(day: MybInsightsDay): boolean {\n return day.sessions > 0 || hasRecovery(day) || hasMacros(day) || day.weight != null;\n}\n\n/**\n * \"What these numbers are built on\" — the evidence behind the charts above.\n *\n * Every figure here is read straight out of the `mybInsights` payload this page\n * already holds. `daily` is zero-filled to one row per calendar day in the\n * range by the service, so a day count is exact; raw entry counts (how many\n * meals, how many measurements) are NOT in the payload and are deliberately not\n * guessed at. Each row deep-links to the page that owns the underlying log.\n */\nfunction InsightsSources({\n daily,\n summary,\n onOpen,\n}: {\n daily: MybInsightsDay[];\n summary: MybInsightsSummary;\n onOpen: (route: string) => void;\n}) {\n const t = useTr();\n const totalDays = daily.length;\n const sources = [\n {\n route: 'train',\n icon: <Dumbbell className=\"h-4 w-4\" />,\n label: t('mybodyshield.pages.insights.training', 'Training'),\n value: summary.totalSessions,\n unit:\n summary.totalSessions === 1\n ? t('mybodyshield.pages.insights.trainingUnitSingular', 'completed session')\n : t('mybodyshield.pages.insights.trainingUnitPlural', 'completed sessions'),\n detail: t('mybodyshield.pages.insights.trainingDetail', 'on {{active}} of {{total}} days', {\n active: formatNumber(daily.filter((d) => d.sessions > 0).length),\n total: formatNumber(totalDays),\n }),\n },\n {\n route: 'recovery',\n icon: <HeartPulse className=\"h-4 w-4\" />,\n label: t('mybodyshield.pages.insights.recovery', 'Recovery'),\n value: daily.filter(hasRecovery).length,\n unit: t('mybodyshield.pages.insights.recoveryUnit', 'days logged'),\n detail: t('mybodyshield.pages.insights.recoveryDetail', 'readiness or sleep entries'),\n },\n {\n route: 'nutrition',\n icon: <Utensils className=\"h-4 w-4\" />,\n label: t('mybodyshield.pages.insights.nutrition', 'Nutrition'),\n value: daily.filter(hasMacros).length,\n unit: t('mybodyshield.pages.insights.nutritionUnit', 'days logged'),\n detail: t('mybodyshield.pages.insights.nutritionDetail', 'days with macros logged'),\n },\n {\n route: 'progress',\n icon: <Scale className=\"h-4 w-4\" />,\n label: t('mybodyshield.pages.insights.bodyWeight', 'Body weight'),\n value: daily.filter((d) => d.weight != null).length,\n unit: t('mybodyshield.pages.insights.bodyWeightUnit', 'days logged'),\n detail: t('mybodyshield.pages.insights.bodyWeightDetail', 'weight readings only'),\n },\n ];\n\n return (\n <Section\n title={t('mybodyshield.pages.insights.sourcesTitle', 'What these numbers are built on')}\n description={t(\n 'mybodyshield.pages.insights.sourcesDescription',\n 'The logs feeding the charts above. Gaps here are why a trend can look flat.'\n )}\n >\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4\">\n {sources.map((source) => (\n <GlassCard key={source.route} treatment=\"plain\" className=\"flex flex-col gap-3 p-5\">\n <div className=\"flex items-center justify-between gap-2\">\n <div className=\"flex min-w-0 items-center gap-2 text-text-secondary\">\n {source.icon}\n <span className=\"truncate text-sm font-medium text-text-primary\">\n {source.label}\n </span>\n </div>\n <Button variant=\"ghost\" size=\"sm\" onClick={() => onOpen(source.route)}>\n {t('mybodyshield.pages.insights.viewButton', 'View')}\n </Button>\n </div>\n <div>\n <p className=\"text-2xl font-bold tabular-nums text-text-primary\">\n {formatNumber(source.value)}\n </p>\n <p className=\"text-xs text-text-secondary\">{source.unit}</p>\n <p className=\"mt-1 text-xs text-text-tertiary\">{source.detail}</p>\n </div>\n </GlassCard>\n ))}\n </div>\n </Section>\n );\n}\n\nfunction useInsightsRange(days: RangeDays) {\n return useMemo(() => {\n const end = new Date();\n const start = new Date();\n start.setDate(end.getDate() - days + 1);\n return {\n dateRangeStart: toISODate(start),\n dateRangeEnd: toISODate(end),\n };\n }, [days]);\n}\n\nexport function InsightsPage() {\n const t = useTr();\n const { workspaceId, navigateTo } = useMyBodyShield();\n const [days, setDays] = useState<RangeDays>(30);\n const range = useInsightsRange(days);\n const insights = useMybInsights(range);\n const analytics = useMyBodyShieldAnalytics();\n\n // Emit a page-view event once on mount.\n useEffect(() => {\n analytics.emitPageView('insights', { workspaceId });\n }, [analytics, workspaceId]);\n\n if (insights.isLoading) {\n return (\n <PageLayout title={t('mybodyshield.pages.insights.title', 'Insights')}>\n <LoadingView label={t('mybodyshield.pages.insights.loading', 'Loading insights…')} />\n </PageLayout>\n );\n }\n\n if (insights.isError) {\n return (\n <PageLayout title={t('mybodyshield.pages.insights.title', 'Insights')}>\n <ErrorView\n title={t('mybodyshield.pages.insights.errorTitle', 'Could not load insights')}\n message={\n insights.error instanceof Error\n ? insights.error.message\n : t('mybodyshield.common.unknownError', 'Unknown error')\n }\n onRetry={() => insights.refetch()}\n />\n </PageLayout>\n );\n }\n\n const data = insights.data;\n if (!data) {\n return (\n <PageLayout title={t('mybodyshield.pages.insights.title', 'Insights')}>\n <ErrorView\n title={t('mybodyshield.pages.insights.errorTitle', 'Could not load insights')}\n message={t('mybodyshield.pages.insights.error', 'No insight data was returned.')}\n onRetry={() => insights.refetch()}\n />\n </PageLayout>\n );\n }\n\n const { summary, daily, goals } = data;\n\n // `daily` carries one row per calendar day in the range, zero-filled by the\n // service for days with nothing logged — so `daily.length` is the range\n // length, not a measure of tracking. Count the days that actually carry data.\n const daysWithData = daily.filter(hasAnyData).length;\n\n const dayColumns: Column<MybInsightsDay>[] = [\n {\n key: 'date',\n header: t('mybodyshield.pages.insights.dateColumn', 'Date'),\n render: (r) => <span className=\"text-text-primary\">{formatDate(r.date)}</span>,\n },\n {\n key: 'sessions',\n header: t('mybodyshield.pages.insights.sessionsColumn', 'Sessions'),\n align: 'right',\n render: (r) => <span className=\"tabular-nums text-text-secondary\">{r.sessions}</span>,\n },\n {\n key: 'volumeKg',\n header: t('mybodyshield.pages.insights.volumeColumn', 'Volume'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">{formatWeight(r.volumeKg)}</span>\n ),\n },\n {\n key: 'readiness',\n header: t('mybodyshield.pages.insights.readinessColumn', 'Readiness'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">\n {r.readiness != null ? formatNumber(r.readiness) : '—'}\n </span>\n ),\n },\n {\n key: 'sleepHours',\n header: t('mybodyshield.pages.insights.sleepColumn', 'Sleep'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">\n {r.sleepHours != null ? `${formatNumber(r.sleepHours)} h` : '—'}\n </span>\n ),\n },\n {\n key: 'calories',\n header: t('mybodyshield.pages.insights.caloriesColumn', 'Calories'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">{formatKcal(r.calories)}</span>\n ),\n },\n {\n key: 'proteinG',\n header: t('mybodyshield.pages.insights.proteinColumn', 'Protein'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">{formatGrams(r.proteinG)}</span>\n ),\n },\n {\n key: 'weight',\n header: t('mybodyshield.pages.insights.weightColumn', 'Weight'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">\n {r.weight != null ? formatWeight(r.weight) : '—'}\n </span>\n ),\n },\n ];\n\n const goalColumns: Column<MybGoal>[] = [\n {\n key: 'title',\n header: t('mybodyshield.pages.insights.goalColumn', 'Goal'),\n render: (r) => (\n <div>\n <span className=\"block text-sm text-text-primary\">{r.title}</span>\n <span className=\"text-xs text-text-tertiary\">{formatEnumLabel(r.type)}</span>\n </div>\n ),\n },\n {\n key: 'status',\n header: t('mybodyshield.pages.insights.statusColumn', 'Status'),\n render: (r) => (\n <span\n className={`inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${\n r.status === 'ACHIEVED'\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-status-warning-bg text-status-warning-text'\n }`}\n >\n {formatEnumLabel(r.status)}\n </span>\n ),\n },\n {\n key: 'progress',\n header: t('mybodyshield.pages.insights.progressColumn', 'Progress'),\n align: 'right',\n render: (r) => (\n <span className=\"tabular-nums text-text-secondary\">\n {formatNumber(r.currentValue)} / {formatNumber(r.targetValue)} {r.unit}\n </span>\n ),\n },\n ];\n\n return (\n <PageLayout\n title={t('mybodyshield.pages.insights.title', 'Insights')}\n description={t('mybodyshield.pages.insights.description', '{{start}} – {{end}}', {\n start: formatDate(data.dateRangeStart),\n end: formatDate(data.dateRangeEnd),\n })}\n >\n {/* Range selector */}\n <div className=\"flex flex-wrap items-center gap-2\">\n {RANGE_OPTIONS.map((opt) => (\n <button\n key={opt.days}\n type=\"button\"\n onClick={() => setDays(opt.days)}\n className={`rounded-lg px-3 py-1.5 text-sm font-medium transition-colors ${\n days === opt.days\n ? 'bg-accent text-action-primary-text'\n : 'bg-bg-sunken text-text-secondary hover:bg-bg-elevated'\n }`}\n >\n {t(`mybodyshield.pages.insights.${opt.labelKey}`, opt.fallback)}\n </button>\n ))}\n </div>\n\n {/* Summary KPIs */}\n <Section title={t('mybodyshield.pages.insights.summary', 'Summary')}>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <MetricsCard\n icon={<Flame className=\"h-5 w-5\" />}\n value={summary.consistencyStreakDays}\n label={t('mybodyshield.pages.insights.consistencyStreak', 'Consistency streak')}\n />\n <MetricsCard\n icon={<Dumbbell className=\"h-5 w-5\" />}\n value={summary.totalSessions}\n label={t('mybodyshield.pages.insights.totalSessions', 'Total sessions')}\n />\n <MetricsCard\n icon={<TrendingUp className=\"h-5 w-5\" />}\n value={formatWeight(summary.totalVolumeKg)}\n label={t('mybodyshield.pages.insights.totalVolume', 'Total volume')}\n />\n <MetricsCard\n icon={<HeartPulse className=\"h-5 w-5\" />}\n value={formatNumber(summary.averageReadiness)}\n label={t('mybodyshield.pages.insights.averageReadiness', 'Average readiness')}\n />\n <MetricsCard\n icon={<Utensils className=\"h-5 w-5\" />}\n value={formatPercent(summary.macroAdherencePercent)}\n label={t('mybodyshield.pages.insights.macroAdherence', 'Macro adherence')}\n />\n <MetricsCard\n icon={<Calendar className=\"h-5 w-5\" />}\n value={daysWithData}\n label={t('mybodyshield.pages.insights.daysWithData', 'Days with data')}\n footer={\n <p className=\"text-xs text-text-tertiary\">\n {t('mybodyshield.pages.insights.ofDaysInRange', 'of {{count}} days in this range', {\n count: daily.length,\n })}\n </p>\n }\n />\n </div>\n </Section>\n\n {/* Evidence behind the numbers */}\n <InsightsSources daily={daily} summary={summary} onOpen={navigateTo} />\n\n {/* Trends */}\n <Section\n title={t('mybodyshield.pages.insights.trends', 'Trends')}\n description={t(\n 'mybodyshield.pages.insights.trendsDescription',\n 'How training, recovery and nutrition move together.'\n )}\n >\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-2\">\n <VolumeChart daily={daily} />\n <ReadinessSleepChart daily={daily} />\n <MacroChart daily={daily} />\n <CalorieChart daily={daily} />\n </div>\n </Section>\n\n {/* Correlations */}\n <CorrelationView daily={daily} />\n\n {/* Daily breakdown */}\n <Section\n title={t('mybodyshield.pages.insights.dailyBreakdown', 'Daily breakdown')}\n description={t(\n 'mybodyshield.pages.insights.dailyBreakdownDescription',\n 'Per-day training, recovery and nutrition.'\n )}\n >\n <GlassCard treatment=\"plain\" className=\"overflow-hidden p-0\">\n <DataTable\n columns={dayColumns}\n rows={daily}\n rowKey={(r) => r.date}\n defaultSort={{ key: 'date', dir: 'desc' }}\n />\n </GlassCard>\n </Section>\n\n {/* Active goals */}\n <Section\n title={t('mybodyshield.pages.insights.activeGoals', 'Active goals')}\n description={t(\n 'mybodyshield.pages.insights.activeGoalsDescription',\n 'Goals open during this period.'\n )}\n >\n {goals.length === 0 ? (\n <GlassCard treatment=\"plain\" className=\"p-8 text-center\">\n <Target className=\"mx-auto h-8 w-8 text-text-tertiary\" />\n <p className=\"mt-3 text-sm font-medium text-text-primary\">\n {t('mybodyshield.pages.insights.noActiveGoals', 'No active goals')}\n </p>\n <p className=\"mt-1 text-xs text-text-tertiary\">\n {t(\n 'mybodyshield.pages.insights.noActiveGoalsDescription',\n 'Create a goal to see progress here.'\n )}\n </p>\n </GlassCard>\n ) : (\n <GlassCard treatment=\"plain\" className=\"overflow-hidden p-0\">\n <DataTable\n columns={goalColumns}\n rows={goals}\n rowKey={(r) => r.id}\n defaultSort={{ key: 'status', dir: 'asc' }}\n />\n </GlassCard>\n )}\n </Section>\n\n {/* Legend */}\n <div className=\"flex flex-wrap gap-4 text-xs text-text-tertiary\">\n <span className=\"inline-flex items-center gap-1.5\">\n <Dumbbell className=\"h-3.5 w-3.5\" />\n {t(\n 'mybodyshield.pages.insights.legendTraining',\n 'Training data from completed sessions.'\n )}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <Moon className=\"h-3.5 w-3.5\" />\n {t('mybodyshield.pages.insights.legendRecovery', 'Recovery data from recovery logs.')}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <Scale className=\"h-3.5 w-3.5\" />\n {t('mybodyshield.pages.insights.legendBody', 'Body metrics from progress logs.')}\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <BarChart3 className=\"h-3.5 w-3.5\" />\n {t('mybodyshield.pages.insights.legendNutrition', 'Nutrition data from logged meals.')}\n </span>\n </div>\n\n <p className=\"mt-4 border-t border-border-seam pt-4 text-xs leading-relaxed text-text-tertiary\">\n {t(\n 'mybodyshield.pages.insights.medicalDisclaimer',\n 'General fitness information, not medical advice. MyBodyShield is not a medical device and does not diagnose, treat, cure or prevent any condition — talk to a qualified healthcare professional before changing how you train or eat.'\n )}\n </p>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function MindfulnessPage(): import("react").JSX.Element;
2
+ //# sourceMappingURL=MindfulnessPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MindfulnessPage.d.ts","sourceRoot":"","sources":["../../../../src/mybodyshield/pages/recovery/MindfulnessPage.tsx"],"names":[],"mappings":"AA6BA,wBAAgB,eAAe,gCAmL9B"}
@@ -0,0 +1,181 @@
1
+ import { PageLayout as e } from "../../components/PageLayout.js";
2
+ import { EmptyState as t } from "../../components/EmptyState.js";
3
+ import { useMybCompleteMindfulnessSession as n, useMybMindfulnessExercises as r, useMybMindfulnessSessions as i } from "../../hooks/useMyBodyShieldApi.js";
4
+ import { useTr as a } from "../../hooks/useTr.js";
5
+ import "../../hooks/index.js";
6
+ import { ErrorView as o, LoadingView as s, Section as c } from "../../components/StateViews.js";
7
+ import "../../components/index.js";
8
+ import { useEffect as l, useMemo as u, useState as d } from "react";
9
+ import { CheckCircle2 as f, Wind as p } from "lucide-react";
10
+ import { jsx as m, jsxs as h } from "react/jsx-runtime";
11
+ import { Button as g, GlassCard as _, Spinner as v } from "@burdenoff/fe-libs/ui";
12
+ //#region src/mybodyshield/pages/recovery/MindfulnessPage.tsx
13
+ var y = {
14
+ READY: "Ready",
15
+ INHALE: "Inhale",
16
+ HOLD: "Hold",
17
+ EXHALE: "Exhale",
18
+ DONE: "Complete"
19
+ };
20
+ function b(e, t) {
21
+ return t === "INHALE" ? e.inhaleSeconds : t === "HOLD" ? e.holdSeconds : t === "EXHALE" ? e.exhaleSeconds : 0;
22
+ }
23
+ function x() {
24
+ let x = a(), S = r(), C = i(), w = n(), [T, E] = d(null), [D, O] = d("READY"), [k, A] = d(0), [j, M] = d(0), [N, P] = d(null), F = S.data ?? [], I = T ?? F[0], L = C.data?.items ?? [], R = L.length, z = u(() => {
25
+ if (!I || D === "READY" || D === "DONE") return 0;
26
+ let e = b(I, D);
27
+ return e > 0 ? Math.max(0, Math.min(1, k / e)) : 0;
28
+ }, [
29
+ I,
30
+ D,
31
+ k
32
+ ]);
33
+ return l(() => {
34
+ if (!I || D === "READY" || D === "DONE") return;
35
+ let e = window.setInterval(() => A((e) => e - 1), 1e3);
36
+ return () => window.clearInterval(e);
37
+ }, [I, D]), l(() => {
38
+ if (!I || D === "READY" || D === "DONE" || k > 0) return;
39
+ let e = window.setTimeout(() => {
40
+ if (D === "INHALE") O(I.holdSeconds > 0 ? "HOLD" : "EXHALE"), A(I.holdSeconds > 0 ? I.holdSeconds : I.exhaleSeconds);
41
+ else if (D === "HOLD") O("EXHALE"), A(I.exhaleSeconds);
42
+ else if (j < I.cycles) {
43
+ let e = j + 1;
44
+ M(e), e >= I.cycles ? (O("DONE"), N && w.mutateAsync({
45
+ exerciseId: I.id,
46
+ startedAt: N,
47
+ completedAt: (/* @__PURE__ */ new Date()).toISOString(),
48
+ durationSeconds: I.durationSeconds,
49
+ cyclesCompleted: I.cycles
50
+ })) : (O("INHALE"), A(I.inhaleSeconds));
51
+ }
52
+ }, 0);
53
+ return () => window.clearTimeout(e);
54
+ }, [
55
+ I,
56
+ w,
57
+ j,
58
+ D,
59
+ k,
60
+ N
61
+ ]), S.isLoading ? /* @__PURE__ */ m(s, { label: x("mybodyshield.mindfulness.loading", "Loading breathing exercises…") }) : S.isError ? /* @__PURE__ */ m(o, { message: x("mybodyshield.mindfulness.error", "Could not load breathing exercises.") }) : /* @__PURE__ */ h(e, {
62
+ title: x("mybodyshield.mindfulness.title", "Mindfulness"),
63
+ description: x("mybodyshield.mindfulness.description", "Short, self-paced breathing exercises. Stop if they feel uncomfortable."),
64
+ children: [/* @__PURE__ */ h("div", {
65
+ className: "grid gap-6 lg:grid-cols-[minmax(0,1fr)_280px]",
66
+ children: [/* @__PURE__ */ m(_, {
67
+ className: "p-6",
68
+ children: I ? /* @__PURE__ */ h("div", {
69
+ className: "flex flex-col items-center gap-6 text-center",
70
+ children: [
71
+ /* @__PURE__ */ m("div", {
72
+ className: "flex h-36 w-36 items-center justify-center rounded-full bg-status-success-bg transition-transform duration-1000",
73
+ style: { transform: `scale(${D === "INHALE" ? 1.18 : D === "EXHALE" ? .88 : 1})` },
74
+ children: /* @__PURE__ */ m(p, {
75
+ className: "h-14 w-14 text-status-success-text",
76
+ "aria-hidden": "true"
77
+ })
78
+ }),
79
+ /* @__PURE__ */ h("div", { children: [
80
+ /* @__PURE__ */ m("p", {
81
+ className: "text-sm font-medium uppercase tracking-wide text-status-success-text",
82
+ children: y[D]
83
+ }),
84
+ /* @__PURE__ */ m("h2", {
85
+ className: "mt-1 text-2xl font-semibold text-text-primary",
86
+ children: I.title
87
+ }),
88
+ /* @__PURE__ */ m("p", {
89
+ className: "mt-2 max-w-lg text-sm text-text-secondary",
90
+ children: I.description
91
+ })
92
+ ] }),
93
+ D !== "READY" && D !== "DONE" && /* @__PURE__ */ h("div", {
94
+ className: "w-full max-w-sm",
95
+ "aria-live": "polite",
96
+ children: [
97
+ /* @__PURE__ */ m("div", {
98
+ className: "text-5xl font-semibold tabular-nums",
99
+ children: k
100
+ }),
101
+ /* @__PURE__ */ m("div", {
102
+ className: "mt-3 h-2 overflow-hidden rounded-full bg-bg-subtle",
103
+ children: /* @__PURE__ */ m("div", {
104
+ className: "h-full bg-status-success-text transition-all",
105
+ style: { width: `${z * 100}%` }
106
+ })
107
+ }),
108
+ /* @__PURE__ */ h("p", {
109
+ className: "mt-2 text-sm text-text-secondary",
110
+ children: [
111
+ "Cycle ",
112
+ Math.min(j + 1, I.cycles),
113
+ " of ",
114
+ I.cycles
115
+ ]
116
+ })
117
+ ]
118
+ }),
119
+ D === "DONE" ? /* @__PURE__ */ h("div", {
120
+ className: "flex items-center gap-2 text-status-success-text",
121
+ children: [/* @__PURE__ */ m(f, { className: "h-5 w-5" }), " Session saved"]
122
+ }) : /* @__PURE__ */ m(g, {
123
+ onClick: () => {
124
+ I && (P((/* @__PURE__ */ new Date()).toISOString()), M(0), O("INHALE"), A(I.inhaleSeconds));
125
+ },
126
+ disabled: D !== "READY" || w.isPending,
127
+ children: D === "READY" ? "Start exercise" : "In progress"
128
+ })
129
+ ]
130
+ }) : /* @__PURE__ */ m(t, {
131
+ title: "No exercises available",
132
+ description: "The product-authored exercise catalog is empty."
133
+ })
134
+ }), /* @__PURE__ */ m(c, {
135
+ title: "Exercises",
136
+ children: /* @__PURE__ */ m("div", {
137
+ className: "space-y-2",
138
+ children: F.map((e) => /* @__PURE__ */ h("button", {
139
+ type: "button",
140
+ onClick: () => D === "READY" && E(e),
141
+ className: `w-full rounded-lg border border-edge-default p-3 text-left ${I?.id === e.id ? "bg-status-success-bg" : "bg-bg-surface"}`,
142
+ children: [/* @__PURE__ */ m("div", {
143
+ className: "font-medium text-text-primary",
144
+ children: e.title
145
+ }), /* @__PURE__ */ h("div", {
146
+ className: "text-sm text-text-secondary",
147
+ children: [
148
+ Math.round(e.durationSeconds / 60),
149
+ " min · ",
150
+ e.cycles,
151
+ " cycles"
152
+ ]
153
+ })]
154
+ }, e.id))
155
+ })
156
+ })]
157
+ }), /* @__PURE__ */ m(c, {
158
+ title: "Completed sessions",
159
+ children: C.isLoading ? /* @__PURE__ */ m(v, { size: "sm" }) : R === 0 ? /* @__PURE__ */ m(t, {
160
+ title: "No sessions yet",
161
+ description: "Completed exercises will appear here."
162
+ }) : /* @__PURE__ */ m("div", {
163
+ className: "space-y-2",
164
+ children: L.slice(0, 10).map((e) => /* @__PURE__ */ h("div", {
165
+ className: "flex items-center justify-between rounded-lg border border-edge-default p-3 text-sm",
166
+ children: [/* @__PURE__ */ m("span", {
167
+ className: "text-text-primary",
168
+ children: e.exercise.title
169
+ }), /* @__PURE__ */ m("span", {
170
+ className: "text-text-secondary",
171
+ children: new Date(e.completedAt).toLocaleDateString()
172
+ })]
173
+ }, e.id))
174
+ })
175
+ })]
176
+ });
177
+ }
178
+ //#endregion
179
+ export { x as MindfulnessPage };
180
+
181
+ //# sourceMappingURL=MindfulnessPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MindfulnessPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/recovery/MindfulnessPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Wind, CheckCircle2 } from 'lucide-react';\nimport { Button, GlassCard, Spinner } from '@burdenoff/fe-libs/ui';\nimport { EmptyState, ErrorView, LoadingView, PageLayout, Section } from '../../components';\nimport {\n useMybCompleteMindfulnessSession,\n useMybMindfulnessExercises,\n useMybMindfulnessSessions,\n useTr,\n} from '../../hooks';\nimport type { MybMindfulnessExercise } from '../../types';\n\ntype Phase = 'READY' | 'INHALE' | 'HOLD' | 'EXHALE' | 'DONE';\n\nconst phaseLabel: Record<Phase, string> = {\n READY: 'Ready',\n INHALE: 'Inhale',\n HOLD: 'Hold',\n EXHALE: 'Exhale',\n DONE: 'Complete',\n};\n\nfunction phaseDuration(exercise: MybMindfulnessExercise, phase: Phase): number {\n if (phase === 'INHALE') return exercise.inhaleSeconds;\n if (phase === 'HOLD') return exercise.holdSeconds;\n if (phase === 'EXHALE') return exercise.exhaleSeconds;\n return 0;\n}\n\nexport function MindfulnessPage() {\n const t = useTr();\n const exercises = useMybMindfulnessExercises();\n const sessions = useMybMindfulnessSessions();\n const complete = useMybCompleteMindfulnessSession();\n const [selected, setSelected] = useState<MybMindfulnessExercise | null>(null);\n const [phase, setPhase] = useState<Phase>('READY');\n const [remaining, setRemaining] = useState(0);\n const [cycle, setCycle] = useState(0);\n const [startedAt, setStartedAt] = useState<string | null>(null);\n\n const exerciseList = exercises.data ?? [];\n const active = selected ?? exerciseList[0];\n const history = sessions.data?.items ?? [];\n const totalCompleted = history.length;\n\n const phaseProgress = useMemo(() => {\n if (!active || phase === 'READY' || phase === 'DONE') return 0;\n const duration = phaseDuration(active, phase);\n return duration > 0 ? Math.max(0, Math.min(1, remaining / duration)) : 0;\n }, [active, phase, remaining]);\n\n useEffect(() => {\n if (!active || phase === 'READY' || phase === 'DONE') return undefined;\n const timer = window.setInterval(() => setRemaining((value) => value - 1), 1000);\n return () => window.clearInterval(timer);\n }, [active, phase]);\n\n useEffect(() => {\n if (!active || phase === 'READY' || phase === 'DONE' || remaining > 0) return;\n const transition = window.setTimeout(() => {\n if (phase === 'INHALE') {\n setPhase(active.holdSeconds > 0 ? 'HOLD' : 'EXHALE');\n setRemaining(active.holdSeconds > 0 ? active.holdSeconds : active.exhaleSeconds);\n } else if (phase === 'HOLD') {\n setPhase('EXHALE');\n setRemaining(active.exhaleSeconds);\n } else if (cycle < active.cycles) {\n const nextCycle = cycle + 1;\n setCycle(nextCycle);\n if (nextCycle >= active.cycles) {\n setPhase('DONE');\n if (startedAt) {\n void complete.mutateAsync({\n exerciseId: active.id,\n startedAt,\n completedAt: new Date().toISOString(),\n durationSeconds: active.durationSeconds,\n cyclesCompleted: active.cycles,\n });\n }\n } else {\n setPhase('INHALE');\n setRemaining(active.inhaleSeconds);\n }\n }\n }, 0);\n return () => window.clearTimeout(transition);\n }, [active, complete, cycle, phase, remaining, startedAt]);\n\n const start = () => {\n if (!active) return;\n setStartedAt(new Date().toISOString());\n setCycle(0);\n setPhase('INHALE');\n setRemaining(active.inhaleSeconds);\n };\n\n if (exercises.isLoading)\n return (\n <LoadingView label={t('mybodyshield.mindfulness.loading', 'Loading breathing exercises…')} />\n );\n if (exercises.isError)\n return (\n <ErrorView\n message={t('mybodyshield.mindfulness.error', 'Could not load breathing exercises.')}\n />\n );\n\n return (\n <PageLayout\n title={t('mybodyshield.mindfulness.title', 'Mindfulness')}\n description={t(\n 'mybodyshield.mindfulness.description',\n 'Short, self-paced breathing exercises. Stop if they feel uncomfortable.'\n )}\n >\n <div className=\"grid gap-6 lg:grid-cols-[minmax(0,1fr)_280px]\">\n <GlassCard className=\"p-6\">\n {!active ? (\n <EmptyState\n title=\"No exercises available\"\n description=\"The product-authored exercise catalog is empty.\"\n />\n ) : (\n <div className=\"flex flex-col items-center gap-6 text-center\">\n <div\n className=\"flex h-36 w-36 items-center justify-center rounded-full bg-status-success-bg transition-transform duration-1000\"\n style={{\n transform: `scale(${phase === 'INHALE' ? 1.18 : phase === 'EXHALE' ? 0.88 : 1})`,\n }}\n >\n <Wind className=\"h-14 w-14 text-status-success-text\" aria-hidden=\"true\" />\n </div>\n <div>\n <p className=\"text-sm font-medium uppercase tracking-wide text-status-success-text\">\n {phaseLabel[phase]}\n </p>\n <h2 className=\"mt-1 text-2xl font-semibold text-text-primary\">{active.title}</h2>\n <p className=\"mt-2 max-w-lg text-sm text-text-secondary\">{active.description}</p>\n </div>\n {phase !== 'READY' && phase !== 'DONE' && (\n <div className=\"w-full max-w-sm\" aria-live=\"polite\">\n <div className=\"text-5xl font-semibold tabular-nums\">{remaining}</div>\n <div className=\"mt-3 h-2 overflow-hidden rounded-full bg-bg-subtle\">\n <div\n className=\"h-full bg-status-success-text transition-all\"\n style={{ width: `${phaseProgress * 100}%` }}\n />\n </div>\n <p className=\"mt-2 text-sm text-text-secondary\">\n Cycle {Math.min(cycle + 1, active.cycles)} of {active.cycles}\n </p>\n </div>\n )}\n {phase === 'DONE' ? (\n <div className=\"flex items-center gap-2 text-status-success-text\">\n <CheckCircle2 className=\"h-5 w-5\" /> Session saved\n </div>\n ) : (\n <Button onClick={start} disabled={phase !== 'READY' || complete.isPending}>\n {phase === 'READY' ? 'Start exercise' : 'In progress'}\n </Button>\n )}\n </div>\n )}\n </GlassCard>\n\n <Section title=\"Exercises\">\n <div className=\"space-y-2\">\n {exerciseList.map((exercise) => (\n <button\n key={exercise.id}\n type=\"button\"\n onClick={() => phase === 'READY' && setSelected(exercise)}\n className={`w-full rounded-lg border border-edge-default p-3 text-left ${active?.id === exercise.id ? 'bg-status-success-bg' : 'bg-bg-surface'}`}\n >\n <div className=\"font-medium text-text-primary\">{exercise.title}</div>\n <div className=\"text-sm text-text-secondary\">\n {Math.round(exercise.durationSeconds / 60)} min · {exercise.cycles} cycles\n </div>\n </button>\n ))}\n </div>\n </Section>\n </div>\n <Section title=\"Completed sessions\">\n {sessions.isLoading ? (\n <Spinner size=\"sm\" />\n ) : totalCompleted === 0 ? (\n <EmptyState title=\"No sessions yet\" description=\"Completed exercises will appear here.\" />\n ) : (\n <div className=\"space-y-2\">\n {history.slice(0, 10).map((session) => (\n <div\n key={session.id}\n className=\"flex items-center justify-between rounded-lg border border-edge-default p-3 text-sm\"\n >\n <span className=\"text-text-primary\">{session.exercise.title}</span>\n <span className=\"text-text-secondary\">\n {new Date(session.completedAt).toLocaleDateString()}\n </span>\n </div>\n ))}\n </div>\n )}\n </Section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"RecoveryPage.d.ts","sourceRoot":"","sources":["../../../../src/mybodyshield/pages/recovery/RecoveryPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA2GH,wBAAgB,YAAY,gCA2nB3B"}
1
+ {"version":3,"file":"RecoveryPage.d.ts","sourceRoot":"","sources":["../../../../src/mybodyshield/pages/recovery/RecoveryPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA4GH,wBAAgB,YAAY,gCAioB3B"}