@burdenoff/microfe-atheletebridge 2026.731.2 → 2026.801.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 (25) hide show
  1. package/dist/atheletebridge/AthleteBridgeRoutes.js +1 -1
  2. package/dist/atheletebridge/AthleteBridgeRoutes.js.map +1 -1
  3. package/dist/atheletebridge/components/RouteAnalytics.js +1 -1
  4. package/dist/atheletebridge/components/RouteAnalytics.js.map +1 -1
  5. package/dist/atheletebridge/context/AthleteBridgeContext.js +1 -1
  6. package/dist/atheletebridge/context/AthleteBridgeContext.js.map +1 -1
  7. package/dist/atheletebridge/pages/StandingsPage.js +1 -1
  8. package/dist/atheletebridge/pages/StandingsPage.js.map +1 -1
  9. package/dist/atheletebridge/pages/athletes/AthleteProfilePage.js +1 -1
  10. package/dist/atheletebridge/pages/athletes/AthleteProfilePage.js.map +1 -1
  11. package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +1 -1
  12. package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
  13. package/dist/atheletebridge/pages/fixtures/FixtureFormPage.js +1 -1
  14. package/dist/atheletebridge/pages/fixtures/FixtureFormPage.js.map +1 -1
  15. package/dist/atheletebridge/pages/programs/ProgramDetailPage.js +1 -1
  16. package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
  17. package/dist/atheletebridge/pages/programs/ProgramFormPage.js +1 -1
  18. package/dist/atheletebridge/pages/programs/ProgramFormPage.js.map +1 -1
  19. package/dist/atheletebridge/pages/sessions/SessionDetailPage.js +1 -1
  20. package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -1
  21. package/dist/atheletebridge/pages/teams/TeamDetailPage.js +1 -1
  22. package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -1
  23. package/dist/atheletebridge/pages/teams/TeamFormPage.js +1 -1
  24. package/dist/atheletebridge/pages/teams/TeamFormPage.js.map +1 -1
  25. package/package.json +4 -4
@@ -4,7 +4,7 @@ import { DashboardPage as n } from "./pages/DashboardPage.js";
4
4
  import { Suspense as r, lazy as i } from "react";
5
5
  import { FeatureGate as a } from "@burdenoff/fe-libs/shared/feature-flags";
6
6
  import { Loader2 as o } from "lucide-react";
7
- import { Navigate as s, Route as c, Routes as l, useLocation as u } from "react-router-dom";
7
+ import { Navigate as s, Route as c, Routes as l, useLocation as u } from "react-router";
8
8
  import { jsx as d, jsxs as f } from "react/jsx-runtime";
9
9
  //#region src/atheletebridge/AthleteBridgeRoutes.tsx
