@burdenoff/microfe-atheletebridge 2026.805.1 → 2026.829.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/atheletebridge/components/LoadingState.d.ts.map +1 -1
- package/dist/atheletebridge/components/LoadingState.js +39 -32
- package/dist/atheletebridge/components/LoadingState.js.map +1 -1
- package/dist/atheletebridge/components/TeamCard.d.ts.map +1 -1
- package/dist/atheletebridge/components/TeamCard.js +45 -49
- package/dist/atheletebridge/components/TeamCard.js.map +1 -1
- package/dist/atheletebridge/hooks/index.d.ts +1 -1
- package/dist/atheletebridge/hooks/index.d.ts.map +1 -1
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.d.ts +41 -1
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.d.ts.map +1 -1
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js +58 -28
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js.map +1 -1
- package/dist/atheletebridge/pages/DashboardPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/DashboardPage.js +56 -24
- package/dist/atheletebridge/pages/DashboardPage.js.map +1 -1
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.d.ts +6 -2
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js +330 -174
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +110 -106
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js +80 -71
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js.map +1 -1
- package/dist/atheletebridge/pages/onboarding/OnboardingPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/onboarding/OnboardingPage.js +244 -210
- package/dist/atheletebridge/pages/onboarding/OnboardingPage.js.map +1 -1
- package/dist/atheletebridge/pages/performance/PerformancePage.d.ts +7 -0
- package/dist/atheletebridge/pages/performance/PerformancePage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/performance/PerformancePage.js +526 -362
- package/dist/atheletebridge/pages/performance/PerformancePage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js +169 -143
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramsListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js +77 -72
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js +104 -86
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionsListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js +54 -49
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js +286 -267
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamFormPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamFormPage.js +185 -180
- package/dist/atheletebridge/pages/teams/TeamFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamsListPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamsListPage.js +70 -74
- package/dist/atheletebridge/pages/teams/TeamsListPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -65,8 +65,8 @@ function P() {
|
|
|
65
65
|
gradientTitle: !0,
|
|
66
66
|
children: /* @__PURE__ */ O(l, { label: y("atheletebridge.pages.dashboard.loading", "Loading your sporting operation…") })
|
|
67
67
|
}) : /* @__PURE__ */ k(n, {
|
|
68
|
-
title:
|
|
69
|
-
description: "Your teams, fixtures, training and coaching — one connected record.",
|
|
68
|
+
title: `${y("atheletebridge.pages.dashboard.welcomeBack", "Welcome back")}, ${z}`,
|
|
69
|
+
description: y("atheletebridge.pages.dashboard.description", "Your teams, fixtures, training and coaching — one connected record."),
|
|
70
70
|
hero: !0,
|
|
71
71
|
gradientTitle: !0,
|
|
72
72
|
actions: /* @__PURE__ */ k("div", {
|
|
@@ -75,18 +75,26 @@ function P() {
|
|
|
75
75
|
type: "button",
|
|
76
76
|
onClick: () => E("/teams/new"),
|
|
77
77
|
className: M,
|
|
78
|
-
children: [
|
|
78
|
+
children: [
|
|
79
|
+
/* @__PURE__ */ O(S, { className: "h-4 w-4" }),
|
|
80
|
+
" ",
|
|
81
|
+
y("atheletebridge.pages.dashboard.newTeam", "New team")
|
|
82
|
+
]
|
|
79
83
|
}), /* @__PURE__ */ k("button", {
|
|
80
84
|
type: "button",
|
|
81
85
|
onClick: () => E("/performance"),
|
|
82
86
|
className: M,
|
|
83
|
-
children: [
|
|
87
|
+
children: [
|
|
88
|
+
/* @__PURE__ */ O(w, { className: "h-4 w-4" }),
|
|
89
|
+
" ",
|
|
90
|
+
y("atheletebridge.pages.dashboard.trackPerformance", "Track performance")
|
|
91
|
+
]
|
|
84
92
|
})]
|
|
85
93
|
}),
|
|
86
94
|
children: [
|
|
87
95
|
/* @__PURE__ */ O(j, {
|
|
88
96
|
className: "mb-6",
|
|
89
|
-
children: "AthleteBridge is the operating system for sports, coaching and training. From here you can assemble your roster, schedule fixtures, build training programs, run coaching sessions and track athlete performance — all reading and writing the same workspace-scoped source of truth."
|
|
97
|
+
children: y("atheletebridge.pages.dashboard.purpose", "AthleteBridge is the operating system for sports, coaching and training. From here you can assemble your roster, schedule fixtures, build training programs, run coaching sessions and track athlete performance — all reading and writing the same workspace-scoped source of truth.")
|
|
90
98
|
}),
|
|
91
99
|
/* @__PURE__ */ k("div", {
|
|
92
100
|
"data-tour": "dashboard-kpis",
|
|
@@ -95,26 +103,26 @@ function P() {
|
|
|
95
103
|
/* @__PURE__ */ O(i, {
|
|
96
104
|
icon: /* @__PURE__ */ O(D, { className: "h-5 w-5" }),
|
|
97
105
|
value: B.length,
|
|
98
|
-
label: "Teams",
|
|
106
|
+
label: y("atheletebridge.pages.dashboard.kpi.teams", "Teams"),
|
|
99
107
|
tone: "accent",
|
|
100
108
|
featured: !0
|
|
101
109
|
}),
|
|
102
110
|
/* @__PURE__ */ O(i, {
|
|
103
111
|
icon: /* @__PURE__ */ O(v, { className: "h-5 w-5" }),
|
|
104
112
|
value: V.length,
|
|
105
|
-
label: "Fixtures",
|
|
113
|
+
label: y("atheletebridge.pages.dashboard.kpi.fixtures", "Fixtures"),
|
|
106
114
|
tone: "accent"
|
|
107
115
|
}),
|
|
108
116
|
/* @__PURE__ */ O(i, {
|
|
109
117
|
icon: /* @__PURE__ */ O(x, { className: "h-5 w-5" }),
|
|
110
118
|
value: `${q}/${H.length}`,
|
|
111
|
-
label: "Programs published",
|
|
119
|
+
label: y("atheletebridge.pages.dashboard.kpi.programsPublished", "Programs published"),
|
|
112
120
|
tone: "accent"
|
|
113
121
|
}),
|
|
114
122
|
/* @__PURE__ */ O(i, {
|
|
115
123
|
icon: /* @__PURE__ */ O(b, { className: "h-5 w-5" }),
|
|
116
124
|
value: U.length,
|
|
117
|
-
label: "Sessions",
|
|
125
|
+
label: y("atheletebridge.pages.dashboard.kpi.sessions", "Sessions"),
|
|
118
126
|
tone: "accent"
|
|
119
127
|
})
|
|
120
128
|
]
|
|
@@ -148,7 +156,11 @@ function P() {
|
|
|
148
156
|
type: "button",
|
|
149
157
|
onClick: () => E("/fixtures/new"),
|
|
150
158
|
className: M,
|
|
151
|
-
children: [
|
|
159
|
+
children: [
|
|
160
|
+
/* @__PURE__ */ O(S, { className: "h-4 w-4" }),
|
|
161
|
+
" ",
|
|
162
|
+
y("atheletebridge.pages.dashboard.scheduleFixture", "Schedule fixture")
|
|
163
|
+
]
|
|
152
164
|
})
|
|
153
165
|
}) : /* @__PURE__ */ O(A, {
|
|
154
166
|
treatment: "glass",
|
|
@@ -164,7 +176,7 @@ function P() {
|
|
|
164
176
|
className: "mb-4 flex items-center min-h-[1.75rem]",
|
|
165
177
|
children: /* @__PURE__ */ O("h2", {
|
|
166
178
|
className: "text-lg font-semibold text-text-primary",
|
|
167
|
-
children: "Recent activity"
|
|
179
|
+
children: y("atheletebridge.pages.dashboard.recentActivity", "Recent activity")
|
|
168
180
|
})
|
|
169
181
|
}), Y.length === 0 ? /* @__PURE__ */ O(A, {
|
|
170
182
|
treatment: "metric",
|
|
@@ -172,8 +184,8 @@ function P() {
|
|
|
172
184
|
children: /* @__PURE__ */ O(c, {
|
|
173
185
|
illustration: "empty-data",
|
|
174
186
|
icon: /* @__PURE__ */ O(w, { className: "h-10 w-10" }),
|
|
175
|
-
title: "No recent activity",
|
|
176
|
-
description: "Create teams, publish programs or record results — the latest moves land here."
|
|
187
|
+
title: y("atheletebridge.pages.dashboard.noRecentActivity", "No recent activity"),
|
|
188
|
+
description: y("atheletebridge.pages.dashboard.noRecentActivityHint", "Create teams, publish programs or record results — the latest moves land here.")
|
|
177
189
|
})
|
|
178
190
|
}) : /* @__PURE__ */ O(A, {
|
|
179
191
|
treatment: "metric",
|
|
@@ -189,23 +201,31 @@ function P() {
|
|
|
189
201
|
className: "mb-4 flex items-center justify-between",
|
|
190
202
|
children: [/* @__PURE__ */ O("h2", {
|
|
191
203
|
className: "text-lg font-semibold text-text-primary",
|
|
192
|
-
children: "Upcoming sessions"
|
|
204
|
+
children: y("atheletebridge.pages.dashboard.upcomingSessions", "Upcoming sessions")
|
|
193
205
|
}), /* @__PURE__ */ k("button", {
|
|
194
206
|
type: "button",
|
|
195
207
|
onClick: () => E("/sessions"),
|
|
196
208
|
className: "flex items-center gap-1 text-sm font-medium text-text-link hover:underline",
|
|
197
|
-
children: [
|
|
209
|
+
children: [
|
|
210
|
+
y("atheletebridge.pages.dashboard.viewAll", "View all"),
|
|
211
|
+
" ",
|
|
212
|
+
/* @__PURE__ */ O(_, { className: "h-3.5 w-3.5" })
|
|
213
|
+
]
|
|
198
214
|
})]
|
|
199
215
|
}), G.length === 0 ? /* @__PURE__ */ O(c, {
|
|
200
216
|
illustration: "empty-calendar",
|
|
201
217
|
icon: /* @__PURE__ */ O(b, { className: "h-10 w-10" }),
|
|
202
|
-
title: "No upcoming sessions",
|
|
203
|
-
description: "Book a coaching session with an athlete to get started.",
|
|
218
|
+
title: y("atheletebridge.pages.dashboard.noSessions", "No upcoming sessions"),
|
|
219
|
+
description: y("atheletebridge.pages.dashboard.noSessionsHint", "Book a coaching session with an athlete to get started."),
|
|
204
220
|
action: /* @__PURE__ */ k("button", {
|
|
205
221
|
type: "button",
|
|
206
222
|
onClick: () => E("/sessions/new"),
|
|
207
223
|
className: M,
|
|
208
|
-
children: [
|
|
224
|
+
children: [
|
|
225
|
+
/* @__PURE__ */ O(S, { className: "h-4 w-4" }),
|
|
226
|
+
" ",
|
|
227
|
+
y("atheletebridge.pages.dashboard.bookSession", "Book session")
|
|
228
|
+
]
|
|
209
229
|
})
|
|
210
230
|
}) : /* @__PURE__ */ O("div", {
|
|
211
231
|
className: "grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3",
|
|
@@ -221,30 +241,42 @@ function P() {
|
|
|
221
241
|
className: "mb-4 flex items-center justify-between",
|
|
222
242
|
children: [/* @__PURE__ */ O("h2", {
|
|
223
243
|
className: "text-lg font-semibold text-text-primary",
|
|
224
|
-
children: "Your teams"
|
|
244
|
+
children: y("atheletebridge.pages.dashboard.yourTeams", "Your teams")
|
|
225
245
|
}), /* @__PURE__ */ k("button", {
|
|
226
246
|
type: "button",
|
|
227
247
|
onClick: () => E("/teams"),
|
|
228
248
|
className: "flex items-center gap-1 text-sm font-medium text-text-link hover:underline",
|
|
229
|
-
children: [
|
|
249
|
+
children: [
|
|
250
|
+
y("atheletebridge.pages.dashboard.viewAll", "View all"),
|
|
251
|
+
" ",
|
|
252
|
+
/* @__PURE__ */ O(_, { className: "h-3.5 w-3.5" })
|
|
253
|
+
]
|
|
230
254
|
})]
|
|
231
255
|
}), K.length === 0 ? /* @__PURE__ */ O(c, {
|
|
232
256
|
illustration: "empty-data",
|
|
233
257
|
icon: /* @__PURE__ */ O(T, { className: "h-10 w-10" }),
|
|
234
|
-
title: "No teams yet",
|
|
235
|
-
description: "Set up AthleteBridge in about a minute — pick your sport, create your first team and add yourself to the roster.",
|
|
258
|
+
title: y("atheletebridge.pages.dashboard.noTeams", "No teams yet"),
|
|
259
|
+
description: y("atheletebridge.pages.dashboard.noTeamsHint", "Set up AthleteBridge in about a minute — pick your sport, create your first team and add yourself to the roster."),
|
|
236
260
|
action: /* @__PURE__ */ k("div", {
|
|
237
261
|
className: "flex flex-wrap items-center justify-center gap-2",
|
|
238
262
|
children: [/* @__PURE__ */ k("button", {
|
|
239
263
|
type: "button",
|
|
240
264
|
onClick: () => E("/onboarding"),
|
|
241
265
|
className: M,
|
|
242
|
-
children: [
|
|
266
|
+
children: [
|
|
267
|
+
/* @__PURE__ */ O(C, { className: "h-4 w-4" }),
|
|
268
|
+
" ",
|
|
269
|
+
y("atheletebridge.pages.dashboard.getStarted", "Get started")
|
|
270
|
+
]
|
|
243
271
|
}), /* @__PURE__ */ k("button", {
|
|
244
272
|
type: "button",
|
|
245
273
|
onClick: () => E("/teams/new"),
|
|
246
274
|
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",
|
|
247
|
-
children: [
|
|
275
|
+
children: [
|
|
276
|
+
/* @__PURE__ */ O(S, { className: "h-4 w-4" }),
|
|
277
|
+
" ",
|
|
278
|
+
y("atheletebridge.pages.dashboard.createTeam", "Create team")
|
|
279
|
+
]
|
|
248
280
|
})]
|
|
249
281
|
})
|
|
250
282
|
}) : /* @__PURE__ */ O("div", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/atheletebridge/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * Dashboard / Overview\n * @module atheletebridge/pages\n *\n * The AthleteBridge landing page: a coach command-center that reads live counts\n * and recent items across teams, fixtures, programs and sessions from the real\n * `ath*` API. Elevated to the concept bar: glass teal-stat KPI tiles, a\n * crest-vs-crest upcoming-fixtures rail, and a real (derived) recent-activity\n * timeline — every item mapped to a real record, nothing fabricated.\n */\n\nimport { useMemo } from 'react';\nimport {\n Users,\n CalendarDays,\n Dumbbell,\n ClipboardCheck,\n ArrowRight,\n Plus,\n Rocket,\n Trophy,\n TrendingUp,\n UserPlus,\n CheckCircle2,\n type LucideIcon,\n} from 'lucide-react';\nimport { GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { MetricsCard } from '../components/MetricsCard';\nimport { TeamCard } from '../components/TeamCard';\nimport { MatchupRow } from '../components/MatchupRow';\nimport { SessionCard } from '../components/SessionCard';\nimport { EmptyState } from '../components/EmptyState';\nimport { LoadingState } from '../components/LoadingState';\nimport { Timeline, type TimelineItem, type TimelineTone } from '../components/viz';\nimport { useAthleteBridge } from '../context/AthleteBridgeContext';\nimport {\n useTeams,\n useFixtures,\n usePrograms,\n useSessions,\n useRecentActivity,\n type ActivityType,\n} from '../hooks/useAthleteBridgeApi';\nimport { timeAgo } from '../utils/timeAgo';\nimport { useAbTranslate } from '../i18n';\n\n/**\n * Brand gradient CTA — orange action-primary blended toward accent-purple, the\n * same brand gradient the hero title uses. Token-only (no raw colors).\n */\nconst GRADIENT_CTA =\n 'inline-flex items-center gap-1.5 rounded-lg px-4 py-2 text-sm font-semibold text-action-primary-text shadow-elevation-1 transition-[filter,box-shadow] hover:shadow-elevation-2 hover:brightness-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] bg-[linear-gradient(120deg,var(--color-action-primary-bg),color-mix(in_oklab,var(--color-action-primary-bg)_62%,var(--color-accent-purple)),var(--color-action-primary-bg))]';\n\n/** Per-activity-type node styling for the recent-activity timeline. */\nconst ACTIVITY_VISUAL: Record<ActivityType, { tone: TimelineTone; Icon: LucideIcon }> = {\n 'program-published': { tone: 'success', Icon: ClipboardCheck },\n 'member-added': { tone: 'accent', Icon: UserPlus },\n 'fixture-updated': { tone: 'warning', Icon: CalendarDays },\n 'session-completed': { tone: 'info', Icon: CheckCircle2 },\n 'team-created': { tone: 'accent', Icon: Users },\n};\n\nexport function DashboardPage() {\n const tr = useAbTranslate();\n const { navigateTo, currentUser } = useAthleteBridge();\n\n const teams = useTeams({ pagination: { limit: 50 } });\n const fixtures = useFixtures({ pagination: { limit: 50 } });\n const programs = usePrograms({ pagination: { limit: 50 } });\n const sessions = useSessions({ pagination: { limit: 50 } });\n\n const firstName = currentUser?.firstName ?? currentUser?.name ?? 'coach';\n\n const teamItems = useMemo(() => teams.data?.items ?? [], [teams.data]);\n const fixtureItems = useMemo(() => fixtures.data?.items ?? [], [fixtures.data]);\n const programItems = useMemo(() => programs.data?.items ?? [], [programs.data]);\n const sessionItems = useMemo(() => sessions.data?.items ?? [], [sessions.data]);\n\n const upcomingFixtures = useMemo(\n () =>\n [...fixtureItems]\n .filter((f) => f.status === 'SCHEDULED' || f.status === 'LIVE')\n .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime())\n .slice(0, 5),\n [fixtureItems]\n );\n\n const upcomingSessions = useMemo(\n () =>\n [...sessionItems]\n .filter((s) => s.status === 'REQUESTED' || s.status === 'CONFIRMED')\n .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime())\n .slice(0, 3),\n [sessionItems]\n );\n\n const recentTeams = useMemo(() => teamItems.slice(0, 6), [teamItems]);\n\n const publishedPrograms = programItems.filter((p) => p.status === 'PUBLISHED').length;\n\n const activity = useRecentActivity({\n teams: teamItems,\n fixtures: fixtureItems,\n sessions: sessionItems,\n programs: programItems,\n limit: 6,\n });\n\n const activityTimeline = useMemo<TimelineItem[]>(\n () =>\n activity.map((item) => {\n const visual = ACTIVITY_VISUAL[item.type];\n const Icon = visual.Icon;\n return {\n id: item.id,\n tone: visual.tone,\n icon: <Icon className=\"h-3 w-3\" aria-hidden=\"true\" />,\n title: item.title,\n meta: item.subtitle,\n time: timeAgo(item.at),\n };\n }),\n [activity]\n );\n\n const isLoading =\n teams.isLoading || fixtures.isLoading || programs.isLoading || sessions.isLoading;\n\n if (isLoading) {\n return (\n <PageLayout title=\"AthleteBridge\" hero gradientTitle>\n <LoadingState\n label={tr('atheletebridge.pages.dashboard.loading', 'Loading your sporting operation…')}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={`Welcome back, ${firstName}`}\n description=\"Your teams, fixtures, training and coaching — one connected record.\"\n hero\n gradientTitle\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button type=\"button\" onClick={() => navigateTo('/teams/new')} className={GRADIENT_CTA}>\n <Plus className=\"h-4 w-4\" /> New team\n </button>\n <button type=\"button\" onClick={() => navigateTo('/performance')} className={GRADIENT_CTA}>\n <TrendingUp className=\"h-4 w-4\" /> Track performance\n </button>\n </div>\n }\n >\n <PagePurpose className=\"mb-6\">\n AthleteBridge is the operating system for sports, coaching and training. From here you can\n assemble your roster, schedule fixtures, build training programs, run coaching sessions and\n track athlete performance — all reading and writing the same workspace-scoped source of\n truth.\n </PagePurpose>\n\n {/* KPIs — glass tiles with teal stat values (2-col mobile → 4-col lg) */}\n <div data-tour=\"dashboard-kpis\" className=\"mb-8 grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <MetricsCard\n icon={<Users className=\"h-5 w-5\" />}\n value={teamItems.length}\n label=\"Teams\"\n tone=\"accent\"\n featured\n />\n <MetricsCard\n icon={<CalendarDays className=\"h-5 w-5\" />}\n value={fixtureItems.length}\n label=\"Fixtures\"\n tone=\"accent\"\n />\n <MetricsCard\n icon={<Dumbbell className=\"h-5 w-5\" />}\n value={`${publishedPrograms}/${programItems.length}`}\n label=\"Programs published\"\n tone=\"accent\"\n />\n <MetricsCard\n icon={<ClipboardCheck className=\"h-5 w-5\" />}\n value={sessionItems.length}\n label=\"Sessions\"\n tone=\"accent\"\n />\n </div>\n\n {/* Upcoming fixtures (matchup rail) + Recent activity (timeline) */}\n <div className=\"mb-8 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Fixtures — spans two columns on large screens */}\n <section data-tour=\"dashboard-fixtures\" className=\"lg:col-span-2\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('atheletebridge.pages.dashboard.upcomingFixtures', 'Upcoming fixtures')}\n </h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures')}\n className=\"flex items-center gap-1 text-sm font-medium text-text-link hover:underline\"\n >\n {tr('atheletebridge.pages.dashboard.viewAllFixtures', 'View all fixtures')}{' '}\n <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n {upcomingFixtures.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<CalendarDays className=\"h-10 w-10\" />}\n title={tr('atheletebridge.pages.dashboard.noFixtures', 'No upcoming fixtures')}\n description={tr(\n 'atheletebridge.pages.dashboard.noFixturesHint',\n 'Schedule a match to start tracking results and standings.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures/new')}\n className={GRADIENT_CTA}\n >\n <Plus className=\"h-4 w-4\" /> Schedule fixture\n </button>\n }\n />\n ) : (\n <GlassCard treatment=\"glass\" className=\"divide-y divide-border-seam px-4 py-1 sm:px-5\">\n {upcomingFixtures.map((fixture) => (\n <MatchupRow\n key={fixture.id}\n fixture={fixture}\n onClick={() => navigateTo(`/fixtures/${fixture.id}`)}\n />\n ))}\n </GlassCard>\n )}\n </section>\n\n {/* Recent activity */}\n <section data-tour=\"dashboard-activity\">\n <div className=\"mb-4 flex items-center min-h-[1.75rem]\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Recent activity</h2>\n </div>\n {activityTimeline.length === 0 ? (\n <GlassCard treatment=\"metric\" className=\"p-6\">\n <EmptyState\n illustration=\"empty-data\"\n icon={<TrendingUp className=\"h-10 w-10\" />}\n title=\"No recent activity\"\n description=\"Create teams, publish programs or record results — the latest moves land here.\"\n />\n </GlassCard>\n ) : (\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <Timeline items={activityTimeline} />\n </GlassCard>\n )}\n </section>\n </div>\n\n {/* Upcoming sessions */}\n <section data-tour=\"dashboard-sessions\" className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Upcoming sessions</h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions')}\n className=\"flex items-center gap-1 text-sm font-medium text-text-link hover:underline\"\n >\n View all <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n {upcomingSessions.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<ClipboardCheck className=\"h-10 w-10\" />}\n title=\"No upcoming sessions\"\n description=\"Book a coaching session with an athlete to get started.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions/new')}\n className={GRADIENT_CTA}\n >\n <Plus className=\"h-4 w-4\" /> Book session\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {upcomingSessions.map((session) => (\n <SessionCard\n key={session.id}\n session={session}\n onClick={() => navigateTo(`/sessions/${session.id}`)}\n />\n ))}\n </div>\n )}\n </section>\n\n {/* Teams */}\n <section data-tour=\"dashboard-teams\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Your teams</h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams')}\n className=\"flex items-center gap-1 text-sm font-medium text-text-link hover:underline\"\n >\n View all <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n {recentTeams.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Trophy className=\"h-10 w-10\" />}\n title=\"No teams yet\"\n description=\"Set up AthleteBridge in about a minute — pick your sport, create your first team and add yourself to the roster.\"\n action={\n <div className=\"flex flex-wrap items-center justify-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/onboarding')}\n className={GRADIENT_CTA}\n >\n <Rocket className=\"h-4 w-4\" /> Get started\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams/new')}\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 <Plus className=\"h-4 w-4\" /> Create team\n </button>\n </div>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {recentTeams.map((team) => (\n <TeamCard key={team.id} team={team} onClick={() => navigateTo(`/teams/${team.id}`)} />\n ))}\n </div>\n )}\n </section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/atheletebridge/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * Dashboard / Overview\n * @module atheletebridge/pages\n *\n * The AthleteBridge landing page: a coach command-center that reads live counts\n * and recent items across teams, fixtures, programs and sessions from the real\n * `ath*` API. Elevated to the concept bar: glass teal-stat KPI tiles, a\n * crest-vs-crest upcoming-fixtures rail, and a real (derived) recent-activity\n * timeline — every item mapped to a real record, nothing fabricated.\n */\n\nimport { useMemo } from 'react';\nimport {\n Users,\n CalendarDays,\n Dumbbell,\n ClipboardCheck,\n ArrowRight,\n Plus,\n Rocket,\n Trophy,\n TrendingUp,\n UserPlus,\n CheckCircle2,\n type LucideIcon,\n} from 'lucide-react';\nimport { GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { MetricsCard } from '../components/MetricsCard';\nimport { TeamCard } from '../components/TeamCard';\nimport { MatchupRow } from '../components/MatchupRow';\nimport { SessionCard } from '../components/SessionCard';\nimport { EmptyState } from '../components/EmptyState';\nimport { LoadingState } from '../components/LoadingState';\nimport { Timeline, type TimelineItem, type TimelineTone } from '../components/viz';\nimport { useAthleteBridge } from '../context/AthleteBridgeContext';\nimport {\n useTeams,\n useFixtures,\n usePrograms,\n useSessions,\n useRecentActivity,\n type ActivityType,\n} from '../hooks/useAthleteBridgeApi';\nimport { timeAgo } from '../utils/timeAgo';\nimport { useAbTranslate } from '../i18n';\n\n/**\n * Brand gradient CTA — orange action-primary blended toward accent-purple, the\n * same brand gradient the hero title uses. Token-only (no raw colors).\n */\nconst GRADIENT_CTA =\n 'inline-flex items-center gap-1.5 rounded-lg px-4 py-2 text-sm font-semibold text-action-primary-text shadow-elevation-1 transition-[filter,box-shadow] hover:shadow-elevation-2 hover:brightness-110 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] bg-[linear-gradient(120deg,var(--color-action-primary-bg),color-mix(in_oklab,var(--color-action-primary-bg)_62%,var(--color-accent-purple)),var(--color-action-primary-bg))]';\n\n/** Per-activity-type node styling for the recent-activity timeline. */\nconst ACTIVITY_VISUAL: Record<ActivityType, { tone: TimelineTone; Icon: LucideIcon }> = {\n 'program-published': { tone: 'success', Icon: ClipboardCheck },\n 'member-added': { tone: 'accent', Icon: UserPlus },\n 'fixture-updated': { tone: 'warning', Icon: CalendarDays },\n 'session-completed': { tone: 'info', Icon: CheckCircle2 },\n 'team-created': { tone: 'accent', Icon: Users },\n};\n\nexport function DashboardPage() {\n const tr = useAbTranslate();\n const { navigateTo, currentUser } = useAthleteBridge();\n\n const teams = useTeams({ pagination: { limit: 50 } });\n const fixtures = useFixtures({ pagination: { limit: 50 } });\n const programs = usePrograms({ pagination: { limit: 50 } });\n const sessions = useSessions({ pagination: { limit: 50 } });\n\n const firstName = currentUser?.firstName ?? currentUser?.name ?? 'coach';\n\n const teamItems = useMemo(() => teams.data?.items ?? [], [teams.data]);\n const fixtureItems = useMemo(() => fixtures.data?.items ?? [], [fixtures.data]);\n const programItems = useMemo(() => programs.data?.items ?? [], [programs.data]);\n const sessionItems = useMemo(() => sessions.data?.items ?? [], [sessions.data]);\n\n const upcomingFixtures = useMemo(\n () =>\n [...fixtureItems]\n .filter((f) => f.status === 'SCHEDULED' || f.status === 'LIVE')\n .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime())\n .slice(0, 5),\n [fixtureItems]\n );\n\n const upcomingSessions = useMemo(\n () =>\n [...sessionItems]\n .filter((s) => s.status === 'REQUESTED' || s.status === 'CONFIRMED')\n .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime())\n .slice(0, 3),\n [sessionItems]\n );\n\n const recentTeams = useMemo(() => teamItems.slice(0, 6), [teamItems]);\n\n const publishedPrograms = programItems.filter((p) => p.status === 'PUBLISHED').length;\n\n const activity = useRecentActivity({\n teams: teamItems,\n fixtures: fixtureItems,\n sessions: sessionItems,\n programs: programItems,\n limit: 6,\n });\n\n const activityTimeline = useMemo<TimelineItem[]>(\n () =>\n activity.map((item) => {\n const visual = ACTIVITY_VISUAL[item.type];\n const Icon = visual.Icon;\n return {\n id: item.id,\n tone: visual.tone,\n icon: <Icon className=\"h-3 w-3\" aria-hidden=\"true\" />,\n title: item.title,\n meta: item.subtitle,\n time: timeAgo(item.at),\n };\n }),\n [activity]\n );\n\n const isLoading =\n teams.isLoading || fixtures.isLoading || programs.isLoading || sessions.isLoading;\n\n if (isLoading) {\n return (\n <PageLayout title=\"AthleteBridge\" hero gradientTitle>\n <LoadingState\n label={tr('atheletebridge.pages.dashboard.loading', 'Loading your sporting operation…')}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={`${tr('atheletebridge.pages.dashboard.welcomeBack', 'Welcome back')}, ${firstName}`}\n description={tr(\n 'atheletebridge.pages.dashboard.description',\n 'Your teams, fixtures, training and coaching — one connected record.'\n )}\n hero\n gradientTitle\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button type=\"button\" onClick={() => navigateTo('/teams/new')} className={GRADIENT_CTA}>\n <Plus className=\"h-4 w-4\" /> {tr('atheletebridge.pages.dashboard.newTeam', 'New team')}\n </button>\n <button type=\"button\" onClick={() => navigateTo('/performance')} className={GRADIENT_CTA}>\n <TrendingUp className=\"h-4 w-4\" /> {tr('atheletebridge.pages.dashboard.trackPerformance', 'Track performance')}\n </button>\n </div>\n }\n >\n <PagePurpose className=\"mb-6\">\n {tr(\n 'atheletebridge.pages.dashboard.purpose',\n 'AthleteBridge is the operating system for sports, coaching and training. From here you can assemble your roster, schedule fixtures, build training programs, run coaching sessions and track athlete performance — all reading and writing the same workspace-scoped source of truth.'\n )}\n </PagePurpose>\n\n {/* KPIs — glass tiles with teal stat values (2-col mobile → 4-col lg) */}\n <div data-tour=\"dashboard-kpis\" className=\"mb-8 grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <MetricsCard\n icon={<Users className=\"h-5 w-5\" />}\n value={teamItems.length}\n label={tr('atheletebridge.pages.dashboard.kpi.teams', 'Teams')}\n tone=\"accent\"\n featured\n />\n <MetricsCard\n icon={<CalendarDays className=\"h-5 w-5\" />}\n value={fixtureItems.length}\n label={tr('atheletebridge.pages.dashboard.kpi.fixtures', 'Fixtures')}\n tone=\"accent\"\n />\n <MetricsCard\n icon={<Dumbbell className=\"h-5 w-5\" />}\n value={`${publishedPrograms}/${programItems.length}`}\n label={tr('atheletebridge.pages.dashboard.kpi.programsPublished', 'Programs published')}\n tone=\"accent\"\n />\n <MetricsCard\n icon={<ClipboardCheck className=\"h-5 w-5\" />}\n value={sessionItems.length}\n label={tr('atheletebridge.pages.dashboard.kpi.sessions', 'Sessions')}\n tone=\"accent\"\n />\n </div>\n\n {/* Upcoming fixtures (matchup rail) + Recent activity (timeline) */}\n <div className=\"mb-8 grid grid-cols-1 gap-6 lg:grid-cols-3\">\n {/* Fixtures — spans two columns on large screens */}\n <section data-tour=\"dashboard-fixtures\" className=\"lg:col-span-2\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('atheletebridge.pages.dashboard.upcomingFixtures', 'Upcoming fixtures')}\n </h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures')}\n className=\"flex items-center gap-1 text-sm font-medium text-text-link hover:underline\"\n >\n {tr('atheletebridge.pages.dashboard.viewAllFixtures', 'View all fixtures')}{' '}\n <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n {upcomingFixtures.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<CalendarDays className=\"h-10 w-10\" />}\n title={tr('atheletebridge.pages.dashboard.noFixtures', 'No upcoming fixtures')}\n description={tr(\n 'atheletebridge.pages.dashboard.noFixturesHint',\n 'Schedule a match to start tracking results and standings.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures/new')}\n className={GRADIENT_CTA}\n >\n <Plus className=\"h-4 w-4\" /> {tr('atheletebridge.pages.dashboard.scheduleFixture', 'Schedule fixture')}\n </button>\n }\n />\n ) : (\n <GlassCard treatment=\"glass\" className=\"divide-y divide-border-seam px-4 py-1 sm:px-5\">\n {upcomingFixtures.map((fixture) => (\n <MatchupRow\n key={fixture.id}\n fixture={fixture}\n onClick={() => navigateTo(`/fixtures/${fixture.id}`)}\n />\n ))}\n </GlassCard>\n )}\n </section>\n\n {/* Recent activity */}\n <section data-tour=\"dashboard-activity\">\n <div className=\"mb-4 flex items-center min-h-[1.75rem]\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('atheletebridge.pages.dashboard.recentActivity', 'Recent activity')}\n </h2>\n </div>\n {activityTimeline.length === 0 ? (\n <GlassCard treatment=\"metric\" className=\"p-6\">\n <EmptyState\n illustration=\"empty-data\"\n icon={<TrendingUp className=\"h-10 w-10\" />}\n title={tr('atheletebridge.pages.dashboard.noRecentActivity', 'No recent activity')}\n description={tr(\n 'atheletebridge.pages.dashboard.noRecentActivityHint',\n 'Create teams, publish programs or record results — the latest moves land here.'\n )}\n />\n </GlassCard>\n ) : (\n <GlassCard treatment=\"metric\" className=\"p-5\">\n <Timeline items={activityTimeline} />\n </GlassCard>\n )}\n </section>\n </div>\n\n {/* Upcoming sessions */}\n <section data-tour=\"dashboard-sessions\" className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('atheletebridge.pages.dashboard.upcomingSessions', 'Upcoming sessions')}\n </h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions')}\n className=\"flex items-center gap-1 text-sm font-medium text-text-link hover:underline\"\n >\n {tr('atheletebridge.pages.dashboard.viewAll', 'View all')} <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n {upcomingSessions.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<ClipboardCheck className=\"h-10 w-10\" />}\n title={tr('atheletebridge.pages.dashboard.noSessions', 'No upcoming sessions')}\n description={tr(\n 'atheletebridge.pages.dashboard.noSessionsHint',\n 'Book a coaching session with an athlete to get started.'\n )}\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions/new')}\n className={GRADIENT_CTA}\n >\n <Plus className=\"h-4 w-4\" /> {tr('atheletebridge.pages.dashboard.bookSession', 'Book session')}\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {upcomingSessions.map((session) => (\n <SessionCard\n key={session.id}\n session={session}\n onClick={() => navigateTo(`/sessions/${session.id}`)}\n />\n ))}\n </div>\n )}\n </section>\n\n {/* Teams */}\n <section data-tour=\"dashboard-teams\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">\n {tr('atheletebridge.pages.dashboard.yourTeams', 'Your teams')}\n </h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams')}\n className=\"flex items-center gap-1 text-sm font-medium text-text-link hover:underline\"\n >\n {tr('atheletebridge.pages.dashboard.viewAll', 'View all')} <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n {recentTeams.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Trophy className=\"h-10 w-10\" />}\n title={tr('atheletebridge.pages.dashboard.noTeams', 'No teams yet')}\n description={tr(\n 'atheletebridge.pages.dashboard.noTeamsHint',\n 'Set up AthleteBridge in about a minute — pick your sport, create your first team and add yourself to the roster.'\n )}\n action={\n <div className=\"flex flex-wrap items-center justify-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/onboarding')}\n className={GRADIENT_CTA}\n >\n <Rocket className=\"h-4 w-4\" /> {tr('atheletebridge.pages.dashboard.getStarted', 'Get started')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams/new')}\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 <Plus className=\"h-4 w-4\" /> {tr('atheletebridge.pages.dashboard.createTeam', 'Create team')}\n </button>\n </div>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {recentTeams.map((team) => (\n <TeamCard key={team.id} team={team} onClick={() => navigateTo(`/teams/${team.id}`)} />\n ))}\n </div>\n )}\n </section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -5,13 +5,17 @@
|
|
|
5
5
|
* An honest coaching-analytics dashboard built ENTIRELY from real aggregates via
|
|
6
6
|
* `useCoachAnalytics()` — total sessions, average session rating, completion
|
|
7
7
|
* rate, session-status mix, average rating by coach, weekly training-load /
|
|
8
|
-
* session trends,
|
|
8
|
+
* session trends, a per-athlete acute:chronic training-load table, and a
|
|
9
|
+
* league-standings snapshot.
|
|
9
10
|
*
|
|
10
11
|
* Deliberately OMITS the mock's fabricated composite indices (Form Index,
|
|
11
12
|
* Fitness Score, Fatigue Index, Injury Status, Availability %) — there is no
|
|
12
13
|
* wellness / injury / availability data in the schema, so those are not faked.
|
|
13
14
|
* The one derived metric shown (ACWR training load) uses standard sports-science
|
|
14
|
-
* maths over real `workout.totalLoad` and carries an explanatory tooltip
|
|
15
|
+
* maths over real `workout.totalLoad` and carries an explanatory tooltip — both
|
|
16
|
+
* workspace-wide and, in the per-athlete table, grouped by each log's real
|
|
17
|
+
* `athleteId`. Where an athlete's workouts carry no recorded load the table
|
|
18
|
+
* says so rather than showing a fabricated zero.
|
|
15
19
|
* Semantic tokens only; responsive.
|
|
16
20
|
*/
|
|
17
21
|
export declare function CoachAnalyticsPage(): import("react").JSX.Element;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CoachAnalyticsPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/analytics/CoachAnalyticsPage.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"CoachAnalyticsPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/analytics/CoachAnalyticsPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AA2FH,wBAAgB,kBAAkB,gCAuVjC"}
|