@burdenoff/microfe-atheletebridge 2026.827.1 → 2026.829.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/atheletebridge/components/LoadingState.d.ts.map +1 -1
  2. package/dist/atheletebridge/components/LoadingState.js +39 -32
  3. package/dist/atheletebridge/components/LoadingState.js.map +1 -1
  4. package/dist/atheletebridge/components/TeamCard.d.ts.map +1 -1
  5. package/dist/atheletebridge/components/TeamCard.js +45 -49
  6. package/dist/atheletebridge/components/TeamCard.js.map +1 -1
  7. package/dist/atheletebridge/pages/DashboardPage.d.ts.map +1 -1
  8. package/dist/atheletebridge/pages/DashboardPage.js +56 -24
  9. package/dist/atheletebridge/pages/DashboardPage.js.map +1 -1
  10. package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.d.ts.map +1 -1
  11. package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js +58 -56
  12. package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js.map +1 -1
  13. package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.d.ts.map +1 -1
  14. package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +110 -106
  15. package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
  16. package/dist/atheletebridge/pages/fixtures/FixturesListPage.d.ts.map +1 -1
  17. package/dist/atheletebridge/pages/fixtures/FixturesListPage.js +80 -71
  18. package/dist/atheletebridge/pages/fixtures/FixturesListPage.js.map +1 -1
  19. package/dist/atheletebridge/pages/onboarding/OnboardingPage.d.ts.map +1 -1
  20. package/dist/atheletebridge/pages/onboarding/OnboardingPage.js +244 -210
  21. package/dist/atheletebridge/pages/onboarding/OnboardingPage.js.map +1 -1
  22. package/dist/atheletebridge/pages/performance/PerformancePage.d.ts.map +1 -1
  23. package/dist/atheletebridge/pages/performance/PerformancePage.js +197 -148
  24. package/dist/atheletebridge/pages/performance/PerformancePage.js.map +1 -1
  25. package/dist/atheletebridge/pages/programs/ProgramDetailPage.d.ts.map +1 -1
  26. package/dist/atheletebridge/pages/programs/ProgramDetailPage.js +169 -143
  27. package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
  28. package/dist/atheletebridge/pages/programs/ProgramsListPage.d.ts.map +1 -1
  29. package/dist/atheletebridge/pages/programs/ProgramsListPage.js +77 -72
  30. package/dist/atheletebridge/pages/programs/ProgramsListPage.js.map +1 -1
  31. package/dist/atheletebridge/pages/sessions/SessionDetailPage.d.ts.map +1 -1
  32. package/dist/atheletebridge/pages/sessions/SessionDetailPage.js +104 -86
  33. package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -1
  34. package/dist/atheletebridge/pages/sessions/SessionsListPage.d.ts.map +1 -1
  35. package/dist/atheletebridge/pages/sessions/SessionsListPage.js +54 -49
  36. package/dist/atheletebridge/pages/sessions/SessionsListPage.js.map +1 -1
  37. package/dist/atheletebridge/pages/teams/TeamDetailPage.d.ts.map +1 -1
  38. package/dist/atheletebridge/pages/teams/TeamDetailPage.js +286 -267
  39. package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -1
  40. package/dist/atheletebridge/pages/teams/TeamFormPage.d.ts.map +1 -1
  41. package/dist/atheletebridge/pages/teams/TeamFormPage.js +185 -180
  42. package/dist/atheletebridge/pages/teams/TeamFormPage.js.map +1 -1
  43. package/dist/atheletebridge/pages/teams/TeamsListPage.d.ts.map +1 -1
  44. package/dist/atheletebridge/pages/teams/TeamsListPage.js +70 -74
  45. package/dist/atheletebridge/pages/teams/TeamsListPage.js.map +1 -1
  46. 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
