@burdenoff/microfe-mybodyshield 2026.821.1 → 2026.821.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/dist/mybodyshield/pages/insights/InsightsCharts.d.ts +18 -0
  2. package/dist/mybodyshield/pages/insights/InsightsCharts.d.ts.map +1 -0
  3. package/dist/mybodyshield/pages/insights/InsightsCharts.js +549 -0
  4. package/dist/mybodyshield/pages/insights/InsightsCharts.js.map +1 -0
  5. package/dist/mybodyshield/pages/insights/InsightsPage.d.ts.map +1 -1
  6. package/dist/mybodyshield/pages/insights/InsightsPage.js +100 -85
  7. package/dist/mybodyshield/pages/insights/InsightsPage.js.map +1 -1
  8. package/dist/node_modules/recharts/es6/cartesian/Line.js +473 -0
  9. package/dist/node_modules/recharts/es6/cartesian/Line.js.map +1 -0
  10. package/dist/node_modules/recharts/es6/cartesian/Scatter.js +368 -0
  11. package/dist/node_modules/recharts/es6/cartesian/Scatter.js.map +1 -0
  12. package/dist/node_modules/recharts/es6/cartesian/ZAxis.js +103 -0
  13. package/dist/node_modules/recharts/es6/cartesian/ZAxis.js.map +1 -0
  14. package/dist/node_modules/recharts/es6/chart/BarChart.js +24 -0
  15. package/dist/node_modules/recharts/es6/chart/BarChart.js.map +1 -0
  16. package/dist/node_modules/recharts/es6/chart/ComposedChart.js +38 -0
  17. package/dist/node_modules/recharts/es6/chart/ComposedChart.js.map +1 -0
  18. package/dist/node_modules/recharts/es6/util/DataUtils.js +12 -2
  19. package/dist/node_modules/recharts/es6/util/DataUtils.js.map +1 -1
  20. package/dist/node_modules/recharts/es6/util/ScatterUtils.js +48 -0
  21. package/dist/node_modules/recharts/es6/util/ScatterUtils.js.map +1 -0
  22. package/dist/stories/InsightsCharts.stories.d.ts +15 -0
  23. package/dist/stories/InsightsCharts.stories.d.ts.map +1 -0
  24. package/package.json +1 -1
@@ -9,12 +9,13 @@ import { useMybInsights as m } from "../../hooks/useMyBodyShieldApi.js";
9
9
  import { useMyBodyShieldAnalytics as h } from "../../hooks/useMyBodyShieldAnalytics.js";
10
10
  import "../../hooks/index.js";
11
11
  import "../../components/index.js";
12
- import { useMemo as g, useState as _ } from "react";
13
- import { BarChart3 as v, Calendar as y, Dumbbell as b, Flame as x, HeartPulse as S, Moon as C, Scale as w, Target as T, TrendingUp as E, Utensils as D } from "lucide-react";
14
- import { jsx as O, jsxs as k } from "react/jsx-runtime";
15
- import { GlassCard as A } from "@burdenoff/fe-libs/ui";
12
+ import { CalorieChart as g, CorrelationView as _, MacroChart as v, ReadinessSleepChart as y, VolumeChart as b } from "./InsightsCharts.js";
13
+ import { useEffect as x, useMemo as S, useState as C } from "react";
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
+ import { jsx as P, jsxs as F } from "react/jsx-runtime";
16
+ import { GlassCard as I } from "@burdenoff/fe-libs/ui";
16
17
  //#region src/mybodyshield/pages/insights/InsightsPage.tsx
