@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.
- package/dist/mybodyshield/pages/insights/InsightsCharts.d.ts +18 -0
- package/dist/mybodyshield/pages/insights/InsightsCharts.d.ts.map +1 -0
- package/dist/mybodyshield/pages/insights/InsightsCharts.js +549 -0
- package/dist/mybodyshield/pages/insights/InsightsCharts.js.map +1 -0
- package/dist/mybodyshield/pages/insights/InsightsPage.d.ts.map +1 -1
- package/dist/mybodyshield/pages/insights/InsightsPage.js +100 -85
- package/dist/mybodyshield/pages/insights/InsightsPage.js.map +1 -1
- package/dist/node_modules/recharts/es6/cartesian/Line.js +473 -0
- package/dist/node_modules/recharts/es6/cartesian/Line.js.map +1 -0
- package/dist/node_modules/recharts/es6/cartesian/Scatter.js +368 -0
- package/dist/node_modules/recharts/es6/cartesian/Scatter.js.map +1 -0
- package/dist/node_modules/recharts/es6/cartesian/ZAxis.js +103 -0
- package/dist/node_modules/recharts/es6/cartesian/ZAxis.js.map +1 -0
- package/dist/node_modules/recharts/es6/chart/BarChart.js +24 -0
- package/dist/node_modules/recharts/es6/chart/BarChart.js.map +1 -0
- package/dist/node_modules/recharts/es6/chart/ComposedChart.js +38 -0
- package/dist/node_modules/recharts/es6/chart/ComposedChart.js.map +1 -0
- package/dist/node_modules/recharts/es6/util/DataUtils.js +12 -2
- package/dist/node_modules/recharts/es6/util/DataUtils.js.map +1 -1
- package/dist/node_modules/recharts/es6/util/ScatterUtils.js +48 -0
- package/dist/node_modules/recharts/es6/util/ScatterUtils.js.map +1 -0
- package/dist/stories/InsightsCharts.stories.d.ts +15 -0
- package/dist/stories/InsightsCharts.stories.d.ts.map +1 -0
- 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 {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
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
|
|
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
|
|
32
|
+
function R(e) {
|
|
32
33
|
return e.toISOString().split("T")[0];
|
|
33
34
|
}
|
|
34
|
-
function
|
|
35
|
-
return
|
|
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:
|
|
39
|
-
dateRangeEnd:
|
|
39
|
+
dateRangeStart: R(n),
|
|
40
|
+
dateRangeEnd: R(t)
|
|
40
41
|
};
|
|
41
42
|
}, [e]);
|
|
42
43
|
}
|
|
43
|
-
function
|
|
44
|
-
let { workspaceId:
|
|
45
|
-
if (
|
|
46
|
-
|
|
47
|
-
}, []),
|
|
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__ */
|
|
50
|
+
children: /* @__PURE__ */ P(d, { label: "Loading insights…" })
|
|
50
51
|
});
|
|
51
|
-
if (
|
|
52
|
+
if (V.isError) return /* @__PURE__ */ P(t, {
|
|
52
53
|
title: "Insights",
|
|
53
|
-
children: /* @__PURE__ */
|
|
54
|
+
children: /* @__PURE__ */ P(u, {
|
|
54
55
|
title: "Could not load insights",
|
|
55
|
-
message:
|
|
56
|
-
onRetry: () =>
|
|
56
|
+
message: V.error instanceof Error ? V.error.message : "Unknown error",
|
|
57
|
+
onRetry: () => V.refetch()
|
|
57
58
|
})
|
|
58
59
|
});
|
|
59
|
-
let
|
|
60
|
-
if (!
|
|
60
|
+
let U = V.data;
|
|
61
|
+
if (!U) return /* @__PURE__ */ P(t, {
|
|
61
62
|
title: "Insights",
|
|
62
|
-
children: /* @__PURE__ */
|
|
63
|
+
children: /* @__PURE__ */ P(u, {
|
|
63
64
|
title: "Could not load insights",
|
|
64
65
|
message: "No insight data was returned.",
|
|
65
|
-
onRetry: () =>
|
|
66
|
+
onRetry: () => V.refetch()
|
|
66
67
|
})
|
|
67
68
|
});
|
|
68
|
-
let { summary:
|
|
69
|
-
return /* @__PURE__ */
|
|
69
|
+
let { summary: W, daily: G, goals: K } = U;
|
|
70
|
+
return /* @__PURE__ */ F(t, {
|
|
70
71
|
title: "Insights",
|
|
71
|
-
description: `${r(
|
|
72
|
+
description: `${r(U.dateRangeStart)} – ${r(U.dateRangeEnd)}`,
|
|
72
73
|
children: [
|
|
73
|
-
/* @__PURE__ */
|
|
74
|
+
/* @__PURE__ */ P("div", {
|
|
74
75
|
className: "flex flex-wrap items-center gap-2",
|
|
75
|
-
children:
|
|
76
|
+
children: L.map((e) => /* @__PURE__ */ P("button", {
|
|
76
77
|
type: "button",
|
|
77
|
-
onClick: () =>
|
|
78
|
-
className: `rounded-lg px-3 py-1.5 text-sm font-medium transition-colors ${
|
|
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__ */
|
|
83
|
+
/* @__PURE__ */ P(f, {
|
|
83
84
|
title: "Summary",
|
|
84
|
-
children: /* @__PURE__ */
|
|
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__ */
|
|
88
|
-
icon: /* @__PURE__ */
|
|
89
|
-
value:
|
|
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__ */
|
|
93
|
-
icon: /* @__PURE__ */
|
|
94
|
-
value:
|
|
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__ */
|
|
98
|
-
icon: /* @__PURE__ */
|
|
99
|
-
value: l(
|
|
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__ */
|
|
103
|
-
icon: /* @__PURE__ */ O
|
|
104
|
-
value: s(
|
|
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__ */
|
|
108
|
-
icon: /* @__PURE__ */
|
|
109
|
-
value: c(
|
|
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__ */
|
|
113
|
-
icon: /* @__PURE__ */
|
|
114
|
-
value:
|
|
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__ */
|
|
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__ */
|
|
138
|
+
children: /* @__PURE__ */ P(I, {
|
|
124
139
|
treatment: "plain",
|
|
125
140
|
className: "overflow-hidden p-0",
|
|
126
|
-
children: /* @__PURE__ */
|
|
141
|
+
children: /* @__PURE__ */ P(p, {
|
|
127
142
|
columns: [
|
|
128
143
|
{
|
|
129
144
|
key: "date",
|
|
130
145
|
header: "Date",
|
|
131
|
-
render: (e) => /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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:
|
|
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__ */
|
|
224
|
+
/* @__PURE__ */ P(f, {
|
|
210
225
|
title: "Active goals",
|
|
211
226
|
description: "Goals open during this period.",
|
|
212
|
-
children:
|
|
227
|
+
children: K.length === 0 ? /* @__PURE__ */ F(I, {
|
|
213
228
|
treatment: "plain",
|
|
214
229
|
className: "p-8 text-center",
|
|
215
230
|
children: [
|
|
216
|
-
/* @__PURE__ */
|
|
217
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
241
|
+
}) : /* @__PURE__ */ P(I, {
|
|
227
242
|
treatment: "plain",
|
|
228
243
|
className: "overflow-hidden p-0",
|
|
229
|
-
children: /* @__PURE__ */
|
|
244
|
+
children: /* @__PURE__ */ P(p, {
|
|
230
245
|
columns: [
|
|
231
246
|
{
|
|
232
247
|
key: "title",
|
|
233
248
|
header: "Goal",
|
|
234
|
-
render: (e) => /* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
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__ */
|
|
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:
|
|
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__ */
|
|
290
|
+
/* @__PURE__ */ F("div", {
|
|
276
291
|
className: "flex flex-wrap gap-4 text-xs text-text-tertiary",
|
|
277
292
|
children: [
|
|
278
|
-
/* @__PURE__ */
|
|
293
|
+
/* @__PURE__ */ F("span", {
|
|
279
294
|
className: "inline-flex items-center gap-1.5",
|
|
280
|
-
children: [/* @__PURE__ */
|
|
295
|
+
children: [/* @__PURE__ */ P(E, { className: "h-3.5 w-3.5" }), "Training data from completed sessions."]
|
|
281
296
|
}),
|
|
282
|
-
/* @__PURE__ */
|
|
297
|
+
/* @__PURE__ */ F("span", {
|
|
283
298
|
className: "inline-flex items-center gap-1.5",
|
|
284
|
-
children: [/* @__PURE__ */
|
|
299
|
+
children: [/* @__PURE__ */ P(k, { className: "h-3.5 w-3.5" }), "Recovery data from recovery logs."]
|
|
285
300
|
}),
|
|
286
|
-
/* @__PURE__ */
|
|
301
|
+
/* @__PURE__ */ F("span", {
|
|
287
302
|
className: "inline-flex items-center gap-1.5",
|
|
288
|
-
children: [/* @__PURE__ */
|
|
303
|
+
children: [/* @__PURE__ */ P(A, { className: "h-3.5 w-3.5" }), "Body metrics from progress logs."]
|
|
289
304
|
}),
|
|
290
|
-
/* @__PURE__ */
|
|
305
|
+
/* @__PURE__ */ F("span", {
|
|
291
306
|
className: "inline-flex items-center gap-1.5",
|
|
292
|
-
children: [/* @__PURE__ */
|
|
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 {
|
|
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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"}
|