@burdenoff/microfe-mybodyshield 2026.827.1 → 2026.830.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/mybodyshield/MyBodyShieldRoot.d.ts.map +1 -1
- package/dist/mybodyshield/MyBodyShieldRoot.js +54 -56
- package/dist/mybodyshield/MyBodyShieldRoot.js.map +1 -1
- package/dist/mybodyshield/MyBodyShieldRoutes.d.ts.map +1 -1
- package/dist/mybodyshield/MyBodyShieldRoutes.js +121 -62
- package/dist/mybodyshield/MyBodyShieldRoutes.js.map +1 -1
- package/dist/mybodyshield/components/EvidenceUpload.d.ts.map +1 -1
- package/dist/mybodyshield/components/EvidenceUpload.js +46 -45
- package/dist/mybodyshield/components/EvidenceUpload.js.map +1 -1
- package/dist/mybodyshield/components/FileLink.d.ts.map +1 -1
- package/dist/mybodyshield/components/FileLink.js +27 -18
- package/dist/mybodyshield/components/FileLink.js.map +1 -1
- package/dist/mybodyshield/components/MybDiscussion.d.ts +1 -1
- package/dist/mybodyshield/components/MybDiscussion.d.ts.map +1 -1
- package/dist/mybodyshield/components/MybDiscussion.js +59 -58
- package/dist/mybodyshield/components/MybDiscussion.js.map +1 -1
- package/dist/mybodyshield/components/MybTags.d.ts +1 -1
- package/dist/mybodyshield/components/MybTags.d.ts.map +1 -1
- package/dist/mybodyshield/components/MybTags.js +83 -74
- package/dist/mybodyshield/components/MybTags.js.map +1 -1
- package/dist/mybodyshield/components/PageDisabled.d.ts.map +1 -1
- package/dist/mybodyshield/components/PageDisabled.js +16 -11
- package/dist/mybodyshield/components/PageDisabled.js.map +1 -1
- package/dist/mybodyshield/components/Pagination.d.ts.map +1 -1
- package/dist/mybodyshield/components/Pagination.js +36 -27
- package/dist/mybodyshield/components/Pagination.js.map +1 -1
- package/dist/mybodyshield/components/StateViews.d.ts.map +1 -1
- package/dist/mybodyshield/components/StateViews.js +42 -34
- package/dist/mybodyshield/components/StateViews.js.map +1 -1
- package/dist/mybodyshield/components/UserSearchSelect.d.ts.map +1 -1
- package/dist/mybodyshield/components/UserSearchSelect.js +66 -68
- package/dist/mybodyshield/components/UserSearchSelect.js.map +1 -1
- package/dist/mybodyshield/hooks/index.d.ts +1 -0
- package/dist/mybodyshield/hooks/index.d.ts.map +1 -1
- package/dist/mybodyshield/hooks/index.js +1 -0
- package/dist/mybodyshield/hooks/useTr.d.ts +5 -0
- package/dist/mybodyshield/hooks/useTr.d.ts.map +1 -0
- package/dist/mybodyshield/hooks/useTr.js +12 -0
- package/dist/mybodyshield/hooks/useTr.js.map +1 -0
- package/dist/mybodyshield/pages/DashboardPage.d.ts.map +1 -1
- package/dist/mybodyshield/pages/DashboardPage.js +157 -136
- package/dist/mybodyshield/pages/DashboardPage.js.map +1 -1
- package/dist/mybodyshield/pages/SettingsPage.d.ts.map +1 -1
- package/dist/mybodyshield/pages/SettingsPage.js +50 -59
- package/dist/mybodyshield/pages/SettingsPage.js.map +1 -1
- package/dist/mybodyshield/pages/_shared/visuals.d.ts +2 -1
- package/dist/mybodyshield/pages/_shared/visuals.d.ts.map +1 -1
- package/dist/mybodyshield/pages/_shared/visuals.js +91 -81
- package/dist/mybodyshield/pages/_shared/visuals.js.map +1 -1
- package/dist/mybodyshield/pages/ai/AiControlsPage.d.ts.map +1 -1
- package/dist/mybodyshield/pages/ai/AiControlsPage.js +255 -266
- package/dist/mybodyshield/pages/ai/AiControlsPage.js.map +1 -1
- package/dist/mybodyshield/pages/coaching/CoachingPage.d.ts.map +1 -1
- package/dist/mybodyshield/pages/coaching/CoachingPage.js +321 -301
- package/dist/mybodyshield/pages/coaching/CoachingPage.js.map +1 -1
- package/dist/mybodyshield/pages/goals/GoalsPage.d.ts.map +1 -1
- package/dist/mybodyshield/pages/goals/GoalsPage.js +247 -251
- package/dist/mybodyshield/pages/goals/GoalsPage.js.map +1 -1
- package/dist/mybodyshield/pages/insights/InsightsCharts.d.ts.map +1 -1
- package/dist/mybodyshield/pages/insights/InsightsCharts.js +216 -213
- package/dist/mybodyshield/pages/insights/InsightsCharts.js.map +1 -1
- package/dist/mybodyshield/pages/insights/InsightsPage.d.ts.map +1 -1
- package/dist/mybodyshield/pages/insights/InsightsPage.js +290 -284
- package/dist/mybodyshield/pages/insights/InsightsPage.js.map +1 -1
- package/dist/mybodyshield/pages/nutrition/NutritionPage.d.ts.map +1 -1
- package/dist/mybodyshield/pages/nutrition/NutritionPage.js +575 -529
- package/dist/mybodyshield/pages/nutrition/NutritionPage.js.map +1 -1
- package/dist/mybodyshield/pages/programs/ProgramDetailPage.d.ts.map +1 -1
- package/dist/mybodyshield/pages/programs/ProgramDetailPage.js +333 -275
- package/dist/mybodyshield/pages/programs/ProgramDetailPage.js.map +1 -1
- package/dist/mybodyshield/pages/programs/ProgramsPage.d.ts.map +1 -1
- package/dist/mybodyshield/pages/programs/ProgramsPage.js +258 -253
- package/dist/mybodyshield/pages/programs/ProgramsPage.js.map +1 -1
- package/dist/mybodyshield/pages/progress/ProgressPage.d.ts.map +1 -1
- package/dist/mybodyshield/pages/progress/ProgressPage.js +271 -254
- package/dist/mybodyshield/pages/progress/ProgressPage.js.map +1 -1
- package/dist/mybodyshield/pages/recovery/RecoveryPage.d.ts.map +1 -1
- package/dist/mybodyshield/pages/recovery/RecoveryPage.js +206 -200
- package/dist/mybodyshield/pages/recovery/RecoveryPage.js.map +1 -1
- package/dist/mybodyshield/pages/train/SessionDetailPage.d.ts.map +1 -1
- package/dist/mybodyshield/pages/train/SessionDetailPage.js +327 -294
- package/dist/mybodyshield/pages/train/SessionDetailPage.js.map +1 -1
- package/dist/mybodyshield/pages/train/SessionsPage.d.ts.map +1 -1
- package/dist/mybodyshield/pages/train/SessionsPage.js +180 -163
- package/dist/mybodyshield/pages/train/SessionsPage.js.map +1 -1
- package/dist/mybodyshield/utils/i18n.d.ts +1 -2
- package/dist/mybodyshield/utils/i18n.d.ts.map +1 -1
- package/dist/mybodyshield/utils/i18n.js +15 -0
- package/dist/mybodyshield/utils/i18n.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionsPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/train/SessionsPage.tsx"],"sourcesContent":["/**\n * Train — live workout sessions.\n * @module mybodyshield/pages/train\n *\n * Lists the member's workout sessions (real `mybListSessions`) filtered by\n * status, and lets you start a new live session (optionally attached to an\n * active program). In-progress sessions are highlighted with an accent ring and\n * a \"Resume\" affordance. No mock data — everything comes from the myb* hooks.\n */\n\nimport { useState } from 'react';\nimport { motion, useReducedMotion } from 'framer-motion';\nimport {\n Plus,\n Flame,\n CheckCircle2,\n CalendarClock,\n PlayCircle,\n SkipForward,\n Gauge,\n ListChecks,\n} from 'lucide-react';\nimport {\n Button,\n GlassCard,\n Label,\n Textarea,\n Input,\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 StatusBadge,\n FilterTabs,\n Pagination,\n LoadingView,\n ErrorView,\n EmptyState,\n type FilterOption,\n} from '../../components';\nimport { IconChip, Meter } from '../_shared/visuals';\nimport { useMyBodyShield } from '../../context';\nimport {\n useMybSessions,\n useMybStartSession,\n useMybPrograms,\n useMybProgramWorkouts,\n useOffsetPager,\n useMyBodyShieldAnalytics,\n} from '../../hooks';\nimport { formatRelativeTime, formatKcal, formatNumber } from '../../utils/formatters';\nimport type { MybSessionStatus, MybWorkoutSession } from '../../types';\n\ntype SessionFilterValue = MybSessionStatus | 'ALL';\n\n// Only statuses a control can actually reach are offered as filters: start →\n// IN_PROGRESS / SCHEDULED, complete → COMPLETED. Nothing moves a session to\n// SKIPPED, so that tab would always be empty. sessionMeta still handles the\n// SKIPPED case for display safety.\nconst STATUS_TABS: FilterOption<SessionFilterValue>[] = [\n { value: 'ALL', label: 'All' },\n { value: 'SCHEDULED', label: 'Scheduled' },\n { value: 'IN_PROGRESS', label: 'Live' },\n { value: 'COMPLETED', label: 'Completed' },\n];\n\nconst PAGE_SIZE = 24;\n\n/** Shared entrance easing — a soft, decelerating cubic-bezier. */\nconst MOTION_EASE: [number, number, number, number] = [0.16, 1, 0.3, 1];\n\n/** Sentinel for the optional program/workout pickers (Radix Select forbids an empty value). */\nconst NO_PROGRAM = '__none__';\nconst NO_WORKOUT = '__none__';\n\ninterface StartForm {\n programId: string;\n workoutId: string;\n scheduledFor: string;\n notes: string;\n}\n\nconst EMPTY_START_FORM: StartForm = {\n programId: NO_PROGRAM,\n workoutId: NO_WORKOUT,\n scheduledFor: '',\n notes: '',\n};\n\ninterface SessionMeta {\n icon: typeof Flame;\n tint: string;\n title: string;\n stampLabel: string;\n stamp?: string | null;\n}\n\nfunction sessionMeta(s: MybWorkoutSession): SessionMeta {\n switch (s.status) {\n case 'IN_PROGRESS':\n return {\n icon: PlayCircle,\n tint: 'var(--color-accent-teal)',\n title: 'Live workout in progress',\n stampLabel: 'Started',\n stamp: s.startedAt ?? s.createdAt,\n };\n case 'COMPLETED':\n return {\n icon: CheckCircle2,\n tint: 'var(--color-status-success-text)',\n title:\n s.caloriesBurned > 0 ? `${formatKcal(s.caloriesBurned)} burned` : 'Workout completed',\n stampLabel: 'Completed',\n stamp: s.completedAt ?? s.startedAt ?? s.createdAt,\n };\n case 'SKIPPED':\n return {\n icon: SkipForward,\n tint: 'var(--color-text-secondary)',\n title: 'Skipped session',\n stampLabel: 'Scheduled',\n stamp: s.scheduledFor ?? s.createdAt,\n };\n case 'SCHEDULED':\n default:\n return {\n icon: CalendarClock,\n tint: 'var(--color-accent-primary)',\n title: 'Scheduled session',\n stampLabel: 'Scheduled',\n stamp: s.scheduledFor ?? s.createdAt,\n };\n }\n}\n\nexport function SessionsPage() {\n const { navigateTo, workspaceId } = useMyBodyShield();\n const reduce = useReducedMotion();\n const analytics = useMyBodyShieldAnalytics();\n\n const [filter, setFilter] = useState<SessionFilterValue>('ALL');\n const pager = useOffsetPager(PAGE_SIZE);\n\n const sessions = useMybSessions({\n status: filter === 'ALL' ? undefined : filter,\n pagination: { limit: PAGE_SIZE, offset: pager.offset },\n });\n\n const programs = useMybPrograms({ status: 'ACTIVE', pagination: { limit: 50 } });\n const activePrograms = programs.data?.items ?? [];\n\n const startSession = useMybStartSession();\n\n const [startOpen, setStartOpen] = useState(false);\n const [form, setForm] = useState<StartForm>(EMPTY_START_FORM);\n const [formError, setFormError] = useState<string | null>(null);\n\n // Workouts for the selected program, so a session can be tied to a specific\n // planned workout — required for the guided next-exercise stepper on\n // SessionDetailPage (BOFF-4096) to have anything to walk through.\n const selectedProgramId = form.programId === NO_PROGRAM ? undefined : form.programId;\n const programWorkouts = useMybProgramWorkouts(selectedProgramId);\n const workoutOptions = programWorkouts.data ?? [];\n\n const rows = sessions.data?.items ?? [];\n const hasMore = sessions.data?.pageInfo?.hasMore ?? false;\n\n const onFilterChange = (value: SessionFilterValue) => {\n setFilter(value);\n pager.reset();\n };\n\n const openStart = () => {\n setForm(EMPTY_START_FORM);\n setFormError(null);\n setStartOpen(true);\n analytics.emitIntent('session', 'create', { workspaceId });\n };\n\n const submitStart = async () => {\n setFormError(null);\n try {\n const created = await startSession.mutateAsync({\n programId: selectedProgramId,\n workoutId: form.workoutId === NO_WORKOUT ? undefined : form.workoutId,\n scheduledFor: form.scheduledFor ? new Date(form.scheduledFor).toISOString() : undefined,\n notes: form.notes.trim() ? form.notes.trim() : undefined,\n });\n analytics.emitFact('session', 'started', { workspaceId, entityId: created.id });\n setStartOpen(false);\n navigateTo(`train/${created.id}`);\n } catch (e) {\n setFormError(e instanceof Error ? e.message : 'Failed to start the session.');\n }\n };\n\n return (\n <PageLayout\n title=\"Train\"\n description=\"Live workout sessions — start, log sets, and complete.\"\n actions={\n <Button onClick={openStart}>\n <Plus className=\"h-4 w-4\" /> Start session\n </Button>\n }\n >\n <div className=\"mb-6\">\n <FilterTabs options={STATUS_TABS} value={filter} onChange={onFilterChange} />\n </div>\n\n {sessions.isLoading ? (\n <LoadingView label=\"Loading your sessions…\" />\n ) : sessions.isError ? (\n <ErrorView\n message=\"We couldn't load your workout sessions.\"\n onRetry={() => sessions.refetch()}\n />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Flame className=\"h-10 w-10\" />}\n title={filter === 'ALL' ? 'No sessions yet' : 'No sessions in this state'}\n description={\n filter === 'ALL'\n ? 'Start a workout session to begin logging sets and tracking your training.'\n : 'Try a different filter, or start a new workout session.'\n }\n action={\n <Button onClick={openStart}>\n <Plus className=\"h-4 w-4\" /> Start session\n </Button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {rows.map((s, i) => {\n const meta = sessionMeta(s);\n const Icon = meta.icon;\n const isLive = s.status === 'IN_PROGRESS';\n const setCount = s.sets?.length;\n const rpe = s.perceivedExertion;\n return (\n <motion.button\n key={s.id}\n type=\"button\"\n onClick={() => navigateTo(`train/${s.id}`)}\n className=\"block w-full text-left\"\n initial={reduce ? false : { opacity: 0, y: 12 }}\n animate={{ opacity: 1, y: 0 }}\n transition={\n reduce\n ? { duration: 0 }\n : { duration: 0.35, delay: Math.min(i, 8) * 0.05, ease: MOTION_EASE }\n }\n whileHover={reduce ? undefined : { y: -3 }}\n whileTap={reduce ? undefined : { scale: 0.995 }}\n >\n <GlassCard\n treatment=\"plain\"\n glow={isLive}\n className={\n 'h-full p-4 transition-shadow hover:shadow-float' +\n (isLive\n ? ' ring-1 ring-inset ring-[color-mix(in_oklab,var(--color-action-primary-bg)_45%,transparent)]'\n : '')\n }\n >\n <div className=\"flex items-center justify-between gap-2\">\n <IconChip icon={<Icon className=\"h-4 w-4\" />} tint={meta.tint} />\n <StatusBadge status={s.status} />\n </div>\n <p className=\"mt-3 text-base font-semibold text-text-primary\">{meta.title}</p>\n <p className=\"mt-0.5 text-xs text-text-tertiary\">\n {meta.stampLabel} {formatRelativeTime(meta.stamp)}\n </p>\n\n <div className=\"mt-3 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-text-secondary\">\n {s.caloriesBurned > 0 && (\n <span className=\"inline-flex items-center gap-1 tabular-nums\">\n <Flame className=\"h-3.5 w-3.5 text-status-warning-text\" />\n {formatKcal(s.caloriesBurned)}\n </span>\n )}\n {rpe != null && (\n <span className=\"inline-flex items-center gap-1 tabular-nums\">\n <Gauge className=\"h-3.5 w-3.5 text-accent\" />\n RPE {rpe}/10\n </span>\n )}\n {setCount != null && setCount > 0 && (\n <span className=\"inline-flex items-center gap-1 tabular-nums\">\n <ListChecks className=\"h-3.5 w-3.5 text-accent\" />\n {formatNumber(setCount)} sets\n </span>\n )}\n </div>\n\n {rpe != null && (\n <div className=\"mt-3\">\n <div className=\"mb-1 flex items-center justify-between text-[11px] text-text-tertiary\">\n <span>RPE intensity</span>\n <span className=\"tabular-nums\">{rpe}/10</span>\n </div>\n <Meter value={rpe * 10} height={6} />\n </div>\n )}\n\n {isLive && (\n <p className=\"mt-3 inline-flex items-center gap-1 text-xs font-semibold text-action-primary-bg\">\n In progress — Resume →\n </p>\n )}\n </GlassCard>\n </motion.button>\n );\n })}\n </div>\n\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={hasMore}\n onPageChange={(t) => pager.onPageChange(t)}\n />\n </>\n )}\n\n {/* Start session dialog */}\n <ResponsiveDialog open={startOpen} onOpenChange={setStartOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>Start a workout session</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Begin a live session now, or schedule one for later. Attach an active program to keep\n your training organised.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4 px-1\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-start-program\">Program (optional)</Label>\n <Select\n value={form.programId}\n onValueChange={(v) =>\n // Reset the workout choice too — the previous selection almost\n // certainly doesn't belong to the newly-picked program.\n setForm((f) => ({ ...f, programId: v, workoutId: NO_WORKOUT }))\n }\n >\n <SelectTrigger id=\"myb-start-program\">\n <SelectValue placeholder=\"No program\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={NO_PROGRAM}>No program</SelectItem>\n {activePrograms.map((p) => (\n <SelectItem key={p.id} value={p.id}>\n {p.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {selectedProgramId && workoutOptions.length > 0 && (\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-start-workout\">Workout (optional)</Label>\n <Select\n value={form.workoutId}\n onValueChange={(v) => setForm((f) => ({ ...f, workoutId: v }))}\n >\n <SelectTrigger id=\"myb-start-workout\">\n <SelectValue placeholder=\"No specific workout\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={NO_WORKOUT}>No specific workout</SelectItem>\n {workoutOptions.map((w) => (\n <SelectItem key={w.id} value={w.id}>\n {w.name} — Week {w.weekNumber}, Day {w.dayOfWeek}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className=\"text-xs text-text-tertiary\">\n Attach a planned workout to guide this session exercise-by-exercise.\n </p>\n </div>\n )}\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-start-scheduled\">Scheduled for (optional)</Label>\n <Input\n id=\"myb-start-scheduled\"\n type=\"datetime-local\"\n value={form.scheduledFor}\n onChange={(e) => setForm((f) => ({ ...f, scheduledFor: e.target.value }))}\n />\n <p className=\"text-xs text-text-tertiary\">\n Leave empty to start a live session right now.\n </p>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-start-notes\">Notes (optional)</Label>\n <Textarea\n id=\"myb-start-notes\"\n rows={3}\n value={form.notes}\n onChange={(e) => setForm((f) => ({ ...f, notes: e.target.value }))}\n placeholder=\"Focus, target lifts, how you're feeling…\"\n />\n </div>\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => setStartOpen(false)}\n disabled={startSession.isPending}\n >\n Cancel\n </Button>\n <Button onClick={submitStart} disabled={startSession.isPending}>\n {startSession.isPending ? 'Starting…' : 'Start session'}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SessionsPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/train/SessionsPage.tsx"],"sourcesContent":["/**\n * Train — live workout sessions.\n * @module mybodyshield/pages/train\n *\n * Lists the member's workout sessions (real `mybListSessions`) filtered by\n * status, and lets you start a new live session (optionally attached to an\n * active program). In-progress sessions are highlighted with an accent ring and\n * a \"Resume\" affordance. No mock data — everything comes from the myb* hooks.\n */\n\nimport { useState } from 'react';\nimport { motion, useReducedMotion } from 'framer-motion';\nimport {\n Plus,\n Flame,\n CheckCircle2,\n CalendarClock,\n PlayCircle,\n SkipForward,\n Gauge,\n ListChecks,\n} from 'lucide-react';\nimport {\n Button,\n GlassCard,\n Label,\n Textarea,\n Input,\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 StatusBadge,\n FilterTabs,\n Pagination,\n LoadingView,\n ErrorView,\n EmptyState,\n type FilterOption,\n} from '../../components';\nimport { IconChip, Meter } from '../_shared/visuals';\nimport { useMyBodyShield } from '../../context';\nimport {\n useMybSessions,\n useMybStartSession,\n useMybPrograms,\n useMybProgramWorkouts,\n useOffsetPager,\n useMyBodyShieldAnalytics,\n useTr,\n} from '../../hooks';\nimport { formatRelativeTime, formatKcal, formatNumber } from '../../utils/formatters';\nimport type { MybSessionStatus, MybWorkoutSession } from '../../types';\n\ntype SessionFilterValue = MybSessionStatus | 'ALL';\n\n// Only statuses a control can actually reach are offered as filters: start →\n// IN_PROGRESS / SCHEDULED, complete → COMPLETED. Nothing moves a session to\n// SKIPPED, so that tab would always be empty. sessionMeta still handles the\n// SKIPPED case for display safety.\nfunction useStatusTabs(t: ReturnType<typeof useTr>): FilterOption<SessionFilterValue>[] {\n return [\n { value: 'ALL', label: t('mybodyshield.pages.train.all', 'All') },\n { value: 'SCHEDULED', label: t('mybodyshield.pages.train.scheduled', 'Scheduled') },\n { value: 'IN_PROGRESS', label: t('mybodyshield.pages.train.live', 'Live') },\n { value: 'COMPLETED', label: t('mybodyshield.pages.train.completed', 'Completed') },\n ];\n}\n\nconst PAGE_SIZE = 24;\n\n/** Shared entrance easing — a soft, decelerating cubic-bezier. */\nconst MOTION_EASE: [number, number, number, number] = [0.16, 1, 0.3, 1];\n\n/** Sentinel for the optional program/workout pickers (Radix Select forbids an empty value). */\nconst NO_PROGRAM = '__none__';\nconst NO_WORKOUT = '__none__';\n\ninterface StartForm {\n programId: string;\n workoutId: string;\n scheduledFor: string;\n notes: string;\n}\n\nconst EMPTY_START_FORM: StartForm = {\n programId: NO_PROGRAM,\n workoutId: NO_WORKOUT,\n scheduledFor: '',\n notes: '',\n};\n\ninterface SessionMeta {\n icon: typeof Flame;\n tint: string;\n titleKey: string;\n titleFallback: string;\n titleParams?: Record<string, string>;\n stampLabelKey: string;\n stampLabelFallback: string;\n stamp?: string | null;\n}\n\nfunction sessionMeta(s: MybWorkoutSession): SessionMeta {\n switch (s.status) {\n case 'IN_PROGRESS':\n return {\n icon: PlayCircle,\n tint: 'var(--color-accent-teal)',\n titleKey: 'mybodyshield.pages.train.liveWorkoutInProgress',\n titleFallback: 'Live workout in progress',\n stampLabelKey: 'mybodyshield.pages.train.startedAgo',\n stampLabelFallback: 'Started {{time}}',\n stamp: s.startedAt ?? s.createdAt,\n };\n case 'COMPLETED':\n return {\n icon: CheckCircle2,\n tint: 'var(--color-status-success-text)',\n titleKey: 'mybodyshield.pages.train.completedTitle',\n titleFallback: s.caloriesBurned > 0 ? '{{kcal}} burned' : 'Workout completed',\n titleParams: s.caloriesBurned > 0 ? { kcal: formatKcal(s.caloriesBurned) } : undefined,\n stampLabelKey: 'mybodyshield.pages.train.completedAgo',\n stampLabelFallback: 'Completed {{time}}',\n stamp: s.completedAt ?? s.startedAt ?? s.createdAt,\n };\n case 'SKIPPED':\n return {\n icon: SkipForward,\n tint: 'var(--color-text-secondary)',\n titleKey: 'mybodyshield.pages.train.skippedSession',\n titleFallback: 'Skipped session',\n stampLabelKey: 'mybodyshield.pages.train.scheduledAgo',\n stampLabelFallback: 'Scheduled {{time}}',\n stamp: s.scheduledFor ?? s.createdAt,\n };\n case 'SCHEDULED':\n default:\n return {\n icon: CalendarClock,\n tint: 'var(--color-accent-primary)',\n titleKey: 'mybodyshield.pages.train.scheduledSession',\n titleFallback: 'Scheduled session',\n stampLabelKey: 'mybodyshield.pages.train.scheduledAgo',\n stampLabelFallback: 'Scheduled {{time}}',\n stamp: s.scheduledFor ?? s.createdAt,\n };\n }\n}\n\nexport function SessionsPage() {\n const t = useTr();\n const { navigateTo, workspaceId } = useMyBodyShield();\n const reduce = useReducedMotion();\n const analytics = useMyBodyShieldAnalytics();\n const STATUS_TABS = useStatusTabs(t);\n\n const [filter, setFilter] = useState<SessionFilterValue>('ALL');\n const pager = useOffsetPager(PAGE_SIZE);\n\n const sessions = useMybSessions({\n status: filter === 'ALL' ? undefined : filter,\n pagination: { limit: PAGE_SIZE, offset: pager.offset },\n });\n\n const programs = useMybPrograms({ status: 'ACTIVE', pagination: { limit: 50 } });\n const activePrograms = programs.data?.items ?? [];\n\n const startSession = useMybStartSession();\n\n const [startOpen, setStartOpen] = useState(false);\n const [form, setForm] = useState<StartForm>(EMPTY_START_FORM);\n const [formError, setFormError] = useState<string | null>(null);\n\n // Workouts for the selected program, so a session can be tied to a specific\n // planned workout — required for the guided next-exercise stepper on\n // SessionDetailPage (BOFF-4096) to have anything to walk through.\n const selectedProgramId = form.programId === NO_PROGRAM ? undefined : form.programId;\n const programWorkouts = useMybProgramWorkouts(selectedProgramId);\n const workoutOptions = programWorkouts.data ?? [];\n\n const rows = sessions.data?.items ?? [];\n const hasMore = sessions.data?.pageInfo?.hasMore ?? false;\n\n const onFilterChange = (value: SessionFilterValue) => {\n setFilter(value);\n pager.reset();\n };\n\n const openStart = () => {\n setForm(EMPTY_START_FORM);\n setFormError(null);\n setStartOpen(true);\n analytics.emitIntent('session', 'create', { workspaceId });\n };\n\n const submitStart = async () => {\n setFormError(null);\n try {\n const created = await startSession.mutateAsync({\n programId: selectedProgramId,\n workoutId: form.workoutId === NO_WORKOUT ? undefined : form.workoutId,\n scheduledFor: form.scheduledFor ? new Date(form.scheduledFor).toISOString() : undefined,\n notes: form.notes.trim() ? form.notes.trim() : undefined,\n });\n analytics.emitFact('session', 'started', { workspaceId, entityId: created.id });\n setStartOpen(false);\n navigateTo(`train/${created.id}`);\n } catch (e) {\n setFormError(\n e instanceof Error\n ? e.message\n : t('mybodyshield.pages.train.failedToStart', 'Failed to start the session.')\n );\n }\n };\n\n return (\n <PageLayout\n title={t('mybodyshield.pages.train.title', 'Train')}\n description={t(\n 'mybodyshield.pages.train.description',\n 'Live workout sessions — start, log sets, and complete.'\n )}\n actions={\n <Button onClick={openStart}>\n <Plus className=\"h-4 w-4\" /> {t('mybodyshield.pages.train.startSession', 'Start session')}\n </Button>\n }\n >\n <div className=\"mb-6\">\n <FilterTabs options={STATUS_TABS} value={filter} onChange={onFilterChange} />\n </div>\n\n {sessions.isLoading ? (\n <LoadingView label={t('mybodyshield.pages.train.loading', 'Loading your sessions…')} />\n ) : sessions.isError ? (\n <ErrorView\n message={t('mybodyshield.pages.train.error', \"We couldn't load your workout sessions.\")}\n onRetry={() => sessions.refetch()}\n />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Flame className=\"h-10 w-10\" />}\n title={\n filter === 'ALL'\n ? t('mybodyshield.pages.train.emptyTitle', 'No sessions yet')\n : t('mybodyshield.pages.train.emptyFilteredTitle', 'No sessions in this state')\n }\n description={\n filter === 'ALL'\n ? t(\n 'mybodyshield.pages.train.emptyDescription',\n 'Start a workout session to begin logging sets and tracking your training.'\n )\n : t(\n 'mybodyshield.pages.train.emptyFilteredDescription',\n 'Try a different filter, or start a new workout session.'\n )\n }\n action={\n <Button onClick={openStart}>\n <Plus className=\"h-4 w-4\" />{' '}\n {t('mybodyshield.pages.train.startSession', 'Start session')}\n </Button>\n }\n />\n ) : (\n <>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {rows.map((s, i) => {\n const meta = sessionMeta(s);\n const Icon = meta.icon;\n const isLive = s.status === 'IN_PROGRESS';\n const setCount = s.sets?.length;\n const rpe = s.perceivedExertion;\n return (\n <motion.button\n key={s.id}\n type=\"button\"\n onClick={() => navigateTo(`train/${s.id}`)}\n className=\"block w-full text-left\"\n initial={reduce ? false : { opacity: 0, y: 12 }}\n animate={{ opacity: 1, y: 0 }}\n transition={\n reduce\n ? { duration: 0 }\n : { duration: 0.35, delay: Math.min(i, 8) * 0.05, ease: MOTION_EASE }\n }\n whileHover={reduce ? undefined : { y: -3 }}\n whileTap={reduce ? undefined : { scale: 0.995 }}\n >\n <GlassCard\n treatment=\"plain\"\n glow={isLive}\n className={\n 'h-full p-4 transition-shadow hover:shadow-float' +\n (isLive\n ? ' ring-1 ring-inset ring-[color-mix(in_oklab,var(--color-action-primary-bg)_45%,transparent)]'\n : '')\n }\n >\n <div className=\"flex items-center justify-between gap-2\">\n <IconChip icon={<Icon className=\"h-4 w-4\" />} tint={meta.tint} />\n <StatusBadge status={s.status} />\n </div>\n <p className=\"mt-3 text-base font-semibold text-text-primary\">\n {t(meta.titleKey, meta.titleFallback, meta.titleParams)}\n </p>\n <p className=\"mt-0.5 text-xs text-text-tertiary\">\n {t(meta.stampLabelKey, meta.stampLabelFallback, {\n time: formatRelativeTime(meta.stamp),\n })}\n </p>\n\n <div className=\"mt-3 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-text-secondary\">\n {s.caloriesBurned > 0 && (\n <span className=\"inline-flex items-center gap-1 tabular-nums\">\n <Flame className=\"h-3.5 w-3.5 text-status-warning-text\" />\n {formatKcal(s.caloriesBurned)}\n </span>\n )}\n {rpe != null && (\n <span className=\"inline-flex items-center gap-1 tabular-nums\">\n <Gauge className=\"h-3.5 w-3.5 text-accent\" />\n {t('mybodyshield.pages.train.rpe', 'RPE {{rpe}}/10', { rpe })}\n </span>\n )}\n {setCount != null && setCount > 0 && (\n <span className=\"inline-flex items-center gap-1 tabular-nums\">\n <ListChecks className=\"h-3.5 w-3.5 text-accent\" />\n {formatNumber(setCount)} {t('mybodyshield.pages.train.sets', 'sets')}\n </span>\n )}\n </div>\n\n {rpe != null && (\n <div className=\"mt-3\">\n <div className=\"mb-1 flex items-center justify-between text-[11px] text-text-tertiary\">\n <span>{t('mybodyshield.pages.train.rpeIntensity', 'RPE intensity')}</span>\n <span className=\"tabular-nums\">\n {t('mybodyshield.pages.train.rpeOutOfTen', '{{rpe}}/10', { rpe })}\n </span>\n </div>\n <Meter value={rpe * 10} height={6} />\n </div>\n )}\n\n {isLive && (\n <p className=\"mt-3 inline-flex items-center gap-1 text-xs font-semibold text-action-primary-bg\">\n {t('mybodyshield.pages.train.resume', 'In progress — Resume →')}\n </p>\n )}\n </GlassCard>\n </motion.button>\n );\n })}\n </div>\n\n <Pagination\n page={pager.page}\n pageSize={PAGE_SIZE}\n itemCount={rows.length}\n hasMore={hasMore}\n onPageChange={(t) => pager.onPageChange(t)}\n />\n </>\n )}\n\n {/* Start session dialog */}\n <ResponsiveDialog open={startOpen} onOpenChange={setStartOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {t('mybodyshield.pages.train.startDialogTitle', 'Start a workout session')}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {t(\n 'mybodyshield.pages.train.startDialogDescription',\n 'Begin a live session now, or schedule one for later. Attach an active program to keep your training organised.'\n )}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4 px-1\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-start-program\">\n {t('mybodyshield.pages.train.programOptional', 'Program (optional)')}\n </Label>\n <Select\n value={form.programId}\n onValueChange={(v) =>\n // Reset the workout choice too — the previous selection almost\n // certainly doesn't belong to the newly-picked program.\n setForm((f) => ({ ...f, programId: v, workoutId: NO_WORKOUT }))\n }\n >\n <SelectTrigger id=\"myb-start-program\">\n <SelectValue\n placeholder={t('mybodyshield.pages.train.noProgram', 'No program')}\n />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={NO_PROGRAM}>\n {t('mybodyshield.pages.train.noProgram', 'No program')}\n </SelectItem>\n {activePrograms.map((p) => (\n <SelectItem key={p.id} value={p.id}>\n {p.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n {selectedProgramId && workoutOptions.length > 0 && (\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-start-workout\">\n {t('mybodyshield.pages.train.workoutOptional', 'Workout (optional)')}\n </Label>\n <Select\n value={form.workoutId}\n onValueChange={(v) => setForm((f) => ({ ...f, workoutId: v }))}\n >\n <SelectTrigger id=\"myb-start-workout\">\n <SelectValue\n placeholder={t(\n 'mybodyshield.pages.train.noSpecificWorkout',\n 'No specific workout'\n )}\n />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={NO_WORKOUT}>\n {t('mybodyshield.pages.train.noSpecificWorkout', 'No specific workout')}\n </SelectItem>\n {workoutOptions.map((w) => (\n <SelectItem key={w.id} value={w.id}>\n {w.name} — {t('mybodyshield.pages.programDetail.week', 'Week')}{' '}\n {w.weekNumber}, {t('mybodyshield.pages.programDetail.day', 'Day')}{' '}\n {w.dayOfWeek}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <p className=\"text-xs text-text-tertiary\">\n {t(\n 'mybodyshield.pages.train.attachWorkoutHint',\n 'Attach a planned workout to guide this session exercise-by-exercise.'\n )}\n </p>\n </div>\n )}\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-start-scheduled\">\n {t('mybodyshield.pages.train.scheduledForOptional', 'Scheduled for (optional)')}\n </Label>\n <Input\n id=\"myb-start-scheduled\"\n type=\"datetime-local\"\n value={form.scheduledFor}\n onChange={(e) => setForm((f) => ({ ...f, scheduledFor: e.target.value }))}\n />\n <p className=\"text-xs text-text-tertiary\">\n {t(\n 'mybodyshield.pages.train.scheduledForHint',\n 'Leave empty to start a live session right now.'\n )}\n </p>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"myb-start-notes\">\n {t('mybodyshield.pages.train.notesOptional', 'Notes (optional)')}\n </Label>\n <Textarea\n id=\"myb-start-notes\"\n rows={3}\n value={form.notes}\n onChange={(e) => setForm((f) => ({ ...f, notes: e.target.value }))}\n placeholder={t(\n 'mybodyshield.pages.train.notesPlaceholder',\n \"Focus, target lifts, how you're feeling…\"\n )}\n />\n </div>\n\n {formError && <p className=\"text-sm text-status-error-text\">{formError}</p>}\n </div>\n\n <ResponsiveDialogFooter>\n <Button\n variant=\"outline\"\n onClick={() => setStartOpen(false)}\n disabled={startSession.isPending}\n >\n {t('mybodyshield.common.cancel', 'Cancel')}\n </Button>\n <Button onClick={submitStart} disabled={startSession.isPending}>\n {startSession.isPending\n ? t('mybodyshield.common.starting', 'Starting') + '…'\n : t('mybodyshield.pages.train.startSession', 'Start session')}\n </Button>\n </ResponsiveDialogFooter>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { I18nContextValue } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';
|
|
2
2
|
|
|
3
|
-
type TranslationParams = Record<string, string | number>;
|
|
3
|
+
export type TranslationParams = Record<string, string | number>;
|
|
4
4
|
export declare function tWithFallback(t: I18nContextValue['t'], key: string, fallback: string, params?: TranslationParams): string;
|
|
5
|
-
export {};
|
|
6
5
|
//# sourceMappingURL=i18n.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../../src/mybodyshield/utils/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wDAAwD,CAAC;AAE/F,
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../../src/mybodyshield/utils/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wDAAwD,CAAC;AAE/F,MAAM,MAAM,iBAAiB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAahE,wBAAgB,aAAa,CAC3B,CAAC,EAAE,gBAAgB,CAAC,GAAG,CAAC,EACxB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,MAAM,CAAC,EAAE,iBAAiB,GACzB,MAAM,CAOR"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
//#region src/mybodyshield/utils/i18n.ts
|
|
2
|
+
function e(e, t) {
|
|
3
|
+
return t ? e.replace(/\{\{\s*(\w+)\s*\}\}/g, (e, n) => {
|
|
4
|
+
let r = t[n];
|
|
5
|
+
return r === void 0 ? `{{${n}}}` : String(r);
|
|
6
|
+
}) : e;
|
|
7
|
+
}
|
|
8
|
+
function t(t, n, r, i) {
|
|
9
|
+
let a = t(n, i);
|
|
10
|
+
return a === n ? e(r, i) : a;
|
|
11
|
+
}
|
|
12
|
+
//#endregion
|
|
13
|
+
export { t as tWithFallback };
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=i18n.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.js","names":[],"sources":["../../../src/mybodyshield/utils/i18n.ts"],"sourcesContent":["import type { I18nContextValue } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\nexport type TranslationParams = Record<string, string | number>;\n\nfunction interpolateFallback(fallback: string, params?: TranslationParams): string {\n if (!params) {\n return fallback;\n }\n\n return fallback.replace(/\\{\\{\\s*(\\w+)\\s*\\}\\}/g, (_, key: string) => {\n const value = params[key];\n return value === undefined ? `{{${key}}}` : String(value);\n });\n}\n\nexport function tWithFallback(\n t: I18nContextValue['t'],\n key: string,\n fallback: string,\n params?: TranslationParams\n): string {\n const translated = t(key, params);\n if (translated === key) {\n return interpolateFallback(fallback, params);\n }\n\n return translated;\n}\n"],"mappings":";AAIA,SAAS,EAAoB,GAAkB,GAAoC;AAKjF,QAJK,IAIE,EAAS,QAAQ,yBAAyB,GAAG,MAAgB;EAClE,IAAM,IAAQ,EAAO;AACrB,SAAO,MAAU,KAAA,IAAY,KAAK,EAAI,MAAM,OAAO,EAAM;GACzD,GANO;;AASX,SAAgB,EACd,GACA,GACA,GACA,GACQ;CACR,IAAM,IAAa,EAAE,GAAK,EAAO;AAKjC,QAJI,MAAe,IACV,EAAoB,GAAU,EAAO,GAGvC"}
|
package/package.json
CHANGED