@burdenoff/microfe-atheletebridge 2026.827.1 → 2026.829.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/atheletebridge/components/LoadingState.d.ts.map +1 -1
- package/dist/atheletebridge/components/LoadingState.js +39 -32
- package/dist/atheletebridge/components/LoadingState.js.map +1 -1
- package/dist/atheletebridge/components/TeamCard.d.ts.map +1 -1
- package/dist/atheletebridge/components/TeamCard.js +45 -49
- package/dist/atheletebridge/components/TeamCard.js.map +1 -1
- package/dist/atheletebridge/pages/DashboardPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/DashboardPage.js +56 -24
- package/dist/atheletebridge/pages/DashboardPage.js.map +1 -1
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js +58 -56
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +110 -106
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js +80 -71
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js.map +1 -1
- package/dist/atheletebridge/pages/onboarding/OnboardingPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/onboarding/OnboardingPage.js +244 -210
- package/dist/atheletebridge/pages/onboarding/OnboardingPage.js.map +1 -1
- package/dist/atheletebridge/pages/performance/PerformancePage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/performance/PerformancePage.js +197 -148
- package/dist/atheletebridge/pages/performance/PerformancePage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js +169 -143
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramsListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js +77 -72
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js +104 -86
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionsListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js +54 -49
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js +286 -267
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamFormPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamFormPage.js +185 -180
- package/dist/atheletebridge/pages/teams/TeamFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamsListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamsListPage.js +70 -74
- package/dist/atheletebridge/pages/teams/TeamsListPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -26,18 +26,18 @@ var I = {
|
|
|
26
26
|
CANCELLED: r.danger,
|
|
27
27
|
NO_SHOW: "var(--color-text-muted)"
|
|
28
28
|
};
|
|
29
|
-
function L(e) {
|
|
29
|
+
function L(e, t) {
|
|
30
30
|
return e == null ? {
|
|
31
|
-
label: "No data",
|
|
31
|
+
label: t("atheletebridge.pages.analytics.acwr.noData", "No data"),
|
|
32
32
|
variant: "secondary"
|
|
33
33
|
} : e > 1.5 ? {
|
|
34
|
-
label: "At risk",
|
|
34
|
+
label: t("atheletebridge.pages.analytics.acwr.atRisk", "At risk"),
|
|
35
35
|
variant: "destructive"
|
|
36
36
|
} : e < .8 || e > 1.3 ? {
|
|
37
|
-
label: "Watch",
|
|
37
|
+
label: t("atheletebridge.pages.analytics.acwr.watch", "Watch"),
|
|
38
38
|
variant: "secondary"
|
|
39
39
|
} : {
|
|
40
|
-
label: "Optimal",
|
|
40
|
+
label: t("atheletebridge.pages.analytics.acwr.optimal", "Optimal"),
|
|
41
41
|
variant: "default"
|
|
42
42
|
};
|
|
43
43
|
}
|
|
@@ -62,12 +62,16 @@ function V() {
|
|
|
62
62
|
value: e.count,
|
|
63
63
|
color: I[e.status]
|
|
64
64
|
})), [z.sessionsByStatus]), U = _(() => z.ratingByCoach.map((e) => ({
|
|
65
|
-
label: R(B.nameOf(e.coachId), e.coachId, "Coach"),
|
|
65
|
+
label: R(B.nameOf(e.coachId), e.coachId, r("atheletebridge.rolePrefix.coach", "Coach")),
|
|
66
66
|
value: Number(e.avgRating.toFixed(2))
|
|
67
|
-
})), [
|
|
67
|
+
})), [
|
|
68
|
+
z.ratingByCoach,
|
|
69
|
+
B,
|
|
70
|
+
r
|
|
71
|
+
]), W = [
|
|
68
72
|
{
|
|
69
73
|
key: "rank",
|
|
70
|
-
header: "#",
|
|
74
|
+
header: r("atheletebridge.pages.analytics.standings.rank", "#"),
|
|
71
75
|
priority: "secondary",
|
|
72
76
|
cell: (e, t) => /* @__PURE__ */ E("span", {
|
|
73
77
|
className: "tabular-nums text-text-secondary",
|
|
@@ -76,24 +80,24 @@ function V() {
|
|
|
76
80
|
},
|
|
77
81
|
{
|
|
78
82
|
key: "team",
|
|
79
|
-
header: "Team",
|
|
83
|
+
header: r("atheletebridge.pages.analytics.standings.team", "Team"),
|
|
80
84
|
priority: "primary",
|
|
81
85
|
cell: (e) => /* @__PURE__ */ D("span", {
|
|
82
86
|
className: "flex items-center gap-2",
|
|
83
87
|
children: [/* @__PURE__ */ E(u, {
|
|
84
|
-
name: e.team?.name ?? "Team",
|
|
88
|
+
name: e.team?.name ?? r("atheletebridge.pages.analytics.standings.teamFallback", "Team"),
|
|
85
89
|
sport: e.team?.sport,
|
|
86
90
|
size: 20,
|
|
87
91
|
showSportGlyph: !1
|
|
88
92
|
}), /* @__PURE__ */ E("span", {
|
|
89
93
|
className: "truncate font-medium text-text-primary",
|
|
90
|
-
children: e.team?.name ?? "Team"
|
|
94
|
+
children: e.team?.name ?? r("atheletebridge.pages.analytics.standings.teamFallback", "Team")
|
|
91
95
|
})]
|
|
92
96
|
})
|
|
93
97
|
},
|
|
94
98
|
{
|
|
95
99
|
key: "played",
|
|
96
|
-
header: "P",
|
|
100
|
+
header: r("atheletebridge.pages.analytics.standings.played", "P"),
|
|
97
101
|
priority: "hidden-on-mobile",
|
|
98
102
|
cell: (e) => /* @__PURE__ */ E("span", {
|
|
99
103
|
className: "tabular-nums text-text-secondary",
|
|
@@ -102,7 +106,7 @@ function V() {
|
|
|
102
106
|
},
|
|
103
107
|
{
|
|
104
108
|
key: "gd",
|
|
105
|
-
header: "GD",
|
|
109
|
+
header: r("atheletebridge.pages.analytics.standings.gd", "GD"),
|
|
106
110
|
priority: "hidden-on-mobile",
|
|
107
111
|
cell: (e) => {
|
|
108
112
|
let t = e.goalsFor - e.goalsAgainst;
|
|
@@ -114,7 +118,7 @@ function V() {
|
|
|
114
118
|
},
|
|
115
119
|
{
|
|
116
120
|
key: "points",
|
|
117
|
-
header: "Pts",
|
|
121
|
+
header: r("atheletebridge.pages.analytics.standings.points", "Pts"),
|
|
118
122
|
priority: "secondary",
|
|
119
123
|
cell: (e) => /* @__PURE__ */ E("span", {
|
|
120
124
|
className: "font-semibold tabular-nums text-text-primary",
|
|
@@ -123,26 +127,26 @@ function V() {
|
|
|
123
127
|
}
|
|
124
128
|
];
|
|
125
129
|
if (z.isLoading) return /* @__PURE__ */ E(n, {
|
|
126
|
-
title: "Coach analytics",
|
|
127
|
-
children: /* @__PURE__ */ E(m, { label: "Crunching coaching data…" })
|
|
130
|
+
title: r("atheletebridge.pages.analytics.title", "Coach analytics"),
|
|
131
|
+
children: /* @__PURE__ */ E(m, { label: r("atheletebridge.pages.analytics.loading", "Crunching coaching data…") })
|
|
128
132
|
});
|
|
129
133
|
if (z.isError) return /* @__PURE__ */ E(n, {
|
|
130
|
-
title: "Coach analytics",
|
|
134
|
+
title: r("atheletebridge.pages.analytics.title", "Coach analytics"),
|
|
131
135
|
children: /* @__PURE__ */ E(p, {
|
|
132
|
-
title: "Analytics unavailable",
|
|
133
|
-
message: "We could not load coaching analytics. Please try again."
|
|
136
|
+
title: r("atheletebridge.pages.analytics.errorTitle", "Analytics unavailable"),
|
|
137
|
+
message: r("atheletebridge.pages.analytics.errorMessage", "We could not load coaching analytics. Please try again.")
|
|
134
138
|
})
|
|
135
139
|
});
|
|
136
|
-
let G = z.totalSessions > 0 || z.standings.length > 0 || z.loadByAthlete.length > 0, K = L(z.trainingLoad.ratio);
|
|
140
|
+
let G = z.totalSessions > 0 || z.standings.length > 0 || z.loadByAthlete.length > 0, K = L(z.trainingLoad.ratio, r);
|
|
137
141
|
return /* @__PURE__ */ E(n, {
|
|
138
142
|
title: /* @__PURE__ */ D("span", {
|
|
139
143
|
className: "flex items-center gap-2",
|
|
140
|
-
children: [/* @__PURE__ */ E(A, { children: "Coach analytics" }), /* @__PURE__ */ E(O, {
|
|
144
|
+
children: [/* @__PURE__ */ E(A, { children: r("atheletebridge.pages.analytics.title", "Coach analytics") }), /* @__PURE__ */ E(O, {
|
|
141
145
|
variant: "outline",
|
|
142
|
-
children: "Beta"
|
|
146
|
+
children: r("common.beta", "Beta")
|
|
143
147
|
})]
|
|
144
148
|
}),
|
|
145
|
-
description: "Honest aggregates over your real sessions, workouts and results.",
|
|
149
|
+
description: r("atheletebridge.pages.analytics.description", "Honest aggregates over your real sessions, workouts and results."),
|
|
146
150
|
children: G ? /* @__PURE__ */ D("div", {
|
|
147
151
|
className: "space-y-8",
|
|
148
152
|
children: [
|
|
@@ -152,7 +156,7 @@ function V() {
|
|
|
152
156
|
/* @__PURE__ */ E(c, {
|
|
153
157
|
icon: /* @__PURE__ */ E(S, { className: "h-5 w-5" }),
|
|
154
158
|
value: z.totalSessions,
|
|
155
|
-
label: "Total sessions",
|
|
159
|
+
label: r("atheletebridge.pages.analytics.totalSessions", "Total sessions"),
|
|
156
160
|
tone: "accent"
|
|
157
161
|
}),
|
|
158
162
|
/* @__PURE__ */ D(k, {
|
|
@@ -163,19 +167,19 @@ function V() {
|
|
|
163
167
|
className: "flex items-start justify-between gap-2",
|
|
164
168
|
children: [/* @__PURE__ */ E("span", {
|
|
165
169
|
className: "text-sm font-medium text-text-secondary",
|
|
166
|
-
children: "Avg session rating"
|
|
170
|
+
children: r("atheletebridge.pages.analytics.avgRating", "Avg session rating")
|
|
167
171
|
}), /* @__PURE__ */ E(C, { className: "h-5 w-5 text-text-muted" })]
|
|
168
172
|
}),
|
|
169
173
|
z.avgRating == null ? /* @__PURE__ */ E("span", {
|
|
170
174
|
className: "text-sm text-text-muted",
|
|
171
|
-
children: "No rated sessions yet"
|
|
175
|
+
children: r("atheletebridge.pages.analytics.noRatedSessions", "No rated sessions yet")
|
|
172
176
|
}) : /* @__PURE__ */ D("div", {
|
|
173
177
|
className: "flex items-end gap-3",
|
|
174
178
|
children: [/* @__PURE__ */ D("span", {
|
|
175
179
|
className: "text-3xl font-semibold leading-none tabular-nums text-text-primary",
|
|
176
180
|
children: [z.avgRating.toFixed(1), /* @__PURE__ */ E("span", {
|
|
177
181
|
className: "ml-1 text-base text-text-secondary",
|
|
178
|
-
children: "/5"
|
|
182
|
+
children: r("atheletebridge.pages.analytics.ratingOutOf", "/5")
|
|
179
183
|
})]
|
|
180
184
|
}), /* @__PURE__ */ E("div", {
|
|
181
185
|
className: "pb-1",
|
|
@@ -190,7 +194,9 @@ function V() {
|
|
|
190
194
|
className: "text-xs text-text-muted tabular-nums",
|
|
191
195
|
children: [
|
|
192
196
|
z.ratedCount,
|
|
193
|
-
"
|
|
197
|
+
" ",
|
|
198
|
+
r("atheletebridge.pages.analytics.ratedOf", "rated of"),
|
|
199
|
+
" ",
|
|
194
200
|
z.totalSessions
|
|
195
201
|
]
|
|
196
202
|
})
|
|
@@ -199,7 +205,7 @@ function V() {
|
|
|
199
205
|
/* @__PURE__ */ E(c, {
|
|
200
206
|
icon: /* @__PURE__ */ E(b, { className: "h-5 w-5" }),
|
|
201
207
|
value: `${Math.round(z.completedPct)}%`,
|
|
202
|
-
label: "Sessions completed",
|
|
208
|
+
label: r("atheletebridge.pages.analytics.sessionsCompleted", "Sessions completed"),
|
|
203
209
|
tone: "success"
|
|
204
210
|
})
|
|
205
211
|
]
|
|
@@ -211,22 +217,22 @@ function V() {
|
|
|
211
217
|
className: "p-6",
|
|
212
218
|
children: [/* @__PURE__ */ E("h2", {
|
|
213
219
|
className: "mb-4 text-lg font-semibold text-text-primary",
|
|
214
|
-
children: "Session status overview"
|
|
220
|
+
children: r("atheletebridge.pages.analytics.statusOverviewTitle", "Session status overview")
|
|
215
221
|
}), V.length > 0 ? /* @__PURE__ */ E(o, {
|
|
216
222
|
segments: V,
|
|
217
223
|
centerLabel: z.totalSessions,
|
|
218
|
-
centerCaption: "sessions",
|
|
224
|
+
centerCaption: r("atheletebridge.pages.analytics.sessionsCaption", "sessions"),
|
|
219
225
|
valueFormatter: (e) => e.toLocaleString()
|
|
220
226
|
}) : /* @__PURE__ */ E("p", {
|
|
221
227
|
className: "text-sm text-text-muted",
|
|
222
|
-
children: "No sessions to break down yet."
|
|
228
|
+
children: r("atheletebridge.pages.analytics.statusOverviewEmpty", "No sessions to break down yet.")
|
|
223
229
|
})]
|
|
224
230
|
}), /* @__PURE__ */ D(k, {
|
|
225
231
|
treatment: "insight",
|
|
226
232
|
className: "p-6",
|
|
227
233
|
children: [/* @__PURE__ */ E("h2", {
|
|
228
234
|
className: "mb-4 text-lg font-semibold text-text-primary",
|
|
229
|
-
children: "Average rating by coach"
|
|
235
|
+
children: r("atheletebridge.pages.analytics.ratingByCoachTitle", "Average rating by coach")
|
|
230
236
|
}), U.length > 0 ? /* @__PURE__ */ D(T, { children: [/* @__PURE__ */ E(a, {
|
|
231
237
|
series: U,
|
|
232
238
|
height: 220,
|
|
@@ -242,7 +248,7 @@ function V() {
|
|
|
242
248
|
size: "xs"
|
|
243
249
|
}), /* @__PURE__ */ D("span", {
|
|
244
250
|
className: "truncate",
|
|
245
|
-
children: [R(B.nameOf(e.coachId), e.coachId, "Coach"), /* @__PURE__ */ D("span", {
|
|
251
|
+
children: [R(B.nameOf(e.coachId), e.coachId, r("atheletebridge.rolePrefix.coach", "Coach")), /* @__PURE__ */ D("span", {
|
|
246
252
|
className: "ml-1 text-text-muted tabular-nums",
|
|
247
253
|
children: [
|
|
248
254
|
"(",
|
|
@@ -254,7 +260,7 @@ function V() {
|
|
|
254
260
|
}, e.coachId))
|
|
255
261
|
})] }) : /* @__PURE__ */ E("p", {
|
|
256
262
|
className: "text-sm text-text-muted",
|
|
257
|
-
children: "Rate completed sessions to compare coaches."
|
|
263
|
+
children: r("atheletebridge.pages.analytics.ratingByCoachEmpty", "Rate completed sessions to compare coaches.")
|
|
258
264
|
})]
|
|
259
265
|
})]
|
|
260
266
|
}),
|
|
@@ -267,26 +273,22 @@ function V() {
|
|
|
267
273
|
className: "mb-4 flex items-center justify-between gap-2",
|
|
268
274
|
children: [/* @__PURE__ */ E("h2", {
|
|
269
275
|
className: "text-lg font-semibold text-text-primary",
|
|
270
|
-
children: "Weekly training load"
|
|
276
|
+
children: r("atheletebridge.pages.analytics.weeklyLoadTitle", "Weekly training load")
|
|
271
277
|
}), /* @__PURE__ */ E(P, { children: /* @__PURE__ */ D(M, { children: [/* @__PURE__ */ E(F, {
|
|
272
278
|
asChild: !0,
|
|
273
279
|
children: /* @__PURE__ */ E("button", {
|
|
274
280
|
type: "button",
|
|
275
|
-
"aria-label": "About ACWR training load",
|
|
281
|
+
"aria-label": r("atheletebridge.pages.analytics.acwrAboutLabel", "About ACWR training load"),
|
|
276
282
|
className: "rounded-full p-1 text-text-muted transition-colors hover:text-text-primary",
|
|
277
283
|
children: /* @__PURE__ */ E(x, { className: "h-4 w-4" })
|
|
278
284
|
})
|
|
279
|
-
}), /* @__PURE__ */
|
|
285
|
+
}), /* @__PURE__ */ E(N, {
|
|
280
286
|
className: "max-w-xs",
|
|
281
|
-
children:
|
|
282
|
-
"Total workout load per week (sum of real ",
|
|
283
|
-
/* @__PURE__ */ E("code", { children: "totalLoad" }),
|
|
284
|
-
"). The badge is the acute:chronic workload ratio (last 7 days vs 28-day average) — a standard sports-science load indicator; 0.8–1.3 is the typical sweet spot."
|
|
285
|
-
]
|
|
287
|
+
children: r("atheletebridge.pages.analytics.acwrAbout", "Total workout load per week (sum of real totalLoad). The badge is the acute:chronic workload ratio (last 7 days vs 28-day average) — a standard sports-science load indicator; 0.8–1.3 is the typical sweet spot.")
|
|
286
288
|
})] }) })]
|
|
287
289
|
}), z.loadTrend.some((e) => e.value > 0) ? /* @__PURE__ */ D(T, { children: [/* @__PURE__ */ E(i, {
|
|
288
290
|
series: z.loadTrend,
|
|
289
|
-
seriesLabel: "Training load",
|
|
291
|
+
seriesLabel: r("atheletebridge.pages.analytics.trainingLoadLabel", "Training load"),
|
|
290
292
|
height: 200,
|
|
291
293
|
valueFormatter: (e) => e.toLocaleString()
|
|
292
294
|
}), /* @__PURE__ */ D("div", {
|
|
@@ -295,7 +297,7 @@ function V() {
|
|
|
295
297
|
/* @__PURE__ */ D("span", {
|
|
296
298
|
className: "text-text-secondary",
|
|
297
299
|
children: [
|
|
298
|
-
"Acute (7d)",
|
|
300
|
+
r("atheletebridge.pages.analytics.acuteLoad", "Acute (7d)"),
|
|
299
301
|
" ",
|
|
300
302
|
/* @__PURE__ */ E("span", {
|
|
301
303
|
className: "font-semibold tabular-nums text-text-primary",
|
|
@@ -306,7 +308,7 @@ function V() {
|
|
|
306
308
|
/* @__PURE__ */ D("span", {
|
|
307
309
|
className: "text-text-secondary",
|
|
308
310
|
children: [
|
|
309
|
-
"Chronic (28d avg)",
|
|
311
|
+
r("atheletebridge.pages.analytics.chronicLoad", "Chronic (28d avg)"),
|
|
310
312
|
" ",
|
|
311
313
|
/* @__PURE__ */ E("span", {
|
|
312
314
|
className: "font-semibold tabular-nums text-text-primary",
|
|
@@ -316,7 +318,7 @@ function V() {
|
|
|
316
318
|
}),
|
|
317
319
|
/* @__PURE__ */ D("span", {
|
|
318
320
|
className: "flex items-center gap-2 text-text-secondary",
|
|
319
|
-
children: ["ACWR", /* @__PURE__ */ E(O, {
|
|
321
|
+
children: [r("atheletebridge.pages.analytics.acwrAbbreviation", "ACWR"), /* @__PURE__ */ E(O, {
|
|
320
322
|
variant: K.variant,
|
|
321
323
|
className: "tabular-nums",
|
|
322
324
|
children: z.trainingLoad.ratio == null ? K.label : `${z.trainingLoad.ratio.toFixed(2)} · ${K.label}`
|
|
@@ -325,21 +327,21 @@ function V() {
|
|
|
325
327
|
]
|
|
326
328
|
})] }) : /* @__PURE__ */ E("p", {
|
|
327
329
|
className: "text-sm text-text-muted",
|
|
328
|
-
children: "Log workouts with a load to see training trend."
|
|
330
|
+
children: r("atheletebridge.pages.analytics.weeklyLoadEmpty", "Log workouts with a load to see training trend.")
|
|
329
331
|
})]
|
|
330
332
|
}), /* @__PURE__ */ D(k, {
|
|
331
333
|
treatment: "insight",
|
|
332
334
|
className: "p-6",
|
|
333
335
|
children: [/* @__PURE__ */ E("h2", {
|
|
334
336
|
className: "mb-4 text-lg font-semibold text-text-primary",
|
|
335
|
-
children: "Sessions per week"
|
|
337
|
+
children: r("atheletebridge.pages.analytics.sessionsPerWeekTitle", "Sessions per week")
|
|
336
338
|
}), z.sessionTrend.some((e) => e.value > 0) ? /* @__PURE__ */ E(a, {
|
|
337
339
|
series: z.sessionTrend,
|
|
338
340
|
height: 200,
|
|
339
341
|
valueFormatter: (e) => e.toLocaleString()
|
|
340
342
|
}) : /* @__PURE__ */ E("p", {
|
|
341
343
|
className: "text-sm text-text-muted",
|
|
342
|
-
children: "Schedule sessions to see weekly volume."
|
|
344
|
+
children: r("atheletebridge.pages.analytics.sessionsPerWeekEmpty", "Schedule sessions to see weekly volume.")
|
|
343
345
|
})]
|
|
344
346
|
})]
|
|
345
347
|
}),
|
|
@@ -355,7 +357,7 @@ function V() {
|
|
|
355
357
|
className: "flex items-center gap-2 text-lg font-semibold text-text-primary",
|
|
356
358
|
children: [
|
|
357
359
|
/* @__PURE__ */ E(v, { className: "h-5 w-5 text-text-muted" }),
|
|
358
|
-
"League standings",
|
|
360
|
+
r("atheletebridge.pages.analytics.standingsTitle", "League standings"),
|
|
359
361
|
z.competition ? /* @__PURE__ */ E(O, {
|
|
360
362
|
variant: "secondary",
|
|
361
363
|
children: z.competition
|
|
@@ -365,7 +367,7 @@ function V() {
|
|
|
365
367
|
type: "button",
|
|
366
368
|
onClick: () => w("/standings"),
|
|
367
369
|
className: "text-sm font-medium text-[var(--color-accent-teal)] transition-colors hover:underline",
|
|
368
|
-
children: "View full standings"
|
|
370
|
+
children: r("atheletebridge.pages.analytics.viewFullStandings", "View full standings")
|
|
369
371
|
})]
|
|
370
372
|
}), /* @__PURE__ */ E(k, {
|
|
371
373
|
treatment: "insight",
|
|
@@ -380,8 +382,8 @@ function V() {
|
|
|
380
382
|
}) : /* @__PURE__ */ E(f, {
|
|
381
383
|
illustration: "empty-data",
|
|
382
384
|
icon: /* @__PURE__ */ E(y, { className: "h-10 w-10" }),
|
|
383
|
-
title: "No coaching data yet",
|
|
384
|
-
description: "Book and complete sessions, log workouts and record fixtures to unlock analytics."
|
|
385
|
+
title: r("atheletebridge.pages.analytics.emptyTitle", "No coaching data yet"),
|
|
386
|
+
description: r("atheletebridge.pages.analytics.emptyDescription", "Book and complete sessions, log workouts and record fixtures to unlock analytics.")
|
|
385
387
|
})
|
|
386
388
|
});
|
|
387
389
|
}
|
|
@@ -401,7 +403,7 @@ function H({ rows: e, directory: t, truncated: n, tr: r }) {
|
|
|
401
403
|
className: "min-w-0",
|
|
402
404
|
children: [/* @__PURE__ */ E("span", {
|
|
403
405
|
className: "block truncate font-medium text-text-primary",
|
|
404
|
-
children: R(t.nameOf(e.athleteId), e.athleteId, "Athlete")
|
|
406
|
+
children: R(t.nameOf(e.athleteId), e.athleteId, r("atheletebridge.rolePrefix.athlete", "Athlete"))
|
|
405
407
|
}), /* @__PURE__ */ D("span", {
|
|
406
408
|
className: "block text-xs tabular-nums text-text-muted",
|
|
407
409
|
children: [
|
|
@@ -470,7 +472,7 @@ function H({ rows: e, directory: t, truncated: n, tr: r }) {
|
|
|
470
472
|
priority: "primary",
|
|
471
473
|
mobileLabel: r("atheletebridge.pages.analytics.loadByAthlete.status", "Status"),
|
|
472
474
|
cell: (e) => {
|
|
473
|
-
let t = L(e.ratio);
|
|
475
|
+
let t = L(e.ratio, r);
|
|
474
476
|
return /* @__PURE__ */ E(O, {
|
|
475
477
|
variant: t.variant,
|
|
476
478
|
children: t.label
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CoachAnalyticsPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/analytics/CoachAnalyticsPage.tsx"],"sourcesContent":["/**\n * Coach Analytics\n * @module atheletebridge/pages/analytics\n *\n * An honest coaching-analytics dashboard built ENTIRELY from real aggregates via\n * `useCoachAnalytics()` — total sessions, average session rating, completion\n * rate, session-status mix, average rating by coach, weekly training-load /\n * session trends, a per-athlete acute:chronic training-load table, and a\n * league-standings snapshot.\n *\n * Deliberately OMITS the mock's fabricated composite indices (Form Index,\n * Fitness Score, Fatigue Index, Injury Status, Availability %) — there is no\n * wellness / injury / availability data in the schema, so those are not faked.\n * The one derived metric shown (ACWR training load) uses standard sports-science\n * maths over real `workout.totalLoad` and carries an explanatory tooltip — both\n * workspace-wide and, in the per-athlete table, grouped by each log's real\n * `athleteId`. Where an athlete's workouts carry no recorded load the table\n * says so rather than showing a fabricated zero.\n * Semantic tokens only; responsive.\n */\n\nimport { useMemo } from 'react';\nimport { Activity, BarChart3, CheckCircle2, Info, ListChecks, Star, Users } from 'lucide-react';\nimport {\n Badge,\n GlassCard,\n GradientText,\n ResponsiveTable,\n type ResponsiveTableColumn,\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { Crest } from '../../components/Crest';\nimport { UserAvatar } from '../../components/UserAvatar';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { LineChart, BarChart, DonutChart, RatingStars, STATUS } from '../../components/viz';\nimport { humanizeEnum } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useAbTranslate, type TranslateFn } from '../../i18n';\nimport {\n useCoachAnalytics,\n useUserDirectory,\n type AthleteTrainingLoad,\n type UserDirectory,\n} from '../../hooks/useAthleteBridgeApi';\nimport type { AthSessionStatus, AthStanding } from '../../types';\n\n/** Tokenized color per session status for the status donut. */\nconst STATUS_COLOR: Record<AthSessionStatus, string> = {\n COMPLETED: STATUS.success,\n CONFIRMED: STATUS.info,\n REQUESTED: STATUS.warning,\n CANCELLED: STATUS.danger,\n NO_SHOW: 'var(--color-text-muted)',\n};\n\n/** ACWR banding (acute:chronic workload ratio) — standard sweet-spot 0.8–1.3. */\nfunction acwrBand(ratio: number | null): {\n label: string;\n variant: 'default' | 'secondary' | 'destructive';\n} {\n if (ratio == null) {\n return { label: 'No data', variant: 'secondary' };\n }\n if (ratio > 1.5) {\n return { label: 'At risk', variant: 'destructive' };\n }\n if (ratio < 0.8 || ratio > 1.3) {\n return { label: 'Watch', variant: 'secondary' };\n }\n return { label: 'Optimal', variant: 'default' };\n}\n\n/**\n * Short person display: the resolved directory name, else a monogram-friendly\n * `<role> <id fragment>` so an unresolvable id still reads as a person rather\n * than as a raw uuid.\n */\nfunction shortName(name: string | undefined, id: string, rolePrefix: string): string {\n if (name && name.trim()) {\n return name;\n }\n return `${rolePrefix} ${id.slice(0, 4)}`;\n}\n\n/** Whole-number load display; loads are arbitrary units, so no decimals. */\nconst fmtLoad = (value: number): string => Math.round(value).toLocaleString();\n\n/**\n * A load bar scaled against the heaviest load in the table, so acute and\n * chronic columns stay visually comparable to each other and across rows.\n * Purely decorative — the number beside it is the fact.\n */\nfunction LoadBar({ value, max }: { value: number; max: number }) {\n const pct = max > 0 ? Math.round((value / max) * 100) : 0;\n return (\n <span className=\"block h-1.5 w-16 overflow-hidden rounded-full bg-bg-sunken\" aria-hidden=\"true\">\n <span\n className=\"block h-1.5 rounded-full bg-[var(--color-accent-teal)]\"\n style={{ width: `${Math.max(value > 0 ? 4 : 0, Math.min(100, pct))}%` }}\n />\n </span>\n );\n}\n\nexport function CoachAnalyticsPage() {\n const tr = useAbTranslate();\n const { navigateTo } = useAthleteBridge();\n const analytics = useCoachAnalytics();\n // One directory round-trip for both surfaces — `useUsersByIds` dedupes/sorts.\n const directoryIds = useMemo(\n () => [...analytics.coachIds, ...analytics.athleteIds],\n [analytics.coachIds, analytics.athleteIds]\n );\n const directory = useUserDirectory(directoryIds);\n\n const statusSegments = useMemo(\n () =>\n analytics.sessionsByStatus.map((entry) => ({\n label: humanizeEnum(entry.status),\n value: entry.count,\n color: STATUS_COLOR[entry.status],\n })),\n [analytics.sessionsByStatus]\n );\n\n const coachBars = useMemo(\n () =>\n analytics.ratingByCoach.map((entry) => ({\n label: shortName(directory.nameOf(entry.coachId), entry.coachId, 'Coach'),\n value: Number(entry.avgRating.toFixed(2)),\n })),\n [analytics.ratingByCoach, directory]\n );\n\n const standingsColumns: ResponsiveTableColumn<AthStanding>[] = [\n {\n key: 'rank',\n header: '#',\n priority: 'secondary',\n cell: (row, i) => (\n <span className=\"tabular-nums text-text-secondary\">{row.rank ?? i + 1}</span>\n ),\n },\n {\n key: 'team',\n header: 'Team',\n priority: 'primary',\n cell: (row) => (\n <span className=\"flex items-center gap-2\">\n <Crest\n name={row.team?.name ?? 'Team'}\n sport={row.team?.sport}\n size={20}\n showSportGlyph={false}\n />\n <span className=\"truncate font-medium text-text-primary\">{row.team?.name ?? 'Team'}</span>\n </span>\n ),\n },\n {\n key: 'played',\n header: 'P',\n priority: 'hidden-on-mobile',\n cell: (row) => <span className=\"tabular-nums text-text-secondary\">{row.played}</span>,\n },\n {\n key: 'gd',\n header: 'GD',\n priority: 'hidden-on-mobile',\n cell: (row) => {\n const gd = row.goalsFor - row.goalsAgainst;\n return (\n <span className=\"tabular-nums text-text-secondary\">\n {gd > 0 ? '+' : ''}\n {gd}\n </span>\n );\n },\n },\n {\n key: 'points',\n header: 'Pts',\n priority: 'secondary',\n cell: (row) => (\n <span className=\"font-semibold tabular-nums text-text-primary\">{row.points}</span>\n ),\n },\n ];\n\n if (analytics.isLoading) {\n return (\n <PageLayout title=\"Coach analytics\">\n <LoadingState label=\"Crunching coaching data…\" />\n </PageLayout>\n );\n }\n\n if (analytics.isError) {\n return (\n <PageLayout title=\"Coach analytics\">\n <ErrorState\n title=\"Analytics unavailable\"\n message=\"We could not load coaching analytics. Please try again.\"\n />\n </PageLayout>\n );\n }\n\n const hasData =\n analytics.totalSessions > 0 ||\n analytics.standings.length > 0 ||\n analytics.loadByAthlete.length > 0;\n const band = acwrBand(analytics.trainingLoad.ratio);\n\n return (\n <PageLayout\n title={\n <span className=\"flex items-center gap-2\">\n <GradientText>Coach analytics</GradientText>\n <Badge variant=\"outline\">Beta</Badge>\n </span>\n }\n description=\"Honest aggregates over your real sessions, workouts and results.\"\n >\n {!hasData ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<BarChart3 className=\"h-10 w-10\" />}\n title=\"No coaching data yet\"\n description=\"Book and complete sessions, log workouts and record fixtures to unlock analytics.\"\n />\n ) : (\n <div className=\"space-y-8\">\n {/* KPI row */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <MetricsCard\n icon={<ListChecks className=\"h-5 w-5\" />}\n value={analytics.totalSessions}\n label=\"Total sessions\"\n tone=\"accent\"\n />\n <GlassCard treatment=\"metric\" className=\"flex flex-col justify-between gap-3 p-5\">\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-sm font-medium text-text-secondary\">Avg session rating</span>\n <Star className=\"h-5 w-5 text-text-muted\" />\n </div>\n {analytics.avgRating != null ? (\n <div className=\"flex items-end gap-3\">\n <span className=\"text-3xl font-semibold leading-none tabular-nums text-text-primary\">\n {analytics.avgRating.toFixed(1)}\n <span className=\"ml-1 text-base text-text-secondary\">/5</span>\n </span>\n <div className=\"pb-1\">\n <RatingStars value={analytics.avgRating} max={5} size={16} />\n </div>\n </div>\n ) : (\n <span className=\"text-sm text-text-muted\">No rated sessions yet</span>\n )}\n <span className=\"text-xs text-text-muted tabular-nums\">\n {analytics.ratedCount} rated of {analytics.totalSessions}\n </span>\n </GlassCard>\n <MetricsCard\n icon={<CheckCircle2 className=\"h-5 w-5\" />}\n value={`${Math.round(analytics.completedPct)}%`}\n label=\"Sessions completed\"\n tone=\"success\"\n />\n </div>\n\n {/* Charts row: status donut + rating-by-coach bars */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <GlassCard treatment=\"insight\" className=\"p-6\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">\n Session status overview\n </h2>\n {statusSegments.length > 0 ? (\n <DonutChart\n segments={statusSegments}\n centerLabel={analytics.totalSessions}\n centerCaption=\"sessions\"\n valueFormatter={(v) => v.toLocaleString()}\n />\n ) : (\n <p className=\"text-sm text-text-muted\">No sessions to break down yet.</p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"insight\" className=\"p-6\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">\n Average rating by coach\n </h2>\n {coachBars.length > 0 ? (\n <>\n <BarChart\n series={coachBars}\n height={220}\n colorful\n valueFormatter={(v) => v.toFixed(1)}\n />\n <div className=\"mt-3 flex flex-wrap gap-x-4 gap-y-2\">\n {analytics.ratingByCoach.map((entry) => (\n <span\n key={entry.coachId}\n className=\"flex items-center gap-2 text-xs text-text-secondary\"\n >\n <UserAvatar\n name={directory.nameOf(entry.coachId)}\n id={entry.coachId}\n size=\"xs\"\n />\n <span className=\"truncate\">\n {shortName(directory.nameOf(entry.coachId), entry.coachId, 'Coach')}\n <span className=\"ml-1 text-text-muted tabular-nums\">({entry.count})</span>\n </span>\n </span>\n ))}\n </div>\n </>\n ) : (\n <p className=\"text-sm text-text-muted\">\n Rate completed sessions to compare coaches.\n </p>\n )}\n </GlassCard>\n </div>\n\n {/* Trends row: training load + sessions per week */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <GlassCard treatment=\"insight\" className=\"p-6\">\n <div className=\"mb-4 flex items-center justify-between gap-2\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Weekly training load</h2>\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"About ACWR training load\"\n className=\"rounded-full p-1 text-text-muted transition-colors hover:text-text-primary\"\n >\n <Info className=\"h-4 w-4\" />\n </button>\n </TooltipTrigger>\n <TooltipContent className=\"max-w-xs\">\n Total workout load per week (sum of real <code>totalLoad</code>). The badge is\n the acute:chronic workload ratio (last 7 days vs 28-day average) — a standard\n sports-science load indicator; 0.8–1.3 is the typical sweet spot.\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n </div>\n {analytics.loadTrend.some((p) => p.value > 0) ? (\n <>\n <LineChart\n series={analytics.loadTrend}\n seriesLabel=\"Training load\"\n height={200}\n valueFormatter={(v) => v.toLocaleString()}\n />\n <div className=\"mt-4 flex flex-wrap items-center gap-x-6 gap-y-2 text-sm\">\n <span className=\"text-text-secondary\">\n Acute (7d){' '}\n <span className=\"font-semibold tabular-nums text-text-primary\">\n {Math.round(analytics.trainingLoad.acute).toLocaleString()}\n </span>\n </span>\n <span className=\"text-text-secondary\">\n Chronic (28d avg){' '}\n <span className=\"font-semibold tabular-nums text-text-primary\">\n {Math.round(analytics.trainingLoad.chronic).toLocaleString()}\n </span>\n </span>\n <span className=\"flex items-center gap-2 text-text-secondary\">\n ACWR\n <Badge variant={band.variant} className=\"tabular-nums\">\n {analytics.trainingLoad.ratio != null\n ? `${analytics.trainingLoad.ratio.toFixed(2)} · ${band.label}`\n : band.label}\n </Badge>\n </span>\n </div>\n </>\n ) : (\n <p className=\"text-sm text-text-muted\">\n Log workouts with a load to see training trend.\n </p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"insight\" className=\"p-6\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Sessions per week</h2>\n {analytics.sessionTrend.some((p) => p.value > 0) ? (\n <BarChart\n series={analytics.sessionTrend}\n height={200}\n valueFormatter={(v) => v.toLocaleString()}\n />\n ) : (\n <p className=\"text-sm text-text-muted\">Schedule sessions to see weekly volume.</p>\n )}\n </GlassCard>\n </div>\n\n {/* Training load by athlete — the workspace ACWR, broken out per athlete */}\n <TrainingLoadByAthlete\n rows={analytics.loadByAthlete}\n directory={directory}\n truncated={analytics.loadSampleTruncated}\n tr={tr}\n />\n\n {/* League standings snapshot */}\n {analytics.standings.length > 0 ? (\n <section>\n <div className=\"mb-4 flex flex-wrap items-center justify-between gap-3\">\n <h2 className=\"flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Activity className=\"h-5 w-5 text-text-muted\" />\n League standings\n {analytics.competition ? (\n <Badge variant=\"secondary\">{analytics.competition}</Badge>\n ) : null}\n </h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/standings')}\n className=\"text-sm font-medium text-[var(--color-accent-teal)] transition-colors hover:underline\"\n >\n View full standings\n </button>\n </div>\n <GlassCard treatment=\"insight\" className=\"p-2 sm:p-4\">\n <ResponsiveTable\n columns={standingsColumns}\n data={analytics.standings}\n rowKey={(row) => row.id}\n />\n </GlassCard>\n </section>\n ) : null}\n </div>\n )}\n </PageLayout>\n );\n}\n\ninterface TrainingLoadByAthleteProps {\n rows: AthleteTrainingLoad[];\n directory: UserDirectory;\n /** The sampled workout page did not reach back 28 days — loads under-count. */\n truncated: boolean;\n tr: TranslateFn;\n}\n\n/**\n * \"Training load by athlete\" — the same acute:chronic maths the workspace-wide\n * ACWR badge already uses, broken out per athlete from each workout log's own\n * `athleteId` + `totalLoad` + `performedAt`.\n *\n * Nothing here is synthesised: an athlete whose 28-day workouts carry no\n * `totalLoad` renders as em-dashes and the shared `acwrBand(null)` \"No data\"\n * badge rather than as a real zero, and when the sampled page stops short of\n * 28 days the section says so instead of presenting a floor as a total.\n */\nfunction TrainingLoadByAthlete({ rows, directory, truncated, tr }: TrainingLoadByAthleteProps) {\n const maxLoad = useMemo(\n () => rows.reduce((max, row) => Math.max(max, row.acute, row.chronic), 0),\n [rows]\n );\n\n const columns: ResponsiveTableColumn<AthleteTrainingLoad>[] = useMemo(\n () => [\n {\n key: 'athlete',\n header: tr('atheletebridge.pages.analytics.loadByAthlete.athlete', 'Athlete'),\n priority: 'primary',\n cell: (row) => (\n <span className=\"flex items-center gap-2\">\n <UserAvatar name={directory.nameOf(row.athleteId)} id={row.athleteId} size=\"xs\" />\n <span className=\"min-w-0\">\n <span className=\"block truncate font-medium text-text-primary\">\n {shortName(directory.nameOf(row.athleteId), row.athleteId, 'Athlete')}\n </span>\n <span className=\"block text-xs tabular-nums text-text-muted\">\n {row.workoutCount}{' '}\n {tr('atheletebridge.pages.analytics.loadByAthlete.workouts28d', 'workouts · 28d')}\n </span>\n </span>\n </span>\n ),\n },\n {\n key: 'acute',\n header: tr('atheletebridge.pages.analytics.loadByAthlete.acute', 'Acute load (7d)'),\n priority: 'secondary',\n mobileLabel: tr('atheletebridge.pages.analytics.loadByAthlete.acute', 'Acute load (7d)'),\n cell: (row) =>\n row.loadedCount === 0 ? (\n <span className=\"text-text-muted\">—</span>\n ) : (\n <span className=\"flex items-center gap-2\">\n <span className=\"w-14 text-right font-medium tabular-nums text-text-primary\">\n {fmtLoad(row.acute)}\n </span>\n <LoadBar value={row.acute} max={maxLoad} />\n </span>\n ),\n },\n {\n key: 'chronic',\n header: tr(\n 'atheletebridge.pages.analytics.loadByAthlete.chronic',\n 'Chronic load (28d avg)'\n ),\n priority: 'secondary',\n mobileLabel: tr(\n 'atheletebridge.pages.analytics.loadByAthlete.chronic',\n 'Chronic load (28d avg)'\n ),\n cell: (row) =>\n row.loadedCount === 0 ? (\n <span className=\"text-text-muted\">—</span>\n ) : (\n <span className=\"flex items-center gap-2\">\n <span className=\"w-14 text-right font-medium tabular-nums text-text-primary\">\n {fmtLoad(row.chronic)}\n </span>\n <LoadBar value={row.chronic} max={maxLoad} />\n </span>\n ),\n },\n {\n key: 'ratio',\n header: tr('atheletebridge.pages.analytics.loadByAthlete.ratio', 'Load ratio'),\n priority: 'secondary',\n mobileLabel: tr('atheletebridge.pages.analytics.loadByAthlete.ratio', 'Load ratio'),\n cell: (row) =>\n row.ratio != null ? (\n <span className=\"font-semibold tabular-nums text-text-primary\">\n {row.ratio.toFixed(2)}\n </span>\n ) : (\n <span className=\"text-text-muted\">—</span>\n ),\n },\n {\n key: 'band',\n header: tr('atheletebridge.pages.analytics.loadByAthlete.status', 'Status'),\n priority: 'primary',\n mobileLabel: tr('atheletebridge.pages.analytics.loadByAthlete.status', 'Status'),\n cell: (row) => {\n const rowBand = acwrBand(row.ratio);\n return <Badge variant={rowBand.variant}>{rowBand.label}</Badge>;\n },\n },\n ],\n [directory, maxLoad, tr]\n );\n\n return (\n <section>\n <div className=\"mb-4 flex flex-wrap items-center gap-2\">\n <h2 className=\"flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Users className=\"h-5 w-5 text-text-muted\" />\n {tr('atheletebridge.pages.analytics.loadByAthlete.title', 'Training load by athlete')}\n </h2>\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n aria-label={tr(\n 'atheletebridge.pages.analytics.loadByAthlete.aboutLabel',\n 'About per-athlete training load'\n )}\n className=\"rounded-full p-1 text-text-muted transition-colors hover:text-text-primary\"\n >\n <Info className=\"h-4 w-4\" />\n </button>\n </TooltipTrigger>\n <TooltipContent className=\"max-w-xs\">\n {tr(\n 'atheletebridge.pages.analytics.loadByAthlete.about',\n 'Each athlete’s acute:chronic workload ratio — their last 7 days of logged workout load against their 28-day weekly average. 0.8–1.3 is the typical sweet spot; above 1.5 is a spike worth reviewing.'\n )}\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n </div>\n\n {rows.length === 0 ? (\n <GlassCard treatment=\"insight\" className=\"p-6\">\n <p className=\"text-sm text-text-muted\">\n {tr(\n 'atheletebridge.pages.analytics.loadByAthlete.empty',\n 'No workouts logged in the last 28 days. Log workouts with a total load to compare athletes.'\n )}\n </p>\n </GlassCard>\n ) : (\n <>\n <GlassCard treatment=\"insight\" className=\"p-2 sm:p-4\">\n <ResponsiveTable\n columns={columns}\n data={rows}\n rowKey={(row) => row.athleteId}\n caption={tr(\n 'atheletebridge.pages.analytics.loadByAthlete.caption',\n 'Acute and chronic training load per athlete over the last 28 days'\n )}\n />\n </GlassCard>\n {truncated ? (\n <p className=\"mt-3 text-xs text-text-muted\">\n {tr(\n 'atheletebridge.pages.analytics.loadByAthlete.truncated',\n 'Computed from the most recent page of workout logs, which does not reach back a full 28 days — these loads are a floor, not a total.'\n )}\n </p>\n ) : null}\n </>\n )}\n </section>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CoachAnalyticsPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/analytics/CoachAnalyticsPage.tsx"],"sourcesContent":["/**\n * Coach Analytics\n * @module atheletebridge/pages/analytics\n *\n * An honest coaching-analytics dashboard built ENTIRELY from real aggregates via\n * `useCoachAnalytics()` — total sessions, average session rating, completion\n * rate, session-status mix, average rating by coach, weekly training-load /\n * session trends, a per-athlete acute:chronic training-load table, and a\n * league-standings snapshot.\n *\n * Deliberately OMITS the mock's fabricated composite indices (Form Index,\n * Fitness Score, Fatigue Index, Injury Status, Availability %) — there is no\n * wellness / injury / availability data in the schema, so those are not faked.\n * The one derived metric shown (ACWR training load) uses standard sports-science\n * maths over real `workout.totalLoad` and carries an explanatory tooltip — both\n * workspace-wide and, in the per-athlete table, grouped by each log's real\n * `athleteId`. Where an athlete's workouts carry no recorded load the table\n * says so rather than showing a fabricated zero.\n * Semantic tokens only; responsive.\n */\n\nimport { useMemo } from 'react';\nimport { Activity, BarChart3, CheckCircle2, Info, ListChecks, Star, Users } from 'lucide-react';\nimport {\n Badge,\n GlassCard,\n GradientText,\n ResponsiveTable,\n type ResponsiveTableColumn,\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { Crest } from '../../components/Crest';\nimport { UserAvatar } from '../../components/UserAvatar';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { LineChart, BarChart, DonutChart, RatingStars, STATUS } from '../../components/viz';\nimport { humanizeEnum } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useAbTranslate, type TranslateFn } from '../../i18n';\nimport {\n useCoachAnalytics,\n useUserDirectory,\n type AthleteTrainingLoad,\n type UserDirectory,\n} from '../../hooks/useAthleteBridgeApi';\nimport type { AthSessionStatus, AthStanding } from '../../types';\n\n/** Tokenized color per session status for the status donut. */\nconst STATUS_COLOR: Record<AthSessionStatus, string> = {\n COMPLETED: STATUS.success,\n CONFIRMED: STATUS.info,\n REQUESTED: STATUS.warning,\n CANCELLED: STATUS.danger,\n NO_SHOW: 'var(--color-text-muted)',\n};\n\n/** ACWR banding (acute:chronic workload ratio) — standard sweet-spot 0.8–1.3. */\nfunction acwrBand(ratio: number | null, tr: TranslateFn): {\n label: string;\n variant: 'default' | 'secondary' | 'destructive';\n} {\n if (ratio == null) {\n return { label: tr('atheletebridge.pages.analytics.acwr.noData', 'No data'), variant: 'secondary' };\n }\n if (ratio > 1.5) {\n return { label: tr('atheletebridge.pages.analytics.acwr.atRisk', 'At risk'), variant: 'destructive' };\n }\n if (ratio < 0.8 || ratio > 1.3) {\n return { label: tr('atheletebridge.pages.analytics.acwr.watch', 'Watch'), variant: 'secondary' };\n }\n return { label: tr('atheletebridge.pages.analytics.acwr.optimal', 'Optimal'), variant: 'default' };\n}\n\n/**\n * Short person display: the resolved directory name, else a monogram-friendly\n * `<role> <id fragment>` so an unresolvable id still reads as a person rather\n * than as a raw uuid.\n */\nfunction shortName(name: string | undefined, id: string, rolePrefix: string): string {\n if (name && name.trim()) {\n return name;\n }\n return `${rolePrefix} ${id.slice(0, 4)}`;\n}\n\n/** Whole-number load display; loads are arbitrary units, so no decimals. */\nconst fmtLoad = (value: number): string => Math.round(value).toLocaleString();\n\n/**\n * A load bar scaled against the heaviest load in the table, so acute and\n * chronic columns stay visually comparable to each other and across rows.\n * Purely decorative — the number beside it is the fact.\n */\nfunction LoadBar({ value, max }: { value: number; max: number }) {\n const pct = max > 0 ? Math.round((value / max) * 100) : 0;\n return (\n <span className=\"block h-1.5 w-16 overflow-hidden rounded-full bg-bg-sunken\" aria-hidden=\"true\">\n <span\n className=\"block h-1.5 rounded-full bg-[var(--color-accent-teal)]\"\n style={{ width: `${Math.max(value > 0 ? 4 : 0, Math.min(100, pct))}%` }}\n />\n </span>\n );\n}\n\nexport function CoachAnalyticsPage() {\n const tr = useAbTranslate();\n const { navigateTo } = useAthleteBridge();\n const analytics = useCoachAnalytics();\n // One directory round-trip for both surfaces — `useUsersByIds` dedupes/sorts.\n const directoryIds = useMemo(\n () => [...analytics.coachIds, ...analytics.athleteIds],\n [analytics.coachIds, analytics.athleteIds]\n );\n const directory = useUserDirectory(directoryIds);\n\n const statusSegments = useMemo(\n () =>\n analytics.sessionsByStatus.map((entry) => ({\n label: humanizeEnum(entry.status),\n value: entry.count,\n color: STATUS_COLOR[entry.status],\n })),\n [analytics.sessionsByStatus]\n );\n\n const coachBars = useMemo(\n () =>\n analytics.ratingByCoach.map((entry) => ({\n label: shortName(directory.nameOf(entry.coachId), entry.coachId, tr('atheletebridge.rolePrefix.coach', 'Coach')),\n value: Number(entry.avgRating.toFixed(2)),\n })),\n [analytics.ratingByCoach, directory, tr]\n );\n\n const standingsColumns: ResponsiveTableColumn<AthStanding>[] = [\n {\n key: 'rank',\n header: tr('atheletebridge.pages.analytics.standings.rank', '#'),\n priority: 'secondary',\n cell: (row, i) => (\n <span className=\"tabular-nums text-text-secondary\">{row.rank ?? i + 1}</span>\n ),\n },\n {\n key: 'team',\n header: tr('atheletebridge.pages.analytics.standings.team', 'Team'),\n priority: 'primary',\n cell: (row) => (\n <span className=\"flex items-center gap-2\">\n <Crest\n name={row.team?.name ?? tr('atheletebridge.pages.analytics.standings.teamFallback', 'Team')}\n sport={row.team?.sport}\n size={20}\n showSportGlyph={false}\n />\n <span className=\"truncate font-medium text-text-primary\">{row.team?.name ?? tr('atheletebridge.pages.analytics.standings.teamFallback', 'Team')}</span>\n </span>\n ),\n },\n {\n key: 'played',\n header: tr('atheletebridge.pages.analytics.standings.played', 'P'),\n priority: 'hidden-on-mobile',\n cell: (row) => <span className=\"tabular-nums text-text-secondary\">{row.played}</span>,\n },\n {\n key: 'gd',\n header: tr('atheletebridge.pages.analytics.standings.gd', 'GD'),\n priority: 'hidden-on-mobile',\n cell: (row) => {\n const gd = row.goalsFor - row.goalsAgainst;\n return (\n <span className=\"tabular-nums text-text-secondary\">\n {gd > 0 ? '+' : ''}\n {gd}\n </span>\n );\n },\n },\n {\n key: 'points',\n header: tr('atheletebridge.pages.analytics.standings.points', 'Pts'),\n priority: 'secondary',\n cell: (row) => (\n <span className=\"font-semibold tabular-nums text-text-primary\">{row.points}</span>\n ),\n },\n ];\n\n if (analytics.isLoading) {\n return (\n <PageLayout title={tr('atheletebridge.pages.analytics.title', 'Coach analytics')}>\n <LoadingState label={tr('atheletebridge.pages.analytics.loading', 'Crunching coaching data…')} />\n </PageLayout>\n );\n }\n\n if (analytics.isError) {\n return (\n <PageLayout title={tr('atheletebridge.pages.analytics.title', 'Coach analytics')}>\n <ErrorState\n title={tr('atheletebridge.pages.analytics.errorTitle', 'Analytics unavailable')}\n message={tr('atheletebridge.pages.analytics.errorMessage', 'We could not load coaching analytics. Please try again.')}\n />\n </PageLayout>\n );\n }\n\n const hasData =\n analytics.totalSessions > 0 ||\n analytics.standings.length > 0 ||\n analytics.loadByAthlete.length > 0;\n const band = acwrBand(analytics.trainingLoad.ratio, tr);\n\n return (\n <PageLayout\n title={\n <span className=\"flex items-center gap-2\">\n <GradientText>{tr('atheletebridge.pages.analytics.title', 'Coach analytics')}</GradientText>\n <Badge variant=\"outline\">{tr('common.beta', 'Beta')}</Badge>\n </span>\n }\n description={tr('atheletebridge.pages.analytics.description', 'Honest aggregates over your real sessions, workouts and results.')}\n >\n {!hasData ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<BarChart3 className=\"h-10 w-10\" />}\n title={tr('atheletebridge.pages.analytics.emptyTitle', 'No coaching data yet')}\n description={tr('atheletebridge.pages.analytics.emptyDescription', 'Book and complete sessions, log workouts and record fixtures to unlock analytics.')}\n />\n ) : (\n <div className=\"space-y-8\">\n {/* KPI row */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <MetricsCard\n icon={<ListChecks className=\"h-5 w-5\" />}\n value={analytics.totalSessions}\n label={tr('atheletebridge.pages.analytics.totalSessions', 'Total sessions')}\n tone=\"accent\"\n />\n <GlassCard treatment=\"metric\" className=\"flex flex-col justify-between gap-3 p-5\">\n <div className=\"flex items-start justify-between gap-2\">\n <span className=\"text-sm font-medium text-text-secondary\">{tr('atheletebridge.pages.analytics.avgRating', 'Avg session rating')}</span>\n <Star className=\"h-5 w-5 text-text-muted\" />\n </div>\n {analytics.avgRating != null ? (\n <div className=\"flex items-end gap-3\">\n <span className=\"text-3xl font-semibold leading-none tabular-nums text-text-primary\">\n {analytics.avgRating.toFixed(1)}\n <span className=\"ml-1 text-base text-text-secondary\">{tr('atheletebridge.pages.analytics.ratingOutOf', '/5')}</span>\n </span>\n <div className=\"pb-1\">\n <RatingStars value={analytics.avgRating} max={5} size={16} />\n </div>\n </div>\n ) : (\n <span className=\"text-sm text-text-muted\">{tr('atheletebridge.pages.analytics.noRatedSessions', 'No rated sessions yet')}</span>\n )}\n <span className=\"text-xs text-text-muted tabular-nums\">\n {analytics.ratedCount}{' '}\n {tr('atheletebridge.pages.analytics.ratedOf', 'rated of')}{' '}\n {analytics.totalSessions}\n </span>\n </GlassCard>\n <MetricsCard\n icon={<CheckCircle2 className=\"h-5 w-5\" />}\n value={`${Math.round(analytics.completedPct)}%`}\n label={tr('atheletebridge.pages.analytics.sessionsCompleted', 'Sessions completed')}\n tone=\"success\"\n />\n </div>\n\n {/* Charts row: status donut + rating-by-coach bars */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <GlassCard treatment=\"insight\" className=\"p-6\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">\n {tr('atheletebridge.pages.analytics.statusOverviewTitle', 'Session status overview')}\n </h2>\n {statusSegments.length > 0 ? (\n <DonutChart\n segments={statusSegments}\n centerLabel={analytics.totalSessions}\n centerCaption={tr('atheletebridge.pages.analytics.sessionsCaption', 'sessions')}\n valueFormatter={(v) => v.toLocaleString()}\n />\n ) : (\n <p className=\"text-sm text-text-muted\">{tr('atheletebridge.pages.analytics.statusOverviewEmpty', 'No sessions to break down yet.')}</p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"insight\" className=\"p-6\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">\n {tr('atheletebridge.pages.analytics.ratingByCoachTitle', 'Average rating by coach')}\n </h2>\n {coachBars.length > 0 ? (\n <>\n <BarChart\n series={coachBars}\n height={220}\n colorful\n valueFormatter={(v) => v.toFixed(1)}\n />\n <div className=\"mt-3 flex flex-wrap gap-x-4 gap-y-2\">\n {analytics.ratingByCoach.map((entry) => (\n <span\n key={entry.coachId}\n className=\"flex items-center gap-2 text-xs text-text-secondary\"\n >\n <UserAvatar\n name={directory.nameOf(entry.coachId)}\n id={entry.coachId}\n size=\"xs\"\n />\n <span className=\"truncate\">\n {shortName(directory.nameOf(entry.coachId), entry.coachId, tr('atheletebridge.rolePrefix.coach', 'Coach'))}\n <span className=\"ml-1 text-text-muted tabular-nums\">({entry.count})</span>\n </span>\n </span>\n ))}\n </div>\n </>\n ) : (\n <p className=\"text-sm text-text-muted\">\n {tr('atheletebridge.pages.analytics.ratingByCoachEmpty', 'Rate completed sessions to compare coaches.')}\n </p>\n )}\n </GlassCard>\n </div>\n\n {/* Trends row: training load + sessions per week */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <GlassCard treatment=\"insight\" className=\"p-6\">\n <div className=\"mb-4 flex items-center justify-between gap-2\">\n <h2 className=\"text-lg font-semibold text-text-primary\">{tr('atheletebridge.pages.analytics.weeklyLoadTitle', 'Weekly training load')}</h2>\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n aria-label={tr('atheletebridge.pages.analytics.acwrAboutLabel', 'About ACWR training load')}\n className=\"rounded-full p-1 text-text-muted transition-colors hover:text-text-primary\"\n >\n <Info className=\"h-4 w-4\" />\n </button>\n </TooltipTrigger>\n <TooltipContent className=\"max-w-xs\">\n {tr(\n 'atheletebridge.pages.analytics.acwrAbout',\n 'Total workout load per week (sum of real totalLoad). The badge is the acute:chronic workload ratio (last 7 days vs 28-day average) — a standard sports-science load indicator; 0.8–1.3 is the typical sweet spot.'\n )}\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n </div>\n {analytics.loadTrend.some((p) => p.value > 0) ? (\n <>\n <LineChart\n series={analytics.loadTrend}\n seriesLabel={tr('atheletebridge.pages.analytics.trainingLoadLabel', 'Training load')}\n height={200}\n valueFormatter={(v) => v.toLocaleString()}\n />\n <div className=\"mt-4 flex flex-wrap items-center gap-x-6 gap-y-2 text-sm\">\n <span className=\"text-text-secondary\">\n {tr('atheletebridge.pages.analytics.acuteLoad', 'Acute (7d)')}{' '}\n <span className=\"font-semibold tabular-nums text-text-primary\">\n {Math.round(analytics.trainingLoad.acute).toLocaleString()}\n </span>\n </span>\n <span className=\"text-text-secondary\">\n {tr('atheletebridge.pages.analytics.chronicLoad', 'Chronic (28d avg)')}{' '}\n <span className=\"font-semibold tabular-nums text-text-primary\">\n {Math.round(analytics.trainingLoad.chronic).toLocaleString()}\n </span>\n </span>\n <span className=\"flex items-center gap-2 text-text-secondary\">\n {tr('atheletebridge.pages.analytics.acwrAbbreviation', 'ACWR')}\n <Badge variant={band.variant} className=\"tabular-nums\">\n {analytics.trainingLoad.ratio != null\n ? `${analytics.trainingLoad.ratio.toFixed(2)} · ${band.label}`\n : band.label}\n </Badge>\n </span>\n </div>\n </>\n ) : (\n <p className=\"text-sm text-text-muted\">\n {tr('atheletebridge.pages.analytics.weeklyLoadEmpty', 'Log workouts with a load to see training trend.')}\n </p>\n )}\n </GlassCard>\n\n <GlassCard treatment=\"insight\" className=\"p-6\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">{tr('atheletebridge.pages.analytics.sessionsPerWeekTitle', 'Sessions per week')}</h2>\n {analytics.sessionTrend.some((p) => p.value > 0) ? (\n <BarChart\n series={analytics.sessionTrend}\n height={200}\n valueFormatter={(v) => v.toLocaleString()}\n />\n ) : (\n <p className=\"text-sm text-text-muted\">{tr('atheletebridge.pages.analytics.sessionsPerWeekEmpty', 'Schedule sessions to see weekly volume.')}</p>\n )}\n </GlassCard>\n </div>\n\n {/* Training load by athlete — the workspace ACWR, broken out per athlete */}\n <TrainingLoadByAthlete\n rows={analytics.loadByAthlete}\n directory={directory}\n truncated={analytics.loadSampleTruncated}\n tr={tr}\n />\n\n {/* League standings snapshot */}\n {analytics.standings.length > 0 ? (\n <section>\n <div className=\"mb-4 flex flex-wrap items-center justify-between gap-3\">\n <h2 className=\"flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Activity className=\"h-5 w-5 text-text-muted\" />\n {tr('atheletebridge.pages.analytics.standingsTitle', 'League standings')}\n {analytics.competition ? (\n <Badge variant=\"secondary\">{analytics.competition}</Badge>\n ) : null}\n </h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/standings')}\n className=\"text-sm font-medium text-[var(--color-accent-teal)] transition-colors hover:underline\"\n >\n {tr('atheletebridge.pages.analytics.viewFullStandings', 'View full standings')}\n </button>\n </div>\n <GlassCard treatment=\"insight\" className=\"p-2 sm:p-4\">\n <ResponsiveTable\n columns={standingsColumns}\n data={analytics.standings}\n rowKey={(row) => row.id}\n />\n </GlassCard>\n </section>\n ) : null}\n </div>\n )}\n </PageLayout>\n );\n}\n\ninterface TrainingLoadByAthleteProps {\n rows: AthleteTrainingLoad[];\n directory: UserDirectory;\n /** The sampled workout page did not reach back 28 days — loads under-count. */\n truncated: boolean;\n tr: TranslateFn;\n}\n\n/**\n * \"Training load by athlete\" — the same acute:chronic maths the workspace-wide\n * ACWR badge already uses, broken out per athlete from each workout log's own\n * `athleteId` + `totalLoad` + `performedAt`.\n *\n * Nothing here is synthesised: an athlete whose 28-day workouts carry no\n * `totalLoad` renders as em-dashes and the shared `acwrBand(null)` \"No data\"\n * badge rather than as a real zero, and when the sampled page stops short of\n * 28 days the section says so instead of presenting a floor as a total.\n */\nfunction TrainingLoadByAthlete({ rows, directory, truncated, tr }: TrainingLoadByAthleteProps) {\n const maxLoad = useMemo(\n () => rows.reduce((max, row) => Math.max(max, row.acute, row.chronic), 0),\n [rows]\n );\n\n const columns: ResponsiveTableColumn<AthleteTrainingLoad>[] = useMemo(\n () => [\n {\n key: 'athlete',\n header: tr('atheletebridge.pages.analytics.loadByAthlete.athlete', 'Athlete'),\n priority: 'primary',\n cell: (row) => (\n <span className=\"flex items-center gap-2\">\n <UserAvatar name={directory.nameOf(row.athleteId)} id={row.athleteId} size=\"xs\" />\n <span className=\"min-w-0\">\n <span className=\"block truncate font-medium text-text-primary\">\n {shortName(directory.nameOf(row.athleteId), row.athleteId, tr('atheletebridge.rolePrefix.athlete', 'Athlete'))}\n </span>\n <span className=\"block text-xs tabular-nums text-text-muted\">\n {row.workoutCount}{' '}\n {tr('atheletebridge.pages.analytics.loadByAthlete.workouts28d', 'workouts · 28d')}\n </span>\n </span>\n </span>\n ),\n },\n {\n key: 'acute',\n header: tr('atheletebridge.pages.analytics.loadByAthlete.acute', 'Acute load (7d)'),\n priority: 'secondary',\n mobileLabel: tr('atheletebridge.pages.analytics.loadByAthlete.acute', 'Acute load (7d)'),\n cell: (row) =>\n row.loadedCount === 0 ? (\n <span className=\"text-text-muted\">—</span>\n ) : (\n <span className=\"flex items-center gap-2\">\n <span className=\"w-14 text-right font-medium tabular-nums text-text-primary\">\n {fmtLoad(row.acute)}\n </span>\n <LoadBar value={row.acute} max={maxLoad} />\n </span>\n ),\n },\n {\n key: 'chronic',\n header: tr(\n 'atheletebridge.pages.analytics.loadByAthlete.chronic',\n 'Chronic load (28d avg)'\n ),\n priority: 'secondary',\n mobileLabel: tr(\n 'atheletebridge.pages.analytics.loadByAthlete.chronic',\n 'Chronic load (28d avg)'\n ),\n cell: (row) =>\n row.loadedCount === 0 ? (\n <span className=\"text-text-muted\">—</span>\n ) : (\n <span className=\"flex items-center gap-2\">\n <span className=\"w-14 text-right font-medium tabular-nums text-text-primary\">\n {fmtLoad(row.chronic)}\n </span>\n <LoadBar value={row.chronic} max={maxLoad} />\n </span>\n ),\n },\n {\n key: 'ratio',\n header: tr('atheletebridge.pages.analytics.loadByAthlete.ratio', 'Load ratio'),\n priority: 'secondary',\n mobileLabel: tr('atheletebridge.pages.analytics.loadByAthlete.ratio', 'Load ratio'),\n cell: (row) =>\n row.ratio != null ? (\n <span className=\"font-semibold tabular-nums text-text-primary\">\n {row.ratio.toFixed(2)}\n </span>\n ) : (\n <span className=\"text-text-muted\">—</span>\n ),\n },\n {\n key: 'band',\n header: tr('atheletebridge.pages.analytics.loadByAthlete.status', 'Status'),\n priority: 'primary',\n mobileLabel: tr('atheletebridge.pages.analytics.loadByAthlete.status', 'Status'),\n cell: (row) => {\n const rowBand = acwrBand(row.ratio, tr);\n return <Badge variant={rowBand.variant}>{rowBand.label}</Badge>;\n },\n },\n ],\n [directory, maxLoad, tr]\n );\n\n return (\n <section>\n <div className=\"mb-4 flex flex-wrap items-center gap-2\">\n <h2 className=\"flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Users className=\"h-5 w-5 text-text-muted\" />\n {tr('atheletebridge.pages.analytics.loadByAthlete.title', 'Training load by athlete')}\n </h2>\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n aria-label={tr(\n 'atheletebridge.pages.analytics.loadByAthlete.aboutLabel',\n 'About per-athlete training load'\n )}\n className=\"rounded-full p-1 text-text-muted transition-colors hover:text-text-primary\"\n >\n <Info className=\"h-4 w-4\" />\n </button>\n </TooltipTrigger>\n <TooltipContent className=\"max-w-xs\">\n {tr(\n 'atheletebridge.pages.analytics.loadByAthlete.about',\n 'Each athlete’s acute:chronic workload ratio — their last 7 days of logged workout load against their 28-day weekly average. 0.8–1.3 is the typical sweet spot; above 1.5 is a spike worth reviewing.'\n )}\n </TooltipContent>\n </Tooltip>\n </TooltipProvider>\n </div>\n\n {rows.length === 0 ? (\n <GlassCard treatment=\"insight\" className=\"p-6\">\n <p className=\"text-sm text-text-muted\">\n {tr(\n 'atheletebridge.pages.analytics.loadByAthlete.empty',\n 'No workouts logged in the last 28 days. Log workouts with a total load to compare athletes.'\n )}\n </p>\n </GlassCard>\n ) : (\n <>\n <GlassCard treatment=\"insight\" className=\"p-2 sm:p-4\">\n <ResponsiveTable\n columns={columns}\n data={rows}\n rowKey={(row) => row.athleteId}\n caption={tr(\n 'atheletebridge.pages.analytics.loadByAthlete.caption',\n 'Acute and chronic training load per athlete over the last 28 days'\n )}\n />\n </GlassCard>\n {truncated ? (\n <p className=\"mt-3 text-xs text-text-muted\">\n {tr(\n 'atheletebridge.pages.analytics.loadByAthlete.truncated',\n 'Computed from the most recent page of workout logs, which does not reach back a full 28 days — these loads are a floor, not a total.'\n )}\n </p>\n ) : null}\n </>\n )}\n </section>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FixtureDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/fixtures/FixtureDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;
|
|
1
|
+
{"version":3,"file":"FixtureDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/fixtures/FixtureDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAkBH,wBAAgB,iBAAiB,gCAuLhC"}
|