- })), [z.ratingByCoach, B]), W = [
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
- " rated of ",
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__ */ D(N, {
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(\n ratio: number | null,\n tr: TranslateFn\n): {\n label: string;\n variant: 'default' | 'secondary' | 'destructive';\n} {\n if (ratio == null) {\n return {\n label: tr('atheletebridge.pages.analytics.acwr.noData', 'No data'),\n variant: 'secondary',\n };\n }\n if (ratio > 1.5) {\n return {\n label: tr('atheletebridge.pages.analytics.acwr.atRisk', 'At risk'),\n variant: 'destructive',\n };\n }\n if (ratio < 0.8 || ratio > 1.3) {\n return {\n label: tr('atheletebridge.pages.analytics.acwr.watch', 'Watch'),\n variant: 'secondary',\n };\n }\n return {\n label: tr('atheletebridge.pages.analytics.acwr.optimal', 'Optimal'),\n variant: 'default',\n };\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(\n directory.nameOf(entry.coachId),\n entry.coachId,\n tr('atheletebridge.rolePrefix.coach', 'Coach')\n ),\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={\n row.team?.name ?? tr('atheletebridge.pages.analytics.standings.teamFallback', 'Team')\n }\n sport={row.team?.sport}\n size={20}\n showSportGlyph={false}\n />\n <span className=\"truncate font-medium text-text-primary\">\n {row.team?.name ?? tr('atheletebridge.pages.analytics.standings.teamFallback', 'Team')}\n </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\n label={tr('atheletebridge.pages.analytics.loading', 'Crunching coaching data…')}\n />\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(\n 'atheletebridge.pages.analytics.errorMessage',\n 'We could not load coaching analytics. Please try again.'\n )}\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>\n {tr('atheletebridge.pages.analytics.title', 'Coach analytics')}\n </GradientText>\n <Badge variant=\"outline\">{tr('common.beta', 'Beta')}</Badge>\n </span>\n }\n description={tr(\n 'atheletebridge.pages.analytics.description',\n 'Honest aggregates over your real sessions, workouts and results.'\n )}\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(\n 'atheletebridge.pages.analytics.emptyDescription',\n 'Book and complete sessions, log workouts and record fixtures to unlock analytics.'\n )}\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\">\n {tr('atheletebridge.pages.analytics.avgRating', 'Avg session rating')}\n </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\">\n {tr('atheletebridge.pages.analytics.ratingOutOf', '/5')}\n </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\">\n {tr('atheletebridge.pages.analytics.noRatedSessions', 'No rated sessions yet')}\n </span>\n )}\n <span className=\"text-xs text-text-muted tabular-nums\">\n {analytics.ratedCount} {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(\n 'atheletebridge.pages.analytics.statusOverviewTitle',\n 'Session status overview'\n )}\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\">\n {tr(\n 'atheletebridge.pages.analytics.statusOverviewEmpty',\n 'No sessions to break down yet.'\n )}\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\">\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(\n directory.nameOf(entry.coachId),\n entry.coachId,\n tr('atheletebridge.rolePrefix.coach', 'Coach')\n )}\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(\n 'atheletebridge.pages.analytics.ratingByCoachEmpty',\n 'Rate completed sessions to compare coaches.'\n )}\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\">\n {tr('atheletebridge.pages.analytics.weeklyLoadTitle', 'Weekly training load')}\n </h2>\n <TooltipProvider>\n <Tooltip>\n <TooltipTrigger asChild>\n <button\n type=\"button\"\n aria-label={tr(\n 'atheletebridge.pages.analytics.acwrAboutLabel',\n 'About ACWR 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.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(\n 'atheletebridge.pages.analytics.trainingLoadLabel',\n 'Training load'\n )}\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(\n 'atheletebridge.pages.analytics.weeklyLoadEmpty',\n 'Log workouts with a load to see training trend.'\n )}\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\">\n {tr('atheletebridge.pages.analytics.sessionsPerWeekTitle', 'Sessions per week')}\n </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\">\n {tr(\n 'atheletebridge.pages.analytics.sessionsPerWeekEmpty',\n 'Schedule sessions to see weekly volume.'\n )}\n </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(\n directory.nameOf(row.athleteId),\n row.athleteId,\n tr('atheletebridge.rolePrefix.athlete', 'Athlete')\n )}\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;AAiBH,wBAAgB,iBAAiB,gCAiLhC"}
1
+ {"version":3,"file":"FixtureDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/fixtures/FixtureDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAkBH,wBAAgB,iBAAiB,gCA0MhC"}