@burdenoff/microfe-mybodyshield 2026.821.2 → 2026.827.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/pages/ai/AiControlsPage.d.ts.map +1 -1
- package/dist/mybodyshield/pages/ai/AiControlsPage.js +167 -108
- 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 +222 -151
- package/dist/mybodyshield/pages/coaching/CoachingPage.js.map +1 -1
- package/dist/mybodyshield/pages/insights/InsightsPage.d.ts.map +1 -1
- package/dist/mybodyshield/pages/insights/InsightsPage.js +135 -34
- package/dist/mybodyshield/pages/insights/InsightsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CoachingPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/coaching/CoachingPage.tsx"],"sourcesContent":["/**\n * Coaching — coach ↔ member relationships and shared data scope.\n * @module mybodyshield/pages/coaching\n *\n * Lists the member's `MybCoachingRelationship`s (a PLAIN ARRAY from\n * `mybListCoaching` — no pagination) with a status filter, per-relationship\n * sharing scope (workouts / nutrition / metrics), lifecycle transition actions\n * (accept / pause / resume / end) and a create flow. No mock data.\n */\n\nimport { useMemo, useState } from 'react';\nimport { motion, AnimatePresence, useReducedMotion } from 'framer-motion';\nimport {\n HeartHandshake,\n Plus,\n Dumbbell,\n Salad,\n LineChart,\n CalendarClock,\n CalendarX,\n Check,\n Minus,\n MessageSquare,\n ChevronDown,\n ChevronUp,\n User,\n} from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Switch,\n GlassCard,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n EmptyState,\n LoadingView,\n ErrorView,\n FilterTabs,\n MybDiscussion,\n UserSearchSelect,\n type FilterOption,\n type UserSelectValue,\n} from '../../components';\nimport { IconChip } from '../_shared/visuals';\nimport { useMyBodyShield } from '../../context';\nimport { useMybCoaching, useMybCreateCoaching, useMybTransitionCoaching } from '../../hooks';\nimport type {\n MybCoachingRelationship,\n MybCoachingStatus,\n MybCreateCoachingInput,\n} from '../../types';\nimport { formatDate } from '../../utils/formatters';\n\ntype StatusFilter = MybCoachingStatus | 'ALL';\n\nconst FILTER_ORDER: MybCoachingStatus[] = ['PENDING', 'ACTIVE', 'PAUSED', 'ENDED'];\n\n/** Shared entrance easing — a soft, decelerating cubic-bezier. */\nconst MOTION_EASE: [number, number, number, number] = [0.16, 1, 0.3, 1];\n\nconst STAGGER_CONTAINER = {\n hidden: {},\n show: { transition: { staggerChildren: 0.06 } },\n};\nconst RISE_ITEM = {\n hidden: { opacity: 0, y: 12 },\n show: { opacity: 1, y: 0, transition: { duration: 0.35, ease: MOTION_EASE } },\n};\n\n/** Allowed lifecycle transitions per current status (contract-defined). */\nconst TRANSITIONS: Record<\n MybCoachingStatus,\n { to: MybCoachingStatus; label: string; primary: boolean }[]\n> = {\n PENDING: [{ to: 'ACTIVE', label: 'Accept', primary: true }],\n ACTIVE: [\n { to: 'PAUSED', label: 'Pause', primary: false },\n { to: 'ENDED', label: 'End', primary: false },\n ],\n PAUSED: [\n { to: 'ACTIVE', label: 'Resume', primary: true },\n { to: 'ENDED', label: 'End', primary: false },\n ],\n ENDED: [],\n};\n\n/** Shorten an opaque actor id for compact display (full id kept as a title). */\nfunction shortId(id: string): string {\n return id.length <= 10 ? id : `${id.slice(0, 8)}…`;\n}\n\n// ============================================================================\n// Create-coaching dialog\n// ============================================================================\n\ninterface CreateCoachingForm {\n memberId: string;\n memberName: string;\n coachId: string;\n coachName: string;\n specialty: string;\n shareWorkouts: boolean;\n shareNutrition: boolean;\n shareMetrics: boolean;\n}\n\nfunction buildEmptyForm(defaultMember: UserSelectValue): CreateCoachingForm {\n return {\n memberId: defaultMember.userId,\n memberName: defaultMember.displayName,\n coachId: '',\n coachName: '',\n specialty: '',\n shareWorkouts: true,\n shareNutrition: true,\n shareMetrics: true,\n };\n}\n\nfunction ShareToggle({\n id,\n label,\n description,\n checked,\n onChange,\n}: {\n id: string;\n label: string;\n description: string;\n checked: boolean;\n onChange: (value: boolean) => void;\n}) {\n return (\n <div className=\"flex items-center justify-between gap-4 rounded-xl bg-bg-sunken/60 p-3\">\n <div className=\"min-w-0\">\n <Label htmlFor={id} className=\"cursor-pointer\">\n {label}\n </Label>\n <p className=\"text-xs text-text-tertiary\">{description}</p>\n </div>\n <Switch id={id} checked={checked} onCheckedChange={onChange} />\n </div>\n );\n}\n\nfunction CreateCoachingDialog({\n open,\n onOpenChange,\n defaultMember,\n}: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n defaultMember: UserSelectValue;\n}) {\n const create = useMybCreateCoaching();\n const [form, setForm] = useState<CreateCoachingForm>(() => buildEmptyForm(defaultMember));\n const [error, setError] = useState<string | null>(null);\n\n const handleOpenChange = (next: boolean) => {\n if (!next) {\n setForm(buildEmptyForm(defaultMember));\n setError(null);\n }\n onOpenChange(next);\n };\n\n const submit = async () => {\n const coachId = form.coachId.trim();\n const memberId = form.memberId.trim();\n if (!coachId) {\n setError('Coach user id is required.');\n return;\n }\n if (!memberId) {\n setError('Member user id is required.');\n return;\n }\n setError(null);\n const input: MybCreateCoachingInput = {\n coachId,\n memberId,\n specialty: form.specialty.trim() || undefined,\n shareWorkouts: form.shareWorkouts,\n shareNutrition: form.shareNutrition,\n shareMetrics: form.shareMetrics,\n };\n try {\n await create.mutateAsync(input);\n setForm(buildEmptyForm(defaultMember));\n onOpenChange(false);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create coaching relationship.');\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={handleOpenChange}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New coaching</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Link a coach and member, and choose exactly what training, nutrition and progress data\n is shared.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <form\n className=\"space-y-4\"\n onSubmit={(event) => {\n event.preventDefault();\n void submit();\n }}\n >\n {error && (\n <p className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {error}\n </p>\n )}\n <UserSearchSelect\n label=\"Coach\"\n required\n htmlId=\"myb-coaching-coach\"\n userId={form.coachId}\n displayName={form.coachName}\n onSelect={({ userId, displayName }) =>\n setForm((f) => ({ ...f, coachId: userId, coachName: displayName }))\n }\n />\n <UserSearchSelect\n label=\"Member\"\n htmlId=\"myb-coaching-member\"\n userId={form.memberId}\n displayName={form.memberName}\n onSelect={({ userId, displayName }) =>\n setForm((f) => ({ ...f, memberId: userId, memberName: displayName }))\n }\n />\n <div className=\"space-y-2\">\n <Label htmlFor=\"myb-coaching-specialty\">Specialty</Label>\n <Input\n id=\"myb-coaching-specialty\"\n value={form.specialty}\n onChange={(event) => setForm((f) => ({ ...f, specialty: event.target.value }))}\n placeholder=\"e.g., Strength & conditioning\"\n />\n </div>\n <div className=\"space-y-2\">\n <p className=\"text-sm font-medium text-text-primary\">Shared with the coach</p>\n <ShareToggle\n id=\"myb-coaching-share-workouts\"\n label=\"Workouts\"\n description=\"Programs, sessions and logged sets.\"\n checked={form.shareWorkouts}\n onChange={(value) => setForm((f) => ({ ...f, shareWorkouts: value }))}\n />\n <ShareToggle\n id=\"myb-coaching-share-nutrition\"\n label=\"Nutrition\"\n description=\"Plans, meals and daily macros.\"\n checked={form.shareNutrition}\n onChange={(value) => setForm((f) => ({ ...f, shareNutrition: value }))}\n />\n <ShareToggle\n id=\"myb-coaching-share-metrics\"\n label=\"Metrics\"\n description=\"Body metrics and recovery logs.\"\n checked={form.shareMetrics}\n onChange={(value) => setForm((f) => ({ ...f, shareMetrics: value }))}\n />\n </div>\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => handleOpenChange(false)}\n disabled={create.isPending}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={create.isPending}>\n {create.isPending ? 'Creating…' : 'Create coaching'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ============================================================================\n// Sharing scope chips\n// ============================================================================\n\nfunction ShareChip({ label, icon, on }: { label: string; icon: typeof Dumbbell; on: boolean }) {\n const Icon = icon;\n const StatusIcon = on ? Check : Minus;\n return (\n <span\n aria-label={`${label} ${on ? 'shared' : 'not shared'}`}\n className={`inline-flex items-center gap-1.5 rounded-full py-0.5 pl-1 pr-2.5 text-xs font-medium ${\n on\n ? 'bg-status-success-bg-subtle text-status-success-text ring-1 ring-inset ring-[color-mix(in_oklab,var(--color-status-success-text)_28%,transparent)]'\n : 'bg-bg-sunken text-text-tertiary ring-1 ring-inset ring-border-seam'\n }`}\n >\n <span\n aria-hidden=\"true\"\n className={`inline-flex size-4 items-center justify-center rounded-full ${\n on\n ? 'bg-[color-mix(in_oklab,var(--color-status-success-text)_22%,transparent)]'\n : 'bg-[color-mix(in_oklab,var(--color-text-tertiary)_18%,transparent)]'\n }`}\n >\n <StatusIcon className=\"h-2.5 w-2.5\" />\n </span>\n <Icon className=\"h-3 w-3\" aria-hidden=\"true\" />\n <span>\n {label} · {on ? 'On' : 'Off'}\n </span>\n </span>\n );\n}\n\n// ============================================================================\n// Coaching card\n// ============================================================================\n\nfunction CoachingCard({\n relationship,\n currentUserId,\n busy,\n onTransition,\n}: {\n relationship: MybCoachingRelationship;\n currentUserId?: string;\n busy: boolean;\n onTransition: (id: string, status: MybCoachingStatus) => void;\n}) {\n const coachIsYou = Boolean(currentUserId) && relationship.coachId === currentUserId;\n const memberIsYou = Boolean(currentUserId) && relationship.memberId === currentUserId;\n const actions = TRANSITIONS[relationship.status];\n const canDiscuss = relationship.status === 'ACTIVE' || relationship.status === 'PAUSED';\n const [showDiscussion, setShowDiscussion] = useState(false);\n const reduce = useReducedMotion();\n\n const isActive = relationship.status === 'ACTIVE';\n const isPending = relationship.status === 'PENDING';\n\n return (\n <GlassCard\n treatment=\"glass\"\n glow={isActive}\n className={\n 'flex h-full flex-col gap-4 p-5 transition-shadow hover:shadow-float' +\n (isPending\n ? ' ring-1 ring-inset ring-[color-mix(in_oklab,var(--color-status-warning-text)_35%,transparent)]'\n : '')\n }\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-start gap-3\">\n <IconChip icon={<HeartHandshake className=\"h-4 w-4\" />} />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {relationship.specialty?.trim() || 'Coaching relationship'}\n </p>\n <p className=\"text-xs text-text-tertiary\">Coach ↔ member</p>\n </div>\n </div>\n <StatusBadge status={relationship.status} />\n </div>\n\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"flex items-center gap-2.5 rounded-lg bg-bg-sunken/60 p-2.5\">\n <span\n aria-hidden=\"true\"\n className={`flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ring-inset ${\n coachIsYou\n ? 'bg-[color-mix(in_oklab,var(--color-action-primary-bg)_16%,transparent)] text-action-primary-bg ring-[color-mix(in_oklab,var(--color-action-primary-bg)_40%,transparent)]'\n : 'bg-bg-surface text-text-tertiary ring-border-seam'\n }`}\n >\n <User className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"text-[11px] font-medium uppercase tracking-wide text-text-tertiary\">\n Coach\n </p>\n <p\n className=\"mt-0.5 truncate text-sm font-medium text-text-primary\"\n title={relationship.coachId}\n >\n {coachIsYou ? 'You' : `#${shortId(relationship.coachId)}`}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2.5 rounded-lg bg-bg-sunken/60 p-2.5\">\n <span\n aria-hidden=\"true\"\n className={`flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ring-inset ${\n memberIsYou\n ? 'bg-[color-mix(in_oklab,var(--color-action-primary-bg)_16%,transparent)] text-action-primary-bg ring-[color-mix(in_oklab,var(--color-action-primary-bg)_40%,transparent)]'\n : 'bg-bg-surface text-text-tertiary ring-border-seam'\n }`}\n >\n <User className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"text-[11px] font-medium uppercase tracking-wide text-text-tertiary\">\n Member\n </p>\n <p\n className=\"mt-0.5 truncate text-sm font-medium text-text-primary\"\n title={relationship.memberId}\n >\n {memberIsYou ? 'You' : `#${shortId(relationship.memberId)}`}\n </p>\n </div>\n </div>\n </div>\n\n <div className=\"flex flex-wrap gap-1.5\">\n <ShareChip label=\"Workouts\" icon={Dumbbell} on={relationship.shareWorkouts} />\n <ShareChip label=\"Nutrition\" icon={Salad} on={relationship.shareNutrition} />\n <ShareChip label=\"Metrics\" icon={LineChart} on={relationship.shareMetrics} />\n </div>\n\n {(relationship.startedAt || relationship.endedAt) && (\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-text-tertiary\">\n {relationship.startedAt && (\n <span className=\"inline-flex items-center gap-1\">\n <CalendarClock className=\"h-3.5 w-3.5\" />\n Started {formatDate(relationship.startedAt)}\n </span>\n )}\n {relationship.endedAt && (\n <span className=\"inline-flex items-center gap-1\">\n <CalendarX className=\"h-3.5 w-3.5\" />\n Ended {formatDate(relationship.endedAt)}\n </span>\n )}\n </div>\n )}\n\n {actions.length > 0 && (\n <div className=\"mt-auto flex flex-wrap gap-2 pt-1\">\n {actions.map((action) => (\n <Button\n key={action.to}\n size=\"sm\"\n variant={action.primary ? undefined : 'outline'}\n disabled={busy}\n onClick={() => onTransition(relationship.id, action.to)}\n >\n {action.label}\n </Button>\n ))}\n </div>\n )}\n\n {canDiscuss && (\n <div\n className={`border-t border-border-seam pt-3 ${actions.length === 0 ? 'mt-auto' : ''}`}\n >\n <button\n type=\"button\"\n onClick={() => setShowDiscussion((prev) => !prev)}\n aria-expanded={showDiscussion}\n className=\"inline-flex items-center gap-1.5 text-sm font-medium text-accent hover:underline\"\n >\n <MessageSquare className=\"h-4 w-4\" />\n {showDiscussion ? 'Hide discussion' : 'Discussion'}\n {showDiscussion ? (\n <ChevronUp className=\"h-4 w-4\" />\n ) : (\n <ChevronDown className=\"h-4 w-4\" />\n )}\n </button>\n <AnimatePresence initial={false}>\n {showDiscussion && (\n <motion.div\n key=\"discussion\"\n initial={reduce ? false : { opacity: 0, height: 0 }}\n animate={{ opacity: 1, height: 'auto' }}\n exit={reduce ? { opacity: 0 } : { opacity: 0, height: 0 }}\n transition={reduce ? { duration: 0 } : { duration: 0.25, ease: MOTION_EASE }}\n className=\"overflow-hidden\"\n >\n <div className=\"mt-3\">\n <MybDiscussion\n entityType=\"mybodyshield-coaching\"\n entityId={relationship.id}\n title=\"Coaching discussion\"\n description=\"A private thread between the coach and member.\"\n />\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </GlassCard>\n );\n}\n\n// ============================================================================\n// Page\n// ============================================================================\n\nexport function CoachingPage() {\n const { currentUser } = useMyBodyShield();\n const reduce = useReducedMotion();\n const [statusFilter, setStatusFilter] = useState<StatusFilter>('ALL');\n const [createOpen, setCreateOpen] = useState(false);\n const [transitioningId, setTransitioningId] = useState<string | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n\n // `useMybCoaching` returns a paginated connection — fetch every relationship\n // once and filter client-side so the tab counts stay accurate across filters.\n const coaching = useMybCoaching();\n const transition = useMybTransitionCoaching();\n\n const all = coaching.data?.items ?? [];\n\n const counts = useMemo(() => {\n const map: Record<MybCoachingStatus, number> = {\n PENDING: 0,\n ACTIVE: 0,\n PAUSED: 0,\n ENDED: 0,\n };\n for (const rel of all) map[rel.status] += 1;\n return map;\n }, [all]);\n\n const statusFilters = useMemo<FilterOption<StatusFilter>[]>(\n () => [\n { value: 'ALL', label: 'All', count: all.length },\n ...FILTER_ORDER.map((status) => ({\n value: status as StatusFilter,\n label: status.charAt(0) + status.slice(1).toLowerCase(),\n count: counts[status],\n })),\n ],\n [all.length, counts]\n );\n\n const rows = useMemo(\n () => (statusFilter === 'ALL' ? all : all.filter((rel) => rel.status === statusFilter)),\n [all, statusFilter]\n );\n\n const handleTransition = async (id: string, status: MybCoachingStatus) => {\n setTransitioningId(id);\n setActionError(null);\n try {\n await transition.mutateAsync({ id, status });\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Failed to update the relationship.');\n } finally {\n setTransitioningId(null);\n }\n };\n\n const showLoading = coaching.isLoading && !coaching.data;\n\n return (\n <PageLayout\n title=\"Coaching\"\n description=\"Coach ↔ member relationships and what's shared.\"\n actions={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-2 h-4 w-4\" />\n New coaching\n </Button>\n }\n >\n <div className=\"mb-6\">\n <FilterTabs options={statusFilters} value={statusFilter} onChange={setStatusFilter} />\n </div>\n\n {actionError && (\n <p className=\"mb-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {actionError}\n </p>\n )}\n\n {showLoading ? (\n <LoadingView label=\"Loading coaching relationships…\" />\n ) : coaching.isError ? (\n <ErrorView\n message=\"We couldn't load your coaching relationships.\"\n onRetry={() => void coaching.refetch()}\n />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<HeartHandshake className=\"h-10 w-10\" />}\n title={\n all.length === 0 ? 'No coaching relationships' : 'No relationships match this filter'\n }\n description={\n all.length === 0\n ? 'Connect a coach and a member to start sharing training, nutrition and progress data.'\n : 'Try a different status filter, or create a new coaching relationship.'\n }\n action={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-2 h-4 w-4\" />\n New coaching\n </Button>\n }\n />\n ) : (\n <motion.div\n className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\"\n variants={reduce ? undefined : STAGGER_CONTAINER}\n initial={reduce ? false : 'hidden'}\n animate=\"show\"\n >\n {rows.map((relationship) => (\n <motion.div\n key={relationship.id}\n className=\"h-full\"\n variants={reduce ? undefined : RISE_ITEM}\n >\n <CoachingCard\n relationship={relationship}\n currentUserId={currentUser?.id}\n busy={transitioningId === relationship.id}\n onTransition={(id, status) => void handleTransition(id, status)}\n />\n </motion.div>\n ))}\n </motion.div>\n )}\n\n <CreateCoachingDialog\n open={createOpen}\n onOpenChange={setCreateOpen}\n defaultMember={{\n userId: currentUser?.id ?? '',\n displayName: currentUser?.name || currentUser?.email || '',\n }}\n />\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAgEA,IAAM,IAAoC;CAAC;CAAW;CAAU;CAAU;CAAQ,EAG5E,IAAgD;CAAC;CAAM;CAAG;CAAK;CAAE,EAEjE,IAAoB;CACxB,QAAQ,EAAE;CACV,MAAM,EAAE,YAAY,EAAE,iBAAiB,KAAM,EAAE;CAChD,EACK,IAAY;CAChB,QAAQ;EAAE,SAAS;EAAG,GAAG;EAAI;CAC7B,MAAM;EAAE,SAAS;EAAG,GAAG;EAAG,YAAY;GAAE,UAAU;GAAM,MAAM;GAAa;EAAE;CAC9E,EAGK,IAGF;CACF,SAAS,CAAC;EAAE,IAAI;EAAU,OAAO;EAAU,SAAS;EAAM,CAAC;CAC3D,QAAQ,CACN;EAAE,IAAI;EAAU,OAAO;EAAS,SAAS;EAAO,EAChD;EAAE,IAAI;EAAS,OAAO;EAAO,SAAS;EAAO,CAC9C;CACD,QAAQ,CACN;EAAE,IAAI;EAAU,OAAO;EAAU,SAAS;EAAM,EAChD;EAAE,IAAI;EAAS,OAAO;EAAO,SAAS;EAAO,CAC9C;CACD,OAAO,EAAE;CACV;AAGD,SAAS,EAAQ,GAAoB;AACnC,QAAO,EAAG,UAAU,KAAK,IAAK,GAAG,EAAG,MAAM,GAAG,EAAE,CAAC;;AAkBlD,SAAS,EAAe,GAAoD;AAC1E,QAAO;EACL,UAAU,EAAc;EACxB,YAAY,EAAc;EAC1B,SAAS;EACT,WAAW;EACX,WAAW;EACX,eAAe;EACf,gBAAgB;EAChB,cAAc;EACf;;AAGH,SAAS,EAAY,EACnB,OACA,UACA,gBACA,YACA,eAOC;AACD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IAAO,SAAS;IAAI,WAAU;cAC3B;IACK,CAAA,EACR,kBAAC,KAAD;IAAG,WAAU;cAA8B;IAAgB,CAAA,CACvD;MACN,kBAAC,GAAD;GAAY;GAAa;GAAS,iBAAiB;GAAY,CAAA,CAC3D;;;AAIV,SAAS,GAAqB,EAC5B,SACA,iBACA,oBAKC;CACD,IAAM,IAAS,GAAsB,EAC/B,CAAC,GAAM,KAAW,QAAmC,EAAe,EAAc,CAAC,EACnF,CAAC,GAAO,KAAY,EAAwB,KAAK,EAEjD,KAAoB,MAAkB;AAK1C,EAJK,MACH,EAAQ,EAAe,EAAc,CAAC,EACtC,EAAS,KAAK,GAEhB,EAAa,EAAK;IAGd,IAAS,YAAY;EACzB,IAAM,IAAU,EAAK,QAAQ,MAAM,EAC7B,IAAW,EAAK,SAAS,MAAM;AACrC,MAAI,CAAC,GAAS;AACZ,KAAS,6BAA6B;AACtC;;AAEF,MAAI,CAAC,GAAU;AACb,KAAS,8BAA8B;AACvC;;AAEF,IAAS,KAAK;EACd,IAAM,IAAgC;GACpC;GACA;GACA,WAAW,EAAK,UAAU,MAAM,IAAI,KAAA;GACpC,eAAe,EAAK;GACpB,gBAAgB,EAAK;GACrB,cAAc,EAAK;GACpB;AACD,MAAI;AAGF,GAFA,MAAM,EAAO,YAAY,EAAM,EAC/B,EAAQ,EAAe,EAAc,CAAC,EACtC,EAAa,GAAM;WACZ,GAAK;AACZ,KAAS,aAAe,QAAQ,EAAI,UAAU,0CAA0C;;;AAI5F,QACE,kBAAC,GAAD;EAAwB;EAAM,cAAc;YAC1C,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UAAuB,gBAAoC,CAAA,EAC3D,kBAAC,GAAD,EAAA,UAA6B,qGAGC,CAAA,CACP,EAAA,CAAA,EACzB,kBAAC,QAAD;GACE,WAAU;GACV,WAAW,MAAU;AAEd,IADL,EAAM,gBAAgB,EACjB,GAAQ;;aAJjB;IAOG,KACC,kBAAC,KAAD;KAAG,WAAU;eACV;KACC,CAAA;IAEN,kBAAC,GAAD;KACE,OAAM;KACN,UAAA;KACA,QAAO;KACP,QAAQ,EAAK;KACb,aAAa,EAAK;KAClB,WAAW,EAAE,WAAQ,qBACnB,GAAS,OAAO;MAAE,GAAG;MAAG,SAAS;MAAQ,WAAW;MAAa,EAAE;KAErE,CAAA;IACF,kBAAC,GAAD;KACE,OAAM;KACN,QAAO;KACP,QAAQ,EAAK;KACb,aAAa,EAAK;KAClB,WAAW,EAAE,WAAQ,qBACnB,GAAS,OAAO;MAAE,GAAG;MAAG,UAAU;MAAQ,YAAY;MAAa,EAAE;KAEvE,CAAA;IACF,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAO,SAAQ;gBAAyB;MAAiB,CAAA,EACzD,kBAAC,GAAD;MACE,IAAG;MACH,OAAO,EAAK;MACZ,WAAW,MAAU,GAAS,OAAO;OAAE,GAAG;OAAG,WAAW,EAAM,OAAO;OAAO,EAAE;MAC9E,aAAY;MACZ,CAAA,CACE;;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,KAAD;OAAG,WAAU;iBAAwC;OAAyB,CAAA;MAC9E,kBAAC,GAAD;OACE,IAAG;OACH,OAAM;OACN,aAAY;OACZ,SAAS,EAAK;OACd,WAAW,MAAU,GAAS,OAAO;QAAE,GAAG;QAAG,eAAe;QAAO,EAAE;OACrE,CAAA;MACF,kBAAC,GAAD;OACE,IAAG;OACH,OAAM;OACN,aAAY;OACZ,SAAS,EAAK;OACd,WAAW,MAAU,GAAS,OAAO;QAAE,GAAG;QAAG,gBAAgB;QAAO,EAAE;OACtE,CAAA;MACF,kBAAC,GAAD;OACE,IAAG;OACH,OAAM;OACN,aAAY;OACZ,SAAS,EAAK;OACd,WAAW,MAAU,GAAS,OAAO;QAAE,GAAG;QAAG,cAAc;QAAO,EAAE;OACpE,CAAA;MACE;;IACN,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;KACE,MAAK;KACL,SAAQ;KACR,eAAe,EAAiB,GAAM;KACtC,UAAU,EAAO;eAClB;KAEQ,CAAA,EACT,kBAAC,GAAD;KAAQ,MAAK;KAAS,UAAU,EAAO;eACpC,EAAO,YAAY,cAAc;KAC3B,CAAA,CACc,EAAA,CAAA;IACpB;KACiB,EAAA,CAAA;EACT,CAAA;;AAQvB,SAAS,EAAU,EAAE,UAAO,SAAM,SAA6D;CAC7F,IAAM,IAAO,GACP,IAAa,IAAK,IAAQ;AAChC,QACE,kBAAC,QAAD;EACE,cAAY,GAAG,EAAM,GAAG,IAAK,WAAW;EACxC,WAAW,wFACT,IACI,uJACA;YALR;GAQE,kBAAC,QAAD;IACE,eAAY;IACZ,WAAW,+DACT,IACI,8EACA;cAGN,kBAAC,GAAD,EAAY,WAAU,eAAgB,CAAA;IACjC,CAAA;GACP,kBAAC,GAAD;IAAM,WAAU;IAAU,eAAY;IAAS,CAAA;GAC/C,kBAAC,QAAD,EAAA,UAAA;IACG;IAAM;IAAI,IAAK,OAAO;IAClB,EAAA,CAAA;GACF;;;AAQX,SAAS,GAAa,EACpB,iBACA,kBACA,SACA,mBAMC;CACD,IAAM,IAAa,EAAQ,KAAkB,EAAa,YAAY,GAChE,IAAc,EAAQ,KAAkB,EAAa,aAAa,GAClE,IAAU,EAAY,EAAa,SACnC,IAAa,EAAa,WAAW,YAAY,EAAa,WAAW,UACzE,CAAC,GAAgB,KAAqB,EAAS,GAAM,EACrD,IAAS,GAAkB;AAKjC,QACE,kBAAC,GAAD;EACE,WAAU;EACV,MANa,EAAa,WAAW;EAOrC,WACE,yEAPY,EAAa,WAAW,YAShC,mGACA;YAPR;GAUE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAU,MAAM,kBAAC,GAAD,EAAgB,WAAU,WAAY,CAAA,EAAI,CAAA,EAC1D,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAa,WAAW,MAAM,IAAI;OACjC,CAAA,EACJ,kBAAC,KAAD;OAAG,WAAU;iBAA6B;OAAkB,CAAA,CACxD;QACF;QACN,kBAAC,GAAD,EAAa,QAAQ,EAAa,QAAU,CAAA,CACxC;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MACE,eAAY;MACZ,WAAW,mFACT,IACI,6KACA;gBAGN,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;MACvB,CAAA,EACP,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAqE;OAE9E,CAAA,EACJ,kBAAC,KAAD;OACE,WAAU;OACV,OAAO,EAAa;iBAEnB,IAAa,QAAQ,IAAI,EAAQ,EAAa,QAAQ;OACrD,CAAA,CACA;QACF;QACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,QAAD;MACE,eAAY;MACZ,WAAW,mFACT,IACI,6KACA;gBAGN,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;MACvB,CAAA,EACP,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,KAAD;OAAG,WAAU;iBAAqE;OAE9E,CAAA,EACJ,kBAAC,KAAD;OACE,WAAU;OACV,OAAO,EAAa;iBAEnB,IAAc,QAAQ,IAAI,EAAQ,EAAa,SAAS;OACvD,CAAA,CACA;QACF;OACF;;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,GAAD;MAAW,OAAM;MAAW,MAAM;MAAU,IAAI,EAAa;MAAiB,CAAA;KAC9E,kBAAC,GAAD;MAAW,OAAM;MAAY,MAAM;MAAO,IAAI,EAAa;MAAkB,CAAA;KAC7E,kBAAC,GAAD;MAAW,OAAM;MAAU,MAAM;MAAW,IAAI,EAAa;MAAgB,CAAA;KACzE;;IAEJ,EAAa,aAAa,EAAa,YACvC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,EAAa,aACZ,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACE,kBAAC,GAAD,EAAe,WAAU,eAAgB,CAAA;;MAChC,EAAW,EAAa,UAAU;MACtC;QAER,EAAa,WACZ,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACE,kBAAC,GAAD,EAAW,WAAU,eAAgB,CAAA;;MAC9B,EAAW,EAAa,QAAQ;MAClC;OAEL;;GAGP,EAAQ,SAAS,KAChB,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAQ,KAAK,MACZ,kBAAC,GAAD;KAEE,MAAK;KACL,SAAS,EAAO,UAAU,KAAA,IAAY;KACtC,UAAU;KACV,eAAe,EAAa,EAAa,IAAI,EAAO,GAAG;eAEtD,EAAO;KACD,EAPF,EAAO,GAOL,CACT;IACE,CAAA;GAGP,KACC,kBAAC,OAAD;IACE,WAAW,oCAAoC,EAAQ,WAAW,IAAI,YAAY;cADpF,CAGE,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,GAAmB,MAAS,CAAC,EAAK;KACjD,iBAAe;KACf,WAAU;eAJZ;MAME,kBAAC,GAAD,EAAe,WAAU,WAAY,CAAA;MACpC,IAAiB,oBAAoB;MAEpC,EADD,IACE,IAEA,GAFD,EAAW,WAAU,WAAY,CAEE;MAE9B;QACT,kBAAC,GAAD;KAAiB,SAAS;eACvB,KACC,kBAAC,EAAO,KAAR;MAEE,SAAS,IAAS,KAAQ;OAAE,SAAS;OAAG,QAAQ;OAAG;MACnD,SAAS;OAAE,SAAS;OAAG,QAAQ;OAAQ;MACvC,MAAM,IAAS,EAAE,SAAS,GAAG,GAAG;OAAE,SAAS;OAAG,QAAQ;OAAG;MACzD,YAAY,IAAS,EAAE,UAAU,GAAG,GAAG;OAAE,UAAU;OAAM,MAAM;OAAa;MAC5E,WAAU;gBAEV,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD;QACE,YAAW;QACX,UAAU,EAAa;QACvB,OAAM;QACN,aAAY;QACZ,CAAA;OACE,CAAA;MACK,EAfP,aAeO;KAEC,CAAA,CACd;;GAEE;;;AAQhB,SAAgB,KAAe;CAC7B,IAAM,EAAE,mBAAgB,GAAiB,EACnC,IAAS,GAAkB,EAC3B,CAAC,GAAc,KAAmB,EAAuB,MAAM,EAC/D,CAAC,GAAY,KAAiB,EAAS,GAAM,EAC7C,CAAC,GAAiB,KAAsB,EAAwB,KAAK,EACrE,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAI7D,IAAW,GAAgB,EAC3B,IAAa,GAA0B,EAEvC,IAAM,EAAS,MAAM,SAAS,EAAE,EAEhC,IAAS,QAAc;EAC3B,IAAM,IAAyC;GAC7C,SAAS;GACT,QAAQ;GACR,QAAQ;GACR,OAAO;GACR;AACD,OAAK,IAAM,KAAO,EAAK,GAAI,EAAI,WAAW;AAC1C,SAAO;IACN,CAAC,EAAI,CAAC,EAEH,IAAgB,QACd,CACJ;EAAE,OAAO;EAAO,OAAO;EAAO,OAAO,EAAI;EAAQ,EACjD,GAAG,EAAa,KAAK,OAAY;EAC/B,OAAO;EACP,OAAO,EAAO,OAAO,EAAE,GAAG,EAAO,MAAM,EAAE,CAAC,aAAa;EACvD,OAAO,EAAO;EACf,EAAE,CACJ,EACD,CAAC,EAAI,QAAQ,EAAO,CACrB,EAEK,IAAO,QACJ,MAAiB,QAAQ,IAAM,EAAI,QAAQ,MAAQ,EAAI,WAAW,EAAa,EACtF,CAAC,GAAK,EAAa,CACpB,EAEK,IAAmB,OAAO,GAAY,MAA8B;AAExE,EADA,EAAmB,EAAG,EACtB,EAAe,KAAK;AACpB,MAAI;AACF,SAAM,EAAW,YAAY;IAAE;IAAI;IAAQ,CAAC;WACrC,GAAK;AACZ,KAAe,aAAe,QAAQ,EAAI,UAAU,qCAAqC;YACjF;AACR,KAAmB,KAAK;;IAItB,IAAc,EAAS,aAAa,CAAC,EAAS;AAEpD,QACE,kBAAC,GAAD;EACE,OAAM;EACN,aAAY;EACZ,SACE,kBAAC,GAAD;GAAQ,eAAe,EAAc,GAAK;aAA1C,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,eAE1B;;YAPb;GAUE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KAAY,SAAS;KAAe,OAAO;KAAc,UAAU;KAAmB,CAAA;IAClF,CAAA;GAEL,KACC,kBAAC,KAAD;IAAG,WAAU;cACV;IACC,CAAA;GAGL,IACC,kBAAC,GAAD,EAAa,OAAM,mCAAoC,CAAA,GACrD,EAAS,UACX,kBAAC,GAAD;IACE,SAAQ;IACR,eAAe,KAAK,EAAS,SAAS;IACtC,CAAA,GACA,EAAK,WAAW,IAClB,kBAAC,GAAD;IACE,cAAa;IACb,MAAM,kBAAC,GAAD,EAAgB,WAAU,aAAc,CAAA;IAC9C,OACE,EAAI,WAAW,IAAI,8BAA8B;IAEnD,aACE,EAAI,WAAW,IACX,yFACA;IAEN,QACE,kBAAC,GAAD;KAAQ,eAAe,EAAc,GAAK;eAA1C,CACE,kBAAC,GAAD,EAAM,WAAU,gBAAiB,CAAA,EAAA,eAE1B;;IAEX,CAAA,GAEF,kBAAC,EAAO,KAAR;IACE,WAAU;IACV,UAAU,IAAS,KAAA,IAAY;IAC/B,SAAS,IAAS,KAAQ;IAC1B,SAAQ;cAEP,EAAK,KAAK,MACT,kBAAC,EAAO,KAAR;KAEE,WAAU;KACV,UAAU,IAAS,KAAA,IAAY;eAE/B,kBAAC,IAAD;MACgB;MACd,eAAe,GAAa;MAC5B,MAAM,MAAoB,EAAa;MACvC,eAAe,GAAI,MAAW,KAAK,EAAiB,GAAI,EAAO;MAC/D,CAAA;KACS,EAVN,EAAa,GAUP,CACb;IACS,CAAA;GAGf,kBAAC,IAAD;IACE,MAAM;IACN,cAAc;IACd,eAAe;KACb,QAAQ,GAAa,MAAM;KAC3B,aAAa,GAAa,QAAQ,GAAa,SAAS;KACzD;IACD,CAAA;GACS"}
|
|
1
|
+
{"version":3,"file":"CoachingPage.js","names":[],"sources":["../../../../src/mybodyshield/pages/coaching/CoachingPage.tsx"],"sourcesContent":["/**\n * Coaching — coach ↔ member relationships and shared data scope.\n * @module mybodyshield/pages/coaching\n *\n * Lists the member's `MybCoachingRelationship`s (a PLAIN ARRAY from\n * `mybListCoaching` — no pagination) with a status filter, per-relationship\n * sharing scope (workouts / nutrition / metrics), lifecycle transition actions\n * (accept / pause / resume / end) and a create flow. No mock data.\n */\n\nimport { useMemo, useState } from 'react';\nimport { motion, AnimatePresence, useReducedMotion } from 'framer-motion';\nimport {\n HeartHandshake,\n Plus,\n Dumbbell,\n Salad,\n LineChart,\n CalendarClock,\n CalendarX,\n Check,\n Minus,\n MessageSquare,\n ChevronDown,\n ChevronUp,\n User,\n ShieldCheck,\n ScanEye,\n FileLock2,\n EyeOff,\n Lock,\n} from 'lucide-react';\nimport {\n Button,\n Input,\n Label,\n Switch,\n GlassCard,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogDescription,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport {\n PageLayout,\n StatusBadge,\n EmptyState,\n LoadingView,\n ErrorView,\n Section,\n FilterTabs,\n MybDiscussion,\n UserSearchSelect,\n type FilterOption,\n type UserSelectValue,\n} from '../../components';\nimport { IconChip } from '../_shared/visuals';\nimport { useMyBodyShield } from '../../context';\nimport { useMybCoaching, useMybCreateCoaching, useMybTransitionCoaching } from '../../hooks';\nimport type {\n MybCoachingRelationship,\n MybCoachingStatus,\n MybCreateCoachingInput,\n} from '../../types';\nimport { formatDate } from '../../utils/formatters';\n\ntype StatusFilter = MybCoachingStatus | 'ALL';\n\nconst FILTER_ORDER: MybCoachingStatus[] = ['PENDING', 'ACTIVE', 'PAUSED', 'ENDED'];\n\n/** Shared entrance easing — a soft, decelerating cubic-bezier. */\nconst MOTION_EASE: [number, number, number, number] = [0.16, 1, 0.3, 1];\n\nconst STAGGER_CONTAINER = {\n hidden: {},\n show: { transition: { staggerChildren: 0.06 } },\n};\nconst RISE_ITEM = {\n hidden: { opacity: 0, y: 12 },\n show: { opacity: 1, y: 0, transition: { duration: 0.35, ease: MOTION_EASE } },\n};\n\n/**\n * Allowed lifecycle transitions per current status. Mirrors the service's\n * `COACHING_TRANSITIONS` map exactly — PENDING may be accepted (-> ACTIVE) or\n * declined (-> ENDED), ACTIVE may be paused or ended, PAUSED may be resumed or\n * ended, and ENDED is terminal.\n */\nconst TRANSITIONS: Record<\n MybCoachingStatus,\n { to: MybCoachingStatus; label: string; primary: boolean }[]\n> = {\n PENDING: [\n { to: 'ACTIVE', label: 'Accept', primary: true },\n // Neutral label: PENDING -> ENDED is open to BOTH parties, so this is a\n // decline for the recipient and a withdrawal for the sender, and the\n // payload carries no `createdBy` to tell the two apart.\n { to: 'ENDED', label: 'End request', primary: false },\n ],\n ACTIVE: [\n { to: 'PAUSED', label: 'Pause', primary: false },\n { to: 'ENDED', label: 'End', primary: false },\n ],\n PAUSED: [\n { to: 'ACTIVE', label: 'Resume', primary: true },\n { to: 'ENDED', label: 'End', primary: false },\n ],\n ENDED: [],\n};\n\n/**\n * What the sharing chips actually mean in each lifecycle state.\n *\n * The service gates every member-scoped read through `canAccessMember`, whose\n * lookup requires `status: 'ACTIVE'` **and** the matching `share*` flag. So a\n * PENDING, PAUSED or ENDED relationship grants nothing regardless of the chips\n * above, and accepting/pausing/ending changes access on the very next read.\n * A PENDING request can only be accepted by the counterparty — the service\n * refuses `PENDING -> ACTIVE` when the caller is the one who created it.\n */\nconst ACCESS_NOTE: Record<MybCoachingStatus, string> = {\n PENDING:\n 'Nothing is shared yet. The other party has to accept this request — the person who sent it cannot accept it themselves.',\n ACTIVE:\n 'The coach can read the categories marked On. Pausing or ending this revokes that access on the next request.',\n PAUSED: 'Sharing is suspended while paused. Resuming restores exactly the scope above.',\n ENDED: 'Access was revoked when this relationship ended.',\n};\n\n/**\n * How the sharing scope is enforced, stated only where the behaviour is\n * implemented in `wspace-mybodyshield-svc` (see `src/utils/memberAccess.ts`,\n * `src/utils/field-encryption.ts` and `src/plugins/mutation-audit-plugin.ts`).\n */\nconst ENFORCEMENT_FACTS: { icon: typeof ShieldCheck; title: string; detail: string }[] = [\n {\n icon: ScanEye,\n title: 'Checked on every read',\n detail:\n 'Each request re-checks the relationship and the matching switch. Access is not granted once and cached for the session.',\n },\n {\n icon: EyeOff,\n title: 'Refusals look like \"not found\"',\n detail:\n 'Asking for data you have not shared returns not-found, never \"forbidden\" — so nobody can use the error to confirm a record exists.',\n },\n {\n icon: Lock,\n title: 'Health values are encrypted at rest',\n detail:\n 'Body metrics, recovery scores, sleep hours and free-text notes are stored encrypted and decrypted only when returned to someone allowed to read them.',\n },\n {\n icon: FileLock2,\n title: 'Health fields are redacted from the audit log',\n detail:\n 'Every mutation is audited, but weights, scores, macros and notes are replaced with a redaction marker before the entry leaves this service.',\n },\n {\n icon: ShieldCheck,\n title: 'Scope is fixed for the life of the relationship',\n detail:\n 'The three switches are chosen when the relationship is created. To change them, end this one and create a new relationship with the scope you want.',\n },\n];\n\n/** Shorten an opaque actor id for compact display (full id kept as a title). */\nfunction shortId(id: string): string {\n return id.length <= 10 ? id : `${id.slice(0, 8)}…`;\n}\n\n// ============================================================================\n// Create-coaching dialog\n// ============================================================================\n\ninterface CreateCoachingForm {\n memberId: string;\n memberName: string;\n coachId: string;\n coachName: string;\n specialty: string;\n shareWorkouts: boolean;\n shareNutrition: boolean;\n shareMetrics: boolean;\n}\n\nfunction buildEmptyForm(defaultMember: UserSelectValue): CreateCoachingForm {\n return {\n memberId: defaultMember.userId,\n memberName: defaultMember.displayName,\n coachId: '',\n coachName: '',\n specialty: '',\n shareWorkouts: true,\n shareNutrition: true,\n shareMetrics: true,\n };\n}\n\nfunction ShareToggle({\n id,\n label,\n description,\n checked,\n onChange,\n}: {\n id: string;\n label: string;\n description: string;\n checked: boolean;\n onChange: (value: boolean) => void;\n}) {\n return (\n <div className=\"flex items-center justify-between gap-4 rounded-xl bg-bg-sunken/60 p-3\">\n <div className=\"min-w-0\">\n <Label htmlFor={id} className=\"cursor-pointer\">\n {label}\n </Label>\n <p className=\"text-xs text-text-tertiary\">{description}</p>\n </div>\n <Switch id={id} checked={checked} onCheckedChange={onChange} />\n </div>\n );\n}\n\nfunction CreateCoachingDialog({\n open,\n onOpenChange,\n defaultMember,\n}: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n defaultMember: UserSelectValue;\n}) {\n const create = useMybCreateCoaching();\n const [form, setForm] = useState<CreateCoachingForm>(() => buildEmptyForm(defaultMember));\n const [error, setError] = useState<string | null>(null);\n\n const handleOpenChange = (next: boolean) => {\n if (!next) {\n setForm(buildEmptyForm(defaultMember));\n setError(null);\n }\n onOpenChange(next);\n };\n\n const submit = async () => {\n const coachId = form.coachId.trim();\n const memberId = form.memberId.trim();\n if (!coachId) {\n setError('Coach user id is required.');\n return;\n }\n if (!memberId) {\n setError('Member user id is required.');\n return;\n }\n setError(null);\n const input: MybCreateCoachingInput = {\n coachId,\n memberId,\n specialty: form.specialty.trim() || undefined,\n shareWorkouts: form.shareWorkouts,\n shareNutrition: form.shareNutrition,\n shareMetrics: form.shareMetrics,\n };\n try {\n await create.mutateAsync(input);\n setForm(buildEmptyForm(defaultMember));\n onOpenChange(false);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to create coaching relationship.');\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={handleOpenChange}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>New coaching</ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n Link a coach and member, and choose exactly what training, nutrition and progress data\n is shared.\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n <form\n className=\"space-y-4\"\n onSubmit={(event) => {\n event.preventDefault();\n void submit();\n }}\n >\n {error && (\n <p className=\"rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {error}\n </p>\n )}\n <UserSearchSelect\n label=\"Coach\"\n required\n htmlId=\"myb-coaching-coach\"\n userId={form.coachId}\n displayName={form.coachName}\n onSelect={({ userId, displayName }) =>\n setForm((f) => ({ ...f, coachId: userId, coachName: displayName }))\n }\n />\n <UserSearchSelect\n label=\"Member\"\n htmlId=\"myb-coaching-member\"\n userId={form.memberId}\n displayName={form.memberName}\n onSelect={({ userId, displayName }) =>\n setForm((f) => ({ ...f, memberId: userId, memberName: displayName }))\n }\n />\n <div className=\"space-y-2\">\n <Label htmlFor=\"myb-coaching-specialty\">Specialty</Label>\n <Input\n id=\"myb-coaching-specialty\"\n value={form.specialty}\n onChange={(event) => setForm((f) => ({ ...f, specialty: event.target.value }))}\n placeholder=\"e.g., Strength & conditioning\"\n />\n </div>\n <div className=\"space-y-2\">\n <p className=\"text-sm font-medium text-text-primary\">Shared with the coach</p>\n <ShareToggle\n id=\"myb-coaching-share-workouts\"\n label=\"Workouts\"\n description=\"Programs, sessions and logged sets.\"\n checked={form.shareWorkouts}\n onChange={(value) => setForm((f) => ({ ...f, shareWorkouts: value }))}\n />\n <ShareToggle\n id=\"myb-coaching-share-nutrition\"\n label=\"Nutrition\"\n description=\"Plans, meals and daily macros.\"\n checked={form.shareNutrition}\n onChange={(value) => setForm((f) => ({ ...f, shareNutrition: value }))}\n />\n <ShareToggle\n id=\"myb-coaching-share-metrics\"\n label=\"Metrics\"\n description=\"Body metrics and recovery logs.\"\n checked={form.shareMetrics}\n onChange={(value) => setForm((f) => ({ ...f, shareMetrics: value }))}\n />\n </div>\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => handleOpenChange(false)}\n disabled={create.isPending}\n >\n Cancel\n </Button>\n <Button type=\"submit\" disabled={create.isPending}>\n {create.isPending ? 'Creating…' : 'Create coaching'}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\n// ============================================================================\n// Sharing scope chips\n// ============================================================================\n\nfunction ShareChip({ label, icon, on }: { label: string; icon: typeof Dumbbell; on: boolean }) {\n const Icon = icon;\n const StatusIcon = on ? Check : Minus;\n return (\n <span\n aria-label={`${label} ${on ? 'shared' : 'not shared'}`}\n className={`inline-flex items-center gap-1.5 rounded-full py-0.5 pl-1 pr-2.5 text-xs font-medium ${\n on\n ? 'bg-status-success-bg-subtle text-status-success-text ring-1 ring-inset ring-[color-mix(in_oklab,var(--color-status-success-text)_28%,transparent)]'\n : 'bg-bg-sunken text-text-tertiary ring-1 ring-inset ring-border-seam'\n }`}\n >\n <span\n aria-hidden=\"true\"\n className={`inline-flex size-4 items-center justify-center rounded-full ${\n on\n ? 'bg-[color-mix(in_oklab,var(--color-status-success-text)_22%,transparent)]'\n : 'bg-[color-mix(in_oklab,var(--color-text-tertiary)_18%,transparent)]'\n }`}\n >\n <StatusIcon className=\"h-2.5 w-2.5\" />\n </span>\n <Icon className=\"h-3 w-3\" aria-hidden=\"true\" />\n <span>\n {label} · {on ? 'On' : 'Off'}\n </span>\n </span>\n );\n}\n\n// ============================================================================\n// Coaching card\n// ============================================================================\n\nfunction CoachingCard({\n relationship,\n currentUserId,\n busy,\n onTransition,\n}: {\n relationship: MybCoachingRelationship;\n currentUserId?: string;\n busy: boolean;\n onTransition: (id: string, status: MybCoachingStatus) => void;\n}) {\n const coachIsYou = Boolean(currentUserId) && relationship.coachId === currentUserId;\n const memberIsYou = Boolean(currentUserId) && relationship.memberId === currentUserId;\n const actions = TRANSITIONS[relationship.status];\n const canDiscuss = relationship.status === 'ACTIVE' || relationship.status === 'PAUSED';\n const [showDiscussion, setShowDiscussion] = useState(false);\n const reduce = useReducedMotion();\n\n const isActive = relationship.status === 'ACTIVE';\n const isPending = relationship.status === 'PENDING';\n\n return (\n <GlassCard\n treatment=\"glass\"\n glow={isActive}\n className={\n 'flex h-full flex-col gap-4 p-5 transition-shadow hover:shadow-float' +\n (isPending\n ? ' ring-1 ring-inset ring-[color-mix(in_oklab,var(--color-status-warning-text)_35%,transparent)]'\n : '')\n }\n >\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"flex min-w-0 items-start gap-3\">\n <IconChip icon={<HeartHandshake className=\"h-4 w-4\" />} />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">\n {relationship.specialty?.trim() || 'Coaching relationship'}\n </p>\n <p className=\"text-xs text-text-tertiary\">Coach ↔ member</p>\n </div>\n </div>\n <StatusBadge status={relationship.status} />\n </div>\n\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"flex items-center gap-2.5 rounded-lg bg-bg-sunken/60 p-2.5\">\n <span\n aria-hidden=\"true\"\n className={`flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ring-inset ${\n coachIsYou\n ? 'bg-[color-mix(in_oklab,var(--color-action-primary-bg)_16%,transparent)] text-action-primary-bg ring-[color-mix(in_oklab,var(--color-action-primary-bg)_40%,transparent)]'\n : 'bg-bg-surface text-text-tertiary ring-border-seam'\n }`}\n >\n <User className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"text-[11px] font-medium uppercase tracking-wide text-text-tertiary\">\n Coach\n </p>\n <p\n className=\"mt-0.5 truncate text-sm font-medium text-text-primary\"\n title={relationship.coachId}\n >\n {coachIsYou ? 'You' : `#${shortId(relationship.coachId)}`}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2.5 rounded-lg bg-bg-sunken/60 p-2.5\">\n <span\n aria-hidden=\"true\"\n className={`flex size-8 shrink-0 items-center justify-center rounded-full ring-1 ring-inset ${\n memberIsYou\n ? 'bg-[color-mix(in_oklab,var(--color-action-primary-bg)_16%,transparent)] text-action-primary-bg ring-[color-mix(in_oklab,var(--color-action-primary-bg)_40%,transparent)]'\n : 'bg-bg-surface text-text-tertiary ring-border-seam'\n }`}\n >\n <User className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0\">\n <p className=\"text-[11px] font-medium uppercase tracking-wide text-text-tertiary\">\n Member\n </p>\n <p\n className=\"mt-0.5 truncate text-sm font-medium text-text-primary\"\n title={relationship.memberId}\n >\n {memberIsYou ? 'You' : `#${shortId(relationship.memberId)}`}\n </p>\n </div>\n </div>\n </div>\n\n <div className=\"space-y-2\">\n <div className=\"flex flex-wrap gap-1.5\">\n <ShareChip label=\"Workouts\" icon={Dumbbell} on={relationship.shareWorkouts} />\n <ShareChip label=\"Nutrition\" icon={Salad} on={relationship.shareNutrition} />\n <ShareChip label=\"Metrics\" icon={LineChart} on={relationship.shareMetrics} />\n </div>\n <p className=\"text-xs leading-relaxed text-text-tertiary\">\n {ACCESS_NOTE[relationship.status]}\n </p>\n </div>\n\n {(relationship.startedAt || relationship.endedAt) && (\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-text-tertiary\">\n {relationship.startedAt && (\n <span className=\"inline-flex items-center gap-1\">\n <CalendarClock className=\"h-3.5 w-3.5\" />\n Started {formatDate(relationship.startedAt)}\n </span>\n )}\n {relationship.endedAt && (\n <span className=\"inline-flex items-center gap-1\">\n <CalendarX className=\"h-3.5 w-3.5\" />\n Ended {formatDate(relationship.endedAt)}\n </span>\n )}\n </div>\n )}\n\n {actions.length > 0 && (\n <div className=\"mt-auto flex flex-wrap gap-2 pt-1\">\n {actions.map((action) => (\n <Button\n key={action.to}\n size=\"sm\"\n variant={action.primary ? undefined : 'outline'}\n disabled={busy}\n onClick={() => onTransition(relationship.id, action.to)}\n >\n {action.label}\n </Button>\n ))}\n </div>\n )}\n\n {canDiscuss && (\n <div\n className={`border-t border-border-seam pt-3 ${actions.length === 0 ? 'mt-auto' : ''}`}\n >\n <button\n type=\"button\"\n onClick={() => setShowDiscussion((prev) => !prev)}\n aria-expanded={showDiscussion}\n className=\"inline-flex items-center gap-1.5 text-sm font-medium text-accent hover:underline\"\n >\n <MessageSquare className=\"h-4 w-4\" />\n {showDiscussion ? 'Hide discussion' : 'Discussion'}\n {showDiscussion ? (\n <ChevronUp className=\"h-4 w-4\" />\n ) : (\n <ChevronDown className=\"h-4 w-4\" />\n )}\n </button>\n <AnimatePresence initial={false}>\n {showDiscussion && (\n <motion.div\n key=\"discussion\"\n initial={reduce ? false : { opacity: 0, height: 0 }}\n animate={{ opacity: 1, height: 'auto' }}\n exit={reduce ? { opacity: 0 } : { opacity: 0, height: 0 }}\n transition={reduce ? { duration: 0 } : { duration: 0.25, ease: MOTION_EASE }}\n className=\"overflow-hidden\"\n >\n <div className=\"mt-3\">\n <MybDiscussion\n entityType=\"mybodyshield-coaching\"\n entityId={relationship.id}\n title=\"Coaching discussion\"\n description=\"A private thread between the coach and member.\"\n />\n </div>\n </motion.div>\n )}\n </AnimatePresence>\n </div>\n )}\n </GlassCard>\n );\n}\n\n// ============================================================================\n// Sharing-enforcement explainer\n// ============================================================================\n\n/**\n * \"Who can see what\" panel. Every statement here mirrors behaviour that is\n * implemented in the subgraph today — see `ENFORCEMENT_FACTS` for the source\n * files. Nothing here is aspirational.\n */\nfunction SharingEnforcement() {\n return (\n <Section\n title=\"How sharing is enforced\"\n description=\"Private by default — a coach only ever sees the categories you switched on, for as long as the relationship is active.\"\n >\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <ul className=\"grid gap-4 sm:grid-cols-2\">\n {ENFORCEMENT_FACTS.map((fact) => {\n const Icon = fact.icon;\n return (\n <li key={fact.title} className=\"flex items-start gap-3\">\n <IconChip icon={<Icon className=\"h-4 w-4\" />} />\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary\">{fact.title}</p>\n <p className=\"mt-0.5 text-xs leading-relaxed text-text-secondary\">\n {fact.detail}\n </p>\n </div>\n </li>\n );\n })}\n </ul>\n </GlassCard>\n </Section>\n );\n}\n\n// ============================================================================\n// Page\n// ============================================================================\n\nexport function CoachingPage() {\n const { currentUser } = useMyBodyShield();\n const reduce = useReducedMotion();\n const [statusFilter, setStatusFilter] = useState<StatusFilter>('ALL');\n const [createOpen, setCreateOpen] = useState(false);\n const [transitioningId, setTransitioningId] = useState<string | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n\n // `useMybCoaching` returns a paginated connection — fetch every relationship\n // once and filter client-side so the tab counts stay accurate across filters.\n const coaching = useMybCoaching();\n const transition = useMybTransitionCoaching();\n\n const all = coaching.data?.items ?? [];\n\n const counts = useMemo(() => {\n const map: Record<MybCoachingStatus, number> = {\n PENDING: 0,\n ACTIVE: 0,\n PAUSED: 0,\n ENDED: 0,\n };\n for (const rel of all) map[rel.status] += 1;\n return map;\n }, [all]);\n\n const statusFilters = useMemo<FilterOption<StatusFilter>[]>(\n () => [\n { value: 'ALL', label: 'All', count: all.length },\n ...FILTER_ORDER.map((status) => ({\n value: status as StatusFilter,\n label: status.charAt(0) + status.slice(1).toLowerCase(),\n count: counts[status],\n })),\n ],\n [all.length, counts]\n );\n\n const rows = useMemo(\n () => (statusFilter === 'ALL' ? all : all.filter((rel) => rel.status === statusFilter)),\n [all, statusFilter]\n );\n\n const handleTransition = async (id: string, status: MybCoachingStatus) => {\n setTransitioningId(id);\n setActionError(null);\n try {\n await transition.mutateAsync({ id, status });\n } catch (err) {\n setActionError(err instanceof Error ? err.message : 'Failed to update the relationship.');\n } finally {\n setTransitioningId(null);\n }\n };\n\n const showLoading = coaching.isLoading && !coaching.data;\n\n return (\n <PageLayout\n title=\"Coaching\"\n description=\"Coach ↔ member relationships and what's shared.\"\n actions={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-2 h-4 w-4\" />\n New coaching\n </Button>\n }\n >\n <div className=\"mb-6\">\n <FilterTabs options={statusFilters} value={statusFilter} onChange={setStatusFilter} />\n </div>\n\n {actionError && (\n <p className=\"mb-4 rounded-lg bg-status-error-bg-subtle px-3 py-2 text-sm text-status-error-text\">\n {actionError}\n </p>\n )}\n\n {showLoading ? (\n <LoadingView label=\"Loading coaching relationships…\" />\n ) : coaching.isError ? (\n <ErrorView\n message=\"We couldn't load your coaching relationships.\"\n onRetry={() => void coaching.refetch()}\n />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<HeartHandshake className=\"h-10 w-10\" />}\n title={\n all.length === 0 ? 'No coaching relationships' : 'No relationships match this filter'\n }\n description={\n all.length === 0\n ? 'Connect a coach and a member to start sharing training, nutrition and progress data.'\n : 'Try a different status filter, or create a new coaching relationship.'\n }\n action={\n <Button onClick={() => setCreateOpen(true)}>\n <Plus className=\"mr-2 h-4 w-4\" />\n New coaching\n </Button>\n }\n />\n ) : (\n <motion.div\n className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\"\n variants={reduce ? undefined : STAGGER_CONTAINER}\n initial={reduce ? false : 'hidden'}\n animate=\"show\"\n >\n {rows.map((relationship) => (\n <motion.div\n key={relationship.id}\n className=\"h-full\"\n variants={reduce ? undefined : RISE_ITEM}\n >\n <CoachingCard\n relationship={relationship}\n currentUserId={currentUser?.id}\n busy={transitioningId === relationship.id}\n onTransition={(id, status) => void handleTransition(id, status)}\n />\n </motion.div>\n ))}\n </motion.div>\n )}\n\n <SharingEnforcement />\n\n <CreateCoachingDialog\n open={createOpen}\n onOpenChange={setCreateOpen}\n defaultMember={{\n userId: currentUser?.id ?? '',\n displayName: currentUser?.name || currentUser?.email || '',\n }}\n />\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InsightsPage.d.ts","sourceRoot":"","sources":["../../../../src/mybodyshield/pages/insights/InsightsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"InsightsPage.d.ts","sourceRoot":"","sources":["../../../../src/mybodyshield/pages/insights/InsightsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAgLH,wBAAgB,YAAY,gCAuS3B"}
|
|
@@ -13,9 +13,9 @@ import { CalorieChart as g, CorrelationView as _, MacroChart as v, ReadinessSlee
|
|
|
13
13
|
import { useEffect as x, useMemo as S, useState as C } from "react";
|
|
14
14
|
import { BarChart3 as w, Calendar as T, Dumbbell as E, Flame as D, HeartPulse as O, Moon as k, Scale as A, Target as j, TrendingUp as M, Utensils as N } from "lucide-react";
|
|
15
15
|
import { jsx as P, jsxs as F } from "react/jsx-runtime";
|
|
16
|
-
import {
|
|
16
|
+
import { Button as I, GlassCard as L } from "@burdenoff/fe-libs/ui";
|
|
17
17
|
//#region src/mybodyshield/pages/insights/InsightsPage.tsx
|
|
18
|
-
var
|
|
18
|
+
var R = [
|
|
19
19
|
{
|
|
20
20
|
label: "Last 7 days",
|
|
21
21
|
days: 7
|
|
@@ -29,23 +29,107 @@ var L = [
|
|
|
29
29
|
days: 90
|
|
30
30
|
}
|
|
31
31
|
];
|
|
32
|
-
function
|
|
32
|
+
function z(e) {
|
|
33
33
|
return e.toISOString().split("T")[0];
|
|
34
34
|
}
|
|
35
|
-
function
|
|
35
|
+
function B(e) {
|
|
36
|
+
return e.calories > 0 || e.proteinG > 0 || e.carbsG > 0 || e.fatG > 0;
|
|
37
|
+
}
|
|
38
|
+
function V(e) {
|
|
39
|
+
return e.readiness != null || e.sleepHours != null;
|
|
40
|
+
}
|
|
41
|
+
function H(e) {
|
|
42
|
+
return e.sessions > 0 || V(e) || B(e) || e.weight != null;
|
|
43
|
+
}
|
|
44
|
+
function U({ daily: e, summary: t, onOpen: n }) {
|
|
45
|
+
let r = e.length;
|
|
46
|
+
return /* @__PURE__ */ P(f, {
|
|
47
|
+
title: "What these numbers are built on",
|
|
48
|
+
description: "The logs feeding the charts above. Gaps here are why a trend can look flat.",
|
|
49
|
+
children: /* @__PURE__ */ P("div", {
|
|
50
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-4",
|
|
51
|
+
children: [
|
|
52
|
+
{
|
|
53
|
+
route: "train",
|
|
54
|
+
icon: /* @__PURE__ */ P(E, { className: "h-4 w-4" }),
|
|
55
|
+
label: "Training",
|
|
56
|
+
value: t.totalSessions,
|
|
57
|
+
unit: t.totalSessions === 1 ? "completed session" : "completed sessions",
|
|
58
|
+
detail: `on ${e.filter((e) => e.sessions > 0).length} of ${r} days`
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
route: "recovery",
|
|
62
|
+
icon: /* @__PURE__ */ P(O, { className: "h-4 w-4" }),
|
|
63
|
+
label: "Recovery",
|
|
64
|
+
value: e.filter(V).length,
|
|
65
|
+
unit: "days logged",
|
|
66
|
+
detail: "readiness or sleep entries"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
route: "nutrition",
|
|
70
|
+
icon: /* @__PURE__ */ P(N, { className: "h-4 w-4" }),
|
|
71
|
+
label: "Nutrition",
|
|
72
|
+
value: e.filter(B).length,
|
|
73
|
+
unit: "days logged",
|
|
74
|
+
detail: "days with macros logged"
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
route: "progress",
|
|
78
|
+
icon: /* @__PURE__ */ P(A, { className: "h-4 w-4" }),
|
|
79
|
+
label: "Body weight",
|
|
80
|
+
value: e.filter((e) => e.weight != null).length,
|
|
81
|
+
unit: "days logged",
|
|
82
|
+
detail: "weight readings only"
|
|
83
|
+
}
|
|
84
|
+
].map((e) => /* @__PURE__ */ F(L, {
|
|
85
|
+
treatment: "plain",
|
|
86
|
+
className: "flex flex-col gap-3 p-5",
|
|
87
|
+
children: [/* @__PURE__ */ F("div", {
|
|
88
|
+
className: "flex items-center justify-between gap-2",
|
|
89
|
+
children: [/* @__PURE__ */ F("div", {
|
|
90
|
+
className: "flex min-w-0 items-center gap-2 text-text-secondary",
|
|
91
|
+
children: [e.icon, /* @__PURE__ */ P("span", {
|
|
92
|
+
className: "truncate text-sm font-medium text-text-primary",
|
|
93
|
+
children: e.label
|
|
94
|
+
})]
|
|
95
|
+
}), /* @__PURE__ */ P(I, {
|
|
96
|
+
variant: "ghost",
|
|
97
|
+
size: "sm",
|
|
98
|
+
onClick: () => n(e.route),
|
|
99
|
+
children: "View"
|
|
100
|
+
})]
|
|
101
|
+
}), /* @__PURE__ */ F("div", { children: [
|
|
102
|
+
/* @__PURE__ */ P("p", {
|
|
103
|
+
className: "text-2xl font-bold tabular-nums text-text-primary",
|
|
104
|
+
children: s(e.value)
|
|
105
|
+
}),
|
|
106
|
+
/* @__PURE__ */ P("p", {
|
|
107
|
+
className: "text-xs text-text-secondary",
|
|
108
|
+
children: e.unit
|
|
109
|
+
}),
|
|
110
|
+
/* @__PURE__ */ P("p", {
|
|
111
|
+
className: "mt-1 text-xs text-text-tertiary",
|
|
112
|
+
children: e.detail
|
|
113
|
+
})
|
|
114
|
+
] })]
|
|
115
|
+
}, e.route))
|
|
116
|
+
})
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
function W(e) {
|
|
36
120
|
return S(() => {
|
|
37
121
|
let t = /* @__PURE__ */ new Date(), n = /* @__PURE__ */ new Date();
|
|
38
122
|
return n.setDate(t.getDate() - e + 1), {
|
|
39
|
-
dateRangeStart:
|
|
40
|
-
dateRangeEnd:
|
|
123
|
+
dateRangeStart: z(n),
|
|
124
|
+
dateRangeEnd: z(t)
|
|
41
125
|
};
|
|
42
126
|
}, [e]);
|
|
43
127
|
}
|
|
44
|
-
function
|
|
45
|
-
let { workspaceId: S } = e(), [
|
|
128
|
+
function G() {
|
|
129
|
+
let { workspaceId: S, navigateTo: I } = e(), [z, B] = C(30), V = m(W(z)), G = h();
|
|
46
130
|
if (x(() => {
|
|
47
|
-
|
|
48
|
-
}, [
|
|
131
|
+
G.emitPageView("insights", { workspaceId: S });
|
|
132
|
+
}, [G, S]), V.isLoading) return /* @__PURE__ */ P(t, {
|
|
49
133
|
title: "Insights",
|
|
50
134
|
children: /* @__PURE__ */ P(d, { label: "Loading insights…" })
|
|
51
135
|
});
|
|
@@ -57,8 +141,8 @@ function B() {
|
|
|
57
141
|
onRetry: () => V.refetch()
|
|
58
142
|
})
|
|
59
143
|
});
|
|
60
|
-
let
|
|
61
|
-
if (!
|
|
144
|
+
let K = V.data;
|
|
145
|
+
if (!K) return /* @__PURE__ */ P(t, {
|
|
62
146
|
title: "Insights",
|
|
63
147
|
children: /* @__PURE__ */ P(u, {
|
|
64
148
|
title: "Could not load insights",
|
|
@@ -66,17 +150,17 @@ function B() {
|
|
|
66
150
|
onRetry: () => V.refetch()
|
|
67
151
|
})
|
|
68
152
|
});
|
|
69
|
-
let { summary:
|
|
153
|
+
let { summary: q, daily: J, goals: Y } = K, X = J.filter(H).length;
|
|
70
154
|
return /* @__PURE__ */ F(t, {
|
|
71
155
|
title: "Insights",
|
|
72
|
-
description: `${r(
|
|
156
|
+
description: `${r(K.dateRangeStart)} – ${r(K.dateRangeEnd)}`,
|
|
73
157
|
children: [
|
|
74
158
|
/* @__PURE__ */ P("div", {
|
|
75
159
|
className: "flex flex-wrap items-center gap-2",
|
|
76
|
-
children:
|
|
160
|
+
children: R.map((e) => /* @__PURE__ */ P("button", {
|
|
77
161
|
type: "button",
|
|
78
162
|
onClick: () => B(e.days),
|
|
79
|
-
className: `rounded-lg px-3 py-1.5 text-sm font-medium transition-colors ${
|
|
163
|
+
className: `rounded-lg px-3 py-1.5 text-sm font-medium transition-colors ${z === e.days ? "bg-accent text-white" : "bg-bg-sunken text-text-secondary hover:bg-bg-elevated"}`,
|
|
80
164
|
children: e.label
|
|
81
165
|
}, e.days))
|
|
82
166
|
}),
|
|
@@ -87,55 +171,68 @@ function B() {
|
|
|
87
171
|
children: [
|
|
88
172
|
/* @__PURE__ */ P(n, {
|
|
89
173
|
icon: /* @__PURE__ */ P(D, { className: "h-5 w-5" }),
|
|
90
|
-
value:
|
|
174
|
+
value: q.consistencyStreakDays,
|
|
91
175
|
label: "Consistency streak"
|
|
92
176
|
}),
|
|
93
177
|
/* @__PURE__ */ P(n, {
|
|
94
178
|
icon: /* @__PURE__ */ P(E, { className: "h-5 w-5" }),
|
|
95
|
-
value:
|
|
179
|
+
value: q.totalSessions,
|
|
96
180
|
label: "Total sessions"
|
|
97
181
|
}),
|
|
98
182
|
/* @__PURE__ */ P(n, {
|
|
99
183
|
icon: /* @__PURE__ */ P(M, { className: "h-5 w-5" }),
|
|
100
|
-
value: l(
|
|
184
|
+
value: l(q.totalVolumeKg),
|
|
101
185
|
label: "Total volume"
|
|
102
186
|
}),
|
|
103
187
|
/* @__PURE__ */ P(n, {
|
|
104
188
|
icon: /* @__PURE__ */ P(O, { className: "h-5 w-5" }),
|
|
105
|
-
value: s(
|
|
189
|
+
value: s(q.averageReadiness),
|
|
106
190
|
label: "Average readiness"
|
|
107
191
|
}),
|
|
108
192
|
/* @__PURE__ */ P(n, {
|
|
109
193
|
icon: /* @__PURE__ */ P(N, { className: "h-5 w-5" }),
|
|
110
|
-
value: c(
|
|
194
|
+
value: c(q.macroAdherencePercent),
|
|
111
195
|
label: "Macro adherence"
|
|
112
196
|
}),
|
|
113
197
|
/* @__PURE__ */ P(n, {
|
|
114
198
|
icon: /* @__PURE__ */ P(T, { className: "h-5 w-5" }),
|
|
115
|
-
value:
|
|
116
|
-
label: "Days
|
|
199
|
+
value: X,
|
|
200
|
+
label: "Days with data",
|
|
201
|
+
footer: /* @__PURE__ */ F("p", {
|
|
202
|
+
className: "text-xs text-text-tertiary",
|
|
203
|
+
children: [
|
|
204
|
+
"of ",
|
|
205
|
+
J.length,
|
|
206
|
+
" days in this range"
|
|
207
|
+
]
|
|
208
|
+
})
|
|
117
209
|
})
|
|
118
210
|
]
|
|
119
211
|
})
|
|
120
212
|
}),
|
|
213
|
+
/* @__PURE__ */ P(U, {
|
|
214
|
+
daily: J,
|
|
215
|
+
summary: q,
|
|
216
|
+
onOpen: I
|
|
217
|
+
}),
|
|
121
218
|
/* @__PURE__ */ P(f, {
|
|
122
219
|
title: "Trends",
|
|
123
220
|
description: "How training, recovery and nutrition move together.",
|
|
124
221
|
children: /* @__PURE__ */ F("div", {
|
|
125
222
|
className: "grid grid-cols-1 gap-6 xl:grid-cols-2",
|
|
126
223
|
children: [
|
|
127
|
-
/* @__PURE__ */ P(b, { daily:
|
|
128
|
-
/* @__PURE__ */ P(y, { daily:
|
|
129
|
-
/* @__PURE__ */ P(v, { daily:
|
|
130
|
-
/* @__PURE__ */ P(g, { daily:
|
|
224
|
+
/* @__PURE__ */ P(b, { daily: J }),
|
|
225
|
+
/* @__PURE__ */ P(y, { daily: J }),
|
|
226
|
+
/* @__PURE__ */ P(v, { daily: J }),
|
|
227
|
+
/* @__PURE__ */ P(g, { daily: J })
|
|
131
228
|
]
|
|
132
229
|
})
|
|
133
230
|
}),
|
|
134
|
-
/* @__PURE__ */ P(_, { daily:
|
|
231
|
+
/* @__PURE__ */ P(_, { daily: J }),
|
|
135
232
|
/* @__PURE__ */ P(f, {
|
|
136
233
|
title: "Daily breakdown",
|
|
137
234
|
description: "Per-day training, recovery and nutrition.",
|
|
138
|
-
children: /* @__PURE__ */ P(
|
|
235
|
+
children: /* @__PURE__ */ P(L, {
|
|
139
236
|
treatment: "plain",
|
|
140
237
|
className: "overflow-hidden p-0",
|
|
141
238
|
children: /* @__PURE__ */ P(p, {
|
|
@@ -212,7 +309,7 @@ function B() {
|
|
|
212
309
|
})
|
|
213
310
|
}
|
|
214
311
|
],
|
|
215
|
-
rows:
|
|
312
|
+
rows: J,
|
|
216
313
|
rowKey: (e) => e.date,
|
|
217
314
|
defaultSort: {
|
|
218
315
|
key: "date",
|
|
@@ -224,7 +321,7 @@ function B() {
|
|
|
224
321
|
/* @__PURE__ */ P(f, {
|
|
225
322
|
title: "Active goals",
|
|
226
323
|
description: "Goals open during this period.",
|
|
227
|
-
children:
|
|
324
|
+
children: Y.length === 0 ? /* @__PURE__ */ F(L, {
|
|
228
325
|
treatment: "plain",
|
|
229
326
|
className: "p-8 text-center",
|
|
230
327
|
children: [
|
|
@@ -238,7 +335,7 @@ function B() {
|
|
|
238
335
|
children: "Create a goal to see progress here."
|
|
239
336
|
})
|
|
240
337
|
]
|
|
241
|
-
}) : /* @__PURE__ */ P(
|
|
338
|
+
}) : /* @__PURE__ */ P(L, {
|
|
242
339
|
treatment: "plain",
|
|
243
340
|
className: "overflow-hidden p-0",
|
|
244
341
|
children: /* @__PURE__ */ P(p, {
|
|
@@ -278,7 +375,7 @@ function B() {
|
|
|
278
375
|
})
|
|
279
376
|
}
|
|
280
377
|
],
|
|
281
|
-
rows:
|
|
378
|
+
rows: Y,
|
|
282
379
|
rowKey: (e) => e.id,
|
|
283
380
|
defaultSort: {
|
|
284
381
|
key: "status",
|
|
@@ -307,11 +404,15 @@ function B() {
|
|
|
307
404
|
children: [/* @__PURE__ */ P(w, { className: "h-3.5 w-3.5" }), "Nutrition data from logged meals."]
|
|
308
405
|
})
|
|
309
406
|
]
|
|
407
|
+
}),
|
|
408
|
+
/* @__PURE__ */ P("p", {
|
|
409
|
+
className: "mt-4 border-t border-border-seam pt-4 text-xs leading-relaxed text-text-tertiary",
|
|
410
|
+
children: "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."
|
|
310
411
|
})
|
|
311
412
|
]
|
|
312
413
|
});
|
|
313
414
|
}
|
|
314
415
|
//#endregion
|
|
315
|
-
export {
|
|
416
|
+
export { G as InsightsPage };
|
|
316
417
|
|
|
317
418
|
//# sourceMappingURL=InsightsPage.js.map
|
|
@@ -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 { 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 } from '../../hooks';\nimport { useMyBodyShield } from '../../context';\nimport type { MybInsightsDay, 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 { label: 'Last 7 days', days: 7 },\n { label: 'Last 30 days', days: 30 },\n { label: '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\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 { workspaceId } = 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=\"Insights\">\n <LoadingView label=\"Loading insights…\" />\n </PageLayout>\n );\n }\n\n if (insights.isError) {\n return (\n <PageLayout title=\"Insights\">\n <ErrorView\n title=\"Could not load insights\"\n message={insights.error instanceof Error ? insights.error.message : 'Unknown error'}\n onRetry={() => insights.refetch()}\n />\n </PageLayout>\n );\n }\n\n const data = insights.data;\n if (!data) {\n return (\n <PageLayout title=\"Insights\">\n <ErrorView\n title=\"Could not load insights\"\n message=\"No insight data was returned.\"\n onRetry={() => insights.refetch()}\n />\n </PageLayout>\n );\n }\n\n const { summary, daily, goals } = data;\n\n const dayColumns: Column<MybInsightsDay>[] = [\n {\n key: 'date',\n header: 'Date',\n render: (r) => <span className=\"text-text-primary\">{formatDate(r.date)}</span>,\n },\n {\n key: 'sessions',\n header: 'Sessions',\n align: 'right',\n render: (r) => <span className=\"tabular-nums text-text-secondary\">{r.sessions}</span>,\n },\n {\n key: 'volumeKg',\n header: '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: '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: '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: '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: '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: '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: '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: '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: '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=\"Insights\"\n description={`${formatDate(data.dateRangeStart)} – ${formatDate(data.dateRangeEnd)}`}\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 {opt.label}\n </button>\n ))}\n </div>\n\n {/* Summary KPIs */}\n <Section title=\"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=\"Consistency streak\"\n />\n <MetricsCard\n icon={<Dumbbell className=\"h-5 w-5\" />}\n value={summary.totalSessions}\n label=\"Total sessions\"\n />\n <MetricsCard\n icon={<TrendingUp className=\"h-5 w-5\" />}\n value={formatWeight(summary.totalVolumeKg)}\n label=\"Total volume\"\n />\n <MetricsCard\n icon={<HeartPulse className=\"h-5 w-5\" />}\n value={formatNumber(summary.averageReadiness)}\n label=\"Average readiness\"\n />\n <MetricsCard\n icon={<Utensils className=\"h-5 w-5\" />}\n value={formatPercent(summary.macroAdherencePercent)}\n label=\"Macro adherence\"\n />\n <MetricsCard\n icon={<Calendar className=\"h-5 w-5\" />}\n value={daily.length}\n label=\"Days tracked\"\n />\n </div>\n </Section>\n\n {/* Trends */}\n <Section title=\"Trends\" description=\"How training, recovery and nutrition move together.\">\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 title=\"Daily breakdown\" description=\"Per-day training, recovery and nutrition.\">\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 title=\"Active goals\" description=\"Goals open during this period.\">\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\">No active goals</p>\n <p className=\"mt-1 text-xs text-text-tertiary\">Create a goal to see progress here.</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 Training data from completed sessions.\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <Moon className=\"h-3.5 w-3.5\" />\n 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 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 Nutrition data from logged meals.\n </span>\n </div>\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 } 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 { label: 'Last 7 days', days: 7 },\n { label: 'Last 30 days', days: 30 },\n { label: '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 totalDays = daily.length;\n const sources = [\n {\n route: 'train',\n icon: <Dumbbell className=\"h-4 w-4\" />,\n label: 'Training',\n value: summary.totalSessions,\n unit: summary.totalSessions === 1 ? 'completed session' : 'completed sessions',\n detail: `on ${daily.filter((d) => d.sessions > 0).length} of ${totalDays} days`,\n },\n {\n route: 'recovery',\n icon: <HeartPulse className=\"h-4 w-4\" />,\n label: 'Recovery',\n value: daily.filter(hasRecovery).length,\n unit: 'days logged',\n detail: 'readiness or sleep entries',\n },\n {\n route: 'nutrition',\n icon: <Utensils className=\"h-4 w-4\" />,\n label: 'Nutrition',\n value: daily.filter(hasMacros).length,\n unit: 'days logged',\n detail: 'days with macros logged',\n },\n {\n route: 'progress',\n icon: <Scale className=\"h-4 w-4\" />,\n label: 'Body weight',\n value: daily.filter((d) => d.weight != null).length,\n unit: 'days logged',\n detail: 'weight readings only',\n },\n ];\n\n return (\n <Section\n title=\"What these numbers are built on\"\n description=\"The logs feeding the charts above. Gaps here are why a trend can look flat.\"\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 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 { 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=\"Insights\">\n <LoadingView label=\"Loading insights…\" />\n </PageLayout>\n );\n }\n\n if (insights.isError) {\n return (\n <PageLayout title=\"Insights\">\n <ErrorView\n title=\"Could not load insights\"\n message={insights.error instanceof Error ? insights.error.message : 'Unknown error'}\n onRetry={() => insights.refetch()}\n />\n </PageLayout>\n );\n }\n\n const data = insights.data;\n if (!data) {\n return (\n <PageLayout title=\"Insights\">\n <ErrorView\n title=\"Could not load insights\"\n message=\"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: 'Date',\n render: (r) => <span className=\"text-text-primary\">{formatDate(r.date)}</span>,\n },\n {\n key: 'sessions',\n header: 'Sessions',\n align: 'right',\n render: (r) => <span className=\"tabular-nums text-text-secondary\">{r.sessions}</span>,\n },\n {\n key: 'volumeKg',\n header: '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: '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: '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: '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: '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: '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: '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: '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: '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=\"Insights\"\n description={`${formatDate(data.dateRangeStart)} – ${formatDate(data.dateRangeEnd)}`}\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 {opt.label}\n </button>\n ))}\n </div>\n\n {/* Summary KPIs */}\n <Section title=\"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=\"Consistency streak\"\n />\n <MetricsCard\n icon={<Dumbbell className=\"h-5 w-5\" />}\n value={summary.totalSessions}\n label=\"Total sessions\"\n />\n <MetricsCard\n icon={<TrendingUp className=\"h-5 w-5\" />}\n value={formatWeight(summary.totalVolumeKg)}\n label=\"Total volume\"\n />\n <MetricsCard\n icon={<HeartPulse className=\"h-5 w-5\" />}\n value={formatNumber(summary.averageReadiness)}\n label=\"Average readiness\"\n />\n <MetricsCard\n icon={<Utensils className=\"h-5 w-5\" />}\n value={formatPercent(summary.macroAdherencePercent)}\n label=\"Macro adherence\"\n />\n <MetricsCard\n icon={<Calendar className=\"h-5 w-5\" />}\n value={daysWithData}\n label=\"Days with data\"\n footer={\n <p className=\"text-xs text-text-tertiary\">of {daily.length} days in this range</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 title=\"Trends\" description=\"How training, recovery and nutrition move together.\">\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 title=\"Daily breakdown\" description=\"Per-day training, recovery and nutrition.\">\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 title=\"Active goals\" description=\"Goals open during this period.\">\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\">No active goals</p>\n <p className=\"mt-1 text-xs text-text-tertiary\">Create a goal to see progress here.</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 Training data from completed sessions.\n </span>\n <span className=\"inline-flex items-center gap-1.5\">\n <Moon className=\"h-3.5 w-3.5\" />\n 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 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 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 General fitness information, not medical advice. MyBodyShield is not a medical device and\n does not diagnose, treat, cure or prevent any condition — talk to a qualified healthcare\n professional before changing how you train or eat.\n </p>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED