@burdenoff/microfe-atheletebridge 2026.705.2 → 2026.706.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/atheletebridge/AthleteBridgeRoot.js +55 -39
- package/dist/atheletebridge/AthleteBridgeRoot.js.map +1 -1
- package/dist/atheletebridge/AthleteBridgeRoutes.js +18 -6
- package/dist/atheletebridge/AthleteBridgeRoutes.js.map +1 -1
- package/dist/atheletebridge/components/Crest.js +70 -0
- package/dist/atheletebridge/components/Crest.js.map +1 -0
- package/dist/atheletebridge/components/FixtureCard.js +8 -72
- package/dist/atheletebridge/components/FixtureCard.js.map +1 -1
- package/dist/atheletebridge/components/MatchupRow.js +74 -0
- package/dist/atheletebridge/components/MatchupRow.js.map +1 -0
- package/dist/atheletebridge/components/MetricsCard.js +61 -21
- package/dist/atheletebridge/components/MetricsCard.js.map +1 -1
- package/dist/atheletebridge/components/ProgramCard.js +75 -43
- package/dist/atheletebridge/components/ProgramCard.js.map +1 -1
- package/dist/atheletebridge/components/Scoreboard.js +163 -0
- package/dist/atheletebridge/components/Scoreboard.js.map +1 -0
- package/dist/atheletebridge/components/SessionCard.js +64 -41
- package/dist/atheletebridge/components/SessionCard.js.map +1 -1
- package/dist/atheletebridge/components/SessionStatusTimeline.js +80 -0
- package/dist/atheletebridge/components/SessionStatusTimeline.js.map +1 -0
- package/dist/atheletebridge/components/StatusBadge.js +5 -5
- package/dist/atheletebridge/components/StatusBadge.js.map +1 -1
- package/dist/atheletebridge/components/TeamCard.js +65 -35
- package/dist/atheletebridge/components/TeamCard.js.map +1 -1
- package/dist/atheletebridge/components/UserAvatar.js +33 -0
- package/dist/atheletebridge/components/UserAvatar.js.map +1 -0
- package/dist/atheletebridge/components/domain.js +5 -2
- package/dist/atheletebridge/components/domain.js.map +1 -1
- package/dist/atheletebridge/components/viz/AvatarStack.js +2 -0
- package/dist/atheletebridge/components/viz/BarChart.js +83 -0
- package/dist/atheletebridge/components/viz/BarChart.js.map +1 -0
- package/dist/atheletebridge/components/viz/DonutChart.js +109 -0
- package/dist/atheletebridge/components/viz/DonutChart.js.map +1 -0
- package/dist/atheletebridge/components/viz/LineChart.js +123 -0
- package/dist/atheletebridge/components/viz/LineChart.js.map +1 -0
- package/dist/atheletebridge/components/viz/ProgressRing.js +6 -0
- package/dist/atheletebridge/components/viz/ProgressRing.js.map +1 -0
- package/dist/atheletebridge/components/viz/RatingStars.js +92 -0
- package/dist/atheletebridge/components/viz/RatingStars.js.map +1 -0
- package/dist/atheletebridge/components/viz/Sparkline.js +74 -0
- package/dist/atheletebridge/components/viz/Sparkline.js.map +1 -0
- package/dist/atheletebridge/components/viz/StatCard.js +60 -0
- package/dist/atheletebridge/components/viz/StatCard.js.map +1 -0
- package/dist/atheletebridge/components/viz/StepProgress.js +3 -0
- package/dist/atheletebridge/components/viz/Timeline.js +66 -0
- package/dist/atheletebridge/components/viz/Timeline.js.map +1 -0
- package/dist/atheletebridge/components/viz/index.js +13 -0
- package/dist/atheletebridge/components/viz/lib/Avatar.js +1 -0
- package/dist/atheletebridge/components/viz/lib/CartesianFrame.js +119 -0
- package/dist/atheletebridge/components/viz/lib/CartesianFrame.js.map +1 -0
- package/dist/atheletebridge/components/viz/lib/ResponsiveSvg.js +35 -0
- package/dist/atheletebridge/components/viz/lib/ResponsiveSvg.js.map +1 -0
- package/dist/atheletebridge/components/viz/lib/format.js +17 -0
- package/dist/atheletebridge/components/viz/lib/format.js.map +1 -0
- package/dist/atheletebridge/components/viz/lib/geometry.js +61 -0
- package/dist/atheletebridge/components/viz/lib/geometry.js.map +1 -0
- package/dist/atheletebridge/components/viz/lib/hooks.js +39 -0
- package/dist/atheletebridge/components/viz/lib/hooks.js.map +1 -0
- package/dist/atheletebridge/components/viz/lib/palette.js +27 -0
- package/dist/atheletebridge/components/viz/lib/palette.js.map +1 -0
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js +369 -163
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js.map +1 -1
- package/dist/atheletebridge/pages/DashboardPage.js +182 -117
- package/dist/atheletebridge/pages/DashboardPage.js.map +1 -1
- package/dist/atheletebridge/pages/StandingsPage.js +119 -95
- package/dist/atheletebridge/pages/StandingsPage.js.map +1 -1
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js +372 -0
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js.map +1 -0
- package/dist/atheletebridge/pages/athletes/AthleteProfilePage.js +511 -0
- package/dist/atheletebridge/pages/athletes/AthleteProfilePage.js.map +1 -0
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +120 -131
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureFormPage.js +264 -176
- package/dist/atheletebridge/pages/fixtures/FixtureFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js +84 -47
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js.map +1 -1
- package/dist/atheletebridge/pages/performance/PerformancePage.js +432 -281
- package/dist/atheletebridge/pages/performance/PerformancePage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js +187 -123
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js +56 -39
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js +267 -139
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionFormPage.js +253 -162
- package/dist/atheletebridge/pages/sessions/SessionFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js +38 -36
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js +437 -189
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamFormPage.js +368 -177
- package/dist/atheletebridge/pages/teams/TeamFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamsListPage.js +3 -0
- package/dist/atheletebridge/pages/teams/TeamsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/tournaments/TournamentBracketPage.js +298 -0
- package/dist/atheletebridge/pages/tournaments/TournamentBracketPage.js.map +1 -0
- package/dist/atheletebridge/utils/identity.js +31 -0
- package/dist/atheletebridge/utils/identity.js.map +1 -0
- package/dist/atheletebridge/utils/timeAgo.js +20 -0
- package/dist/atheletebridge/utils/timeAgo.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
import { useAthleteBridge as e } from "../../context/AthleteBridgeContext.js";
|
|
2
|
+
import { PageLayout as t } from "../../components/PageLayout.js";
|
|
3
|
+
import { LineChart as n } from "../../components/viz/LineChart.js";
|
|
4
|
+
import { StatCard as r } from "../../components/viz/StatCard.js";
|
|
5
|
+
import { RatingStars as i } from "../../components/viz/RatingStars.js";
|
|
6
|
+
import "../../components/viz/index.js";
|
|
7
|
+
import { StatusBadge as a } from "../../components/StatusBadge.js";
|
|
8
|
+
import { MEMBER_ROLE_LABELS as o, METRIC_TYPE_LABELS as s, SPORT_LABELS as ee } from "../../components/domain.js";
|
|
9
|
+
import { Crest as c } from "../../components/Crest.js";
|
|
10
|
+
import { formatDate as l, formatDuration as te } from "../../utils/formatters.js";
|
|
11
|
+
import { UserAvatar as ne } from "../../components/UserAvatar.js";
|
|
12
|
+
import { SessionCard as u } from "../../components/SessionCard.js";
|
|
13
|
+
import { EmptyState as d } from "../../components/EmptyState.js";
|
|
14
|
+
import { ErrorState as f, LoadingState as re } from "../../components/LoadingState.js";
|
|
15
|
+
import { useAthleteProfile as ie, useTeam as ae } from "../../hooks/useAthleteBridgeApi.js";
|
|
16
|
+
import { useMemo as p, useState as oe } from "react";
|
|
17
|
+
import { ArrowLeft as m, CalendarClock as h, Dumbbell as g, Gauge as _, LineChart as v, Ruler as y, Star as se, Users as ce, Weight as le } from "lucide-react";
|
|
18
|
+
import { useParams as b } from "react-router-dom";
|
|
19
|
+
import { Fragment as x, jsx as S, jsxs as C } from "react/jsx-runtime";
|
|
20
|
+
import { Badge as w, GlassCard as T, GradientText as E, ResponsiveTable as D, Tabs as O, TabsContent as k, TabsList as ue, TabsTrigger as A } from "@burdenoff/fe-libs/ui";
|
|
21
|
+
//#region src/atheletebridge/pages/athletes/AthleteProfilePage.tsx
|
|
22
|
+
var j = [
|
|
23
|
+
"1M",
|
|
24
|
+
"3M",
|
|
25
|
+
"6M",
|
|
26
|
+
"YTD",
|
|
27
|
+
"1Y",
|
|
28
|
+
"ALL"
|
|
29
|
+
], M = {
|
|
30
|
+
"1M": "1M",
|
|
31
|
+
"3M": "3M",
|
|
32
|
+
"6M": "6M",
|
|
33
|
+
YTD: "YTD",
|
|
34
|
+
"1Y": "1Y",
|
|
35
|
+
ALL: "All"
|
|
36
|
+
};
|
|
37
|
+
function N(e) {
|
|
38
|
+
let t = Date.now(), n = 864e5;
|
|
39
|
+
switch (e) {
|
|
40
|
+
case "1M": return t - 30 * n;
|
|
41
|
+
case "3M": return t - 90 * n;
|
|
42
|
+
case "6M": return t - 180 * n;
|
|
43
|
+
case "1Y": return t - 365 * n;
|
|
44
|
+
case "YTD": return new Date((/* @__PURE__ */ new Date()).getFullYear(), 0, 1).getTime();
|
|
45
|
+
case "ALL": return 0;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
var P = (e) => new Date(e).toLocaleDateString(void 0, {
|
|
49
|
+
month: "short",
|
|
50
|
+
day: "numeric"
|
|
51
|
+
});
|
|
52
|
+
function F(e, t) {
|
|
53
|
+
let n = /* @__PURE__ */ new Map();
|
|
54
|
+
for (let r of e) {
|
|
55
|
+
let e = new Date(r.recordedAt).getTime();
|
|
56
|
+
if (Number.isNaN(e) || e < t) continue;
|
|
57
|
+
let i = n.get(r.type) ?? [];
|
|
58
|
+
i.push(r), n.set(r.type, i);
|
|
59
|
+
}
|
|
60
|
+
return Array.from(n.entries()).map(([e, t]) => {
|
|
61
|
+
let n = [...t].sort((e, t) => new Date(e.recordedAt).getTime() - new Date(t.recordedAt).getTime()), r = n[0]?.value ?? 0, i = n[n.length - 1]?.value ?? 0, a = n.reduce((e, t) => Math.max(e, t.value), -Infinity), o = n.length >= 2 && r !== 0 ? (i - r) / Math.abs(r) * 100 : null;
|
|
62
|
+
return {
|
|
63
|
+
type: e,
|
|
64
|
+
points: n,
|
|
65
|
+
unit: n[0]?.unit ?? null,
|
|
66
|
+
first: r,
|
|
67
|
+
last: i,
|
|
68
|
+
peak: a,
|
|
69
|
+
deltaPct: o
|
|
70
|
+
};
|
|
71
|
+
}).sort((e, t) => t.points.length - e.points.length);
|
|
72
|
+
}
|
|
73
|
+
function I() {
|
|
74
|
+
let { teamId: j, memberId: M } = b(), { navigateTo: I } = e(), z = ie(j, M), B = ae(j), [V, H] = oe("6M"), U = p(() => N(V), [V]), W = p(() => F(z.metrics, U), [z.metrics, U]), G = p(() => F(z.metrics, 0), [z.metrics]), K = p(() => z.sessions.filter((e) => e.rating !== null), [z.sessions]), q = p(() => K.length > 0 ? K.reduce((e, t) => e + (t.rating ?? 0), 0) / K.length : null, [K]), J = p(() => z.sessions.filter((e) => e.status === "REQUESTED" || e.status === "CONFIRMED").sort((e, t) => new Date(e.scheduledAt).getTime() - new Date(t.scheduledAt).getTime()), [z.sessions]), Y = p(() => [...z.workouts].sort((e, t) => new Date(t.performedAt).getTime() - new Date(e.performedAt).getTime()), [z.workouts]), de = p(() => z.sessions.filter((e) => e.status === "COMPLETED").length, [z.sessions]);
|
|
75
|
+
if (z.isLoading) return /* @__PURE__ */ S(t, {
|
|
76
|
+
title: "Athlete",
|
|
77
|
+
children: /* @__PURE__ */ S(re, { label: "Loading athlete profile…" })
|
|
78
|
+
});
|
|
79
|
+
if (z.isError || !z.member) return /* @__PURE__ */ S(t, {
|
|
80
|
+
title: "Athlete",
|
|
81
|
+
children: /* @__PURE__ */ S(f, {
|
|
82
|
+
title: "Athlete not found",
|
|
83
|
+
message: "This athlete is not on the roster, or you do not have access.",
|
|
84
|
+
onRetry: () => I(j ? `/teams/${j}` : "/teams")
|
|
85
|
+
})
|
|
86
|
+
});
|
|
87
|
+
let X = z.member, Z = B.data?.name ?? null, Q = B.data?.sport, $ = [];
|
|
88
|
+
X.height != null && $.push({
|
|
89
|
+
icon: /* @__PURE__ */ S(y, { className: "h-4 w-4" }),
|
|
90
|
+
label: "Height",
|
|
91
|
+
value: String(X.height)
|
|
92
|
+
}), X.weight != null && $.push({
|
|
93
|
+
icon: /* @__PURE__ */ S(le, { className: "h-4 w-4" }),
|
|
94
|
+
label: "Weight",
|
|
95
|
+
value: String(X.weight)
|
|
96
|
+
}), X.dominantSide && $.push({
|
|
97
|
+
icon: /* @__PURE__ */ S(_, { className: "h-4 w-4" }),
|
|
98
|
+
label: "Dominant side",
|
|
99
|
+
value: X.dominantSide
|
|
100
|
+
}), X.position && $.push({
|
|
101
|
+
icon: /* @__PURE__ */ S(ce, { className: "h-4 w-4" }),
|
|
102
|
+
label: "Position",
|
|
103
|
+
value: X.position
|
|
104
|
+
}), X.joinedAt && $.push({
|
|
105
|
+
icon: /* @__PURE__ */ S(h, { className: "h-4 w-4" }),
|
|
106
|
+
label: "Joined",
|
|
107
|
+
value: l(X.joinedAt)
|
|
108
|
+
});
|
|
109
|
+
let fe = W[0] ?? null;
|
|
110
|
+
return /* @__PURE__ */ C(t, {
|
|
111
|
+
title: /* @__PURE__ */ S(E, { children: X.displayName }),
|
|
112
|
+
description: Z ? `${Z}${Q ? ` · ${ee[Q]}` : ""}` : void 0,
|
|
113
|
+
actions: /* @__PURE__ */ C("button", {
|
|
114
|
+
type: "button",
|
|
115
|
+
onClick: () => I(j ? `/teams/${j}` : "/teams"),
|
|
116
|
+
className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
|
|
117
|
+
children: [/* @__PURE__ */ S(m, { className: "h-4 w-4" }), " Back to team"]
|
|
118
|
+
}),
|
|
119
|
+
children: [/* @__PURE__ */ S(T, {
|
|
120
|
+
treatment: "insight",
|
|
121
|
+
className: "mb-8 p-6",
|
|
122
|
+
children: /* @__PURE__ */ C("div", {
|
|
123
|
+
className: "flex flex-col gap-6 md:flex-row md:items-start",
|
|
124
|
+
children: [/* @__PURE__ */ S(ne, {
|
|
125
|
+
name: X.displayName,
|
|
126
|
+
id: X.userId,
|
|
127
|
+
jersey: X.jerseyNumber,
|
|
128
|
+
size: "xl",
|
|
129
|
+
ring: !0,
|
|
130
|
+
className: "shrink-0"
|
|
131
|
+
}), /* @__PURE__ */ C("div", {
|
|
132
|
+
className: "min-w-0 flex-1",
|
|
133
|
+
children: [/* @__PURE__ */ C("div", {
|
|
134
|
+
className: "flex flex-wrap items-center gap-2",
|
|
135
|
+
children: [
|
|
136
|
+
/* @__PURE__ */ S(w, {
|
|
137
|
+
variant: "default",
|
|
138
|
+
children: o[X.role]
|
|
139
|
+
}),
|
|
140
|
+
X.position ? /* @__PURE__ */ S(w, {
|
|
141
|
+
variant: "secondary",
|
|
142
|
+
children: X.position
|
|
143
|
+
}) : null,
|
|
144
|
+
X.jerseyNumber == null ? null : /* @__PURE__ */ C(w, {
|
|
145
|
+
variant: "outline",
|
|
146
|
+
className: "tabular-nums",
|
|
147
|
+
children: ["#", X.jerseyNumber]
|
|
148
|
+
}),
|
|
149
|
+
Z ? /* @__PURE__ */ C(w, {
|
|
150
|
+
variant: "secondary",
|
|
151
|
+
className: "gap-1.5",
|
|
152
|
+
children: [/* @__PURE__ */ S(c, {
|
|
153
|
+
name: Z,
|
|
154
|
+
sport: Q,
|
|
155
|
+
size: 16,
|
|
156
|
+
showSportGlyph: !1
|
|
157
|
+
}), Z]
|
|
158
|
+
}) : null,
|
|
159
|
+
/* @__PURE__ */ S(a, { status: X.status })
|
|
160
|
+
]
|
|
161
|
+
}), $.length > 0 ? /* @__PURE__ */ S("dl", {
|
|
162
|
+
className: "mt-5 grid grid-cols-2 gap-x-6 gap-y-4 sm:grid-cols-3 lg:grid-cols-5",
|
|
163
|
+
children: $.map((e) => /* @__PURE__ */ C("div", {
|
|
164
|
+
className: "min-w-0",
|
|
165
|
+
children: [/* @__PURE__ */ C("dt", {
|
|
166
|
+
className: "flex items-center gap-1.5 text-xs font-medium text-text-secondary",
|
|
167
|
+
children: [/* @__PURE__ */ S("span", {
|
|
168
|
+
className: "text-text-muted",
|
|
169
|
+
children: e.icon
|
|
170
|
+
}), e.label]
|
|
171
|
+
}), /* @__PURE__ */ S("dd", {
|
|
172
|
+
className: "mt-1 truncate text-lg font-semibold text-text-primary",
|
|
173
|
+
children: e.value
|
|
174
|
+
})]
|
|
175
|
+
}, e.label))
|
|
176
|
+
}) : null]
|
|
177
|
+
})]
|
|
178
|
+
})
|
|
179
|
+
}), /* @__PURE__ */ C(O, {
|
|
180
|
+
defaultValue: "overview",
|
|
181
|
+
children: [
|
|
182
|
+
/* @__PURE__ */ C(ue, {
|
|
183
|
+
className: "mb-6 flex-wrap",
|
|
184
|
+
children: [
|
|
185
|
+
/* @__PURE__ */ S(A, {
|
|
186
|
+
value: "overview",
|
|
187
|
+
children: "Overview"
|
|
188
|
+
}),
|
|
189
|
+
/* @__PURE__ */ S(A, {
|
|
190
|
+
value: "metrics",
|
|
191
|
+
children: "Metrics"
|
|
192
|
+
}),
|
|
193
|
+
/* @__PURE__ */ S(A, {
|
|
194
|
+
value: "workouts",
|
|
195
|
+
children: "Workouts"
|
|
196
|
+
}),
|
|
197
|
+
/* @__PURE__ */ S(A, {
|
|
198
|
+
value: "sessions",
|
|
199
|
+
children: "Sessions"
|
|
200
|
+
})
|
|
201
|
+
]
|
|
202
|
+
}),
|
|
203
|
+
/* @__PURE__ */ C(k, {
|
|
204
|
+
value: "overview",
|
|
205
|
+
className: "space-y-8",
|
|
206
|
+
children: [
|
|
207
|
+
/* @__PURE__ */ C("div", {
|
|
208
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
209
|
+
children: [
|
|
210
|
+
/* @__PURE__ */ C(T, {
|
|
211
|
+
treatment: "metric",
|
|
212
|
+
className: "flex flex-col gap-2 p-5",
|
|
213
|
+
children: [/* @__PURE__ */ S("span", {
|
|
214
|
+
className: "text-sm font-medium text-text-secondary",
|
|
215
|
+
children: "Avg session rating"
|
|
216
|
+
}), q == null ? /* @__PURE__ */ S("span", {
|
|
217
|
+
className: "text-sm text-text-muted",
|
|
218
|
+
children: "No rated sessions yet"
|
|
219
|
+
}) : /* @__PURE__ */ S(x, { children: /* @__PURE__ */ S(i, {
|
|
220
|
+
value: q,
|
|
221
|
+
max: 5,
|
|
222
|
+
size: 20,
|
|
223
|
+
showValue: !0,
|
|
224
|
+
count: K.length
|
|
225
|
+
}) })]
|
|
226
|
+
}),
|
|
227
|
+
/* @__PURE__ */ S(r, {
|
|
228
|
+
label: "Sessions",
|
|
229
|
+
value: z.sessions.length,
|
|
230
|
+
icon: /* @__PURE__ */ S(h, { className: "h-4 w-4" })
|
|
231
|
+
}),
|
|
232
|
+
/* @__PURE__ */ S(r, {
|
|
233
|
+
label: "Completed",
|
|
234
|
+
value: de,
|
|
235
|
+
icon: /* @__PURE__ */ S(se, { className: "h-4 w-4" })
|
|
236
|
+
}),
|
|
237
|
+
/* @__PURE__ */ S(r, {
|
|
238
|
+
label: "Workouts logged",
|
|
239
|
+
value: z.workouts.length,
|
|
240
|
+
icon: /* @__PURE__ */ S(g, { className: "h-4 w-4" })
|
|
241
|
+
})
|
|
242
|
+
]
|
|
243
|
+
}),
|
|
244
|
+
G.some((e) => e.deltaPct != null) ? /* @__PURE__ */ C("section", { children: [/* @__PURE__ */ S("h2", {
|
|
245
|
+
className: "mb-4 text-lg font-semibold text-text-primary",
|
|
246
|
+
children: "Improvement"
|
|
247
|
+
}), /* @__PURE__ */ S("div", {
|
|
248
|
+
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4",
|
|
249
|
+
children: G.filter((e) => e.deltaPct != null).slice(0, 4).map((e) => /* @__PURE__ */ S(r, {
|
|
250
|
+
label: `${s[e.type]}${e.unit ? ` (${e.unit})` : ""}`,
|
|
251
|
+
value: e.last.toLocaleString(),
|
|
252
|
+
delta: e.deltaPct ?? void 0,
|
|
253
|
+
deltaLabel: "first → latest",
|
|
254
|
+
sparkline: e.points.map((e) => e.value)
|
|
255
|
+
}, e.type))
|
|
256
|
+
})] }) : null,
|
|
257
|
+
/* @__PURE__ */ S(R, {
|
|
258
|
+
title: "Performance trend",
|
|
259
|
+
groups: W,
|
|
260
|
+
primary: fe,
|
|
261
|
+
range: V,
|
|
262
|
+
onRangeChange: H
|
|
263
|
+
}),
|
|
264
|
+
/* @__PURE__ */ C("section", { children: [/* @__PURE__ */ C("h2", {
|
|
265
|
+
className: "mb-4 text-lg font-semibold text-text-primary",
|
|
266
|
+
children: [
|
|
267
|
+
"Upcoming sessions (",
|
|
268
|
+
J.length,
|
|
269
|
+
")"
|
|
270
|
+
]
|
|
271
|
+
}), J.length === 0 ? /* @__PURE__ */ S(d, {
|
|
272
|
+
illustration: "empty-calendar",
|
|
273
|
+
icon: /* @__PURE__ */ S(h, { className: "h-10 w-10" }),
|
|
274
|
+
title: "No upcoming sessions",
|
|
275
|
+
description: "Confirmed and requested coaching sessions for this athlete appear here."
|
|
276
|
+
}) : /* @__PURE__ */ S("div", {
|
|
277
|
+
className: "grid grid-cols-1 gap-4 md:grid-cols-2",
|
|
278
|
+
children: J.slice(0, 6).map((e) => /* @__PURE__ */ S(u, {
|
|
279
|
+
session: e,
|
|
280
|
+
athleteName: X.displayName,
|
|
281
|
+
onClick: () => I(`/sessions/${e.id}`)
|
|
282
|
+
}, e.id))
|
|
283
|
+
})] })
|
|
284
|
+
]
|
|
285
|
+
}),
|
|
286
|
+
/* @__PURE__ */ C(k, {
|
|
287
|
+
value: "metrics",
|
|
288
|
+
className: "space-y-8",
|
|
289
|
+
children: [/* @__PURE__ */ C("div", {
|
|
290
|
+
className: "flex flex-wrap items-center justify-between gap-3",
|
|
291
|
+
children: [/* @__PURE__ */ S("h2", {
|
|
292
|
+
className: "text-lg font-semibold text-text-primary",
|
|
293
|
+
children: "Performance metrics"
|
|
294
|
+
}), /* @__PURE__ */ S(L, {
|
|
295
|
+
value: V,
|
|
296
|
+
onChange: H
|
|
297
|
+
})]
|
|
298
|
+
}), W.length === 0 ? /* @__PURE__ */ S(d, {
|
|
299
|
+
illustration: "empty-data",
|
|
300
|
+
icon: /* @__PURE__ */ S(v, { className: "h-10 w-10" }),
|
|
301
|
+
title: "No metrics in this range",
|
|
302
|
+
description: "Record metrics for this athlete — speed, strength, endurance — to chart progress over time."
|
|
303
|
+
}) : /* @__PURE__ */ S("div", {
|
|
304
|
+
className: "grid grid-cols-1 gap-4 lg:grid-cols-2",
|
|
305
|
+
children: W.map((e) => /* @__PURE__ */ C(T, {
|
|
306
|
+
treatment: "metric",
|
|
307
|
+
className: "p-5",
|
|
308
|
+
children: [
|
|
309
|
+
/* @__PURE__ */ C("div", {
|
|
310
|
+
className: "mb-3 flex items-start justify-between gap-3",
|
|
311
|
+
children: [/* @__PURE__ */ C("div", { children: [/* @__PURE__ */ S("h3", {
|
|
312
|
+
className: "text-sm font-semibold uppercase tracking-wide text-text-secondary",
|
|
313
|
+
children: s[e.type]
|
|
314
|
+
}), /* @__PURE__ */ C("p", {
|
|
315
|
+
className: "mt-1 text-2xl font-semibold tabular-nums text-text-primary",
|
|
316
|
+
children: [e.last.toLocaleString(), e.unit ? /* @__PURE__ */ S("span", {
|
|
317
|
+
className: "ml-1 text-sm text-text-secondary",
|
|
318
|
+
children: e.unit
|
|
319
|
+
}) : null]
|
|
320
|
+
})] }), e.deltaPct == null ? null : /* @__PURE__ */ C(w, {
|
|
321
|
+
variant: e.deltaPct >= 0 ? "default" : "destructive",
|
|
322
|
+
className: "tabular-nums",
|
|
323
|
+
children: [
|
|
324
|
+
e.deltaPct >= 0 ? "+" : "",
|
|
325
|
+
e.deltaPct.toFixed(1),
|
|
326
|
+
"%"
|
|
327
|
+
]
|
|
328
|
+
})]
|
|
329
|
+
}),
|
|
330
|
+
/* @__PURE__ */ S(n, {
|
|
331
|
+
series: e.points.map((e) => ({
|
|
332
|
+
label: P(e.recordedAt),
|
|
333
|
+
value: e.value
|
|
334
|
+
})),
|
|
335
|
+
seriesLabel: s[e.type],
|
|
336
|
+
height: 160,
|
|
337
|
+
valueFormatter: (t) => `${t.toLocaleString()}${e.unit ? ` ${e.unit}` : ""}`
|
|
338
|
+
}),
|
|
339
|
+
/* @__PURE__ */ C("p", {
|
|
340
|
+
className: "mt-2 text-xs text-text-muted tabular-nums",
|
|
341
|
+
children: [
|
|
342
|
+
"Peak ",
|
|
343
|
+
e.peak.toLocaleString(),
|
|
344
|
+
e.unit ? ` ${e.unit}` : "",
|
|
345
|
+
" · ",
|
|
346
|
+
e.points.length,
|
|
347
|
+
" readings"
|
|
348
|
+
]
|
|
349
|
+
})
|
|
350
|
+
]
|
|
351
|
+
}, e.type))
|
|
352
|
+
})]
|
|
353
|
+
}),
|
|
354
|
+
/* @__PURE__ */ S(k, {
|
|
355
|
+
value: "workouts",
|
|
356
|
+
children: Y.length === 0 ? /* @__PURE__ */ S(d, {
|
|
357
|
+
illustration: "empty-data",
|
|
358
|
+
icon: /* @__PURE__ */ S(g, { className: "h-10 w-10" }),
|
|
359
|
+
title: "No workouts logged",
|
|
360
|
+
description: "This athlete's logged training sessions will appear here."
|
|
361
|
+
}) : /* @__PURE__ */ S(D, {
|
|
362
|
+
columns: [
|
|
363
|
+
{
|
|
364
|
+
key: "title",
|
|
365
|
+
header: "Workout",
|
|
366
|
+
priority: "primary",
|
|
367
|
+
cell: (e) => /* @__PURE__ */ S("span", {
|
|
368
|
+
className: "font-medium text-text-primary",
|
|
369
|
+
children: e.title
|
|
370
|
+
})
|
|
371
|
+
},
|
|
372
|
+
{
|
|
373
|
+
key: "duration",
|
|
374
|
+
header: "Duration",
|
|
375
|
+
priority: "secondary",
|
|
376
|
+
cell: (e) => /* @__PURE__ */ S("span", {
|
|
377
|
+
className: "tabular-nums text-text-secondary",
|
|
378
|
+
children: e.durationMin == null ? "—" : te(e.durationMin)
|
|
379
|
+
})
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
key: "load",
|
|
383
|
+
header: "Load",
|
|
384
|
+
priority: "hidden-on-mobile",
|
|
385
|
+
cell: (e) => /* @__PURE__ */ S("span", {
|
|
386
|
+
className: "tabular-nums text-text-secondary",
|
|
387
|
+
children: e.totalLoad == null ? "—" : e.totalLoad.toLocaleString()
|
|
388
|
+
})
|
|
389
|
+
},
|
|
390
|
+
{
|
|
391
|
+
key: "rpe",
|
|
392
|
+
header: "RPE",
|
|
393
|
+
priority: "hidden-on-mobile",
|
|
394
|
+
cell: (e) => /* @__PURE__ */ S("span", {
|
|
395
|
+
className: "tabular-nums text-text-secondary",
|
|
396
|
+
children: e.rpe == null ? "—" : e.rpe
|
|
397
|
+
})
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
key: "status",
|
|
401
|
+
header: "Status",
|
|
402
|
+
priority: "secondary",
|
|
403
|
+
cell: (e) => /* @__PURE__ */ S(a, { status: e.status })
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
key: "date",
|
|
407
|
+
header: "Date",
|
|
408
|
+
priority: "secondary",
|
|
409
|
+
cell: (e) => /* @__PURE__ */ S("span", {
|
|
410
|
+
className: "whitespace-nowrap text-text-secondary",
|
|
411
|
+
children: l(e.performedAt)
|
|
412
|
+
})
|
|
413
|
+
}
|
|
414
|
+
],
|
|
415
|
+
data: Y,
|
|
416
|
+
rowKey: (e) => e.id
|
|
417
|
+
})
|
|
418
|
+
}),
|
|
419
|
+
/* @__PURE__ */ S(k, {
|
|
420
|
+
value: "sessions",
|
|
421
|
+
children: z.sessions.length === 0 ? /* @__PURE__ */ S(d, {
|
|
422
|
+
illustration: "empty-calendar",
|
|
423
|
+
icon: /* @__PURE__ */ S(h, { className: "h-10 w-10" }),
|
|
424
|
+
title: "No coaching sessions",
|
|
425
|
+
description: "Booked, completed and cancelled sessions for this athlete will appear here."
|
|
426
|
+
}) : /* @__PURE__ */ S("div", {
|
|
427
|
+
className: "grid grid-cols-1 gap-4 md:grid-cols-2",
|
|
428
|
+
children: [...z.sessions].sort((e, t) => new Date(t.scheduledAt).getTime() - new Date(e.scheduledAt).getTime()).map((e) => /* @__PURE__ */ S(u, {
|
|
429
|
+
session: e,
|
|
430
|
+
athleteName: X.displayName,
|
|
431
|
+
onClick: () => I(`/sessions/${e.id}`)
|
|
432
|
+
}, e.id))
|
|
433
|
+
})
|
|
434
|
+
})
|
|
435
|
+
]
|
|
436
|
+
})]
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
function L({ value: e, onChange: t }) {
|
|
440
|
+
return /* @__PURE__ */ S("div", {
|
|
441
|
+
role: "group",
|
|
442
|
+
"aria-label": "Trend range",
|
|
443
|
+
className: "inline-flex items-center gap-1 rounded-lg border border-border-seam bg-bg-surface p-1",
|
|
444
|
+
children: j.map((n) => {
|
|
445
|
+
let r = n === e;
|
|
446
|
+
return /* @__PURE__ */ S("button", {
|
|
447
|
+
type: "button",
|
|
448
|
+
"aria-pressed": r,
|
|
449
|
+
onClick: () => t(n),
|
|
450
|
+
className: r ? "rounded-md bg-action-primary-bg px-2.5 py-1 text-xs font-medium text-action-primary-text" : "rounded-md px-2.5 py-1 text-xs font-medium text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary",
|
|
451
|
+
children: M[n]
|
|
452
|
+
}, n);
|
|
453
|
+
})
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
function R({ title: e, groups: t, primary: r, range: i, onRangeChange: a }) {
|
|
457
|
+
let o = t.find((e) => e.type !== r?.type) ?? null;
|
|
458
|
+
return /* @__PURE__ */ C(T, {
|
|
459
|
+
treatment: "metric",
|
|
460
|
+
className: "p-5",
|
|
461
|
+
children: [/* @__PURE__ */ C("div", {
|
|
462
|
+
className: "mb-4 flex flex-wrap items-center justify-between gap-3",
|
|
463
|
+
children: [/* @__PURE__ */ C("div", { children: [/* @__PURE__ */ S("h2", {
|
|
464
|
+
className: "text-lg font-semibold text-text-primary",
|
|
465
|
+
children: e
|
|
466
|
+
}), r ? /* @__PURE__ */ C("div", {
|
|
467
|
+
className: "mt-1 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-text-secondary",
|
|
468
|
+
children: [/* @__PURE__ */ C("span", {
|
|
469
|
+
className: "inline-flex items-center gap-1.5",
|
|
470
|
+
children: [
|
|
471
|
+
/* @__PURE__ */ S("span", {
|
|
472
|
+
className: "h-2 w-2 rounded-full bg-[var(--color-accent-primary)]",
|
|
473
|
+
"aria-hidden": "true"
|
|
474
|
+
}),
|
|
475
|
+
s[r.type],
|
|
476
|
+
r.unit ? ` (${r.unit})` : ""
|
|
477
|
+
]
|
|
478
|
+
}), o ? /* @__PURE__ */ C("span", {
|
|
479
|
+
className: "inline-flex items-center gap-1.5",
|
|
480
|
+
children: [
|
|
481
|
+
/* @__PURE__ */ S("span", {
|
|
482
|
+
className: "h-2 w-2 rounded-full bg-[var(--color-accent-teal)]",
|
|
483
|
+
"aria-hidden": "true"
|
|
484
|
+
}),
|
|
485
|
+
s[o.type],
|
|
486
|
+
o.unit ? ` (${o.unit})` : ""
|
|
487
|
+
]
|
|
488
|
+
}) : null]
|
|
489
|
+
}) : null] }), /* @__PURE__ */ S(L, {
|
|
490
|
+
value: i,
|
|
491
|
+
onChange: a
|
|
492
|
+
})]
|
|
493
|
+
}), r ? /* @__PURE__ */ S(n, {
|
|
494
|
+
series: r.points.map((e) => ({
|
|
495
|
+
label: P(e.recordedAt),
|
|
496
|
+
value: e.value
|
|
497
|
+
})),
|
|
498
|
+
compare: o ? o.points.map((e) => e.value) : void 0,
|
|
499
|
+
seriesLabel: s[r.type],
|
|
500
|
+
compareLabel: o ? s[o.type] : void 0,
|
|
501
|
+
height: 240
|
|
502
|
+
}) : /* @__PURE__ */ S("div", {
|
|
503
|
+
className: "flex h-40 items-center justify-center text-sm text-text-muted",
|
|
504
|
+
children: "No metric readings in this range."
|
|
505
|
+
})]
|
|
506
|
+
});
|
|
507
|
+
}
|
|
508
|
+
//#endregion
|
|
509
|
+
export { I as AthleteProfilePage };
|
|
510
|
+
|
|
511
|
+
//# sourceMappingURL=AthleteProfilePage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AthleteProfilePage.js","names":[],"sources":["../../../../src/atheletebridge/pages/athletes/AthleteProfilePage.tsx"],"sourcesContent":["/**\n * Athlete Profile\n * @module atheletebridge/pages/athletes\n *\n * A consolidated single-athlete view composed PURELY from real data via\n * `useAthleteProfile(teamId, memberId)` — the resolved team member (identity +\n * physical stats), their performance metrics (trend charts + improvement\n * tiles), workout history and coaching sessions (with read-only ratings).\n *\n * Honesty rules: there is no photo, age/DOB, or \"fitness score\" field on the\n * schema, so this screen renders an initials/jersey monogram (never a\n * fabricated headshot) and OMITS any composite wellness score. Every number\n * traces to a real row. Semantic tokens only; fully responsive.\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n CalendarClock,\n Dumbbell,\n Gauge,\n LineChart as LineChartIcon,\n Ruler,\n Star,\n Users,\n Weight,\n} from 'lucide-react';\nimport {\n Badge,\n GlassCard,\n GradientText,\n Tabs,\n TabsList,\n TabsTrigger,\n TabsContent,\n ResponsiveTable,\n type ResponsiveTableColumn,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { SessionCard } from '../../components/SessionCard';\nimport { Crest } from '../../components/Crest';\nimport { UserAvatar } from '../../components/UserAvatar';\nimport { EmptyState } from '../../components/EmptyState';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { LineChart, StatCard, RatingStars } from '../../components/viz';\nimport { MEMBER_ROLE_LABELS, METRIC_TYPE_LABELS, SPORT_LABELS } from '../../components/domain';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useAthleteProfile, useTeam } from '../../hooks/useAthleteBridgeApi';\nimport { formatDate, formatDuration } from '../../utils/formatters';\nimport type { AthMetricType, AthPerformanceMetric, AthWorkoutLog } from '../../types';\n\n// ── Trend range chips ────────────────────────────────────────────────────────\n\ntype RangeKey = '1M' | '3M' | '6M' | 'YTD' | '1Y' | 'ALL';\n\nconst RANGE_ORDER: RangeKey[] = ['1M', '3M', '6M', 'YTD', '1Y', 'ALL'];\nconst RANGE_LABELS: Record<RangeKey, string> = {\n '1M': '1M',\n '3M': '3M',\n '6M': '6M',\n YTD: 'YTD',\n '1Y': '1Y',\n ALL: 'All',\n};\n\n/** Lower bound (ms epoch) for a given range, or 0 for \"all time\". */\nfunction rangeStart(range: RangeKey): number {\n const now = Date.now();\n const DAY = 86_400_000;\n switch (range) {\n case '1M':\n return now - 30 * DAY;\n case '3M':\n return now - 90 * DAY;\n case '6M':\n return now - 180 * DAY;\n case '1Y':\n return now - 365 * DAY;\n case 'YTD':\n return new Date(new Date().getFullYear(), 0, 1).getTime();\n case 'ALL':\n return 0;\n }\n}\n\nconst shortDate = (iso: string): string =>\n new Date(iso).toLocaleDateString(undefined, { month: 'short', day: 'numeric' });\n\ninterface MetricGroup {\n type: AthMetricType;\n /** Ascending by recordedAt. */\n points: AthPerformanceMetric[];\n unit: string | null;\n first: number;\n last: number;\n peak: number;\n deltaPct: number | null;\n}\n\n/** Group metrics by type (ascending in time) + derive first/last/peak/delta. */\nfunction buildMetricGroups(metrics: AthPerformanceMetric[], start: number): MetricGroup[] {\n const groups = new Map<AthMetricType, AthPerformanceMetric[]>();\n for (const m of metrics) {\n const at = new Date(m.recordedAt).getTime();\n if (Number.isNaN(at) || at < start) {\n continue;\n }\n const list = groups.get(m.type) ?? [];\n list.push(m);\n groups.set(m.type, list);\n }\n return Array.from(groups.entries())\n .map(([type, list]) => {\n const points = [...list].sort(\n (a, b) => new Date(a.recordedAt).getTime() - new Date(b.recordedAt).getTime()\n );\n const first = points[0]?.value ?? 0;\n const last = points[points.length - 1]?.value ?? 0;\n const peak = points.reduce((acc, p) => Math.max(acc, p.value), Number.NEGATIVE_INFINITY);\n const deltaPct =\n points.length >= 2 && first !== 0 ? ((last - first) / Math.abs(first)) * 100 : null;\n return { type, points, unit: points[0]?.unit ?? null, first, last, peak, deltaPct };\n })\n .sort((a, b) => b.points.length - a.points.length);\n}\n\nexport function AthleteProfilePage() {\n const { teamId, memberId } = useParams<{ teamId: string; memberId: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const profile = useAthleteProfile(teamId, memberId);\n const team = useTeam(teamId);\n const [range, setRange] = useState<RangeKey>('6M');\n\n const start = useMemo(() => rangeStart(range), [range]);\n const metricGroups = useMemo(\n () => buildMetricGroups(profile.metrics, start),\n [profile.metrics, start]\n );\n const allTimeGroups = useMemo(() => buildMetricGroups(profile.metrics, 0), [profile.metrics]);\n\n const ratedSessions = useMemo(\n () => profile.sessions.filter((s) => s.rating !== null),\n [profile.sessions]\n );\n const avgRating = useMemo(\n () =>\n ratedSessions.length > 0\n ? ratedSessions.reduce((acc, s) => acc + (s.rating ?? 0), 0) / ratedSessions.length\n : null,\n [ratedSessions]\n );\n\n const upcomingSessions = useMemo(\n () =>\n profile.sessions\n .filter((s) => s.status === 'REQUESTED' || s.status === 'CONFIRMED')\n .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime()),\n [profile.sessions]\n );\n\n const recentWorkouts = useMemo(\n () =>\n [...profile.workouts].sort(\n (a, b) => new Date(b.performedAt).getTime() - new Date(a.performedAt).getTime()\n ),\n [profile.workouts]\n );\n\n const completedSessions = useMemo(\n () => profile.sessions.filter((s) => s.status === 'COMPLETED').length,\n [profile.sessions]\n );\n\n if (profile.isLoading) {\n return (\n <PageLayout title=\"Athlete\">\n <LoadingState label=\"Loading athlete profile…\" />\n </PageLayout>\n );\n }\n\n if (profile.isError || !profile.member) {\n return (\n <PageLayout title=\"Athlete\">\n <ErrorState\n title=\"Athlete not found\"\n message=\"This athlete is not on the roster, or you do not have access.\"\n onRetry={() => (teamId ? navigateTo(`/teams/${teamId}`) : navigateTo('/teams'))}\n />\n </PageLayout>\n );\n }\n\n const member = profile.member;\n const teamName = team.data?.name ?? null;\n const teamSport = team.data?.sport;\n\n const physicalStats: Array<{ icon: React.ReactNode; label: string; value: string }> = [];\n if (member.height != null) {\n physicalStats.push({\n icon: <Ruler className=\"h-4 w-4\" />,\n label: 'Height',\n value: String(member.height),\n });\n }\n if (member.weight != null) {\n physicalStats.push({\n icon: <Weight className=\"h-4 w-4\" />,\n label: 'Weight',\n value: String(member.weight),\n });\n }\n if (member.dominantSide) {\n physicalStats.push({\n icon: <Gauge className=\"h-4 w-4\" />,\n label: 'Dominant side',\n value: member.dominantSide,\n });\n }\n if (member.position) {\n physicalStats.push({\n icon: <Users className=\"h-4 w-4\" />,\n label: 'Position',\n value: member.position,\n });\n }\n if (member.joinedAt) {\n physicalStats.push({\n icon: <CalendarClock className=\"h-4 w-4\" />,\n label: 'Joined',\n value: formatDate(member.joinedAt),\n });\n }\n\n const primaryGroup = metricGroups[0] ?? null;\n\n const workoutColumns: ResponsiveTableColumn<AthWorkoutLog>[] = [\n {\n key: 'title',\n header: 'Workout',\n priority: 'primary',\n cell: (row) => <span className=\"font-medium text-text-primary\">{row.title}</span>,\n },\n {\n key: 'duration',\n header: 'Duration',\n priority: 'secondary',\n cell: (row) => (\n <span className=\"tabular-nums text-text-secondary\">\n {row.durationMin != null ? formatDuration(row.durationMin) : '—'}\n </span>\n ),\n },\n {\n key: 'load',\n header: 'Load',\n priority: 'hidden-on-mobile',\n cell: (row) => (\n <span className=\"tabular-nums text-text-secondary\">\n {row.totalLoad != null ? row.totalLoad.toLocaleString() : '—'}\n </span>\n ),\n },\n {\n key: 'rpe',\n header: 'RPE',\n priority: 'hidden-on-mobile',\n cell: (row) => (\n <span className=\"tabular-nums text-text-secondary\">{row.rpe != null ? row.rpe : '—'}</span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'secondary',\n cell: (row) => <StatusBadge status={row.status} />,\n },\n {\n key: 'date',\n header: 'Date',\n priority: 'secondary',\n cell: (row) => (\n <span className=\"whitespace-nowrap text-text-secondary\">{formatDate(row.performedAt)}</span>\n ),\n },\n ];\n\n return (\n <PageLayout\n title={<GradientText>{member.displayName}</GradientText>}\n description={\n teamName ? `${teamName}${teamSport ? ` · ${SPORT_LABELS[teamSport]}` : ''}` : undefined\n }\n actions={\n <button\n type=\"button\"\n onClick={() => (teamId ? navigateTo(`/teams/${teamId}`) : navigateTo('/teams'))}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-3 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back to team\n </button>\n }\n >\n {/* Identity header */}\n <GlassCard treatment=\"insight\" className=\"mb-8 p-6\">\n <div className=\"flex flex-col gap-6 md:flex-row md:items-start\">\n <UserAvatar\n name={member.displayName}\n id={member.userId}\n jersey={member.jerseyNumber}\n size=\"xl\"\n ring\n className=\"shrink-0\"\n />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge variant=\"default\">{MEMBER_ROLE_LABELS[member.role]}</Badge>\n {member.position ? <Badge variant=\"secondary\">{member.position}</Badge> : null}\n {member.jerseyNumber != null ? (\n <Badge variant=\"outline\" className=\"tabular-nums\">\n #{member.jerseyNumber}\n </Badge>\n ) : null}\n {teamName ? (\n <Badge variant=\"secondary\" className=\"gap-1.5\">\n <Crest name={teamName} sport={teamSport} size={16} showSportGlyph={false} />\n {teamName}\n </Badge>\n ) : null}\n <StatusBadge status={member.status} />\n </div>\n\n {physicalStats.length > 0 ? (\n <dl className=\"mt-5 grid grid-cols-2 gap-x-6 gap-y-4 sm:grid-cols-3 lg:grid-cols-5\">\n {physicalStats.map((stat) => (\n <div key={stat.label} className=\"min-w-0\">\n <dt className=\"flex items-center gap-1.5 text-xs font-medium text-text-secondary\">\n <span className=\"text-text-muted\">{stat.icon}</span>\n {stat.label}\n </dt>\n <dd className=\"mt-1 truncate text-lg font-semibold text-text-primary\">\n {stat.value}\n </dd>\n </div>\n ))}\n </dl>\n ) : null}\n </div>\n </div>\n </GlassCard>\n\n <Tabs defaultValue=\"overview\">\n <TabsList className=\"mb-6 flex-wrap\">\n <TabsTrigger value=\"overview\">Overview</TabsTrigger>\n <TabsTrigger value=\"metrics\">Metrics</TabsTrigger>\n <TabsTrigger value=\"workouts\">Workouts</TabsTrigger>\n <TabsTrigger value=\"sessions\">Sessions</TabsTrigger>\n </TabsList>\n\n {/* ── Overview ─────────────────────────────────────────────────────── */}\n <TabsContent value=\"overview\" className=\"space-y-8\">\n {/* KPI / improvement tiles */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"flex flex-col gap-2 p-5\">\n <span className=\"text-sm font-medium text-text-secondary\">Avg session rating</span>\n {avgRating != null ? (\n <>\n <RatingStars\n value={avgRating}\n max={5}\n size={20}\n showValue\n count={ratedSessions.length}\n />\n </>\n ) : (\n <span className=\"text-sm text-text-muted\">No rated sessions yet</span>\n )}\n </GlassCard>\n <StatCard\n label=\"Sessions\"\n value={profile.sessions.length}\n icon={<CalendarClock className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Completed\"\n value={completedSessions}\n icon={<Star className=\"h-4 w-4\" />}\n />\n <StatCard\n label=\"Workouts logged\"\n value={profile.workouts.length}\n icon={<Dumbbell className=\"h-4 w-4\" />}\n />\n </div>\n\n {/* Improvement tiles from real metric series */}\n {allTimeGroups.some((g) => g.deltaPct != null) ? (\n <section>\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">Improvement</h2>\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4\">\n {allTimeGroups\n .filter((g) => g.deltaPct != null)\n .slice(0, 4)\n .map((g) => (\n <StatCard\n key={g.type}\n label={`${METRIC_TYPE_LABELS[g.type]}${g.unit ? ` (${g.unit})` : ''}`}\n value={g.last.toLocaleString()}\n delta={g.deltaPct ?? undefined}\n deltaLabel=\"first → latest\"\n sparkline={g.points.map((p) => p.value)}\n />\n ))}\n </div>\n </section>\n ) : null}\n\n {/* Performance trend */}\n <TrendCard\n title=\"Performance trend\"\n groups={metricGroups}\n primary={primaryGroup}\n range={range}\n onRangeChange={setRange}\n />\n\n {/* Upcoming sessions */}\n <section>\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">\n Upcoming sessions ({upcomingSessions.length})\n </h2>\n {upcomingSessions.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<CalendarClock className=\"h-10 w-10\" />}\n title=\"No upcoming sessions\"\n description=\"Confirmed and requested coaching sessions for this athlete appear here.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n {upcomingSessions.slice(0, 6).map((s) => (\n <SessionCard\n key={s.id}\n session={s}\n athleteName={member.displayName}\n onClick={() => navigateTo(`/sessions/${s.id}`)}\n />\n ))}\n </div>\n )}\n </section>\n </TabsContent>\n\n {/* ── Metrics ──────────────────────────────────────────────────────── */}\n <TabsContent value=\"metrics\" className=\"space-y-8\">\n <div className=\"flex flex-wrap items-center justify-between gap-3\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Performance metrics</h2>\n <RangeChips value={range} onChange={setRange} />\n </div>\n {metricGroups.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<LineChartIcon className=\"h-10 w-10\" />}\n title=\"No metrics in this range\"\n description=\"Record metrics for this athlete — speed, strength, endurance — to chart progress over time.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 lg:grid-cols-2\">\n {metricGroups.map((g) => (\n <GlassCard key={g.type} treatment=\"metric\" className=\"p-5\">\n <div className=\"mb-3 flex items-start justify-between gap-3\">\n <div>\n <h3 className=\"text-sm font-semibold uppercase tracking-wide text-text-secondary\">\n {METRIC_TYPE_LABELS[g.type]}\n </h3>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums text-text-primary\">\n {g.last.toLocaleString()}\n {g.unit ? (\n <span className=\"ml-1 text-sm text-text-secondary\">{g.unit}</span>\n ) : null}\n </p>\n </div>\n {g.deltaPct != null ? (\n <Badge\n variant={g.deltaPct >= 0 ? 'default' : 'destructive'}\n className=\"tabular-nums\"\n >\n {g.deltaPct >= 0 ? '+' : ''}\n {g.deltaPct.toFixed(1)}%\n </Badge>\n ) : null}\n </div>\n <LineChart\n series={g.points.map((p) => ({\n label: shortDate(p.recordedAt),\n value: p.value,\n }))}\n seriesLabel={METRIC_TYPE_LABELS[g.type]}\n height={160}\n valueFormatter={(v) => `${v.toLocaleString()}${g.unit ? ` ${g.unit}` : ''}`}\n />\n <p className=\"mt-2 text-xs text-text-muted tabular-nums\">\n Peak {g.peak.toLocaleString()}\n {g.unit ? ` ${g.unit}` : ''} · {g.points.length} readings\n </p>\n </GlassCard>\n ))}\n </div>\n )}\n </TabsContent>\n\n {/* ── Workouts ─────────────────────────────────────────────────────── */}\n <TabsContent value=\"workouts\">\n {recentWorkouts.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Dumbbell className=\"h-10 w-10\" />}\n title=\"No workouts logged\"\n description=\"This athlete's logged training sessions will appear here.\"\n />\n ) : (\n <ResponsiveTable\n columns={workoutColumns}\n data={recentWorkouts}\n rowKey={(row) => row.id}\n />\n )}\n </TabsContent>\n\n {/* ── Sessions ─────────────────────────────────────────────────────── */}\n <TabsContent value=\"sessions\">\n {profile.sessions.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<CalendarClock className=\"h-10 w-10\" />}\n title=\"No coaching sessions\"\n description=\"Booked, completed and cancelled sessions for this athlete will appear here.\"\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2\">\n {[...profile.sessions]\n .sort(\n (a, b) => new Date(b.scheduledAt).getTime() - new Date(a.scheduledAt).getTime()\n )\n .map((s) => (\n <SessionCard\n key={s.id}\n session={s}\n athleteName={member.displayName}\n onClick={() => navigateTo(`/sessions/${s.id}`)}\n />\n ))}\n </div>\n )}\n </TabsContent>\n </Tabs>\n </PageLayout>\n );\n}\n\n// ── Range chips ──────────────────────────────────────────────────────────────\n\ninterface RangeChipsProps {\n value: RangeKey;\n onChange: (value: RangeKey) => void;\n}\n\nfunction RangeChips({ value, onChange }: RangeChipsProps) {\n return (\n <div\n role=\"group\"\n aria-label=\"Trend range\"\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-seam bg-bg-surface p-1\"\n >\n {RANGE_ORDER.map((key) => {\n const active = key === value;\n return (\n <button\n key={key}\n type=\"button\"\n aria-pressed={active}\n onClick={() => onChange(key)}\n className={\n active\n ? 'rounded-md bg-action-primary-bg px-2.5 py-1 text-xs font-medium text-action-primary-text'\n : 'rounded-md px-2.5 py-1 text-xs font-medium text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary'\n }\n >\n {RANGE_LABELS[key]}\n </button>\n );\n })}\n </div>\n );\n}\n\n// ── Trend card ───────────────────────────────────────────────────────────────\n\ninterface TrendCardProps {\n title: string;\n groups: MetricGroup[];\n primary: MetricGroup | null;\n range: RangeKey;\n onRangeChange: (value: RangeKey) => void;\n}\n\nfunction TrendCard({ title, groups, primary, range, onRangeChange }: TrendCardProps) {\n // Overlay a secondary metric series (aligned by index) when available, so the\n // trend reads like the mock's dual-line without fabricating a shared scale.\n const secondary = groups.find((g) => g.type !== primary?.type) ?? null;\n\n return (\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <div className=\"mb-4 flex flex-wrap items-center justify-between gap-3\">\n <div>\n <h2 className=\"text-lg font-semibold text-text-primary\">{title}</h2>\n {primary ? (\n <div className=\"mt-1 flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-text-secondary\">\n <span className=\"inline-flex items-center gap-1.5\">\n <span\n className=\"h-2 w-2 rounded-full bg-[var(--color-accent-primary)]\"\n aria-hidden=\"true\"\n />\n {METRIC_TYPE_LABELS[primary.type]}\n {primary.unit ? ` (${primary.unit})` : ''}\n </span>\n {secondary ? (\n <span className=\"inline-flex items-center gap-1.5\">\n <span\n className=\"h-2 w-2 rounded-full bg-[var(--color-accent-teal)]\"\n aria-hidden=\"true\"\n />\n {METRIC_TYPE_LABELS[secondary.type]}\n {secondary.unit ? ` (${secondary.unit})` : ''}\n </span>\n ) : null}\n </div>\n ) : null}\n </div>\n <RangeChips value={range} onChange={onRangeChange} />\n </div>\n {primary ? (\n <LineChart\n series={primary.points.map((p) => ({ label: shortDate(p.recordedAt), value: p.value }))}\n compare={secondary ? secondary.points.map((p) => p.value) : undefined}\n seriesLabel={METRIC_TYPE_LABELS[primary.type]}\n compareLabel={secondary ? METRIC_TYPE_LABELS[secondary.type] : undefined}\n height={240}\n />\n ) : (\n <div className=\"flex h-40 items-center justify-center text-sm text-text-muted\">\n No metric readings in this range.\n </div>\n )}\n </GlassCard>\n );\n}\n"],"mappings":"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"}
|