10
10
  var p = i(() => import("./pages/onboarding/OnboardingPage.js").then((e) => ({ default: e.OnboardingPage }))), m = i(() => import("./pages/teams/TeamsListPage.js").then((e) => ({ default: e.TeamsListPage }))), h = i(() => import("./pages/teams/TeamDetailPage.js").then((e) => ({ default: e.TeamDetailPage }))), g = i(() => import("./pages/teams/TeamFormPage.js").then((e) => ({ default: e.TeamFormPage }))), _ = i(() => import("./pages/fixtures/FixturesListPage.js").then((e) => ({ default: e.FixturesListPage }))), v = i(() => import("./pages/fixtures/FixtureDetailPage.js").then((e) => ({ default: e.FixtureDetailPage }))), y = i(() => import("./pages/fixtures/FixtureFormPage.js").then((e) => ({ default: e.FixtureFormPage }))), b = i(() => import("./pages/StandingsPage.js").then((e) => ({ default: e.StandingsPage }))), x = i(() => import("./pages/programs/ProgramsListPage.js").then((e) => ({ default: e.ProgramsListPage }))), S = i(() => import("./pages/programs/ProgramDetailPage.js").then((e) => ({ default: e.ProgramDetailPage }))), C = i(() => import("./pages/programs/ProgramFormPage.js").then((e) => ({ default: e.ProgramFormPage }))), w = i(() => import("./pages/sessions/SessionsListPage.js").then((e) => ({ default: e.SessionsListPage }))), T = i(() => import("./pages/sessions/SessionDetailPage.js").then((e) => ({ default: e.SessionDetailPage }))), E = i(() => import("./pages/sessions/SessionFormPage.js").then((e) => ({ default: e.SessionFormPage }))), D = i(() => import("./pages/performance/PerformancePage.js").then((e) => ({ default: e.PerformancePage }))), O = i(() => import("./pages/athletes/AthleteProfilePage.js").then((e) => ({ default: e.AthleteProfilePage }))), k = i(() => import("./pages/analytics/CoachAnalyticsPage.js").then((e) => ({ default: e.CoachAnalyticsPage }))), A = i(() => import("./pages/tournaments/TournamentBracketPage.js").then((e) => ({ default: e.TournamentBracketPage }))), j = () => /* @__PURE__ */ d("div", {
@@ -1 +1 @@
1
- {"version":3,"file":"AthleteBridgeRoutes.js","names":[],"sources":["../../src/atheletebridge/AthleteBridgeRoutes.tsx"],"sourcesContent":["/**\n * AthleteBridge Routes\n * @module atheletebridge\n *\n * Real sports-domain routes wired to the deployed `wspace-atheletebridge-svc`\n * GraphQL contract. Dashboard is eager; entity pages are lazy.\n */\n\nimport { type FC, lazy, Suspense } from 'react';\nimport { Routes, Route, Navigate, useLocation } from 'react-router-dom';\nimport { Loader2 } from 'lucide-react';\nimport { FeatureGate } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { useAthleteBridge } from './context/AthleteBridgeContext';\nimport { RouteAnalytics } from './components/RouteAnalytics';\n\n// Eager — primary landing\nimport { DashboardPage } from './pages/DashboardPage';\n\n// Onboarding (first-run wizard: sport → team → role)\nconst OnboardingPage = lazy(() =>\n import('./pages/onboarding/OnboardingPage').then((m) => ({ default: m.OnboardingPage }))\n);\n\n// Teams\nconst TeamsListPage = lazy(() =>\n import('./pages/teams/TeamsListPage').then((m) => ({ default: m.TeamsListPage }))\n);\nconst TeamDetailPage = lazy(() =>\n import('./pages/teams/TeamDetailPage').then((m) => ({ default: m.TeamDetailPage }))\n);\nconst TeamFormPage = lazy(() =>\n import('./pages/teams/TeamFormPage').then((m) => ({ default: m.TeamFormPage }))\n);\n\n// Fixtures\nconst FixturesListPage = lazy(() =>\n import('./pages/fixtures/FixturesListPage').then((m) => ({ default: m.FixturesListPage }))\n);\nconst FixtureDetailPage = lazy(() =>\n import('./pages/fixtures/FixtureDetailPage').then((m) => ({ default: m.FixtureDetailPage }))\n);\nconst FixtureFormPage = lazy(() =>\n import('./pages/fixtures/FixtureFormPage').then((m) => ({ default: m.FixtureFormPage }))\n);\n\n// Standings\nconst StandingsPage = lazy(() =>\n import('./pages/StandingsPage').then((m) => ({ default: m.StandingsPage }))\n);\n\n// Programs\nconst ProgramsListPage = lazy(() =>\n import('./pages/programs/ProgramsListPage').then((m) => ({ default: m.ProgramsListPage }))\n);\nconst ProgramDetailPage = lazy(() =>\n import('./pages/programs/ProgramDetailPage').then((m) => ({ default: m.ProgramDetailPage }))\n);\nconst ProgramFormPage = lazy(() =>\n import('./pages/programs/ProgramFormPage').then((m) => ({ default: m.ProgramFormPage }))\n);\n\n// Sessions\nconst SessionsListPage = lazy(() =>\n import('./pages/sessions/SessionsListPage').then((m) => ({ default: m.SessionsListPage }))\n);\nconst SessionDetailPage = lazy(() =>\n import('./pages/sessions/SessionDetailPage').then((m) => ({ default: m.SessionDetailPage }))\n);\nconst SessionFormPage = lazy(() =>\n import('./pages/sessions/SessionFormPage').then((m) => ({ default: m.SessionFormPage }))\n);\n\n// Performance\nconst PerformancePage = lazy(() =>\n import('./pages/performance/PerformancePage').then((m) => ({ default: m.PerformancePage }))\n);\n\n// Athlete profile\nconst AthleteProfilePage = lazy(() =>\n import('./pages/athletes/AthleteProfilePage').then((m) => ({ default: m.AthleteProfilePage }))\n);\n\n// Coach analytics\nconst CoachAnalyticsPage = lazy(() =>\n import('./pages/analytics/CoachAnalyticsPage').then((m) => ({ default: m.CoachAnalyticsPage }))\n);\n\n// Tournaments (esports bracket)\nconst TournamentBracketPage = lazy(() =>\n import('./pages/tournaments/TournamentBracketPage').then((m) => ({\n default: m.TournamentBracketPage,\n }))\n);\n\nconst PageLoader: FC = () => (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n </div>\n);\n\nexport const AthleteBridgeRoutes: FC = () => {\n const { basePath, isStandalone } = useAthleteBridge();\n const location = useLocation();\n\n const normalizedBasePath = basePath === '/' ? '' : (basePath ?? '').replace(/\\/$/, '');\n\n // Guard: only render when the path matches basePath (integrated mode).\n if (normalizedBasePath && !isStandalone) {\n const pathMatches =\n location.pathname === normalizedBasePath ||\n location.pathname.startsWith(normalizedBasePath + '/');\n if (!pathMatches) {\n return null;\n }\n }\n\n return (\n <div className=\"flex min-h-full flex-col\">\n <Suspense fallback={<PageLoader />}>\n <RouteAnalytics />\n <Routes>\n {/* Dashboard */}\n <Route index element={<DashboardPage />} />\n\n {/*\n Onboarding — the first-run wizard. Intentionally NOT behind a\n FeatureGate: it only collects sport/team/role and writes through the\n same `athCreateTeam` / `athAddMember` the gated Teams pages use, so a\n flag here would gate nothing that `atheletebridge.teams.enabled` does\n not already gate at the destination.\n */}\n <Route path=\"onboarding\" element={<OnboardingPage />} />\n\n {/* Teams */}\n <Route\n path=\"teams\"\n element={\n <FeatureGate flag=\"atheletebridge.teams.enabled\" defaultValue={true}>\n <TeamsListPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"teams/new\"\n element={\n <FeatureGate flag=\"atheletebridge.teams.enabled\" defaultValue={true}>\n <TeamFormPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"teams/:id\"\n element={\n <FeatureGate flag=\"atheletebridge.teams.enabled\" defaultValue={true}>\n <TeamDetailPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"teams/:id/edit\"\n element={\n <FeatureGate flag=\"atheletebridge.teams.enabled\" defaultValue={true}>\n <TeamFormPage />\n </FeatureGate>\n }\n />\n\n {/* Athlete profile (reached from a team roster) */}\n <Route\n path=\"teams/:teamId/athletes/:memberId\"\n element={\n <FeatureGate flag=\"atheletebridge.teams.enabled\" defaultValue={true}>\n <AthleteProfilePage />\n </FeatureGate>\n }\n />\n\n {/* Fixtures */}\n <Route\n path=\"fixtures\"\n element={\n <FeatureGate flag=\"atheletebridge.fixtures.enabled\" defaultValue={true}>\n <FixturesListPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"fixtures/new\"\n element={\n <FeatureGate flag=\"atheletebridge.fixtures.enabled\" defaultValue={true}>\n <FixtureFormPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"fixtures/:id\"\n element={\n <FeatureGate flag=\"atheletebridge.fixtures.enabled\" defaultValue={true}>\n <FixtureDetailPage />\n </FeatureGate>\n }\n />\n\n {/* Standings */}\n <Route\n path=\"standings\"\n element={\n <FeatureGate flag=\"atheletebridge.standings.enabled\" defaultValue={true}>\n <StandingsPage />\n </FeatureGate>\n }\n />\n\n {/* Programs */}\n <Route\n path=\"programs\"\n element={\n <FeatureGate flag=\"atheletebridge.programs.enabled\" defaultValue={true}>\n <ProgramsListPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"programs/new\"\n element={\n <FeatureGate flag=\"atheletebridge.programs.enabled\" defaultValue={true}>\n <ProgramFormPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"programs/:id\"\n element={\n <FeatureGate flag=\"atheletebridge.programs.enabled\" defaultValue={true}>\n <ProgramDetailPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"programs/:id/edit\"\n element={\n <FeatureGate flag=\"atheletebridge.programs.enabled\" defaultValue={true}>\n <ProgramFormPage />\n </FeatureGate>\n }\n />\n\n {/* Sessions */}\n <Route\n path=\"sessions\"\n element={\n <FeatureGate flag=\"atheletebridge.sessions.enabled\" defaultValue={true}>\n <SessionsListPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"sessions/new\"\n element={\n <FeatureGate flag=\"atheletebridge.sessions.enabled\" defaultValue={true}>\n <SessionFormPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"sessions/:id\"\n element={\n <FeatureGate flag=\"atheletebridge.sessions.enabled\" defaultValue={true}>\n <SessionDetailPage />\n </FeatureGate>\n }\n />\n\n {/* Performance */}\n <Route\n path=\"performance\"\n element={\n <FeatureGate flag=\"atheletebridge.performance.enabled\" defaultValue={true}>\n <PerformancePage />\n </FeatureGate>\n }\n />\n\n {/* Analytics */}\n <Route\n path=\"analytics\"\n element={\n <FeatureGate flag=\"atheletebridge.analytics.enabled\" defaultValue={true}>\n <CoachAnalyticsPage />\n </FeatureGate>\n }\n />\n\n {/* Tournaments (esports bracket) */}\n <Route\n path=\"tournaments\"\n element={\n <FeatureGate flag=\"atheletebridge.tournaments.enabled\" defaultValue={true}>\n <TournamentBracketPage />\n </FeatureGate>\n }\n />\n\n {/* Fallback */}\n <Route path=\"*\" element={<Navigate to=\".\" replace />} />\n </Routes>\n </Suspense>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AthleteBridgeRoutes.js","names":[],"sources":["../../src/atheletebridge/AthleteBridgeRoutes.tsx"],"sourcesContent":["/**\n * AthleteBridge Routes\n * @module atheletebridge\n *\n * Real sports-domain routes wired to the deployed `wspace-atheletebridge-svc`\n * GraphQL contract. Dashboard is eager; entity pages are lazy.\n */\n\nimport { type FC, lazy, Suspense } from 'react';\nimport { Routes, Route, Navigate, useLocation } from 'react-router';\nimport { Loader2 } from 'lucide-react';\nimport { FeatureGate } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { useAthleteBridge } from './context/AthleteBridgeContext';\nimport { RouteAnalytics } from './components/RouteAnalytics';\n\n// Eager — primary landing\nimport { DashboardPage } from './pages/DashboardPage';\n\n// Onboarding (first-run wizard: sport → team → role)\nconst OnboardingPage = lazy(() =>\n import('./pages/onboarding/OnboardingPage').then((m) => ({ default: m.OnboardingPage }))\n);\n\n// Teams\nconst TeamsListPage = lazy(() =>\n import('./pages/teams/TeamsListPage').then((m) => ({ default: m.TeamsListPage }))\n);\nconst TeamDetailPage = lazy(() =>\n import('./pages/teams/TeamDetailPage').then((m) => ({ default: m.TeamDetailPage }))\n);\nconst TeamFormPage = lazy(() =>\n import('./pages/teams/TeamFormPage').then((m) => ({ default: m.TeamFormPage }))\n);\n\n// Fixtures\nconst FixturesListPage = lazy(() =>\n import('./pages/fixtures/FixturesListPage').then((m) => ({ default: m.FixturesListPage }))\n);\nconst FixtureDetailPage = lazy(() =>\n import('./pages/fixtures/FixtureDetailPage').then((m) => ({ default: m.FixtureDetailPage }))\n);\nconst FixtureFormPage = lazy(() =>\n import('./pages/fixtures/FixtureFormPage').then((m) => ({ default: m.FixtureFormPage }))\n);\n\n// Standings\nconst StandingsPage = lazy(() =>\n import('./pages/StandingsPage').then((m) => ({ default: m.StandingsPage }))\n);\n\n// Programs\nconst ProgramsListPage = lazy(() =>\n import('./pages/programs/ProgramsListPage').then((m) => ({ default: m.ProgramsListPage }))\n);\nconst ProgramDetailPage = lazy(() =>\n import('./pages/programs/ProgramDetailPage').then((m) => ({ default: m.ProgramDetailPage }))\n);\nconst ProgramFormPage = lazy(() =>\n import('./pages/programs/ProgramFormPage').then((m) => ({ default: m.ProgramFormPage }))\n);\n\n// Sessions\nconst SessionsListPage = lazy(() =>\n import('./pages/sessions/SessionsListPage').then((m) => ({ default: m.SessionsListPage }))\n);\nconst SessionDetailPage = lazy(() =>\n import('./pages/sessions/SessionDetailPage').then((m) => ({ default: m.SessionDetailPage }))\n);\nconst SessionFormPage = lazy(() =>\n import('./pages/sessions/SessionFormPage').then((m) => ({ default: m.SessionFormPage }))\n);\n\n// Performance\nconst PerformancePage = lazy(() =>\n import('./pages/performance/PerformancePage').then((m) => ({ default: m.PerformancePage }))\n);\n\n// Athlete profile\nconst AthleteProfilePage = lazy(() =>\n import('./pages/athletes/AthleteProfilePage').then((m) => ({ default: m.AthleteProfilePage }))\n);\n\n// Coach analytics\nconst CoachAnalyticsPage = lazy(() =>\n import('./pages/analytics/CoachAnalyticsPage').then((m) => ({ default: m.CoachAnalyticsPage }))\n);\n\n// Tournaments (esports bracket)\nconst TournamentBracketPage = lazy(() =>\n import('./pages/tournaments/TournamentBracketPage').then((m) => ({\n default: m.TournamentBracketPage,\n }))\n);\n\nconst PageLoader: FC = () => (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n </div>\n);\n\nexport const AthleteBridgeRoutes: FC = () => {\n const { basePath, isStandalone } = useAthleteBridge();\n const location = useLocation();\n\n const normalizedBasePath = basePath === '/' ? '' : (basePath ?? '').replace(/\\/$/, '');\n\n // Guard: only render when the path matches basePath (integrated mode).\n if (normalizedBasePath && !isStandalone) {\n const pathMatches =\n location.pathname === normalizedBasePath ||\n location.pathname.startsWith(normalizedBasePath + '/');\n if (!pathMatches) {\n return null;\n }\n }\n\n return (\n <div className=\"flex min-h-full flex-col\">\n <Suspense fallback={<PageLoader />}>\n <RouteAnalytics />\n <Routes>\n {/* Dashboard */}\n <Route index element={<DashboardPage />} />\n\n {/*\n Onboarding — the first-run wizard. Intentionally NOT behind a\n FeatureGate: it only collects sport/team/role and writes through the\n same `athCreateTeam` / `athAddMember` the gated Teams pages use, so a\n flag here would gate nothing that `atheletebridge.teams.enabled` does\n not already gate at the destination.\n */}\n <Route path=\"onboarding\" element={<OnboardingPage />} />\n\n {/* Teams */}\n <Route\n path=\"teams\"\n element={\n <FeatureGate flag=\"atheletebridge.teams.enabled\" defaultValue={true}>\n <TeamsListPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"teams/new\"\n element={\n <FeatureGate flag=\"atheletebridge.teams.enabled\" defaultValue={true}>\n <TeamFormPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"teams/:id\"\n element={\n <FeatureGate flag=\"atheletebridge.teams.enabled\" defaultValue={true}>\n <TeamDetailPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"teams/:id/edit\"\n element={\n <FeatureGate flag=\"atheletebridge.teams.enabled\" defaultValue={true}>\n <TeamFormPage />\n </FeatureGate>\n }\n />\n\n {/* Athlete profile (reached from a team roster) */}\n <Route\n path=\"teams/:teamId/athletes/:memberId\"\n element={\n <FeatureGate flag=\"atheletebridge.teams.enabled\" defaultValue={true}>\n <AthleteProfilePage />\n </FeatureGate>\n }\n />\n\n {/* Fixtures */}\n <Route\n path=\"fixtures\"\n element={\n <FeatureGate flag=\"atheletebridge.fixtures.enabled\" defaultValue={true}>\n <FixturesListPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"fixtures/new\"\n element={\n <FeatureGate flag=\"atheletebridge.fixtures.enabled\" defaultValue={true}>\n <FixtureFormPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"fixtures/:id\"\n element={\n <FeatureGate flag=\"atheletebridge.fixtures.enabled\" defaultValue={true}>\n <FixtureDetailPage />\n </FeatureGate>\n }\n />\n\n {/* Standings */}\n <Route\n path=\"standings\"\n element={\n <FeatureGate flag=\"atheletebridge.standings.enabled\" defaultValue={true}>\n <StandingsPage />\n </FeatureGate>\n }\n />\n\n {/* Programs */}\n <Route\n path=\"programs\"\n element={\n <FeatureGate flag=\"atheletebridge.programs.enabled\" defaultValue={true}>\n <ProgramsListPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"programs/new\"\n element={\n <FeatureGate flag=\"atheletebridge.programs.enabled\" defaultValue={true}>\n <ProgramFormPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"programs/:id\"\n element={\n <FeatureGate flag=\"atheletebridge.programs.enabled\" defaultValue={true}>\n <ProgramDetailPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"programs/:id/edit\"\n element={\n <FeatureGate flag=\"atheletebridge.programs.enabled\" defaultValue={true}>\n <ProgramFormPage />\n </FeatureGate>\n }\n />\n\n {/* Sessions */}\n <Route\n path=\"sessions\"\n element={\n <FeatureGate flag=\"atheletebridge.sessions.enabled\" defaultValue={true}>\n <SessionsListPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"sessions/new\"\n element={\n <FeatureGate flag=\"atheletebridge.sessions.enabled\" defaultValue={true}>\n <SessionFormPage />\n </FeatureGate>\n }\n />\n <Route\n path=\"sessions/:id\"\n element={\n <FeatureGate flag=\"atheletebridge.sessions.enabled\" defaultValue={true}>\n <SessionDetailPage />\n </FeatureGate>\n }\n />\n\n {/* Performance */}\n <Route\n path=\"performance\"\n element={\n <FeatureGate flag=\"atheletebridge.performance.enabled\" defaultValue={true}>\n <PerformancePage />\n </FeatureGate>\n }\n />\n\n {/* Analytics */}\n <Route\n path=\"analytics\"\n element={\n <FeatureGate flag=\"atheletebridge.analytics.enabled\" defaultValue={true}>\n <CoachAnalyticsPage />\n </FeatureGate>\n }\n />\n\n {/* Tournaments (esports bracket) */}\n <Route\n path=\"tournaments\"\n element={\n <FeatureGate flag=\"atheletebridge.tournaments.enabled\" defaultValue={true}>\n <TournamentBracketPage />\n </FeatureGate>\n }\n />\n\n {/* Fallback */}\n <Route path=\"*\" element={<Navigate to=\".\" replace />} />\n </Routes>\n </Suspense>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { useEffect as e, useRef as t } from "react";
2
- import { useLocation as n } from "react-router-dom";
2
+ import { useLocation as n } from "react-router";
3
3
  import { useEventBus as r } from "@burdenoff/fe-libs/shared/events";
4
4
  //#region src/atheletebridge/components/RouteAnalytics.tsx
5
5
  function i(e) {
@@ -1 +1 @@
1
- {"version":3,"file":"RouteAnalytics.js","names":[],"sources":["../../../src/atheletebridge/components/RouteAnalytics.tsx"],"sourcesContent":["/**\n * RouteAnalytics\n *\n * Listens to route changes inside the AthleteBridge microfrontend and emits\n * `atheletebridge.page.viewed` events to the shared event bus. The host shell\n * forwards these to OtelBrowserSink / BackendAuditSink / RybbitSink.\n */\n\nimport { useEffect, useRef } from 'react';\nimport { useLocation } from 'react-router-dom';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\n\nfunction resolvePageName(pathname: string): string {\n const segments = pathname.split('/').filter(Boolean);\n // When mounted under /atheletebridge, the first segment is the product slug.\n const productIndex = segments.findIndex((s) => s === 'atheletebridge');\n const relevant = productIndex >= 0 ? segments.slice(productIndex + 1) : segments;\n if (relevant.length === 0) return 'dashboard';\n return relevant.join('/');\n}\n\nexport function RouteAnalytics() {\n const location = useLocation();\n const bus = useEventBus();\n const lastEmittedRef = useRef<string | null>(null);\n\n useEffect(() => {\n const route = `${location.pathname}${location.search}`;\n if (lastEmittedRef.current === route) return;\n lastEmittedRef.current = route;\n\n const page = resolvePageName(location.pathname);\n bus.emitCustom('atheletebridge.page.viewed', {\n page,\n route: location.pathname,\n product: 'atheletebridge',\n });\n }, [bus, location.pathname, location.search]);\n\n return null;\n}\n"],"mappings":";;;;AAYA,SAAS,EAAgB,GAA0B;CACjD,IAAM,IAAW,EAAS,MAAM,GAAG,EAAE,OAAO,OAAO,GAE7C,IAAe,EAAS,WAAW,MAAM,MAAM,gBAAgB,GAC/D,IAAW,KAAgB,IAAI,EAAS,MAAM,IAAe,CAAC,IAAI;CAExE,OADI,EAAS,WAAW,IAAU,cAC3B,EAAS,KAAK,GAAG;AAC1B;AAEA,SAAgB,IAAiB;CAC/B,IAAM,IAAW,EAAY,GACvB,IAAM,EAAY,GAClB,IAAiB,EAAsB,IAAI;CAejD,OAbA,QAAgB;EACd,IAAM,IAAQ,GAAG,EAAS,WAAW,EAAS;EAC9C,IAAI,EAAe,YAAY,GAAO;EACtC,EAAe,UAAU;EAEzB,IAAM,IAAO,EAAgB,EAAS,QAAQ;EAC9C,EAAI,WAAW,8BAA8B;GAC3C;GACA,OAAO,EAAS;GAChB,SAAS;EACX,CAAC;CACH,GAAG;EAAC;EAAK,EAAS;EAAU,EAAS;CAAM,CAAC,GAErC;AACT"}
1
+ {"version":3,"file":"RouteAnalytics.js","names":[],"sources":["../../../src/atheletebridge/components/RouteAnalytics.tsx"],"sourcesContent":["/**\n * RouteAnalytics\n *\n * Listens to route changes inside the AthleteBridge microfrontend and emits\n * `atheletebridge.page.viewed` events to the shared event bus. The host shell\n * forwards these to OtelBrowserSink / BackendAuditSink / RybbitSink.\n */\n\nimport { useEffect, useRef } from 'react';\nimport { useLocation } from 'react-router';\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\n\nfunction resolvePageName(pathname: string): string {\n const segments = pathname.split('/').filter(Boolean);\n // When mounted under /atheletebridge, the first segment is the product slug.\n const productIndex = segments.findIndex((s) => s === 'atheletebridge');\n const relevant = productIndex >= 0 ? segments.slice(productIndex + 1) : segments;\n if (relevant.length === 0) return 'dashboard';\n return relevant.join('/');\n}\n\nexport function RouteAnalytics() {\n const location = useLocation();\n const bus = useEventBus();\n const lastEmittedRef = useRef<string | null>(null);\n\n useEffect(() => {\n const route = `${location.pathname}${location.search}`;\n if (lastEmittedRef.current === route) return;\n lastEmittedRef.current = route;\n\n const page = resolvePageName(location.pathname);\n bus.emitCustom('atheletebridge.page.viewed', {\n page,\n route: location.pathname,\n product: 'atheletebridge',\n });\n }, [bus, location.pathname, location.search]);\n\n return null;\n}\n"],"mappings":";;;;AAYA,SAAS,EAAgB,GAA0B;CACjD,IAAM,IAAW,EAAS,MAAM,GAAG,EAAE,OAAO,OAAO,GAE7C,IAAe,EAAS,WAAW,MAAM,MAAM,gBAAgB,GAC/D,IAAW,KAAgB,IAAI,EAAS,MAAM,IAAe,CAAC,IAAI;CAExE,OADI,EAAS,WAAW,IAAU,cAC3B,EAAS,KAAK,GAAG;AAC1B;AAEA,SAAgB,IAAiB;CAC/B,IAAM,IAAW,EAAY,GACvB,IAAM,EAAY,GAClB,IAAiB,EAAsB,IAAI;CAejD,OAbA,QAAgB;EACd,IAAM,IAAQ,GAAG,EAAS,WAAW,EAAS;EAC9C,IAAI,EAAe,YAAY,GAAO;EACtC,EAAe,UAAU;EAEzB,IAAM,IAAO,EAAgB,EAAS,QAAQ;EAC9C,EAAI,WAAW,8BAA8B;GAC3C;GACA,OAAO,EAAS;GAChB,SAAS;EACX,CAAC;CACH,GAAG;EAAC;EAAK,EAAS;EAAU,EAAS;CAAM,CAAC,GAErC;AACT"}
@@ -1,6 +1,6 @@
1
1
  import { joinPath as e } from "../utils/navigation.js";
2
2
  import { createContext as t, useCallback as n, useContext as r, useMemo as i } from "react";
3
- import { MemoryRouter as a, useNavigate as o } from "react-router-dom";
3
+ import { MemoryRouter as a, useNavigate as o } from "react-router";
4
4
  import { jsx as s } from "react/jsx-runtime";
5
5
  //#region src/atheletebridge/context/AthleteBridgeContext.tsx
6
6
  var c = t(null);
@@ -1 +1 @@
1
- {"version":3,"file":"AthleteBridgeContext.js","names":[],"sources":["../../../src/atheletebridge/context/AthleteBridgeContext.tsx"],"sourcesContent":["/**\n * AthleteBridge Context Provider\n * @module atheletebridge/context\n *\n * The provider exposes the host-supplied props (basePath, workspaceId,\n * tenantId, apiGatewayUrl, authToken, currentUser, entity-select callbacks) plus\n * path/navigation helpers. The Root mounts this with the exact props the shell\n * passes; the export name/signature is part of the host contract.\n */\n\nimport type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext, useMemo, useCallback } from 'react';\nimport { MemoryRouter, useNavigate as useRouterNavigate } from 'react-router-dom';\nimport type { AthleteBridgeRootProps } from '../types';\nimport { joinPath } from '../utils/navigation';\n\n// ============================================================================\n// Context\n// ============================================================================\n\ninterface AthleteBridgeContextValue extends Omit<\n AthleteBridgeRootProps,\n 'registerNavItems' | 'registerFooterItems' | 'navigate'\n> {\n isStandalone: boolean;\n /** Shell's navigate function (only available in integrated mode) */\n navigate?: (path: string) => void;\n /** Navigate to a path relative to basePath */\n navigateTo: (relativePath: string) => void;\n /** Get the full path for a relative path */\n getPath: (relativePath: string) => string;\n}\n\nconst AthleteBridgeContext = createContext<AthleteBridgeContextValue | null>(null);\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useAthleteBridge(): AthleteBridgeContextValue {\n const context = useContext(AthleteBridgeContext);\n if (!context) {\n throw new Error('useAthleteBridge must be used within a AthleteBridgeProvider');\n }\n return context;\n}\n\n// ============================================================================\n// Provider Component\n// ============================================================================\n\ntype ProviderProps = Omit<AthleteBridgeRootProps, 'registerNavItems' | 'registerFooterItems'> & {\n isStandalone: boolean;\n};\n\n/** Provider for standalone mode (has access to MemoryRouter) */\nconst StandaloneProviderInner: FC<PropsWithChildren<ProviderProps>> = ({\n children,\n basePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n}) => {\n const routerNavigate = useRouterNavigate();\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n const fullPath = joinPath(effectiveBasePath, relativePath);\n routerNavigate(fullPath);\n },\n [effectiveBasePath, routerNavigate]\n );\n\n const contextValue = useMemo<AthleteBridgeContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate: undefined,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n isStandalone: true,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n navigateTo,\n getPath,\n ]\n );\n\n return (\n <AthleteBridgeContext.Provider value={contextValue}>{children}</AthleteBridgeContext.Provider>\n );\n};\n\n/** Provider for integrated mode (shell provides navigate) */\nconst IntegratedProviderInner: FC<\n PropsWithChildren<ProviderProps & { navigate: (path: string) => void }>\n> = ({\n children,\n basePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n}) => {\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n const fullPath = joinPath(effectiveBasePath, relativePath);\n navigate(fullPath);\n },\n [effectiveBasePath, navigate]\n );\n\n const contextValue = useMemo<AthleteBridgeContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n isStandalone: false,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n navigateTo,\n getPath,\n ]\n );\n\n return (\n <AthleteBridgeContext.Provider value={contextValue}>{children}</AthleteBridgeContext.Provider>\n );\n};\n\nexport const AthleteBridgeProvider: FC<PropsWithChildren<AthleteBridgeRootProps>> = (props) => {\n const { children, navigate } = props;\n // Standalone mode: no navigate function provided (MFE manages its own router)\n // Integrated mode: navigate function provided by shell (shell provides router)\n const isStandalone = !navigate;\n\n // Standalone mode: wrap in MemoryRouter\n if (isStandalone) {\n return (\n <MemoryRouter>\n <StandaloneProviderInner {...props} isStandalone={true}>\n {children}\n </StandaloneProviderInner>\n </MemoryRouter>\n );\n }\n\n // Integrated mode: shell provides router\n return (\n <IntegratedProviderInner {...props} navigate={navigate} isStandalone={false}>\n {children}\n </IntegratedProviderInner>\n );\n};\n"],"mappings":";;;;;AAiCA,IAAM,IAAuB,EAAgD,IAAI;AAMjF,SAAgB,IAA8C;CAC5D,IAAM,IAAU,EAAW,CAAoB;CAC/C,IAAI,CAAC,GACH,MAAU,MAAM,8DAA8D;CAEhF,OAAO;AACT;AAWA,IAAM,KAAiE,EACrE,aACA,aACA,gBACA,aACA,gBACA,kBACA,cACA,iBACA,yBACI;CACJ,IAAM,IAAiB,EAAkB,GACnC,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,CAAY,GAClE,CAAC,CAAiB,CACpB,GAEM,IAAa,GAChB,MAAyB;EAExB,EADiB,EAAS,GAAmB,CAC9B,CAAQ;CACzB,GACA,CAAC,GAAmB,CAAc,CACpC,GAEM,IAAe,SACZ;EACL,UAAU;EACV,UAAU,KAAA;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;CACF,IACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CACF;CAEA,OACE,kBAAC,EAAqB,UAAtB;EAA+B,OAAO;EAAe;CAAwC,CAAA;AAEjG,GAGM,KAED,EACH,aACA,aACA,aACA,gBACA,aACA,gBACA,kBACA,cACA,iBACA,yBACI;CACJ,IAAM,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,CAAY,GAClE,CAAC,CAAiB,CACpB,GAEM,IAAa,GAChB,MAAyB;EAExB,EADiB,EAAS,GAAmB,CACpC,CAAQ;CACnB,GACA,CAAC,GAAmB,CAAQ,CAC9B,GAEM,IAAe,SACZ;EACL,UAAU;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;CACF,IACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CACF;CAEA,OACE,kBAAC,EAAqB,UAAtB;EAA+B,OAAO;EAAe;CAAwC,CAAA;AAEjG,GAEa,KAAwE,MAAU;CAC7F,IAAM,EAAE,aAAU,gBAAa;CAiB/B,OAdsB,IAepB,kBAAC,GAAD;EAAyB,GAAI;EAAiB;EAAU,cAAc;EACnE;CACsB,CAAA,IAZvB,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAyB,GAAI;EAAO,cAAc;EAC/C;CACsB,CAAA,EACb,CAAA;AAUpB"}
1
+ {"version":3,"file":"AthleteBridgeContext.js","names":[],"sources":["../../../src/atheletebridge/context/AthleteBridgeContext.tsx"],"sourcesContent":["/**\n * AthleteBridge Context Provider\n * @module atheletebridge/context\n *\n * The provider exposes the host-supplied props (basePath, workspaceId,\n * tenantId, apiGatewayUrl, authToken, currentUser, entity-select callbacks) plus\n * path/navigation helpers. The Root mounts this with the exact props the shell\n * passes; the export name/signature is part of the host contract.\n */\n\nimport type { FC, PropsWithChildren } from 'react';\nimport { createContext, useContext, useMemo, useCallback } from 'react';\nimport { MemoryRouter, useNavigate as useRouterNavigate } from 'react-router';\nimport type { AthleteBridgeRootProps } from '../types';\nimport { joinPath } from '../utils/navigation';\n\n// ============================================================================\n// Context\n// ============================================================================\n\ninterface AthleteBridgeContextValue extends Omit<\n AthleteBridgeRootProps,\n 'registerNavItems' | 'registerFooterItems' | 'navigate'\n> {\n isStandalone: boolean;\n /** Shell's navigate function (only available in integrated mode) */\n navigate?: (path: string) => void;\n /** Navigate to a path relative to basePath */\n navigateTo: (relativePath: string) => void;\n /** Get the full path for a relative path */\n getPath: (relativePath: string) => string;\n}\n\nconst AthleteBridgeContext = createContext<AthleteBridgeContextValue | null>(null);\n\n// ============================================================================\n// Hook\n// ============================================================================\n\nexport function useAthleteBridge(): AthleteBridgeContextValue {\n const context = useContext(AthleteBridgeContext);\n if (!context) {\n throw new Error('useAthleteBridge must be used within a AthleteBridgeProvider');\n }\n return context;\n}\n\n// ============================================================================\n// Provider Component\n// ============================================================================\n\ntype ProviderProps = Omit<AthleteBridgeRootProps, 'registerNavItems' | 'registerFooterItems'> & {\n isStandalone: boolean;\n};\n\n/** Provider for standalone mode (has access to MemoryRouter) */\nconst StandaloneProviderInner: FC<PropsWithChildren<ProviderProps>> = ({\n children,\n basePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n}) => {\n const routerNavigate = useRouterNavigate();\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n const fullPath = joinPath(effectiveBasePath, relativePath);\n routerNavigate(fullPath);\n },\n [effectiveBasePath, routerNavigate]\n );\n\n const contextValue = useMemo<AthleteBridgeContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate: undefined,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n isStandalone: true,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n navigateTo,\n getPath,\n ]\n );\n\n return (\n <AthleteBridgeContext.Provider value={contextValue}>{children}</AthleteBridgeContext.Provider>\n );\n};\n\n/** Provider for integrated mode (shell provides navigate) */\nconst IntegratedProviderInner: FC<\n PropsWithChildren<ProviderProps & { navigate: (path: string) => void }>\n> = ({\n children,\n basePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n}) => {\n const effectiveBasePath = basePath || '/';\n\n const getPath = useCallback(\n (relativePath: string) => joinPath(effectiveBasePath, relativePath),\n [effectiveBasePath]\n );\n\n const navigateTo = useCallback(\n (relativePath: string) => {\n const fullPath = joinPath(effectiveBasePath, relativePath);\n navigate(fullPath);\n },\n [effectiveBasePath, navigate]\n );\n\n const contextValue = useMemo<AthleteBridgeContextValue>(\n () => ({\n basePath: effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n isStandalone: false,\n navigateTo,\n getPath,\n }),\n [\n effectiveBasePath,\n navigate,\n currentUser,\n tenantId,\n workspaceId,\n apiGatewayUrl,\n authToken,\n onTeamSelect,\n onFixtureSelect,\n navigateTo,\n getPath,\n ]\n );\n\n return (\n <AthleteBridgeContext.Provider value={contextValue}>{children}</AthleteBridgeContext.Provider>\n );\n};\n\nexport const AthleteBridgeProvider: FC<PropsWithChildren<AthleteBridgeRootProps>> = (props) => {\n const { children, navigate } = props;\n // Standalone mode: no navigate function provided (MFE manages its own router)\n // Integrated mode: navigate function provided by shell (shell provides router)\n const isStandalone = !navigate;\n\n // Standalone mode: wrap in MemoryRouter\n if (isStandalone) {\n return (\n <MemoryRouter>\n <StandaloneProviderInner {...props} isStandalone={true}>\n {children}\n </StandaloneProviderInner>\n </MemoryRouter>\n );\n }\n\n // Integrated mode: shell provides router\n return (\n <IntegratedProviderInner {...props} navigate={navigate} isStandalone={false}>\n {children}\n </IntegratedProviderInner>\n );\n};\n"],"mappings":";;;;;AAiCA,IAAM,IAAuB,EAAgD,IAAI;AAMjF,SAAgB,IAA8C;CAC5D,IAAM,IAAU,EAAW,CAAoB;CAC/C,IAAI,CAAC,GACH,MAAU,MAAM,8DAA8D;CAEhF,OAAO;AACT;AAWA,IAAM,KAAiE,EACrE,aACA,aACA,gBACA,aACA,gBACA,kBACA,cACA,iBACA,yBACI;CACJ,IAAM,IAAiB,EAAkB,GACnC,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,CAAY,GAClE,CAAC,CAAiB,CACpB,GAEM,IAAa,GAChB,MAAyB;EAExB,EADiB,EAAS,GAAmB,CAC9B,CAAQ;CACzB,GACA,CAAC,GAAmB,CAAc,CACpC,GAEM,IAAe,SACZ;EACL,UAAU;EACV,UAAU,KAAA;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;CACF,IACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CACF;CAEA,OACE,kBAAC,EAAqB,UAAtB;EAA+B,OAAO;EAAe;CAAwC,CAAA;AAEjG,GAGM,KAED,EACH,aACA,aACA,aACA,gBACA,aACA,gBACA,kBACA,cACA,iBACA,yBACI;CACJ,IAAM,IAAoB,KAAY,KAEhC,IAAU,GACb,MAAyB,EAAS,GAAmB,CAAY,GAClE,CAAC,CAAiB,CACpB,GAEM,IAAa,GAChB,MAAyB;EAExB,EADiB,EAAS,GAAmB,CACpC,CAAQ;CACnB,GACA,CAAC,GAAmB,CAAQ,CAC9B,GAEM,IAAe,SACZ;EACL,UAAU;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,cAAc;EACd;EACA;CACF,IACA;EACE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF,CACF;CAEA,OACE,kBAAC,EAAqB,UAAtB;EAA+B,OAAO;EAAe;CAAwC,CAAA;AAEjG,GAEa,KAAwE,MAAU;CAC7F,IAAM,EAAE,aAAU,gBAAa;CAiB/B,OAdsB,IAepB,kBAAC,GAAD;EAAyB,GAAI;EAAiB;EAAU,cAAc;EACnE;CACsB,CAAA,IAZvB,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;EAAyB,GAAI;EAAO,cAAc;EAC/C;CACsB,CAAA,EACb,CAAA;AAUpB"}
@@ -8,7 +8,7 @@ import { useFixtures as s, useStandings as c } from "../hooks/useAthleteBridgeAp
8
8
  import { fieldInputClass as l } from "../components/form.js";
9
9
  import { useEffect as u, useMemo as d, useState as f } from "react";
10
10
  import { Search as p, Trophy as m } from "lucide-react";
11
- import { useSearchParams as h } from "react-router-dom";
11
+ import { useSearchParams as h } from "react-router";
12
12
  import { jsx as g, jsxs as _ } from "react/jsx-runtime";
13
13
  import { EmphasisPanel as v } from "@burdenoff/fe-libs/ui";
14
14
  //#region src/atheletebridge/pages/StandingsPage.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"StandingsPage.js","names":[],"sources":["../../../src/atheletebridge/pages/StandingsPage.tsx"],"sourcesContent":["/**\n * Standings\n * @module atheletebridge/pages\n *\n * Reads the live competition table (athStandings) for a chosen competition.\n * The svc requires a competition name, so the user picks one (seeded from\n * fixtures that carry a competition, or typed directly).\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Search, Trophy } from 'lucide-react';\nimport { EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { EmptyState } from '../components/EmptyState';\nimport { LoadingState, ErrorState } from '../components/LoadingState';\nimport { Crest } from '../components/Crest';\nimport { fieldInputClass } from '../components/form';\nimport { cn } from '../utils/cn';\nimport { useFixtures, useStandings } from '../hooks/useAthleteBridgeApi';\nimport { useAbTranslate } from '../i18n';\n\nexport function StandingsPage() {\n const tr = useAbTranslate();\n const [searchParams] = useSearchParams();\n const initialCompetition = searchParams.get('competition') ?? '';\n\n const [competition, setCompetition] = useState(initialCompetition);\n const [activeCompetition, setActiveCompetition] = useState(initialCompetition);\n\n // Suggest competitions that appear on existing fixtures.\n const fixtures = useFixtures({ pagination: { limit: 100 } });\n const competitions = useMemo(() => {\n const set = new Set<string>();\n for (const f of fixtures.data?.items ?? []) {\n if (f.competition) set.add(f.competition);\n }\n return Array.from(set).sort();\n }, [fixtures.data]);\n\n // If no competition selected yet but fixtures have one, default to the first.\n useEffect(() => {\n if (!activeCompetition && competitions.length > 0) {\n setCompetition(competitions[0]);\n setActiveCompetition(competitions[0]);\n }\n }, [activeCompetition, competitions]);\n\n const standings = useStandings(activeCompetition || undefined);\n const rows = useMemo(() => {\n const items = standings.data ?? [];\n return [...items].sort((a, b) => {\n if (b.points !== a.points) return b.points - a.points;\n return b.goalsFor - a.goalsFor;\n });\n }, [standings.data]);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setActiveCompetition(competition.trim());\n };\n\n return (\n <PageLayout\n title={tr('atheletebridge.pages.standings.title', 'Standings')}\n description={tr(\n 'atheletebridge.pages.standings.description',\n 'Live competition tables — recomputed automatically from results.'\n )}\n >\n <form\n onSubmit={handleSubmit}\n data-tour=\"standings-competition\"\n className=\"mb-6 flex flex-wrap items-end gap-3\"\n >\n <div className=\"flex-1 min-w-[220px]\">\n <label\n htmlFor=\"competition\"\n className=\"mb-1.5 block text-sm font-medium text-text-primary\"\n >\n {tr('atheletebridge.pages.standings.competitionLabel', 'Competition')}\n </label>\n <input\n id=\"competition\"\n list=\"ath-competitions\"\n className={fieldInputClass}\n value={competition}\n onChange={(e) => setCompetition(e.target.value)}\n placeholder={tr(\n 'atheletebridge.pages.standings.competitionPlaceholder',\n 'e.g. Spring League 2026'\n )}\n />\n <datalist id=\"ath-competitions\">\n {competitions.map((c) => (\n <option key={c} value={c} />\n ))}\n </datalist>\n </div>\n <button\n type=\"submit\"\n className=\"inline-flex h-[38px] items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Search className=\"h-4 w-4\" />{' '}\n {tr('atheletebridge.pages.standings.viewTable', 'View table')}\n </button>\n </form>\n\n {!activeCompetition ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Trophy className=\"h-10 w-10\" />}\n title={tr('atheletebridge.pages.standings.pickCompetition', 'Pick a competition')}\n description={tr(\n 'atheletebridge.pages.standings.pickCompetitionHint',\n 'Enter a competition name to see its live standings. Standings are computed from recorded fixture results on a 3-1-0 model.'\n )}\n />\n ) : standings.isLoading ? (\n <LoadingState label={tr('atheletebridge.pages.standings.loading', 'Loading standings…')} />\n ) : standings.isError ? (\n <ErrorState onRetry={() => void standings.refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Trophy className=\"h-10 w-10\" />}\n title={tr('atheletebridge.pages.standings.empty', 'No standings yet')}\n description={`No results have been recorded for \"${activeCompetition}\". Record a fixture result to build the table.`}\n />\n ) : (\n <EmphasisPanel className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[640px] text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-xs uppercase tracking-wide text-text-secondary\">\n <th className=\"px-3 py-2 font-medium\">#</th>\n <th className=\"px-3 py-2 font-medium\">Team</th>\n <th className=\"px-3 py-2 text-center font-medium\">P</th>\n <th className=\"px-3 py-2 text-center font-medium\">W</th>\n <th className=\"px-3 py-2 text-center font-medium\">D</th>\n <th className=\"px-3 py-2 text-center font-medium\">L</th>\n <th className=\"px-3 py-2 text-center font-medium\">GF</th>\n <th className=\"px-3 py-2 text-center font-medium\">GA</th>\n <th className=\"px-3 py-2 text-center font-medium\">GD</th>\n <th className=\"px-3 py-2 text-center font-semibold text-text-primary\">Pts</th>\n </tr>\n </thead>\n <tbody>\n {rows.map((row, idx) => {\n const rank = row.rank ?? idx + 1;\n const isLeader = rank === 1;\n const gd = row.goalsFor - row.goalsAgainst;\n const teamName = row.team?.name ?? 'Unknown team';\n return (\n <tr\n key={row.id}\n className={cn(\n 'border-b border-border-seam/60 last:border-0 hover:bg-bg-sunken/50',\n isLeader && 'bg-[var(--color-accent-soft)]/40'\n )}\n >\n <td className=\"py-2.5 pl-0 pr-3\">\n <span className=\"flex items-center\">\n <span\n aria-hidden=\"true\"\n className={cn(\n 'mr-2 h-6 w-1 rounded-full',\n isLeader ? 'bg-action-primary-bg' : 'bg-transparent'\n )}\n />\n <span\n className={cn(\n 'tabular-nums',\n isLeader ? 'font-bold text-text-primary' : 'text-text-secondary'\n )}\n >\n {rank}\n </span>\n </span>\n </td>\n <td className=\"px-3 py-2.5\">\n <span className=\"flex min-w-0 items-center gap-2.5\">\n <Crest name={teamName} sport={row.team?.sport} size={26} />\n <span className=\"truncate font-medium text-text-primary\">{teamName}</span>\n </span>\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.played}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.won}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.drawn}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.lost}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.goalsFor}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.goalsAgainst}\n </td>\n <td\n className={cn(\n 'px-3 py-2.5 text-center font-medium tabular-nums',\n gd > 0\n ? 'text-status-success-text'\n : gd < 0\n ? 'text-status-error-text'\n : 'text-text-secondary'\n )}\n >\n {gd > 0 ? `+${gd}` : gd}\n </td>\n <td\n className={cn(\n 'px-3 py-2.5 text-center font-bold tabular-nums',\n isLeader ? 'text-primary' : 'text-text-primary'\n )}\n >\n {row.points}\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </EmphasisPanel>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"StandingsPage.js","names":[],"sources":["../../../src/atheletebridge/pages/StandingsPage.tsx"],"sourcesContent":["/**\n * Standings\n * @module atheletebridge/pages\n *\n * Reads the live competition table (athStandings) for a chosen competition.\n * The svc requires a competition name, so the user picks one (seeded from\n * fixtures that carry a competition, or typed directly).\n */\n\nimport { useEffect, useMemo, useState } from 'react';\nimport { useSearchParams } from 'react-router';\nimport { Search, Trophy } from 'lucide-react';\nimport { EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { EmptyState } from '../components/EmptyState';\nimport { LoadingState, ErrorState } from '../components/LoadingState';\nimport { Crest } from '../components/Crest';\nimport { fieldInputClass } from '../components/form';\nimport { cn } from '../utils/cn';\nimport { useFixtures, useStandings } from '../hooks/useAthleteBridgeApi';\nimport { useAbTranslate } from '../i18n';\n\nexport function StandingsPage() {\n const tr = useAbTranslate();\n const [searchParams] = useSearchParams();\n const initialCompetition = searchParams.get('competition') ?? '';\n\n const [competition, setCompetition] = useState(initialCompetition);\n const [activeCompetition, setActiveCompetition] = useState(initialCompetition);\n\n // Suggest competitions that appear on existing fixtures.\n const fixtures = useFixtures({ pagination: { limit: 100 } });\n const competitions = useMemo(() => {\n const set = new Set<string>();\n for (const f of fixtures.data?.items ?? []) {\n if (f.competition) set.add(f.competition);\n }\n return Array.from(set).sort();\n }, [fixtures.data]);\n\n // If no competition selected yet but fixtures have one, default to the first.\n useEffect(() => {\n if (!activeCompetition && competitions.length > 0) {\n setCompetition(competitions[0]);\n setActiveCompetition(competitions[0]);\n }\n }, [activeCompetition, competitions]);\n\n const standings = useStandings(activeCompetition || undefined);\n const rows = useMemo(() => {\n const items = standings.data ?? [];\n return [...items].sort((a, b) => {\n if (b.points !== a.points) return b.points - a.points;\n return b.goalsFor - a.goalsFor;\n });\n }, [standings.data]);\n\n const handleSubmit = (e: React.FormEvent) => {\n e.preventDefault();\n setActiveCompetition(competition.trim());\n };\n\n return (\n <PageLayout\n title={tr('atheletebridge.pages.standings.title', 'Standings')}\n description={tr(\n 'atheletebridge.pages.standings.description',\n 'Live competition tables — recomputed automatically from results.'\n )}\n >\n <form\n onSubmit={handleSubmit}\n data-tour=\"standings-competition\"\n className=\"mb-6 flex flex-wrap items-end gap-3\"\n >\n <div className=\"flex-1 min-w-[220px]\">\n <label\n htmlFor=\"competition\"\n className=\"mb-1.5 block text-sm font-medium text-text-primary\"\n >\n {tr('atheletebridge.pages.standings.competitionLabel', 'Competition')}\n </label>\n <input\n id=\"competition\"\n list=\"ath-competitions\"\n className={fieldInputClass}\n value={competition}\n onChange={(e) => setCompetition(e.target.value)}\n placeholder={tr(\n 'atheletebridge.pages.standings.competitionPlaceholder',\n 'e.g. Spring League 2026'\n )}\n />\n <datalist id=\"ath-competitions\">\n {competitions.map((c) => (\n <option key={c} value={c} />\n ))}\n </datalist>\n </div>\n <button\n type=\"submit\"\n className=\"inline-flex h-[38px] items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Search className=\"h-4 w-4\" />{' '}\n {tr('atheletebridge.pages.standings.viewTable', 'View table')}\n </button>\n </form>\n\n {!activeCompetition ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Trophy className=\"h-10 w-10\" />}\n title={tr('atheletebridge.pages.standings.pickCompetition', 'Pick a competition')}\n description={tr(\n 'atheletebridge.pages.standings.pickCompetitionHint',\n 'Enter a competition name to see its live standings. Standings are computed from recorded fixture results on a 3-1-0 model.'\n )}\n />\n ) : standings.isLoading ? (\n <LoadingState label={tr('atheletebridge.pages.standings.loading', 'Loading standings…')} />\n ) : standings.isError ? (\n <ErrorState onRetry={() => void standings.refetch()} />\n ) : rows.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Trophy className=\"h-10 w-10\" />}\n title={tr('atheletebridge.pages.standings.empty', 'No standings yet')}\n description={`No results have been recorded for \"${activeCompetition}\". Record a fixture result to build the table.`}\n />\n ) : (\n <EmphasisPanel className=\"overflow-x-auto\">\n <table className=\"w-full min-w-[640px] text-sm\">\n <thead>\n <tr className=\"border-b border-border-seam text-left text-xs uppercase tracking-wide text-text-secondary\">\n <th className=\"px-3 py-2 font-medium\">#</th>\n <th className=\"px-3 py-2 font-medium\">Team</th>\n <th className=\"px-3 py-2 text-center font-medium\">P</th>\n <th className=\"px-3 py-2 text-center font-medium\">W</th>\n <th className=\"px-3 py-2 text-center font-medium\">D</th>\n <th className=\"px-3 py-2 text-center font-medium\">L</th>\n <th className=\"px-3 py-2 text-center font-medium\">GF</th>\n <th className=\"px-3 py-2 text-center font-medium\">GA</th>\n <th className=\"px-3 py-2 text-center font-medium\">GD</th>\n <th className=\"px-3 py-2 text-center font-semibold text-text-primary\">Pts</th>\n </tr>\n </thead>\n <tbody>\n {rows.map((row, idx) => {\n const rank = row.rank ?? idx + 1;\n const isLeader = rank === 1;\n const gd = row.goalsFor - row.goalsAgainst;\n const teamName = row.team?.name ?? 'Unknown team';\n return (\n <tr\n key={row.id}\n className={cn(\n 'border-b border-border-seam/60 last:border-0 hover:bg-bg-sunken/50',\n isLeader && 'bg-[var(--color-accent-soft)]/40'\n )}\n >\n <td className=\"py-2.5 pl-0 pr-3\">\n <span className=\"flex items-center\">\n <span\n aria-hidden=\"true\"\n className={cn(\n 'mr-2 h-6 w-1 rounded-full',\n isLeader ? 'bg-action-primary-bg' : 'bg-transparent'\n )}\n />\n <span\n className={cn(\n 'tabular-nums',\n isLeader ? 'font-bold text-text-primary' : 'text-text-secondary'\n )}\n >\n {rank}\n </span>\n </span>\n </td>\n <td className=\"px-3 py-2.5\">\n <span className=\"flex min-w-0 items-center gap-2.5\">\n <Crest name={teamName} sport={row.team?.sport} size={26} />\n <span className=\"truncate font-medium text-text-primary\">{teamName}</span>\n </span>\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.played}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.won}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.drawn}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.lost}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.goalsFor}\n </td>\n <td className=\"px-3 py-2.5 text-center tabular-nums text-text-secondary\">\n {row.goalsAgainst}\n </td>\n <td\n className={cn(\n 'px-3 py-2.5 text-center font-medium tabular-nums',\n gd > 0\n ? 'text-status-success-text'\n : gd < 0\n ? 'text-status-error-text'\n : 'text-text-secondary'\n )}\n >\n {gd > 0 ? `+${gd}` : gd}\n </td>\n <td\n className={cn(\n 'px-3 py-2.5 text-center font-bold tabular-nums',\n isLeader ? 'text-primary' : 'text-text-primary'\n )}\n >\n {row.points}\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </EmphasisPanel>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -15,7 +15,7 @@ import { ErrorState as f, LoadingState as re } from "../../components/LoadingSta
15
15
  import { useAthleteProfile as ie, useTeam as ae } from "../../hooks/useAthleteBridgeApi.js";
16
16
  import { useMemo as p, useState as oe } from "react";
17
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";
18
+ import { useParams as b } from "react-router";
19
19
  import { Fragment as x, jsx as S, jsxs as C } from "react/jsx-runtime";
20
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
21
  //#region src/atheletebridge/pages/athletes/AthleteProfilePage.tsx
@@ -1 +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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"}
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';\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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"}
@@ -10,7 +10,7 @@ import { AvailabilityRsvpControl as d } from "../../components/availability/Avai
10
10
  import { AvailabilityRosterPanel as f } from "../../components/availability/AvailabilityRosterPanel.js";
11
11
  import { useState as p } from "react";
12
12
  import { ArrowLeft as m, Flag as h, Loader2 as g, Minus as _, Trophy as v } from "lucide-react";
13
- import { useParams as y } from "react-router-dom";
13
+ import { useParams as y } from "react-router";
14
14
  import { jsx as b, jsxs as x } from "react/jsx-runtime";
15
15
  import { EmphasisPanel as S, GlassCard as C } from "@burdenoff/fe-libs/ui";
16
16
  //#region src/atheletebridge/pages/fixtures/FixtureDetailPage.tsx
@@ -1 +1 @@
1
- {"version":3,"file":"FixtureDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/fixtures/FixtureDetailPage.tsx"],"sourcesContent":["/**\n * Fixture Detail\n * @module atheletebridge/pages/fixtures\n *\n * Reads a single fixture (athFixture) and lets the user record a final result\n * (athRecordFixtureResult) which marks it COMPLETED and recomputes standings.\n *\n * The header is a crest-vs-crest Scoreboard hero; the record-result panel shows\n * each team's crest beside its score input. Semantic tokens only, responsive.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Trophy, Loader2, Flag, Minus } from 'lucide-react';\nimport { GlassCard, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { Scoreboard } from '../../components/Scoreboard';\nimport { Crest } from '../../components/Crest';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { FIXTURE_TYPE_LABELS } from '../../components/domain';\nimport { AvailabilityRsvpControl } from '../../components/availability/AvailabilityRsvpControl';\nimport { AvailabilityRosterPanel } from '../../components/availability/AvailabilityRosterPanel';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useFixture, useRecordFixtureResult } from '../../hooks/useAthleteBridgeApi';\n\nexport function FixtureDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const fixture = useFixture(id);\n const recordResult = useRecordFixtureResult();\n\n const [homeScore, setHomeScore] = useState('');\n const [awayScore, setAwayScore] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n if (fixture.isLoading) {\n return (\n <PageLayout title=\"Fixture\">\n <LoadingState label=\"Loading fixture…\" />\n </PageLayout>\n );\n }\n\n if (fixture.isError || !fixture.data) {\n return (\n <PageLayout title=\"Fixture\">\n <ErrorState\n title=\"Fixture not found\"\n message=\"This fixture does not exist or you do not have access to it.\"\n onRetry={() => navigateTo('/fixtures')}\n />\n </PageLayout>\n );\n }\n\n const f = fixture.data;\n const homeName = f.homeTeam?.name ?? 'Home';\n const awayName = f.awayTeam?.name ?? (f.awayTeamId ? 'Away' : 'TBD');\n const canRecordResult =\n Boolean(f.awayTeamId) && (f.status === 'SCHEDULED' || f.status === 'LIVE');\n\n const handleRecord = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!id) return;\n const home = Number.parseInt(homeScore, 10);\n const away = Number.parseInt(awayScore, 10);\n if (Number.isNaN(home) || Number.isNaN(away)) {\n setError('Both scores are required and must be numbers.');\n return;\n }\n if (home < 0 || away < 0) {\n setError('Scores cannot be negative.');\n return;\n }\n try {\n await recordResult.mutateAsync({ id, input: { homeScore: home, awayScore: away } });\n setHomeScore('');\n setAwayScore('');\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to record result.');\n }\n };\n\n return (\n <PageLayout\n title={`${homeName} vs ${awayName}`}\n description={`${FIXTURE_TYPE_LABELS[f.type]}${f.competition ? ` · ${f.competition}` : ''}`}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures')}\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 <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n }\n >\n {/* Crest-vs-crest hero scoreboard */}\n <Scoreboard fixture={f} variant=\"hero\" className=\"mb-8\" />\n\n {/* Availability / RSVP (BOFF-4134) — athlete's own response + coach roster view */}\n <div className=\"mb-8 grid grid-cols-1 gap-4 lg:grid-cols-[minmax(0,1fr)_2fr]\">\n <AvailabilityRsvpControl teamId={f.homeTeamId} fixtureId={f.id} />\n <AvailabilityRosterPanel teamId={f.homeTeamId} fixtureId={f.id} />\n </div>\n\n {f.notes && (\n <EmphasisPanel className=\"mb-8\">\n <h2 className=\"mb-2 text-sm font-semibold text-text-primary\">Notes</h2>\n <p className=\"text-sm text-text-secondary\">{f.notes}</p>\n </EmphasisPanel>\n )}\n\n {/* Record result */}\n {canRecordResult && (\n <section className=\"mb-8\">\n <h2 className=\"mb-4 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Flag className=\"h-5 w-5\" /> Record result\n </h2>\n <GlassCard treatment=\"insight\" className=\"p-5 sm:p-6\">\n <p className=\"mb-5 text-sm text-text-secondary\">\n Recording a final score marks this fixture <strong>Completed</strong> and recomputes\n the competition standings for both teams.\n </p>\n <form onSubmit={handleRecord}>\n <div className=\"flex flex-wrap items-end justify-center gap-4 sm:gap-6\">\n <div className=\"flex items-end gap-3\">\n <div className=\"flex flex-col items-center gap-2\">\n <Crest name={homeName} sport={f.homeTeam?.sport} size={40} />\n <span className=\"max-w-[8rem] truncate text-center text-xs font-medium text-text-secondary\">\n {homeName}\n </span>\n </div>\n <FormField label=\"Home\" htmlFor=\"homeScore\" className=\"w-24\">\n <input\n id=\"homeScore\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center text-lg font-bold tabular-nums`}\n value={homeScore}\n onChange={(e) => setHomeScore(e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n </div>\n\n <Minus className=\"mb-3 h-5 w-5 shrink-0 text-text-muted\" aria-hidden=\"true\" />\n\n <div className=\"flex items-end gap-3\">\n <FormField label=\"Away\" htmlFor=\"awayScore\" className=\"w-24\">\n <input\n id=\"awayScore\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center text-lg font-bold tabular-nums`}\n value={awayScore}\n onChange={(e) => setAwayScore(e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n <div className=\"flex flex-col items-center gap-2\">\n <Crest name={awayName} sport={f.awayTeam?.sport} size={40} />\n <span className=\"max-w-[8rem] truncate text-center text-xs font-medium text-text-secondary\">\n {awayName}\n </span>\n </div>\n </div>\n </div>\n\n <div className=\"mt-6 flex justify-center\">\n <button\n type=\"submit\"\n disabled={recordResult.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-6 py-2.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {recordResult.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trophy className=\"h-4 w-4\" />\n )}\n Record result\n </button>\n </div>\n </form>\n {error && <p className=\"mt-3 text-center text-sm text-status-error-text\">{error}</p>}\n </GlassCard>\n </section>\n )}\n\n {f.competition && (\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(`/standings?competition=${encodeURIComponent(f.competition ?? '')}`)\n }\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 <Trophy className=\"h-4 w-4\" /> View {f.competition} standings\n </button>\n )}\n </PageLayout>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA0BA,SAAgB,IAAoB;CAClC,IAAM,EAAE,UAAO,EAA0B,GACnC,EAAE,kBAAe,EAAiB,GAElC,IAAU,EAAW,CAAE,GACvB,IAAe,EAAuB,GAEtC,CAAC,GAAW,KAAgB,EAAS,EAAE,GACvC,CAAC,GAAW,KAAgB,EAAS,EAAE,GACvC,CAAC,GAAO,KAAY,EAAwB,IAAI;CAEtD,IAAI,EAAQ,WACV,OACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD,EAAc,OAAM,mBAAoB,CAAA;CAC9B,CAAA;CAIhB,IAAI,EAAQ,WAAW,CAAC,EAAQ,MAC9B,OACE,kBAAC,GAAD;EAAY,OAAM;YAChB,kBAAC,GAAD;GACE,OAAM;GACN,SAAQ;GACR,eAAe,EAAW,WAAW;EACtC,CAAA;CACS,CAAA;CAIhB,IAAM,IAAI,EAAQ,MACZ,IAAW,EAAE,UAAU,QAAQ,QAC/B,IAAW,EAAE,UAAU,SAAS,EAAE,aAAa,SAAS,QACxD,IACJ,EAAQ,EAAE,eAAgB,EAAE,WAAW,eAAe,EAAE,WAAW;CAyBrE,OACE,kBAAC,GAAD;EACE,OAAO,GAAG,EAAS,MAAM;EACzB,aAAa,GAAG,EAAoB,EAAE,QAAQ,EAAE,cAAc,MAAM,EAAE,gBAAgB;EACtF,SACE,kBAAC,UAAD;GACE,MAAK;GACL,eAAe,EAAW,WAAW;GACrC,WAAU;aAHZ,CAKE,kBAAC,GAAD,EAAW,WAAU,UAAW,CAAA,GAAC,OAC3B;;YAVZ;GAcE,kBAAC,GAAD;IAAY,SAAS;IAAG,SAAQ;IAAO,WAAU;GAAQ,CAAA;GAGzD,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAyB,QAAQ,EAAE;KAAY,WAAW,EAAE;IAAK,CAAA,GACjE,kBAAC,GAAD;KAAyB,QAAQ,EAAE;KAAY,WAAW,EAAE;IAAK,CAAA,CAC9D;;GAEJ,EAAE,SACD,kBAAC,GAAD;IAAe,WAAU;cAAzB,CACE,kBAAC,MAAD;KAAI,WAAU;eAA+C;IAAS,CAAA,GACtE,kBAAC,KAAD;KAAG,WAAU;eAA+B,EAAE;IAAS,CAAA,CAC1C;;GAIhB,KACC,kBAAC,WAAD;IAAS,WAAU;cAAnB,CACE,kBAAC,MAAD;KAAI,WAAU;eAAd,CACE,kBAAC,GAAD,EAAM,WAAU,UAAW,CAAA,GAAC,gBAC1B;QACJ,kBAAC,GAAD;KAAW,WAAU;KAAU,WAAU;eAAzC;MACE,kBAAC,KAAD;OAAG,WAAU;iBAAb;QAAgD;QACH,kBAAC,UAAD,EAAA,UAAQ,YAAiB,CAAA;QAAC;OAEpE;;MACH,kBAAC,QAAD;OAAM,UAAU,OAhEE,MAAuB;QAGjD,IAFA,EAAE,eAAe,GACjB,EAAS,IAAI,GACT,CAAC,GAAI;QACT,IAAM,IAAO,OAAO,SAAS,GAAW,EAAE,GACpC,IAAO,OAAO,SAAS,GAAW,EAAE;QAC1C,IAAI,OAAO,MAAM,CAAI,KAAK,OAAO,MAAM,CAAI,GAAG;SAC5C,EAAS,+CAA+C;SACxD;QACF;QACA,IAAI,IAAO,KAAK,IAAO,GAAG;SACxB,EAAS,4BAA4B;SACrC;QACF;QACA,IAAI;SAGF,AAFA,MAAM,EAAa,YAAY;UAAE;UAAI,OAAO;WAAE,WAAW;WAAM,WAAW;UAAK;SAAE,CAAC,GAClF,EAAa,EAAE,GACf,EAAa,EAAE;QACjB,SAAS,GAAK;SACZ,EAAS,aAAe,QAAQ,EAAI,UAAU,0BAA0B;QAC1E;OACF;iBA2CU,CACE,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,GAAD;YAAO,MAAM;YAAU,OAAO,EAAE,UAAU;YAAO,MAAM;WAAK,CAAA,GAC5D,kBAAC,QAAD;YAAM,WAAU;sBACb;WACG,CAAA,CACH;cACL,kBAAC,GAAD;WAAW,OAAM;WAAO,SAAQ;WAAY,WAAU;qBACpD,kBAAC,SAAD;YACE,IAAG;YACH,WAAU;YACV,WAAW,GAAG,EAAgB;YAC9B,OAAO;YACP,WAAW,MAAM,EAAa,EAAE,OAAO,KAAK;YAC5C,aAAY;WACb,CAAA;UACQ,CAAA,CACR;;SAEL,kBAAC,GAAD;UAAO,WAAU;UAAwC,eAAY;SAAQ,CAAA;SAE7E,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD;WAAW,OAAM;WAAO,SAAQ;WAAY,WAAU;qBACpD,kBAAC,SAAD;YACE,IAAG;YACH,WAAU;YACV,WAAW,GAAG,EAAgB;YAC9B,OAAO;YACP,WAAW,MAAM,EAAa,EAAE,OAAO,KAAK;YAC5C,aAAY;WACb,CAAA;UACQ,CAAA,GACX,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,GAAD;YAAO,MAAM;YAAU,OAAO,EAAE,UAAU;YAAO,MAAM;WAAK,CAAA,GAC5D,kBAAC,QAAD;YAAM,WAAU;sBACb;WACG,CAAA,CACH;YACF;;QACF;WAEL,kBAAC,OAAD;QAAK,WAAU;kBACb,kBAAC,UAAD;SACE,MAAK;SACL,UAAU,EAAa;SACvB,WAAU;mBAHZ,CAKG,EAAa,YACZ,kBAAC,GAAD,EAAS,WAAU,uBAAwB,CAAA,IAE3C,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA,GAC7B,eAEI;;OACL,CAAA,CACD;;MACL,KAAS,kBAAC,KAAD;OAAG,WAAU;iBAAmD;MAAS,CAAA;KAC1E;MACJ;;GAGV,EAAE,eACD,kBAAC,UAAD;IACE,MAAK;IACL,eACE,EAAW,0BAA0B,mBAAmB,EAAE,eAAe,EAAE,GAAG;IAEhF,WAAU;cALZ;KAOE,kBAAC,GAAD,EAAQ,WAAU,UAAW,CAAA;KAAC;KAAO,EAAE;KAAY;IAC7C;;EAEA;;AAEhB"}
1
+ {"version":3,"file":"FixtureDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/fixtures/FixtureDetailPage.tsx"],"sourcesContent":["/**\n * Fixture Detail\n * @module atheletebridge/pages/fixtures\n *\n * Reads a single fixture (athFixture) and lets the user record a final result\n * (athRecordFixtureResult) which marks it COMPLETED and recomputes standings.\n *\n * The header is a crest-vs-crest Scoreboard hero; the record-result panel shows\n * each team's crest beside its score input. Semantic tokens only, responsive.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router';\nimport { ArrowLeft, Trophy, Loader2, Flag, Minus } from 'lucide-react';\nimport { GlassCard, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { Scoreboard } from '../../components/Scoreboard';\nimport { Crest } from '../../components/Crest';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { FIXTURE_TYPE_LABELS } from '../../components/domain';\nimport { AvailabilityRsvpControl } from '../../components/availability/AvailabilityRsvpControl';\nimport { AvailabilityRosterPanel } from '../../components/availability/AvailabilityRosterPanel';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useFixture, useRecordFixtureResult } from '../../hooks/useAthleteBridgeApi';\n\nexport function FixtureDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const fixture = useFixture(id);\n const recordResult = useRecordFixtureResult();\n\n const [homeScore, setHomeScore] = useState('');\n const [awayScore, setAwayScore] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n if (fixture.isLoading) {\n return (\n <PageLayout title=\"Fixture\">\n <LoadingState label=\"Loading fixture…\" />\n </PageLayout>\n );\n }\n\n if (fixture.isError || !fixture.data) {\n return (\n <PageLayout title=\"Fixture\">\n <ErrorState\n title=\"Fixture not found\"\n message=\"This fixture does not exist or you do not have access to it.\"\n onRetry={() => navigateTo('/fixtures')}\n />\n </PageLayout>\n );\n }\n\n const f = fixture.data;\n const homeName = f.homeTeam?.name ?? 'Home';\n const awayName = f.awayTeam?.name ?? (f.awayTeamId ? 'Away' : 'TBD');\n const canRecordResult =\n Boolean(f.awayTeamId) && (f.status === 'SCHEDULED' || f.status === 'LIVE');\n\n const handleRecord = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!id) return;\n const home = Number.parseInt(homeScore, 10);\n const away = Number.parseInt(awayScore, 10);\n if (Number.isNaN(home) || Number.isNaN(away)) {\n setError('Both scores are required and must be numbers.');\n return;\n }\n if (home < 0 || away < 0) {\n setError('Scores cannot be negative.');\n return;\n }\n try {\n await recordResult.mutateAsync({ id, input: { homeScore: home, awayScore: away } });\n setHomeScore('');\n setAwayScore('');\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to record result.');\n }\n };\n\n return (\n <PageLayout\n title={`${homeName} vs ${awayName}`}\n description={`${FIXTURE_TYPE_LABELS[f.type]}${f.competition ? ` · ${f.competition}` : ''}`}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures')}\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 <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n }\n >\n {/* Crest-vs-crest hero scoreboard */}\n <Scoreboard fixture={f} variant=\"hero\" className=\"mb-8\" />\n\n {/* Availability / RSVP (BOFF-4134) — athlete's own response + coach roster view */}\n <div className=\"mb-8 grid grid-cols-1 gap-4 lg:grid-cols-[minmax(0,1fr)_2fr]\">\n <AvailabilityRsvpControl teamId={f.homeTeamId} fixtureId={f.id} />\n <AvailabilityRosterPanel teamId={f.homeTeamId} fixtureId={f.id} />\n </div>\n\n {f.notes && (\n <EmphasisPanel className=\"mb-8\">\n <h2 className=\"mb-2 text-sm font-semibold text-text-primary\">Notes</h2>\n <p className=\"text-sm text-text-secondary\">{f.notes}</p>\n </EmphasisPanel>\n )}\n\n {/* Record result */}\n {canRecordResult && (\n <section className=\"mb-8\">\n <h2 className=\"mb-4 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Flag className=\"h-5 w-5\" /> Record result\n </h2>\n <GlassCard treatment=\"insight\" className=\"p-5 sm:p-6\">\n <p className=\"mb-5 text-sm text-text-secondary\">\n Recording a final score marks this fixture <strong>Completed</strong> and recomputes\n the competition standings for both teams.\n </p>\n <form onSubmit={handleRecord}>\n <div className=\"flex flex-wrap items-end justify-center gap-4 sm:gap-6\">\n <div className=\"flex items-end gap-3\">\n <div className=\"flex flex-col items-center gap-2\">\n <Crest name={homeName} sport={f.homeTeam?.sport} size={40} />\n <span className=\"max-w-[8rem] truncate text-center text-xs font-medium text-text-secondary\">\n {homeName}\n </span>\n </div>\n <FormField label=\"Home\" htmlFor=\"homeScore\" className=\"w-24\">\n <input\n id=\"homeScore\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center text-lg font-bold tabular-nums`}\n value={homeScore}\n onChange={(e) => setHomeScore(e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n </div>\n\n <Minus className=\"mb-3 h-5 w-5 shrink-0 text-text-muted\" aria-hidden=\"true\" />\n\n <div className=\"flex items-end gap-3\">\n <FormField label=\"Away\" htmlFor=\"awayScore\" className=\"w-24\">\n <input\n id=\"awayScore\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center text-lg font-bold tabular-nums`}\n value={awayScore}\n onChange={(e) => setAwayScore(e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n <div className=\"flex flex-col items-center gap-2\">\n <Crest name={awayName} sport={f.awayTeam?.sport} size={40} />\n <span className=\"max-w-[8rem] truncate text-center text-xs font-medium text-text-secondary\">\n {awayName}\n </span>\n </div>\n </div>\n </div>\n\n <div className=\"mt-6 flex justify-center\">\n <button\n type=\"submit\"\n disabled={recordResult.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-6 py-2.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {recordResult.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trophy className=\"h-4 w-4\" />\n )}\n Record result\n </button>\n </div>\n </form>\n {error && <p className=\"mt-3 text-center text-sm text-status-error-text\">{error}</p>}\n </GlassCard>\n </section>\n )}\n\n {f.competition && (\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(`/standings?competition=${encodeURIComponent(f.competition ?? '')}`)\n }\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 <Trophy className=\"h-4 w-4\" /> View {f.competition} standings\n </button>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -9,7 +9,7 @@ import { FormField as l, fieldInputClass as u } from "../../components/form.js";
9
9
  import { toIsoOrUndefined as d } from "../../utils/datetime.js";
10
10
  import { useMemo as f, useState as p } from "react";
11
11
  import { ArrowLeft as m, Calendar as h, Clock as g, Handshake as _, Loader2 as v, Network as y, Save as b, Shield as x, Swords as S, Trophy as C } from "lucide-react";
12
- import { useSearchParams as w } from "react-router-dom";
12
+ import { useSearchParams as w } from "react-router";
13
13
  import { jsx as T, jsxs as E } from "react/jsx-runtime";
14
14
  import { GlassCard as D } from "@burdenoff/fe-libs/ui";
15
15
  //#region src/atheletebridge/pages/fixtures/FixtureFormPage.tsx