@burdenoff/microfe-atheletebridge 2026.629.1 → 2026.705.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 (36) hide show
  1. package/dist/atheletebridge/AthleteBridgeRoot.js +45 -37
  2. package/dist/atheletebridge/AthleteBridgeRoot.js.map +1 -1
  3. package/dist/atheletebridge/AthleteBridgeRoutes.js +10 -6
  4. package/dist/atheletebridge/AthleteBridgeRoutes.js.map +1 -1
  5. package/dist/atheletebridge/components/AthleteSelect.js +66 -0
  6. package/dist/atheletebridge/components/AthleteSelect.js.map +1 -0
  7. package/dist/atheletebridge/components/DrillVideoLink.js +28 -0
  8. package/dist/atheletebridge/components/DrillVideoLink.js.map +1 -0
  9. package/dist/atheletebridge/components/MetricsCard.js +20 -46
  10. package/dist/atheletebridge/components/MetricsCard.js.map +1 -1
  11. package/dist/atheletebridge/components/UserSearchSelect.js +129 -0
  12. package/dist/atheletebridge/components/UserSearchSelect.js.map +1 -0
  13. package/dist/atheletebridge/components/domain.js +10 -1
  14. package/dist/atheletebridge/components/domain.js.map +1 -1
  15. package/dist/atheletebridge/hooks/useAthleteBridgeApi.js +313 -180
  16. package/dist/atheletebridge/hooks/useAthleteBridgeApi.js.map +1 -1
  17. package/dist/atheletebridge/pages/DashboardPage.js +77 -69
  18. package/dist/atheletebridge/pages/DashboardPage.js.map +1 -1
  19. package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +4 -4
  20. package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
  21. package/dist/atheletebridge/pages/performance/PerformancePage.js +496 -0
  22. package/dist/atheletebridge/pages/performance/PerformancePage.js.map +1 -0
  23. package/dist/atheletebridge/pages/programs/ProgramDetailPage.js +176 -142
  24. package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
  25. package/dist/atheletebridge/pages/sessions/SessionDetailPage.js +86 -85
  26. package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -1
  27. package/dist/atheletebridge/pages/sessions/SessionFormPage.js +92 -98
  28. package/dist/atheletebridge/pages/sessions/SessionFormPage.js.map +1 -1
  29. package/dist/atheletebridge/pages/teams/TeamDetailPage.js +132 -137
  30. package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -1
  31. package/dist/atheletebridge/utils/formatters.js +2 -25
  32. package/dist/atheletebridge/utils/formatters.js.map +1 -1
  33. package/dist/atheletebridge/utils/url.js +17 -0
  34. package/dist/atheletebridge/utils/url.js.map +1 -0
  35. package/dist/index.js +4 -4
  36. package/package.json +5 -5