17
- var j = [
18
+ var L = [
18
19
  {
19
20
  label: "Last 7 days",
20
21
  days: 7
@@ -28,107 +29,121 @@ var j = [
28
29
  days: 90
29
30
  }
30
31
  ];
31
- function M(e) {
32
+ function R(e) {
32
33
  return e.toISOString().split("T")[0];
33
34
  }
34
- function N(e) {
35
- return g(() => {
35
+ function z(e) {
36
+ return S(() => {
36
37
  let t = /* @__PURE__ */ new Date(), n = /* @__PURE__ */ new Date();
37
38
  return n.setDate(t.getDate() - e + 1), {
38
- dateRangeStart: M(n),
39
- dateRangeEnd: M(t)
39
+ dateRangeStart: R(n),
40
+ dateRangeEnd: R(t)
40
41
  };
41
42
  }, [e]);
42
43
  }
43
- function P() {
44
- let { workspaceId: M } = e(), [P, F] = _(30), I = m(N(P)), L = h();
45
- if (g(() => {
46
- L.emitPageView("insights", { workspaceId: M });
47
- }, []), I.isLoading) return /* @__PURE__ */ O(t, {
44
+ function B() {
45
+ let { workspaceId: S } = e(), [R, B] = C(30), V = m(z(R)), H = h();
46
+ if (x(() => {
47
+ H.emitPageView("insights", { workspaceId: S });
48
+ }, [H, S]), V.isLoading) return /* @__PURE__ */ P(t, {
48
49
  title: "Insights",
49
- children: /* @__PURE__ */ O(d, { label: "Loading insights…" })
50
+ children: /* @__PURE__ */ P(d, { label: "Loading insights…" })
50
51
  });
51
- if (I.isError) return /* @__PURE__ */ O(t, {
52
+ if (V.isError) return /* @__PURE__ */ P(t, {
52
53
  title: "Insights",
53
- children: /* @__PURE__ */ O(u, {
54
+ children: /* @__PURE__ */ P(u, {
54
55
  title: "Could not load insights",
55
- message: I.error instanceof Error ? I.error.message : "Unknown error",
56
- onRetry: () => I.refetch()
56
+ message: V.error instanceof Error ? V.error.message : "Unknown error",
57
+ onRetry: () => V.refetch()
57
58
  })
58
59
  });
59
- let R = I.data;
60
- if (!R) return /* @__PURE__ */ O(t, {
60
+ let U = V.data;
61
+ if (!U) return /* @__PURE__ */ P(t, {
61
62
  title: "Insights",
62
- children: /* @__PURE__ */ O(u, {
63
+ children: /* @__PURE__ */ P(u, {
63
64
  title: "Could not load insights",
64
65
  message: "No insight data was returned.",
65
- onRetry: () => I.refetch()
66
+ onRetry: () => V.refetch()
66
67
  })
67
68
  });
68
- let { summary: z, daily: B, goals: V } = R;
69
- return /* @__PURE__ */ k(t, {
69
+ let { summary: W, daily: G, goals: K } = U;
70
+ return /* @__PURE__ */ F(t, {
70
71
  title: "Insights",
71
- description: `${r(R.dateRangeStart)} – ${r(R.dateRangeEnd)}`,
72
+ description: `${r(U.dateRangeStart)} – ${r(U.dateRangeEnd)}`,
72
73
  children: [
73
- /* @__PURE__ */ O("div", {
74
+ /* @__PURE__ */ P("div", {
74
75
  className: "flex flex-wrap items-center gap-2",
75
- children: j.map((e) => /* @__PURE__ */ O("button", {
76
+ children: L.map((e) => /* @__PURE__ */ P("button", {
76
77
  type: "button",
77
- onClick: () => F(e.days),
78
- className: `rounded-lg px-3 py-1.5 text-sm font-medium transition-colors ${P === e.days ? "bg-accent text-white" : "bg-bg-sunken text-text-secondary hover:bg-bg-elevated"}`,
78
+ onClick: () => B(e.days),
79
+ className: `rounded-lg px-3 py-1.5 text-sm font-medium transition-colors ${R === e.days ? "bg-accent text-white" : "bg-bg-sunken text-text-secondary hover:bg-bg-elevated"}`,
79
80
  children: e.label
80
81
  }, e.days))
81
82
  }),
82
- /* @__PURE__ */ O(f, {
83
+ /* @__PURE__ */ P(f, {
83
84
  title: "Summary",
84
- children: /* @__PURE__ */ k("div", {
85
+ children: /* @__PURE__ */ F("div", {
85
86
  className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
86
87
  children: [
87
- /* @__PURE__ */ O(n, {
88
- icon: /* @__PURE__ */ O(x, { className: "h-5 w-5" }),
89
- value: z.consistencyStreakDays,
88
+ /* @__PURE__ */ P(n, {
89
+ icon: /* @__PURE__ */ P(D, { className: "h-5 w-5" }),
90
+ value: W.consistencyStreakDays,
90
91
  label: "Consistency streak"
91
92
  }),
92
- /* @__PURE__ */ O(n, {
93
- icon: /* @__PURE__ */ O(b, { className: "h-5 w-5" }),
94
- value: z.totalSessions,
93
+ /* @__PURE__ */ P(n, {
94
+ icon: /* @__PURE__ */ P(E, { className: "h-5 w-5" }),
95
+ value: W.totalSessions,
95
96
  label: "Total sessions"
96
97
  }),
97
- /* @__PURE__ */ O(n, {
98
- icon: /* @__PURE__ */ O(E, { className: "h-5 w-5" }),
99
- value: l(z.totalVolumeKg),
98
+ /* @__PURE__ */ P(n, {
99
+ icon: /* @__PURE__ */ P(M, { className: "h-5 w-5" }),
100
+ value: l(W.totalVolumeKg),
100
101
  label: "Total volume"
101
102
  }),
102
- /* @__PURE__ */ O(n, {
103
- icon: /* @__PURE__ */ O(S, { className: "h-5 w-5" }),
104
- value: s(z.averageReadiness),
103
+ /* @__PURE__ */ P(n, {
104
+ icon: /* @__PURE__ */ P(O, { className: "h-5 w-5" }),
105
+ value: s(W.averageReadiness),
105
106
  label: "Average readiness"
106
107
  }),
107
- /* @__PURE__ */ O(n, {
108
- icon: /* @__PURE__ */ O(D, { className: "h-5 w-5" }),
109
- value: c(z.macroAdherencePercent),
108
+ /* @__PURE__ */ P(n, {
109
+ icon: /* @__PURE__ */ P(N, { className: "h-5 w-5" }),
110
+ value: c(W.macroAdherencePercent),
110
111
  label: "Macro adherence"
111
112
  }),
112
- /* @__PURE__ */ O(n, {
113
- icon: /* @__PURE__ */ O(y, { className: "h-5 w-5" }),
114
- value: B.length,
113
+ /* @__PURE__ */ P(n, {
114
+ icon: /* @__PURE__ */ P(T, { className: "h-5 w-5" }),
115
+ value: G.length,
115
116
  label: "Days tracked"
116
117
  })
117
118
  ]
118
119
  })
119
120
  }),
120
- /* @__PURE__ */ O(f, {
121
+ /* @__PURE__ */ P(f, {
122
+ title: "Trends",
123
+ description: "How training, recovery and nutrition move together.",
124
+ children: /* @__PURE__ */ F("div", {
125
+ className: "grid grid-cols-1 gap-6 xl:grid-cols-2",
126
+ children: [
127
+ /* @__PURE__ */ P(b, { daily: G }),
128
+ /* @__PURE__ */ P(y, { daily: G }),
129
+ /* @__PURE__ */ P(v, { daily: G }),
130
+ /* @__PURE__ */ P(g, { daily: G })
131
+ ]
132
+ })
133
+ }),
134
+ /* @__PURE__ */ P(_, { daily: G }),
135
+ /* @__PURE__ */ P(f, {
121
136
  title: "Daily breakdown",
122
137
  description: "Per-day training, recovery and nutrition.",
123
- children: /* @__PURE__ */ O(A, {
138
+ children: /* @__PURE__ */ P(I, {
124
139
  treatment: "plain",
125
140
  className: "overflow-hidden p-0",
126
- children: /* @__PURE__ */ O(p, {
141
+ children: /* @__PURE__ */ P(p, {
127
142
  columns: [
128
143
  {
129
144
  key: "date",
130
145
  header: "Date",
131
- render: (e) => /* @__PURE__ */ O("span", {
146
+ render: (e) => /* @__PURE__ */ P("span", {
132
147
  className: "text-text-primary",
133
148
  children: r(e.date)
134
149
  })
@@ -137,7 +152,7 @@ function P() {
137
152
  key: "sessions",
138
153
  header: "Sessions",
139
154
  align: "right",
140
- render: (e) => /* @__PURE__ */ O("span", {
155
+ render: (e) => /* @__PURE__ */ P("span", {
141
156
  className: "tabular-nums text-text-secondary",
142
157
  children: e.sessions
143
158
  })
@@ -146,7 +161,7 @@ function P() {
146
161
  key: "volumeKg",
147
162
  header: "Volume",
148
163
  align: "right",
149
- render: (e) => /* @__PURE__ */ O("span", {
164
+ render: (e) => /* @__PURE__ */ P("span", {
150
165
  className: "tabular-nums text-text-secondary",
151
166
  children: l(e.volumeKg)
152
167
  })
@@ -155,7 +170,7 @@ function P() {
155
170
  key: "readiness",
156
171
  header: "Readiness",
157
172
  align: "right",
158
- render: (e) => /* @__PURE__ */ O("span", {
173
+ render: (e) => /* @__PURE__ */ P("span", {
159
174
  className: "tabular-nums text-text-secondary",
160
175
  children: e.readiness == null ? "—" : s(e.readiness)
161
176
  })
@@ -164,7 +179,7 @@ function P() {
164
179
  key: "sleepHours",
165
180
  header: "Sleep",
166
181
  align: "right",
167
- render: (e) => /* @__PURE__ */ O("span", {
182
+ render: (e) => /* @__PURE__ */ P("span", {
168
183
  className: "tabular-nums text-text-secondary",
169
184
  children: e.sleepHours == null ? "—" : `${s(e.sleepHours)} h`
170
185
  })
@@ -173,7 +188,7 @@ function P() {
173
188
  key: "calories",
174
189
  header: "Calories",
175
190
  align: "right",
176
- render: (e) => /* @__PURE__ */ O("span", {
191
+ render: (e) => /* @__PURE__ */ P("span", {
177
192
  className: "tabular-nums text-text-secondary",
178
193
  children: o(e.calories)
179
194
  })
@@ -182,7 +197,7 @@ function P() {
182
197
  key: "proteinG",
183
198
  header: "Protein",
184
199
  align: "right",
185
- render: (e) => /* @__PURE__ */ O("span", {
200
+ render: (e) => /* @__PURE__ */ P("span", {
186
201
  className: "tabular-nums text-text-secondary",
187
202
  children: a(e.proteinG)
188
203
  })
@@ -191,13 +206,13 @@ function P() {
191
206
  key: "weight",
192
207
  header: "Weight",
193
208
  align: "right",
194
- render: (e) => /* @__PURE__ */ O("span", {
209
+ render: (e) => /* @__PURE__ */ P("span", {
195
210
  className: "tabular-nums text-text-secondary",
196
211
  children: e.weight == null ? "—" : l(e.weight)
197
212
  })
198
213
  }
199
214
  ],
200
- rows: B,
215
+ rows: G,
201
216
  rowKey: (e) => e.date,
202
217
  defaultSort: {
203
218
  key: "date",
@@ -206,35 +221,35 @@ function P() {
206
221
  })
207
222
  })
208
223
  }),
209
- /* @__PURE__ */ O(f, {
224
+ /* @__PURE__ */ P(f, {
210
225
  title: "Active goals",
211
226
  description: "Goals open during this period.",
212
- children: V.length === 0 ? /* @__PURE__ */ k(A, {
227
+ children: K.length === 0 ? /* @__PURE__ */ F(I, {
213
228
  treatment: "plain",
214
229
  className: "p-8 text-center",
215
230
  children: [
216
- /* @__PURE__ */ O(T, { className: "mx-auto h-8 w-8 text-text-tertiary" }),
217
- /* @__PURE__ */ O("p", {
231
+ /* @__PURE__ */ P(j, { className: "mx-auto h-8 w-8 text-text-tertiary" }),
232
+ /* @__PURE__ */ P("p", {
218
233
  className: "mt-3 text-sm font-medium text-text-primary",
219
234
  children: "No active goals"
220
235
  }),
221
- /* @__PURE__ */ O("p", {
236
+ /* @__PURE__ */ P("p", {
222
237
  className: "mt-1 text-xs text-text-tertiary",
223
238
  children: "Create a goal to see progress here."
224
239
  })
225
240
  ]
226
- }) : /* @__PURE__ */ O(A, {
241
+ }) : /* @__PURE__ */ P(I, {
227
242
  treatment: "plain",
228
243
  className: "overflow-hidden p-0",
229
- children: /* @__PURE__ */ O(p, {
244
+ children: /* @__PURE__ */ P(p, {
230
245
  columns: [
231
246
  {
232
247
  key: "title",
233
248
  header: "Goal",
234
- render: (e) => /* @__PURE__ */ k("div", { children: [/* @__PURE__ */ O("span", {
249
+ render: (e) => /* @__PURE__ */ F("div", { children: [/* @__PURE__ */ P("span", {
235
250
  className: "block text-sm text-text-primary",
236
251
  children: e.title
237
- }), /* @__PURE__ */ O("span", {
252
+ }), /* @__PURE__ */ P("span", {
238
253
  className: "text-xs text-text-tertiary",
239
254
  children: i(e.type)
240
255
  })] })
@@ -242,7 +257,7 @@ function P() {
242
257
  {
243
258
  key: "status",
244
259
  header: "Status",
245
- render: (e) => /* @__PURE__ */ O("span", {
260
+ render: (e) => /* @__PURE__ */ P("span", {
246
261
  className: `inline-flex items-center rounded-full px-2 py-0.5 text-xs font-medium ${e.status === "ACHIEVED" ? "bg-status-success-bg text-status-success-text" : "bg-status-warning-bg text-status-warning-text"}`,
247
262
  children: i(e.status)
248
263
  })
@@ -251,7 +266,7 @@ function P() {
251
266
  key: "progress",
252
267
  header: "Progress",
253
268
  align: "right",
254
- render: (e) => /* @__PURE__ */ k("span", {
269
+ render: (e) => /* @__PURE__ */ F("span", {
255
270
  className: "tabular-nums text-text-secondary",
256
271
  children: [
257
272
  s(e.currentValue),
@@ -263,7 +278,7 @@ function P() {
263
278
  })
264
279
  }
265
280
  ],
266
- rows: V,
281
+ rows: K,
267
282
  rowKey: (e) => e.id,
268
283
  defaultSort: {
269
284
  key: "status",
@@ -272,24 +287,24 @@ function P() {
272
287
  })
273
288
  })
274
289
  }),
275
- /* @__PURE__ */ k("div", {
290
+ /* @__PURE__ */ F("div", {
276
291
  className: "flex flex-wrap gap-4 text-xs text-text-tertiary",
277
292
  children: [
278
- /* @__PURE__ */ k("span", {
293
+ /* @__PURE__ */ F("span", {
279
294
  className: "inline-flex items-center gap-1.5",
280
- children: [/* @__PURE__ */ O(b, { className: "h-3.5 w-3.5" }), "Training data from completed sessions."]
295
+ children: [/* @__PURE__ */ P(E, { className: "h-3.5 w-3.5" }), "Training data from completed sessions."]
281
296
  }),
282
- /* @__PURE__ */ k("span", {
297
+ /* @__PURE__ */ F("span", {
283
298
  className: "inline-flex items-center gap-1.5",
284
- children: [/* @__PURE__ */ O(C, { className: "h-3.5 w-3.5" }), "Recovery data from recovery logs."]
299
+ children: [/* @__PURE__ */ P(k, { className: "h-3.5 w-3.5" }), "Recovery data from recovery logs."]
285
300
  }),
286
- /* @__PURE__ */ k("span", {
301
+ /* @__PURE__ */ F("span", {
287
302
  className: "inline-flex items-center gap-1.5",
288
- children: [/* @__PURE__ */ O(w, { className: "h-3.5 w-3.5" }), "Body metrics from progress logs."]
303
+ children: [/* @__PURE__ */ P(A, { className: "h-3.5 w-3.5" }), "Body metrics from progress logs."]
289
304
  }),
290
- /* @__PURE__ */ k("span", {
305
+ /* @__PURE__ */ F("span", {
291
306
  className: "inline-flex items-center gap-1.5",
292
- children: [/* @__PURE__ */ O(v, { className: "h-3.5 w-3.5" }), "Nutrition data from logged meals."]
307
+ children: [/* @__PURE__ */ P(w, { className: "h-3.5 w-3.5" }), "Nutrition data from logged meals."]
293
308
  })
294
309
  ]
295
310
  })
@@ -297,6 +312,6 @@ function P() {
297
312
  });
298
313
  }
299
314
  //#endregion
300
- export { P as InsightsPage };
315
+ export { B as InsightsPage };
301
316
 
302
317
  //# 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 { 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';\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 useMemo(() => {\n analytics.emitPageView('insights', { workspaceId });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\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) => <span className=\"tabular-nums text-text-secondary\">{formatWeight(r.volumeKg)}</span>,\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) => <span className=\"tabular-nums text-text-secondary\">{formatKcal(r.calories)}</span>,\n },\n {\n key: 'proteinG',\n header: 'Protein',\n align: 'right',\n render: (r) => <span className=\"tabular-nums text-text-secondary\">{formatGrams(r.proteinG)}</span>,\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 {/* 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\">\n Create a goal to see progress here.\n </p>\n </GlassCard>\n ) : (\n <GlassCard treatment=\"plain\" className=\"overflow-hidden p-0\">\n <DataTable\n columns={goalColumns}\n rows={goals}\n rowKey={(r) => r.id}\n defaultSort={{ key: 'status', dir: 'asc' }}\n />\n </GlassCard>\n )}\n </Section>\n\n {/* Legend */}\n <div className=\"flex flex-wrap gap-4 text-xs text-text-tertiary\">\n <span className=\"inline-flex items-center gap-1.5\">\n <Dumbbell className=\"h-3.5 w-3.5\" />\n 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 { 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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"}