@@ -0,0 +1,496 @@
1
+ import { PageLayout as e } from "../../components/PageLayout.js";
2
+ import { StatusBadge as t } from "../../components/StatusBadge.js";
3
+ import { METRIC_TYPE_LABELS as n } from "../../components/domain.js";
4
+ import { formatDate as r, formatDuration as i } from "../../utils/formatters.js";
5
+ import { EmptyState as a } from "../../components/EmptyState.js";
6
+ import { ErrorState as o, LoadingState as s } from "../../components/LoadingState.js";
7
+ import { useLogWorkout as c, usePerformanceMetrics as l, useRecordMetric as u, useWorkoutLogs as d } from "../../hooks/useAthleteBridgeApi.js";
8
+ import { FormField as f, fieldInputClass as p } from "../../components/form.js";
9
+ import { CursorPager as m } from "../../components/CursorPager.js";
10
+ import { AthleteSelect as h } from "../../components/AthleteSelect.js";
11
+ import { useMemo as g, useState as _ } from "react";
12
+ import { Activity as v, Dumbbell as y, Gauge as b, Loader2 as x, Plus as S, Save as C, Users as w } from "lucide-react";
13
+ import { Fragment as T, jsx as E, jsxs as D } from "react/jsx-runtime";
14
+ import { EmphasisPanel as O, GlassCard as k, ResponsiveDialog as A, ResponsiveDialogContent as j, ResponsiveDialogFooter as M, ResponsiveDialogHeader as N, ResponsiveDialogTitle as P } from "@burdenoff/fe-libs/ui";
15
+ //#region src/atheletebridge/pages/performance/PerformancePage.tsx
16
+ var F = 10, I = [
17
+ "COMPLETED",
18
+ "PLANNED",
19
+ "SKIPPED"
20
+ ], L = Object.keys(n), R = (e) => {
21
+ let t = e.trim();
22
+ if (!t) return;
23
+ let n = Number.parseInt(t, 10);
24
+ return Number.isNaN(n) ? void 0 : n;
25
+ }, z = (e) => {
26
+ let t = e.trim();
27
+ if (!t) return;
28
+ let n = Number.parseFloat(t);
29
+ return Number.isNaN(n) ? void 0 : n;
30
+ }, B = () => {
31
+ let e = /* @__PURE__ */ new Date();
32
+ return (/* @__PURE__ */ new Date(e.getTime() - e.getTimezoneOffset() * 6e4)).toISOString().slice(0, 16);
33
+ };
34
+ function V() {
35
+ let [t, n] = _({
36
+ athleteId: "",
37
+ displayName: ""
38
+ });
39
+ return /* @__PURE__ */ D(e, {
40
+ title: "Performance",
41
+ description: "Log workouts, record metrics and track an athlete's progress over time.",
42
+ children: [/* @__PURE__ */ E(k, {
43
+ treatment: "insight",
44
+ className: "mb-8 p-6",
45
+ children: /* @__PURE__ */ E(h, {
46
+ athleteId: t.athleteId,
47
+ onSelect: n,
48
+ idPrefix: "perf-athlete"
49
+ })
50
+ }), t.athleteId ? /* @__PURE__ */ E(H, {
51
+ athleteId: t.athleteId,
52
+ displayName: t.displayName
53
+ }) : /* @__PURE__ */ E(a, {
54
+ illustration: "empty-data",
55
+ icon: /* @__PURE__ */ E(w, { className: "h-10 w-10" }),
56
+ title: "Pick an athlete to begin",
57
+ description: "Choose a team and an athlete above to log workouts, record metrics and review their history."
58
+ })]
59
+ });
60
+ }
61
+ function H({ athleteId: e, displayName: c }) {
62
+ let [u, f] = _(0), [p, h] = _(!1), [x, S] = _(!1), C = d({
63
+ athleteId: e,
64
+ pagination: {
65
+ limit: F,
66
+ offset: u * F
67
+ }
68
+ }), w = l(e), A = C.data?.items ?? [], j = C.data?.pageInfo.hasMore ?? !1, M = g(() => {
69
+ let e = /* @__PURE__ */ new Map();
70
+ for (let t of w.data ?? []) {
71
+ let n = e.get(t.type) ?? [];
72
+ n.push(t), e.set(t.type, n);
73
+ }
74
+ return Array.from(e.entries()).map(([e, t]) => ({
75
+ type: e,
76
+ metrics: [...t].sort((e, t) => new Date(t.recordedAt).getTime() - new Date(e.recordedAt).getTime()),
77
+ max: Math.max(...t.map((e) => Math.abs(e.value)), 0) || 1
78
+ }));
79
+ }, [w.data]);
80
+ return /* @__PURE__ */ D("div", {
81
+ className: "space-y-8",
82
+ children: [
83
+ /* @__PURE__ */ D("div", {
84
+ className: "flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between",
85
+ children: [/* @__PURE__ */ D("p", {
86
+ className: "text-sm text-text-secondary",
87
+ children: ["Tracking ", /* @__PURE__ */ E("span", {
88
+ className: "font-medium text-text-primary",
89
+ children: c || "athlete"
90
+ })]
91
+ }), /* @__PURE__ */ D("div", {
92
+ className: "flex flex-wrap gap-2",
93
+ children: [/* @__PURE__ */ D("button", {
94
+ type: "button",
95
+ onClick: () => h(!0),
96
+ className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90",
97
+ children: [/* @__PURE__ */ E(y, { className: "h-4 w-4" }), " Log workout"]
98
+ }), /* @__PURE__ */ D("button", {
99
+ type: "button",
100
+ onClick: () => S(!0),
101
+ className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
102
+ children: [/* @__PURE__ */ E(b, { className: "h-4 w-4" }), " Record metric"]
103
+ })]
104
+ })]
105
+ }),
106
+ /* @__PURE__ */ D("section", { children: [/* @__PURE__ */ E("h2", {
107
+ className: "mb-4 text-lg font-semibold text-text-primary",
108
+ children: "Performance metrics"
109
+ }), w.isLoading ? /* @__PURE__ */ E(s, { label: "Loading metrics…" }) : w.isError ? /* @__PURE__ */ E(o, { onRetry: () => void w.refetch() }) : M.length === 0 ? /* @__PURE__ */ E(a, {
110
+ illustration: "empty-data",
111
+ icon: /* @__PURE__ */ E(b, { className: "h-10 w-10" }),
112
+ title: "No metrics recorded",
113
+ description: "Record a metric — speed, strength, endurance and more — to start charting progress."
114
+ }) : /* @__PURE__ */ E("div", {
115
+ className: "grid grid-cols-1 gap-4 lg:grid-cols-2",
116
+ children: M.map((e) => /* @__PURE__ */ D(k, {
117
+ treatment: "metric",
118
+ className: "p-5",
119
+ children: [/* @__PURE__ */ E("h3", {
120
+ className: "mb-3 text-sm font-semibold uppercase tracking-wide text-text-secondary",
121
+ children: n[e.type]
122
+ }), /* @__PURE__ */ E("ul", {
123
+ className: "space-y-3",
124
+ children: e.metrics.map((t) => {
125
+ let n = Math.max(4, Math.round(Math.abs(t.value) / e.max * 100));
126
+ return /* @__PURE__ */ D("li", {
127
+ className: "flex items-center gap-3",
128
+ title: r(t.recordedAt),
129
+ children: [
130
+ /* @__PURE__ */ E("span", {
131
+ className: "w-24 shrink-0 truncate text-sm text-text-primary",
132
+ children: t.label
133
+ }),
134
+ /* @__PURE__ */ E("span", {
135
+ className: "h-2 flex-1 overflow-hidden rounded-full bg-bg-sunken",
136
+ "aria-hidden": "true",
137
+ children: /* @__PURE__ */ E("span", {
138
+ className: "block h-2 rounded-full bg-[var(--color-accent-teal)]",
139
+ style: { width: `${n}%` }
140
+ })
141
+ }),
142
+ /* @__PURE__ */ D("span", {
143
+ className: "w-24 shrink-0 text-right text-sm tabular-nums text-text-primary",
144
+ children: [t.value, t.unit ? ` ${t.unit}` : ""]
145
+ })
146
+ ]
147
+ }, t.id);
148
+ })
149
+ })]
150
+ }, e.type))
151
+ })] }),
152
+ /* @__PURE__ */ D("section", { children: [/* @__PURE__ */ E("h2", {
153
+ className: "mb-4 text-lg font-semibold text-text-primary",
154
+ children: "Workout history"
155
+ }), C.isLoading ? /* @__PURE__ */ E(s, { label: "Loading workouts…" }) : C.isError ? /* @__PURE__ */ E(o, { onRetry: () => void C.refetch() }) : A.length === 0 ? /* @__PURE__ */ E(a, {
156
+ illustration: "empty-data",
157
+ icon: /* @__PURE__ */ E(v, { className: "h-10 w-10" }),
158
+ title: "No workouts logged",
159
+ description: "Log a workout to build this athlete's training history.",
160
+ action: /* @__PURE__ */ E("button", {
161
+ type: "button",
162
+ onClick: () => h(!0),
163
+ className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90",
164
+ children: "Log workout"
165
+ })
166
+ }) : /* @__PURE__ */ D(T, { children: [/* @__PURE__ */ E(O, { children: /* @__PURE__ */ E("div", {
167
+ className: "divide-y divide-border-seam",
168
+ children: A.map((e) => /* @__PURE__ */ D("div", {
169
+ className: "flex flex-wrap items-center gap-x-4 gap-y-1 py-3",
170
+ children: [/* @__PURE__ */ D("div", {
171
+ className: "min-w-0 flex-1",
172
+ children: [
173
+ /* @__PURE__ */ E("p", {
174
+ className: "truncate text-sm font-medium text-text-primary",
175
+ children: e.title
176
+ }),
177
+ /* @__PURE__ */ D("div", {
178
+ className: "mt-0.5 flex flex-wrap gap-x-3 gap-y-1 text-xs text-text-secondary",
179
+ children: [
180
+ /* @__PURE__ */ E("span", { children: r(e.performedAt) }),
181
+ e.durationMin != null && /* @__PURE__ */ E("span", { children: i(e.durationMin) }),
182
+ e.rpe != null && /* @__PURE__ */ D("span", { children: ["RPE ", e.rpe] }),
183
+ e.totalLoad != null && /* @__PURE__ */ D("span", { children: ["Load ", e.totalLoad] })
184
+ ]
185
+ }),
186
+ e.notes && /* @__PURE__ */ E("p", {
187
+ className: "mt-0.5 truncate text-xs text-text-secondary",
188
+ children: e.notes
189
+ })
190
+ ]
191
+ }), /* @__PURE__ */ E(t, { status: e.status })]
192
+ }, e.id))
193
+ }) }), /* @__PURE__ */ E(m, {
194
+ className: "mt-6",
195
+ page: u,
196
+ hasMore: j,
197
+ onPrev: () => f((e) => Math.max(0, e - 1)),
198
+ onNext: () => f((e) => e + 1)
199
+ })] })] }),
200
+ /* @__PURE__ */ E(U, {
201
+ open: p,
202
+ onOpenChange: h,
203
+ athleteId: e,
204
+ displayName: c
205
+ }),
206
+ /* @__PURE__ */ E(W, {
207
+ open: x,
208
+ onOpenChange: S,
209
+ athleteId: e,
210
+ displayName: c
211
+ })
212
+ ]
213
+ });
214
+ }
215
+ function U({ open: e, onOpenChange: t, athleteId: n, displayName: r }) {
216
+ let i = c(), [a, o] = _(""), [s, l] = _("COMPLETED"), [u, d] = _(B), [m, h] = _(""), [g, v] = _(""), [y, b] = _(""), [S, w] = _(""), [T, O] = _(null), k = () => {
217
+ o(""), l("COMPLETED"), d(B()), h(""), v(""), b(""), w(""), O(null);
218
+ };
219
+ return /* @__PURE__ */ E(A, {
220
+ open: e,
221
+ onOpenChange: t,
222
+ children: /* @__PURE__ */ D(j, { children: [/* @__PURE__ */ E(N, { children: /* @__PURE__ */ D(P, { children: ["Log workout", r ? ` — ${r}` : ""] }) }), /* @__PURE__ */ D("form", {
223
+ onSubmit: async (e) => {
224
+ if (e.preventDefault(), O(null), !a.trim()) {
225
+ O("A workout title is required.");
226
+ return;
227
+ }
228
+ if (!u) {
229
+ O("A performed date and time is required.");
230
+ return;
231
+ }
232
+ try {
233
+ await i.mutateAsync({
234
+ athleteId: n,
235
+ title: a.trim(),
236
+ status: s,
237
+ performedAt: new Date(u).toISOString(),
238
+ durationMin: R(m),
239
+ rpe: R(g),
240
+ totalLoad: z(y),
241
+ notes: S.trim() || void 0
242
+ }), k(), t(!1);
243
+ } catch (e) {
244
+ O(e instanceof Error ? e.message : "Failed to log workout.");
245
+ }
246
+ },
247
+ className: "space-y-4 px-1",
248
+ children: [
249
+ /* @__PURE__ */ E(f, {
250
+ label: "Title",
251
+ htmlFor: "workoutTitle",
252
+ required: !0,
253
+ children: /* @__PURE__ */ E("input", {
254
+ id: "workoutTitle",
255
+ className: p,
256
+ value: a,
257
+ onChange: (e) => o(e.target.value),
258
+ placeholder: "e.g. Lower-body strength"
259
+ })
260
+ }),
261
+ /* @__PURE__ */ D("div", {
262
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
263
+ children: [/* @__PURE__ */ E(f, {
264
+ label: "Status",
265
+ htmlFor: "workoutStatus",
266
+ children: /* @__PURE__ */ E("select", {
267
+ id: "workoutStatus",
268
+ className: p,
269
+ value: s,
270
+ onChange: (e) => l(e.target.value),
271
+ children: I.map((e) => /* @__PURE__ */ E("option", {
272
+ value: e,
273
+ children: e.charAt(0) + e.slice(1).toLowerCase()
274
+ }, e))
275
+ })
276
+ }), /* @__PURE__ */ E(f, {
277
+ label: "Performed at",
278
+ htmlFor: "workoutPerformedAt",
279
+ required: !0,
280
+ children: /* @__PURE__ */ E("input", {
281
+ id: "workoutPerformedAt",
282
+ type: "datetime-local",
283
+ className: p,
284
+ value: u,
285
+ onChange: (e) => d(e.target.value)
286
+ })
287
+ })]
288
+ }),
289
+ /* @__PURE__ */ D("div", {
290
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-3",
291
+ children: [
292
+ /* @__PURE__ */ E(f, {
293
+ label: "Duration (min)",
294
+ htmlFor: "workoutDuration",
295
+ children: /* @__PURE__ */ E("input", {
296
+ id: "workoutDuration",
297
+ inputMode: "numeric",
298
+ className: p,
299
+ value: m,
300
+ onChange: (e) => h(e.target.value)
301
+ })
302
+ }),
303
+ /* @__PURE__ */ E(f, {
304
+ label: "RPE (1–10)",
305
+ htmlFor: "workoutRpe",
306
+ children: /* @__PURE__ */ E("input", {
307
+ id: "workoutRpe",
308
+ inputMode: "numeric",
309
+ className: p,
310
+ value: g,
311
+ onChange: (e) => v(e.target.value)
312
+ })
313
+ }),
314
+ /* @__PURE__ */ E(f, {
315
+ label: "Total load",
316
+ htmlFor: "workoutLoad",
317
+ children: /* @__PURE__ */ E("input", {
318
+ id: "workoutLoad",
319
+ inputMode: "decimal",
320
+ className: p,
321
+ value: y,
322
+ onChange: (e) => b(e.target.value)
323
+ })
324
+ })
325
+ ]
326
+ }),
327
+ /* @__PURE__ */ E(f, {
328
+ label: "Notes",
329
+ htmlFor: "workoutNotes",
330
+ children: /* @__PURE__ */ E("textarea", {
331
+ id: "workoutNotes",
332
+ rows: 2,
333
+ className: p,
334
+ value: S,
335
+ onChange: (e) => w(e.target.value),
336
+ placeholder: "How did the session go?"
337
+ })
338
+ }),
339
+ T && /* @__PURE__ */ E("p", {
340
+ className: "text-sm text-status-error-text",
341
+ children: T
342
+ }),
343
+ /* @__PURE__ */ D(M, { children: [/* @__PURE__ */ E("button", {
344
+ type: "button",
345
+ onClick: () => t(!1),
346
+ className: "rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
347
+ children: "Cancel"
348
+ }), /* @__PURE__ */ D("button", {
349
+ type: "submit",
350
+ disabled: i.isPending,
351
+ className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60",
352
+ children: [i.isPending ? /* @__PURE__ */ E(x, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ E(C, { className: "h-4 w-4" }), "Log workout"]
353
+ })] })
354
+ ]
355
+ })] })
356
+ });
357
+ }
358
+ function W({ open: e, onOpenChange: t, athleteId: r, displayName: i }) {
359
+ let a = u(), [o, s] = _("CUSTOM"), [c, l] = _(""), [d, m] = _(""), [h, g] = _(""), [v, y] = _(""), [b, C] = _(B), [w, T] = _(null), O = () => {
360
+ s("CUSTOM"), l(""), m(""), g(""), y(""), C(B()), T(null);
361
+ };
362
+ return /* @__PURE__ */ E(A, {
363
+ open: e,
364
+ onOpenChange: t,
365
+ children: /* @__PURE__ */ D(j, { children: [/* @__PURE__ */ E(N, { children: /* @__PURE__ */ D(P, { children: ["Record metric", i ? ` — ${i}` : ""] }) }), /* @__PURE__ */ D("form", {
366
+ onSubmit: async (e) => {
367
+ if (e.preventDefault(), T(null), !c.trim()) {
368
+ T("A metric label is required.");
369
+ return;
370
+ }
371
+ let n = z(d);
372
+ if (n === void 0) {
373
+ T("A numeric value is required.");
374
+ return;
375
+ }
376
+ if (!b) {
377
+ T("A recorded date and time is required.");
378
+ return;
379
+ }
380
+ try {
381
+ await a.mutateAsync({
382
+ athleteId: r,
383
+ type: o,
384
+ label: c.trim(),
385
+ value: n,
386
+ unit: h.trim() || void 0,
387
+ source: v.trim() || void 0,
388
+ recordedAt: new Date(b).toISOString()
389
+ }), O(), t(!1);
390
+ } catch (e) {
391
+ T(e instanceof Error ? e.message : "Failed to record metric.");
392
+ }
393
+ },
394
+ className: "space-y-4 px-1",
395
+ children: [
396
+ /* @__PURE__ */ D("div", {
397
+ className: "grid grid-cols-1 gap-4 sm:grid-cols-2",
398
+ children: [/* @__PURE__ */ E(f, {
399
+ label: "Type",
400
+ htmlFor: "metricType",
401
+ children: /* @__PURE__ */ E("select", {
402
+ id: "metricType",
403
+ className: p,
404
+ value: o,
405
+ onChange: (e) => s(e.target.value),
406
+ children: L.map((e) => /* @__PURE__ */ E("option", {
407
+ value: e,
408
+ children: n[e]
409
+ }, e))
410
+ })
411
+ }), /* @__PURE__ */ E(f, {
412
+ label: "Recorded at",
413
+ htmlFor: "metricRecordedAt",
414
+ required: !0,
415
+ children: /* @__PURE__ */ E("input", {
416
+ id: "metricRecordedAt",
417
+ type: "datetime-local",
418
+ className: p,
419
+ value: b,
420
+ onChange: (e) => C(e.target.value)
421
+ })
422
+ })]
423
+ }),
424
+ /* @__PURE__ */ E(f, {
425
+ label: "Label",
426
+ htmlFor: "metricLabel",
427
+ required: !0,
428
+ children: /* @__PURE__ */ E("input", {
429
+ id: "metricLabel",
430
+ className: p,
431
+ value: c,
432
+ onChange: (e) => l(e.target.value),
433
+ placeholder: "e.g. 40m sprint"
434
+ })
435
+ }),
436
+ /* @__PURE__ */ D("div", {
437
+ className: "grid grid-cols-2 gap-4",
438
+ children: [/* @__PURE__ */ E(f, {
439
+ label: "Value",
440
+ htmlFor: "metricValue",
441
+ required: !0,
442
+ children: /* @__PURE__ */ E("input", {
443
+ id: "metricValue",
444
+ inputMode: "decimal",
445
+ className: p,
446
+ value: d,
447
+ onChange: (e) => m(e.target.value),
448
+ placeholder: "e.g. 4.8"
449
+ })
450
+ }), /* @__PURE__ */ E(f, {
451
+ label: "Unit",
452
+ htmlFor: "metricUnit",
453
+ children: /* @__PURE__ */ E("input", {
454
+ id: "metricUnit",
455
+ className: p,
456
+ value: h,
457
+ onChange: (e) => g(e.target.value),
458
+ placeholder: "e.g. s, kg, bpm"
459
+ })
460
+ })]
461
+ }),
462
+ /* @__PURE__ */ E(f, {
463
+ label: "Source",
464
+ htmlFor: "metricSource",
465
+ hint: "Optional — device, test or coach.",
466
+ children: /* @__PURE__ */ E("input", {
467
+ id: "metricSource",
468
+ className: p,
469
+ value: v,
470
+ onChange: (e) => y(e.target.value),
471
+ placeholder: "e.g. GPS vest"
472
+ })
473
+ }),
474
+ w && /* @__PURE__ */ E("p", {
475
+ className: "text-sm text-status-error-text",
476
+ children: w
477
+ }),
478
+ /* @__PURE__ */ D(M, { children: [/* @__PURE__ */ E("button", {
479
+ type: "button",
480
+ onClick: () => t(!1),
481
+ className: "rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
482
+ children: "Cancel"
483
+ }), /* @__PURE__ */ D("button", {
484
+ type: "submit",
485
+ disabled: a.isPending,
486
+ className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60",
487
+ children: [a.isPending ? /* @__PURE__ */ E(x, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ E(S, { className: "h-4 w-4" }), "Record metric"]
488
+ })] })
489
+ ]
490
+ })] })
491
+ });
492
+ }
493
+ //#endregion
494
+ export { V as PerformancePage };
495
+
496
+ //# sourceMappingURL=PerformancePage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PerformancePage.js","names":[],"sources":["../../../../src/atheletebridge/pages/performance/PerformancePage.tsx"],"sourcesContent":["/**\n * Performance\n * @module atheletebridge/pages/performance\n *\n * The coach's athlete-performance workbench: pick an athlete from a team roster,\n * then log workouts (athLogWorkout) and record performance metrics\n * (athRecordMetric), and review that athlete's workout history\n * (athListWorkoutLogs, offset-paginated) and metrics grouped by type with a\n * tokenized comparison bar. Backs the dashboard's \"track athlete performance\".\n */\n\nimport { useMemo, useState } from 'react';\nimport { Activity, Dumbbell, Gauge, Loader2, Plus, Save, Users } from 'lucide-react';\nimport {\n GlassCard,\n EmphasisPanel,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { AthleteSelect, type AthleteSelectValue } from '../../components/AthleteSelect';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { CursorPager } from '../../components/CursorPager';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { METRIC_TYPE_LABELS } from '../../components/domain';\nimport {\n useWorkoutLogs,\n usePerformanceMetrics,\n useLogWorkout,\n useRecordMetric,\n} from '../../hooks/useAthleteBridgeApi';\nimport { formatDate, formatDuration } from '../../utils/formatters';\nimport type { AthMetricType, AthPerformanceMetric, AthWorkoutStatus } from '../../types';\n\nconst PAGE_SIZE = 10;\n\nconst WORKOUT_STATUSES: AthWorkoutStatus[] = ['COMPLETED', 'PLANNED', 'SKIPPED'];\nconst METRIC_TYPES = Object.keys(METRIC_TYPE_LABELS) as AthMetricType[];\n\nconst numOrUndefined = (value: string): number | undefined => {\n const t = value.trim();\n if (!t) return undefined;\n const n = Number.parseInt(t, 10);\n return Number.isNaN(n) ? undefined : n;\n};\n\nconst floatOrUndefined = (value: string): number | undefined => {\n const t = value.trim();\n if (!t) return undefined;\n const n = Number.parseFloat(t);\n return Number.isNaN(n) ? undefined : n;\n};\n\n/** Current local time as a `datetime-local` value (`YYYY-MM-DDTHH:mm`). */\nconst nowLocal = (): string => {\n const d = new Date();\n const local = new Date(d.getTime() - d.getTimezoneOffset() * 60_000);\n return local.toISOString().slice(0, 16);\n};\n\nexport function PerformancePage() {\n const [athlete, setAthlete] = useState<AthleteSelectValue>({ athleteId: '', displayName: '' });\n\n return (\n <PageLayout\n title=\"Performance\"\n description=\"Log workouts, record metrics and track an athlete's progress over time.\"\n >\n <GlassCard treatment=\"insight\" className=\"mb-8 p-6\">\n <AthleteSelect\n athleteId={athlete.athleteId}\n onSelect={setAthlete}\n idPrefix=\"perf-athlete\"\n />\n </GlassCard>\n\n {athlete.athleteId ? (\n <AthletePerformance athleteId={athlete.athleteId} displayName={athlete.displayName} />\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Users className=\"h-10 w-10\" />}\n title=\"Pick an athlete to begin\"\n description=\"Choose a team and an athlete above to log workouts, record metrics and review their history.\"\n />\n )}\n </PageLayout>\n );\n}\n\ninterface AthletePerformanceProps {\n athleteId: string;\n displayName: string;\n}\n\nfunction AthletePerformance({ athleteId, displayName }: AthletePerformanceProps) {\n const [page, setPage] = useState(0);\n const [logOpen, setLogOpen] = useState(false);\n const [metricOpen, setMetricOpen] = useState(false);\n\n const logs = useWorkoutLogs({\n athleteId,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n const metrics = usePerformanceMetrics(athleteId);\n\n const logItems = logs.data?.items ?? [];\n const hasMore = logs.data?.pageInfo.hasMore ?? false;\n\n const metricGroups = useMemo(() => {\n const groups = new Map<AthMetricType, AthPerformanceMetric[]>();\n for (const m of metrics.data ?? []) {\n const list = groups.get(m.type) ?? [];\n list.push(m);\n groups.set(m.type, list);\n }\n return Array.from(groups.entries()).map(([type, list]) => ({\n type,\n metrics: [...list].sort(\n (a, b) => new Date(b.recordedAt).getTime() - new Date(a.recordedAt).getTime()\n ),\n max: Math.max(...list.map((m) => Math.abs(m.value)), 0) || 1,\n }));\n }, [metrics.data]);\n\n return (\n <div className=\"space-y-8\">\n {/* Actions */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <p className=\"text-sm text-text-secondary\">\n Tracking <span className=\"font-medium text-text-primary\">{displayName || 'athlete'}</span>\n </p>\n <div className=\"flex flex-wrap gap-2\">\n <button\n type=\"button\"\n onClick={() => setLogOpen(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Dumbbell className=\"h-4 w-4\" /> Log workout\n </button>\n <button\n type=\"button\"\n onClick={() => setMetricOpen(true)}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Gauge className=\"h-4 w-4\" /> Record metric\n </button>\n </div>\n </div>\n\n {/* Metrics */}\n <section>\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Performance metrics</h2>\n {metrics.isLoading ? (\n <LoadingState label=\"Loading metrics…\" />\n ) : metrics.isError ? (\n <ErrorState onRetry={() => void metrics.refetch()} />\n ) : metricGroups.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Gauge className=\"h-10 w-10\" />}\n title=\"No metrics recorded\"\n description=\"Record a metric — speed, strength, endurance and more — to start charting progress.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n {metricGroups.map((group) => (\n <GlassCard key={group.type} treatment=\"metric\" className=\"p-5\">\n <h3 className=\"mb-3 text-sm font-semibold uppercase tracking-wide text-text-secondary\">\n {METRIC_TYPE_LABELS[group.type]}\n </h3>\n <ul className=\"space-y-3\">\n {group.metrics.map((m) => {\n const pct = Math.max(4, Math.round((Math.abs(m.value) / group.max) * 100));\n return (\n <li\n key={m.id}\n className=\"flex items-center gap-3\"\n title={formatDate(m.recordedAt)}\n >\n <span className=\"w-24 shrink-0 truncate text-sm text-text-primary\">\n {m.label}\n </span>\n <span\n className=\"h-2 flex-1 overflow-hidden rounded-full bg-bg-sunken\"\n aria-hidden=\"true\"\n >\n <span\n className=\"block h-2 rounded-full bg-[var(--color-accent-teal)]\"\n style={{ width: `${pct}%` }}\n />\n </span>\n <span className=\"w-24 shrink-0 text-right text-sm tabular-nums text-text-primary\">\n {m.value}\n {m.unit ? ` ${m.unit}` : ''}\n </span>\n </li>\n );\n })}\n </ul>\n </GlassCard>\n ))}\n </div>\n )}\n </section>\n\n {/* Workout logs */}\n <section>\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Workout history</h2>\n {logs.isLoading ? (\n <LoadingState label=\"Loading workouts…\" />\n ) : logs.isError ? (\n <ErrorState onRetry={() => void logs.refetch()} />\n ) : logItems.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Activity className=\"h-10 w-10\" />}\n title=\"No workouts logged\"\n description=\"Log a workout to build this athlete's training history.\"\n action={\n <button\n type=\"button\"\n onClick={() => setLogOpen(true)}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n Log workout\n </button>\n }\n />\n ) : (\n <>\n <EmphasisPanel>\n <div className=\"divide-y divide-border-seam\">\n {logItems.map((log) => (\n <div key={log.id} className=\"flex flex-wrap items-center gap-x-4 gap-y-1 py-3\">\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">{log.title}</p>\n <div className=\"mt-0.5 flex flex-wrap gap-x-3 gap-y-1 text-xs text-text-secondary\">\n <span>{formatDate(log.performedAt)}</span>\n {log.durationMin != null && <span>{formatDuration(log.durationMin)}</span>}\n {log.rpe != null && <span>RPE {log.rpe}</span>}\n {log.totalLoad != null && <span>Load {log.totalLoad}</span>}\n </div>\n {log.notes && (\n <p className=\"mt-0.5 truncate text-xs text-text-secondary\">{log.notes}</p>\n )}\n </div>\n <StatusBadge status={log.status} />\n </div>\n ))}\n </div>\n </EmphasisPanel>\n <CursorPager\n className=\"mt-6\"\n page={page}\n hasMore={hasMore}\n onPrev={() => setPage((p) => Math.max(0, p - 1))}\n onNext={() => setPage((p) => p + 1)}\n />\n </>\n )}\n </section>\n\n <LogWorkoutDialog\n open={logOpen}\n onOpenChange={setLogOpen}\n athleteId={athleteId}\n displayName={displayName}\n />\n <RecordMetricDialog\n open={metricOpen}\n onOpenChange={setMetricOpen}\n athleteId={athleteId}\n displayName={displayName}\n />\n </div>\n );\n}\n\ninterface DialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n athleteId: string;\n displayName: string;\n}\n\nfunction LogWorkoutDialog({ open, onOpenChange, athleteId, displayName }: DialogProps) {\n const logWorkout = useLogWorkout();\n const [title, setTitle] = useState('');\n const [status, setStatus] = useState<AthWorkoutStatus>('COMPLETED');\n const [performedAt, setPerformedAt] = useState(nowLocal);\n const [durationMin, setDurationMin] = useState('');\n const [rpe, setRpe] = useState('');\n const [totalLoad, setTotalLoad] = useState('');\n const [notes, setNotes] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n const reset = () => {\n setTitle('');\n setStatus('COMPLETED');\n setPerformedAt(nowLocal());\n setDurationMin('');\n setRpe('');\n setTotalLoad('');\n setNotes('');\n setError(null);\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!title.trim()) {\n setError('A workout title is required.');\n return;\n }\n if (!performedAt) {\n setError('A performed date and time is required.');\n return;\n }\n try {\n await logWorkout.mutateAsync({\n athleteId,\n title: title.trim(),\n status,\n performedAt: new Date(performedAt).toISOString(),\n durationMin: numOrUndefined(durationMin),\n rpe: numOrUndefined(rpe),\n totalLoad: floatOrUndefined(totalLoad),\n notes: notes.trim() || undefined,\n });\n reset();\n onOpenChange(false);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to log workout.');\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n Log workout{displayName ? ` — ${displayName}` : ''}\n </ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n <form onSubmit={handleSubmit} className=\"space-y-4 px-1\">\n <FormField label=\"Title\" htmlFor=\"workoutTitle\" required>\n <input\n id=\"workoutTitle\"\n className={fieldInputClass}\n value={title}\n onChange={(e) => setTitle(e.target.value)}\n placeholder=\"e.g. Lower-body strength\"\n />\n </FormField>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <FormField label=\"Status\" htmlFor=\"workoutStatus\">\n <select\n id=\"workoutStatus\"\n className={fieldInputClass}\n value={status}\n onChange={(e) => setStatus(e.target.value as AthWorkoutStatus)}\n >\n {WORKOUT_STATUSES.map((s) => (\n <option key={s} value={s}>\n {s.charAt(0) + s.slice(1).toLowerCase()}\n </option>\n ))}\n </select>\n </FormField>\n <FormField label=\"Performed at\" htmlFor=\"workoutPerformedAt\" required>\n <input\n id=\"workoutPerformedAt\"\n type=\"datetime-local\"\n className={fieldInputClass}\n value={performedAt}\n onChange={(e) => setPerformedAt(e.target.value)}\n />\n </FormField>\n </div>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-3\">\n <FormField label=\"Duration (min)\" htmlFor=\"workoutDuration\">\n <input\n id=\"workoutDuration\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={durationMin}\n onChange={(e) => setDurationMin(e.target.value)}\n />\n </FormField>\n <FormField label=\"RPE (1–10)\" htmlFor=\"workoutRpe\">\n <input\n id=\"workoutRpe\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={rpe}\n onChange={(e) => setRpe(e.target.value)}\n />\n </FormField>\n <FormField label=\"Total load\" htmlFor=\"workoutLoad\">\n <input\n id=\"workoutLoad\"\n inputMode=\"decimal\"\n className={fieldInputClass}\n value={totalLoad}\n onChange={(e) => setTotalLoad(e.target.value)}\n />\n </FormField>\n </div>\n <FormField label=\"Notes\" htmlFor=\"workoutNotes\">\n <textarea\n id=\"workoutNotes\"\n rows={2}\n className={fieldInputClass}\n value={notes}\n onChange={(e) => setNotes(e.target.value)}\n placeholder=\"How did the session go?\"\n />\n </FormField>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={() => onOpenChange(false)}\n className=\"rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={logWorkout.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {logWorkout.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Save className=\"h-4 w-4\" />\n )}\n Log workout\n </button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n\nfunction RecordMetricDialog({ open, onOpenChange, athleteId, displayName }: DialogProps) {\n const recordMetric = useRecordMetric();\n const [type, setType] = useState<AthMetricType>('CUSTOM');\n const [label, setLabel] = useState('');\n const [value, setValue] = useState('');\n const [unit, setUnit] = useState('');\n const [source, setSource] = useState('');\n const [recordedAt, setRecordedAt] = useState(nowLocal);\n const [error, setError] = useState<string | null>(null);\n\n const reset = () => {\n setType('CUSTOM');\n setLabel('');\n setValue('');\n setUnit('');\n setSource('');\n setRecordedAt(nowLocal());\n setError(null);\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!label.trim()) {\n setError('A metric label is required.');\n return;\n }\n const numericValue = floatOrUndefined(value);\n if (numericValue === undefined) {\n setError('A numeric value is required.');\n return;\n }\n if (!recordedAt) {\n setError('A recorded date and time is required.');\n return;\n }\n try {\n await recordMetric.mutateAsync({\n athleteId,\n type,\n label: label.trim(),\n value: numericValue,\n unit: unit.trim() || undefined,\n source: source.trim() || undefined,\n recordedAt: new Date(recordedAt).toISOString(),\n });\n reset();\n onOpenChange(false);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to record metric.');\n }\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n Record metric{displayName ? ` — ${displayName}` : ''}\n </ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n <form onSubmit={handleSubmit} className=\"space-y-4 px-1\">\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <FormField label=\"Type\" htmlFor=\"metricType\">\n <select\n id=\"metricType\"\n className={fieldInputClass}\n value={type}\n onChange={(e) => setType(e.target.value as AthMetricType)}\n >\n {METRIC_TYPES.map((t) => (\n <option key={t} value={t}>\n {METRIC_TYPE_LABELS[t]}\n </option>\n ))}\n </select>\n </FormField>\n <FormField label=\"Recorded at\" htmlFor=\"metricRecordedAt\" required>\n <input\n id=\"metricRecordedAt\"\n type=\"datetime-local\"\n className={fieldInputClass}\n value={recordedAt}\n onChange={(e) => setRecordedAt(e.target.value)}\n />\n </FormField>\n </div>\n <FormField label=\"Label\" htmlFor=\"metricLabel\" required>\n <input\n id=\"metricLabel\"\n className={fieldInputClass}\n value={label}\n onChange={(e) => setLabel(e.target.value)}\n placeholder=\"e.g. 40m sprint\"\n />\n </FormField>\n <div className=\"grid grid-cols-2 gap-4\">\n <FormField label=\"Value\" htmlFor=\"metricValue\" required>\n <input\n id=\"metricValue\"\n inputMode=\"decimal\"\n className={fieldInputClass}\n value={value}\n onChange={(e) => setValue(e.target.value)}\n placeholder=\"e.g. 4.8\"\n />\n </FormField>\n <FormField label=\"Unit\" htmlFor=\"metricUnit\">\n <input\n id=\"metricUnit\"\n className={fieldInputClass}\n value={unit}\n onChange={(e) => setUnit(e.target.value)}\n placeholder=\"e.g. s, kg, bpm\"\n />\n </FormField>\n </div>\n <FormField label=\"Source\" htmlFor=\"metricSource\" hint=\"Optional — device, test or coach.\">\n <input\n id=\"metricSource\"\n className={fieldInputClass}\n value={source}\n onChange={(e) => setSource(e.target.value)}\n placeholder=\"e.g. GPS vest\"\n />\n </FormField>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={() => onOpenChange(false)}\n className=\"rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={recordMetric.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {recordMetric.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Plus className=\"h-4 w-4\" />\n )}\n Record metric\n </button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}