@burdenoff/microfe-atheletebridge 2026.720.1 → 2026.731.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/atheletebridge/AthleteBridgeRoot.js.map +1 -1
- package/dist/atheletebridge/AthleteBridgeRoutes.js.map +1 -1
- package/dist/atheletebridge/admin/AdminDashboardPage.js.map +1 -1
- package/dist/atheletebridge/admin/AdminRoot.js.map +1 -1
- package/dist/atheletebridge/components/AthleteSelect.js.map +1 -1
- package/dist/atheletebridge/components/Crest.js.map +1 -1
- package/dist/atheletebridge/components/CursorPager.js.map +1 -1
- package/dist/atheletebridge/components/DrillVideoLink.js.map +1 -1
- package/dist/atheletebridge/components/EmptyState.js.map +1 -1
- package/dist/atheletebridge/components/FilterSelect.js.map +1 -1
- package/dist/atheletebridge/components/FixtureCard.js.map +1 -1
- package/dist/atheletebridge/components/LoadingState.js.map +1 -1
- package/dist/atheletebridge/components/MatchupRow.js.map +1 -1
- package/dist/atheletebridge/components/MetricsCard.js.map +1 -1
- package/dist/atheletebridge/components/PageLayout.js.map +1 -1
- package/dist/atheletebridge/components/ProgramCard.js.map +1 -1
- package/dist/atheletebridge/components/RouteAnalytics.js.map +1 -1
- package/dist/atheletebridge/components/Scoreboard.js.map +1 -1
- package/dist/atheletebridge/components/SessionCard.js.map +1 -1
- package/dist/atheletebridge/components/SessionStatusTimeline.js.map +1 -1
- package/dist/atheletebridge/components/StatusBadge.js.map +1 -1
- package/dist/atheletebridge/components/TeamCard.js.map +1 -1
- package/dist/atheletebridge/components/UserAvatar.js.map +1 -1
- package/dist/atheletebridge/components/UserSearchSelect.js.map +1 -1
- package/dist/atheletebridge/components/availability/AvailabilityRosterPanel.d.ts +9 -0
- package/dist/atheletebridge/components/availability/AvailabilityRosterPanel.d.ts.map +1 -0
- package/dist/atheletebridge/components/availability/AvailabilityRosterPanel.js +118 -0
- package/dist/atheletebridge/components/availability/AvailabilityRosterPanel.js.map +1 -0
- package/dist/atheletebridge/components/availability/AvailabilityRsvpControl.d.ts +9 -0
- package/dist/atheletebridge/components/availability/AvailabilityRsvpControl.d.ts.map +1 -0
- package/dist/atheletebridge/components/availability/AvailabilityRsvpControl.js +88 -0
- package/dist/atheletebridge/components/availability/AvailabilityRsvpControl.js.map +1 -0
- package/dist/atheletebridge/components/domain.js.map +1 -1
- package/dist/atheletebridge/components/form.js.map +1 -1
- package/dist/atheletebridge/components/viz/BarChart.js.map +1 -1
- package/dist/atheletebridge/components/viz/DonutChart.js +1 -1
- package/dist/atheletebridge/components/viz/DonutChart.js.map +1 -1
- package/dist/atheletebridge/components/viz/LineChart.js.map +1 -1
- package/dist/atheletebridge/components/viz/RatingStars.js.map +1 -1
- package/dist/atheletebridge/components/viz/Sparkline.js.map +1 -1
- package/dist/atheletebridge/components/viz/StatCard.js.map +1 -1
- package/dist/atheletebridge/components/viz/Timeline.js.map +1 -1
- package/dist/atheletebridge/components/viz/lib/CartesianFrame.js.map +1 -1
- package/dist/atheletebridge/components/viz/lib/ResponsiveSvg.js.map +1 -1
- package/dist/atheletebridge/components/viz/lib/format.js.map +1 -1
- package/dist/atheletebridge/components/viz/lib/geometry.js.map +1 -1
- package/dist/atheletebridge/components/viz/lib/hooks.js.map +1 -1
- package/dist/atheletebridge/components/viz/lib/palette.js.map +1 -1
- package/dist/atheletebridge/context/AthleteBridgeContext.js.map +1 -1
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.d.ts +18 -1
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.d.ts.map +1 -1
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js +71 -33
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js.map +1 -1
- package/dist/atheletebridge/i18n.js.map +1 -1
- package/dist/atheletebridge/pages/DashboardPage.js.map +1 -1
- package/dist/atheletebridge/pages/StandingsPage.js.map +1 -1
- package/dist/atheletebridge/pages/analytics/CoachAnalyticsPage.js.map +1 -1
- package/dist/atheletebridge/pages/athletes/AthleteProfilePage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +87 -75
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixturesListPage.js.map +1 -1
- package/dist/atheletebridge/pages/onboarding/OnboardingPage.js.map +1 -1
- package/dist/atheletebridge/pages/performance/PerformancePage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/programs/ProgramsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamFormPage.js.map +1 -1
- package/dist/atheletebridge/pages/teams/TeamsListPage.js.map +1 -1
- package/dist/atheletebridge/pages/tournaments/TournamentBracketPage.js.map +1 -1
- package/dist/atheletebridge/store/atheletebridgeStore.js.map +1 -1
- package/dist/atheletebridge/types/index.d.ts +27 -0
- package/dist/atheletebridge/types/index.d.ts.map +1 -1
- package/dist/atheletebridge/utils/cn.js.map +1 -1
- package/dist/atheletebridge/utils/datetime.js.map +1 -1
- package/dist/atheletebridge/utils/formatters.js.map +1 -1
- package/dist/atheletebridge/utils/identity.js.map +1 -1
- package/dist/atheletebridge/utils/navigation.js.map +1 -1
- package/dist/atheletebridge/utils/pageContext.js.map +1 -1
- package/dist/atheletebridge/utils/timeAgo.js.map +1 -1
- package/dist/atheletebridge/utils/url.js.map +1 -1
- package/dist/node_modules/clsx/dist/clsx.js.map +1 -1
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js +1 -1
- package/dist/node_modules/tailwind-merge/dist/bundle-mjs.js.map +1 -1
- package/dist/services/createSandboxAssistantTransport.js.map +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AthleteBridgeRoot.js","names":[],"sources":["../../src/atheletebridge/AthleteBridgeRoot.tsx"],"sourcesContent":["/**\n * AthleteBridge Root Component\n * @module atheletebridge\n *\n * The single Root export consumed by the host app shell. The export name and\n * props (AthleteBridgeRootProps) are the host contract and must not change —\n * only the internal nav config, provider tree, and routes are product-specific.\n */\n\nimport type { FC } from 'react';\nimport { useEffect } from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { useAbTranslate } from './i18n';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport {\n LayoutDashboard,\n Users,\n CalendarDays,\n Trophy,\n Dumbbell,\n ClipboardCheck,\n Gauge,\n BarChart3,\n Swords,\n} from 'lucide-react';\nimport { AthleteBridgeProvider } from './context/AthleteBridgeContext';\nimport { AthleteBridgeRoutes } from './AthleteBridgeRoutes';\nimport type { AthleteBridgeRootProps, NavItem } from './types';\n\n// Create a query client for this microfrontend\nconst queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 5 * 60 * 1000,\n retry: 1,\n },\n },\n});\n\nexport const AthleteBridgeRoot: FC<AthleteBridgeRootProps> = (props) => {\n const { registerNavItems, registerFooterItems, basePath = '/', workspaceId } = props;\n const t = useAbTranslate();\n\n // Helper to join paths correctly\n const joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith('/') ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith('/') ? path : `/${path}`;\n return cleanBase + cleanPath;\n };\n\n // Register navigation items with shell on mount\n useEffect(() => {\n if (!registerNavItems) return;\n\n const navItems: NavItem[] = [\n {\n id: 'atheletebridge-dashboard',\n label: t('atheletebridge.nav.dashboard', 'Dashboard'),\n path: joinPath(basePath, ''),\n icon: <LayoutDashboard size={20} />,\n order: 1,\n group: 'main',\n },\n {\n id: 'atheletebridge-teams',\n label: t('atheletebridge.nav.teams', 'Teams'),\n path: joinPath(basePath, 'teams'),\n icon: <Users size={20} />,\n order: 10,\n group: 'roster',\n },\n {\n id: 'atheletebridge-fixtures',\n label: t('atheletebridge.nav.fixtures', 'Fixtures'),\n path: joinPath(basePath, 'fixtures'),\n icon: <CalendarDays size={20} />,\n order: 11,\n group: 'roster',\n },\n {\n id: 'atheletebridge-standings',\n label: t('atheletebridge.nav.standings', 'Standings'),\n path: joinPath(basePath, 'standings'),\n icon: <Trophy size={20} />,\n order: 12,\n group: 'roster',\n },\n {\n id: 'atheletebridge-tournaments',\n label: t('atheletebridge.nav.tournaments', 'Tournaments'),\n path: joinPath(basePath, 'tournaments'),\n icon: <Swords size={20} />,\n order: 13,\n group: 'roster',\n },\n {\n id: 'atheletebridge-programs',\n label: t('atheletebridge.nav.programs', 'Programs'),\n path: joinPath(basePath, 'programs'),\n icon: <Dumbbell size={20} />,\n order: 20,\n group: 'training',\n },\n {\n id: 'atheletebridge-sessions',\n label: t('atheletebridge.nav.sessions', 'Sessions'),\n path: joinPath(basePath, 'sessions'),\n icon: <ClipboardCheck size={20} />,\n order: 21,\n group: 'training',\n },\n {\n id: 'atheletebridge-performance',\n label: t('atheletebridge.nav.performance', 'Performance'),\n path: joinPath(basePath, 'performance'),\n icon: <Gauge size={20} />,\n order: 22,\n group: 'training',\n },\n {\n id: 'atheletebridge-analytics',\n label: t('atheletebridge.nav.analytics', 'Analytics'),\n path: joinPath(basePath, 'analytics'),\n icon: <BarChart3 size={20} />,\n order: 23,\n group: 'training',\n },\n ];\n\n const cleanup = registerNavItems(navItems);\n return cleanup;\n }, [registerNavItems, basePath, t]);\n\n // Register footer items\n useEffect(() => {\n if (!registerFooterItems) return;\n\n const footerItems = [\n {\n id: 'atheletebridge-status',\n label: t('atheletebridge.footer.ready', 'AthleteBridge Ready'),\n type: 'status' as const,\n order: 10,\n section: 'left' as const,\n },\n ];\n\n const cleanup = registerFooterItems(footerItems);\n return cleanup;\n }, [registerFooterItems, t]);\n\n return (\n <QueryClientProvider client={queryClient}>\n {/*\n * The host-supplied workspaceId MUST be forwarded: FeatureFlagProvider\n * skips the evaluation fetch entirely when it is null, which collapses\n * every gate onto its defaultValue and makes per-workspace/per-tenant\n * overrides unresolvable. defaultEnabled stays true so an outage of the\n * flag service degrades open rather than hiding shipped modules.\n */}\n <FeatureFlagProvider workspaceId={workspaceId ?? null} gateway=\"global\" defaultEnabled={true}>\n <AthleteBridgeProvider {...props}>\n <AthleteBridgeRoutes />\n </AthleteBridgeProvider>\n </FeatureFlagProvider>\n </QueryClientProvider>\n );\n};\n"],"mappings":";;;;;;;;;AA8BA,IAAM,IAAc,IAAI,EAAY,EAClC,gBAAgB,EACd,SAAS;CACP,WAAW,MAAS;CACpB,OAAO;
|
|
1
|
+
{"version":3,"file":"AthleteBridgeRoot.js","names":[],"sources":["../../src/atheletebridge/AthleteBridgeRoot.tsx"],"sourcesContent":["/**\n * AthleteBridge Root Component\n * @module atheletebridge\n *\n * The single Root export consumed by the host app shell. The export name and\n * props (AthleteBridgeRootProps) are the host contract and must not change —\n * only the internal nav config, provider tree, and routes are product-specific.\n */\n\nimport type { FC } from 'react';\nimport { useEffect } from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\nimport { useAbTranslate } from './i18n';\nimport { FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport {\n LayoutDashboard,\n Users,\n CalendarDays,\n Trophy,\n Dumbbell,\n ClipboardCheck,\n Gauge,\n BarChart3,\n Swords,\n} from 'lucide-react';\nimport { AthleteBridgeProvider } from './context/AthleteBridgeContext';\nimport { AthleteBridgeRoutes } from './AthleteBridgeRoutes';\nimport type { AthleteBridgeRootProps, NavItem } from './types';\n\n// Create a query client for this microfrontend\nconst queryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 5 * 60 * 1000,\n retry: 1,\n },\n },\n});\n\nexport const AthleteBridgeRoot: FC<AthleteBridgeRootProps> = (props) => {\n const { registerNavItems, registerFooterItems, basePath = '/', workspaceId } = props;\n const t = useAbTranslate();\n\n // Helper to join paths correctly\n const joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith('/') ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith('/') ? path : `/${path}`;\n return cleanBase + cleanPath;\n };\n\n // Register navigation items with shell on mount\n useEffect(() => {\n if (!registerNavItems) return;\n\n const navItems: NavItem[] = [\n {\n id: 'atheletebridge-dashboard',\n label: t('atheletebridge.nav.dashboard', 'Dashboard'),\n path: joinPath(basePath, ''),\n icon: <LayoutDashboard size={20} />,\n order: 1,\n group: 'main',\n },\n {\n id: 'atheletebridge-teams',\n label: t('atheletebridge.nav.teams', 'Teams'),\n path: joinPath(basePath, 'teams'),\n icon: <Users size={20} />,\n order: 10,\n group: 'roster',\n },\n {\n id: 'atheletebridge-fixtures',\n label: t('atheletebridge.nav.fixtures', 'Fixtures'),\n path: joinPath(basePath, 'fixtures'),\n icon: <CalendarDays size={20} />,\n order: 11,\n group: 'roster',\n },\n {\n id: 'atheletebridge-standings',\n label: t('atheletebridge.nav.standings', 'Standings'),\n path: joinPath(basePath, 'standings'),\n icon: <Trophy size={20} />,\n order: 12,\n group: 'roster',\n },\n {\n id: 'atheletebridge-tournaments',\n label: t('atheletebridge.nav.tournaments', 'Tournaments'),\n path: joinPath(basePath, 'tournaments'),\n icon: <Swords size={20} />,\n order: 13,\n group: 'roster',\n },\n {\n id: 'atheletebridge-programs',\n label: t('atheletebridge.nav.programs', 'Programs'),\n path: joinPath(basePath, 'programs'),\n icon: <Dumbbell size={20} />,\n order: 20,\n group: 'training',\n },\n {\n id: 'atheletebridge-sessions',\n label: t('atheletebridge.nav.sessions', 'Sessions'),\n path: joinPath(basePath, 'sessions'),\n icon: <ClipboardCheck size={20} />,\n order: 21,\n group: 'training',\n },\n {\n id: 'atheletebridge-performance',\n label: t('atheletebridge.nav.performance', 'Performance'),\n path: joinPath(basePath, 'performance'),\n icon: <Gauge size={20} />,\n order: 22,\n group: 'training',\n },\n {\n id: 'atheletebridge-analytics',\n label: t('atheletebridge.nav.analytics', 'Analytics'),\n path: joinPath(basePath, 'analytics'),\n icon: <BarChart3 size={20} />,\n order: 23,\n group: 'training',\n },\n ];\n\n const cleanup = registerNavItems(navItems);\n return cleanup;\n }, [registerNavItems, basePath, t]);\n\n // Register footer items\n useEffect(() => {\n if (!registerFooterItems) return;\n\n const footerItems = [\n {\n id: 'atheletebridge-status',\n label: t('atheletebridge.footer.ready', 'AthleteBridge Ready'),\n type: 'status' as const,\n order: 10,\n section: 'left' as const,\n },\n ];\n\n const cleanup = registerFooterItems(footerItems);\n return cleanup;\n }, [registerFooterItems, t]);\n\n return (\n <QueryClientProvider client={queryClient}>\n {/*\n * The host-supplied workspaceId MUST be forwarded: FeatureFlagProvider\n * skips the evaluation fetch entirely when it is null, which collapses\n * every gate onto its defaultValue and makes per-workspace/per-tenant\n * overrides unresolvable. defaultEnabled stays true so an outage of the\n * flag service degrades open rather than hiding shipped modules.\n */}\n <FeatureFlagProvider workspaceId={workspaceId ?? null} gateway=\"global\" defaultEnabled={true}>\n <AthleteBridgeProvider {...props}>\n <AthleteBridgeRoutes />\n </AthleteBridgeProvider>\n </FeatureFlagProvider>\n </QueryClientProvider>\n );\n};\n"],"mappings":";;;;;;;;;AA8BA,IAAM,IAAc,IAAI,EAAY,EAClC,gBAAgB,EACd,SAAS;CACP,WAAW,MAAS;CACpB,OAAO;AACT,EACF,EACF,CAAC,GAEY,KAAiD,MAAU;CACtE,IAAM,EAAE,qBAAkB,wBAAqB,cAAW,KAAK,mBAAgB,GACzE,IAAI,EAAe,GAGnB,KAAY,GAAc,OACZ,EAAK,SAAS,GAAG,IAAI,EAAK,MAAM,GAAG,EAAE,IAAI,MACzC,EAAK,WAAW,GAAG,IAAI,IAAO,IAAI;CAyGtD,OApGA,QAAgB;EACT,OA8EL,OADgB,EAAiB;GA1E/B;IACE,IAAI;IACJ,OAAO,EAAE,gCAAgC,WAAW;IACpD,MAAM,EAAS,GAAU,EAAE;IAC3B,MAAM,kBAAC,GAAD,EAAiB,MAAM,GAAK,CAAA;IAClC,OAAO;IACP,OAAO;GACT;GACA;IACE,IAAI;IACJ,OAAO,EAAE,4BAA4B,OAAO;IAC5C,MAAM,EAAS,GAAU,OAAO;IAChC,MAAM,kBAAC,GAAD,EAAO,MAAM,GAAK,CAAA;IACxB,OAAO;IACP,OAAO;GACT;GACA;IACE,IAAI;IACJ,OAAO,EAAE,+BAA+B,UAAU;IAClD,MAAM,EAAS,GAAU,UAAU;IACnC,MAAM,kBAAC,GAAD,EAAc,MAAM,GAAK,CAAA;IAC/B,OAAO;IACP,OAAO;GACT;GACA;IACE,IAAI;IACJ,OAAO,EAAE,gCAAgC,WAAW;IACpD,MAAM,EAAS,GAAU,WAAW;IACpC,MAAM,kBAAC,GAAD,EAAQ,MAAM,GAAK,CAAA;IACzB,OAAO;IACP,OAAO;GACT;GACA;IACE,IAAI;IACJ,OAAO,EAAE,kCAAkC,aAAa;IACxD,MAAM,EAAS,GAAU,aAAa;IACtC,MAAM,kBAAC,GAAD,EAAQ,MAAM,GAAK,CAAA;IACzB,OAAO;IACP,OAAO;GACT;GACA;IACE,IAAI;IACJ,OAAO,EAAE,+BAA+B,UAAU;IAClD,MAAM,EAAS,GAAU,UAAU;IACnC,MAAM,kBAAC,GAAD,EAAU,MAAM,GAAK,CAAA;IAC3B,OAAO;IACP,OAAO;GACT;GACA;IACE,IAAI;IACJ,OAAO,EAAE,+BAA+B,UAAU;IAClD,MAAM,EAAS,GAAU,UAAU;IACnC,MAAM,kBAAC,GAAD,EAAgB,MAAM,GAAK,CAAA;IACjC,OAAO;IACP,OAAO;GACT;GACA;IACE,IAAI;IACJ,OAAO,EAAE,kCAAkC,aAAa;IACxD,MAAM,EAAS,GAAU,aAAa;IACtC,MAAM,kBAAC,GAAD,EAAO,MAAM,GAAK,CAAA;IACxB,OAAO;IACP,OAAO;GACT;GACA;IACE,IAAI;IACJ,OAAO,EAAE,gCAAgC,WAAW;IACpD,MAAM,EAAS,GAAU,WAAW;IACpC,MAAM,kBAAC,GAAD,EAAW,MAAM,GAAK,CAAA;IAC5B,OAAO;IACP,OAAO;GACT;EAG+B,CAC1B;CACT,GAAG;EAAC;EAAkB;EAAU;CAAC,CAAC,GAGlC,QAAgB;EACT,OAaL,OADgB,EAAoB,CATlC;GACE,IAAI;GACJ,OAAO,EAAE,+BAA+B,qBAAqB;GAC7D,MAAM;GACN,OAAO;GACP,SAAS;EACX,CAGkC,CAC7B;CACT,GAAG,CAAC,GAAqB,CAAC,CAAC,GAGzB,kBAAC,GAAD;EAAqB,QAAQ;YAQ3B,kBAAC,GAAD;GAAqB,aAAa,KAAe;GAAM,SAAQ;GAAS,gBAAgB;aACtF,kBAAC,GAAD;IAAuB,GAAI;cACzB,kBAAC,GAAD,CAAsB,CAAA;GACD,CAAA;EACJ,CAAA;CACF,CAAA;AAEzB"}
|
|
@@ -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-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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminDashboardPage.js","names":[],"sources":["../../../src/atheletebridge/admin/AdminDashboardPage.tsx"],"sourcesContent":["/**\n * AthleteBridge Admin Dashboard\n * @module atheletebridge/admin\n *\n * Product-specific governance surface mounted inside `admin-app` at\n * `/admin/atheletebridge` (BOFF-3258).\n *\n * Every tab below is backed by a REAL, shipped `wspace-atheletebridge-svc`\n * root field reached through the workspace gateway:\n *\n * Teams → athListTeams (useTeams)\n * Programs → athListPrograms (usePrograms)\n * Fixtures → athListFixtures (useFixtures) + athStandings (useStandings)\n * Sessions → athListSessions (useSessions)\n *\n * DELIBERATE OMISSION (RULINGS R6): governance surfaces that have no backing\n * query are OMITTED ENTIRELY rather than shipped with invented rows. This file\n * intentionally does NOT copy the `PLACEHOLDER_ADAPTERS` / `PLACEHOLDER_LISTINGS`\n * pattern from `microfe-subscriberbot/src/subscriberbot/admin/AdminDashboardPage.tsx`,\n * which renders fabricated data behind a \"backend not implemented\" banner. There\n * are zero hard-coded rows here.\n *\n * READ-ONLY BY DESIGN: this is an operations/support oversight view. Lifecycle\n * mutations (archive team, transition program, cancel session, …) already exist\n * in the product UI behind the same RBAC pairs; duplicating them here would add\n * a second write path to a service already on the duplication watch list\n * (05-OVERLAP-AND-DUPLICATION-REPORT.md:249) without a governance need.\n */\n\nimport { useMemo, useState, type FC } from 'react';\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\nimport { CalendarDays, ClipboardCheck, Dumbbell, Trophy, Users } from 'lucide-react';\n\nimport { CursorPager } from '../components/CursorPager';\nimport { EmptyState } from '../components/EmptyState';\nimport { FilterSelect } from '../components/FilterSelect';\nimport { LoadingState, ErrorState } from '../components/LoadingState';\nimport { PageLayout } from '../components/PageLayout';\nimport { StatusBadge } from '../components/StatusBadge';\nimport {\n FIXTURE_TYPE_LABELS,\n PROGRAM_LEVEL_LABELS,\n SESSION_TYPE_LABELS,\n SPORT_LABELS,\n} from '../components/domain';\nimport { useFixtures, usePrograms, useSessions, useStandings, useTeams } from '../hooks';\nimport { formatDate, formatDuration } from '../utils/formatters';\nimport type {\n AthFixtureStatus,\n AthProgramStatus,\n AthSessionStatus,\n AthSport,\n AthTeamStatus,\n} from '../types';\n\nconst PAGE_SIZE = 20;\n\nconst SPORT_OPTS = (Object.keys(SPORT_LABELS) as AthSport[]).map((value) => ({\n value,\n label: SPORT_LABELS[value],\n}));\n\nconst TEAM_STATUS_OPTS: ReadonlyArray<{ value: AthTeamStatus; label: string }> = [\n { value: 'FORMING', label: 'Forming' },\n { value: 'ACTIVE', label: 'Active' },\n { value: 'INACTIVE', label: 'Inactive' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nconst PROGRAM_STATUS_OPTS: ReadonlyArray<{ value: AthProgramStatus; label: string }> = [\n { value: 'DRAFT', label: 'Draft' },\n { value: 'PUBLISHED', label: 'Published' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nconst FIXTURE_STATUS_OPTS: ReadonlyArray<{ value: AthFixtureStatus; label: string }> = [\n { value: 'SCHEDULED', label: 'Scheduled' },\n { value: 'LIVE', label: 'Live' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'POSTPONED', label: 'Postponed' },\n { value: 'CANCELLED', label: 'Cancelled' },\n];\n\nconst SESSION_STATUS_OPTS: ReadonlyArray<{ value: AthSessionStatus; label: string }> = [\n { value: 'REQUESTED', label: 'Requested' },\n { value: 'CONFIRMED', label: 'Confirmed' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'CANCELLED', label: 'Cancelled' },\n { value: 'NO_SHOW', label: 'No show' },\n];\n\n/** Renders an em dash for absent values so empty cells never read as a bug. */\nconst orDash = (value: string | number | null | undefined): string =>\n value === null || value === undefined || value === '' ? '—' : String(value);\n\n// ============================================================================\n// Teams\n// ============================================================================\n\nconst TeamsTab: FC = () => {\n const [sport, setSport] = useState<AthSport | ''>('');\n const [status, setStatus] = useState<AthTeamStatus | ''>('');\n const [page, setPage] = useState(0);\n\n const teams = useTeams({\n sport: sport || undefined,\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const items = teams.data?.items ?? [];\n\n return (\n <Card>\n <CardHeader>\n <CardTitle>Teams</CardTitle>\n <CardDescription>\n Every squad, club side, academy group and esports roster in the active workspace.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <FilterSelect\n ariaLabel=\"Filter teams by sport\"\n allLabel=\"All sports\"\n value={sport}\n onChange={(v) => {\n setSport(v);\n setPage(0);\n }}\n options={SPORT_OPTS}\n />\n <FilterSelect\n ariaLabel=\"Filter teams by status\"\n allLabel=\"All statuses\"\n value={status}\n onChange={(v) => {\n setStatus(v);\n setPage(0);\n }}\n options={TEAM_STATUS_OPTS}\n />\n </div>\n\n {teams.isLoading ? (\n <LoadingState label=\"Loading teams…\" />\n ) : teams.isError ? (\n <ErrorState onRetry={() => void teams.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n icon={<Users className=\"h-6 w-6\" />}\n title=\"No teams match these filters\"\n description=\"Teams created in this workspace will appear here for oversight.\"\n />\n ) : (\n <>\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Sport</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Age group</TableHead>\n <TableHead>Season</TableHead>\n <TableHead className=\"text-right\">Members</TableHead>\n <TableHead>Created</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {items.map((team) => (\n <TableRow key={team.id}>\n <TableCell className=\"font-medium text-text-primary\">{team.name}</TableCell>\n <TableCell className=\"text-text-secondary\">\n {SPORT_LABELS[team.sport]}\n </TableCell>\n <TableCell>\n <StatusBadge status={team.status} />\n </TableCell>\n <TableCell className=\"text-text-secondary\">{orDash(team.ageGroup)}</TableCell>\n <TableCell className=\"text-text-secondary\">{orDash(team.season)}</TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {team.members?.items.length ?? 0}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(team.createdAt)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n <CursorPager\n page={page}\n hasMore={teams.data?.pageInfo.hasMore ?? false}\n onPrev={() => setPage((p) => Math.max(0, p - 1))}\n onNext={() => setPage((p) => p + 1)}\n />\n </>\n )}\n </CardContent>\n </Card>\n );\n};\n\n// ============================================================================\n// Programs\n// ============================================================================\n\nconst ProgramsTab: FC = () => {\n const [sport, setSport] = useState<AthSport | ''>('');\n const [status, setStatus] = useState<AthProgramStatus | ''>('');\n const [page, setPage] = useState(0);\n\n const programs = usePrograms({\n sport: sport || undefined,\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const items = programs.data?.items ?? [];\n\n return (\n <Card>\n <CardHeader>\n <CardTitle>Training programs</CardTitle>\n <CardDescription>\n Coaching programs and templates, with their drill counts and publication state.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <FilterSelect\n ariaLabel=\"Filter programs by sport\"\n allLabel=\"All sports\"\n value={sport}\n onChange={(v) => {\n setSport(v);\n setPage(0);\n }}\n options={SPORT_OPTS}\n />\n <FilterSelect\n ariaLabel=\"Filter programs by status\"\n allLabel=\"All statuses\"\n value={status}\n onChange={(v) => {\n setStatus(v);\n setPage(0);\n }}\n options={PROGRAM_STATUS_OPTS}\n />\n </div>\n\n {programs.isLoading ? (\n <LoadingState label=\"Loading programs…\" />\n ) : programs.isError ? (\n <ErrorState onRetry={() => void programs.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n icon={<Dumbbell className=\"h-6 w-6\" />}\n title=\"No programs match these filters\"\n description=\"Programs authored by coaches in this workspace will appear here.\"\n />\n ) : (\n <>\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Sport</TableHead>\n <TableHead>Level</TableHead>\n <TableHead>Status</TableHead>\n <TableHead className=\"text-right\">Weeks</TableHead>\n <TableHead className=\"text-right\">Drills</TableHead>\n <TableHead>Template</TableHead>\n <TableHead>Created</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {items.map((program) => (\n <TableRow key={program.id}>\n <TableCell className=\"font-medium text-text-primary\">\n {program.name}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {SPORT_LABELS[program.sport]}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {PROGRAM_LEVEL_LABELS[program.level]}\n </TableCell>\n <TableCell>\n <StatusBadge status={program.status} />\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {program.weeks}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {program.drills?.items.length ?? 0}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {program.isTemplate ? 'Yes' : 'No'}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(program.createdAt)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n <CursorPager\n page={page}\n hasMore={programs.data?.pageInfo.hasMore ?? false}\n onPrev={() => setPage((p) => Math.max(0, p - 1))}\n onNext={() => setPage((p) => p + 1)}\n />\n </>\n )}\n </CardContent>\n </Card>\n );\n};\n\n// ============================================================================\n// Fixtures + standings\n// ============================================================================\n\nconst FixturesTab: FC = () => {\n const [status, setStatus] = useState<AthFixtureStatus | ''>('');\n const [page, setPage] = useState(0);\n const [competition, setCompetition] = useState<string | ''>('');\n\n const fixtures = useFixtures({\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const items = useMemo(() => fixtures.data?.items ?? [], [fixtures.data]);\n\n // Competition options are derived from the competitions actually present on\n // the loaded fixtures — never a hard-coded league list.\n const competitionOpts = useMemo(() => {\n const names = new Set<string>();\n for (const fixture of items) {\n if (fixture.competition) names.add(fixture.competition);\n }\n return [...names].sort().map((value) => ({ value, label: value }));\n }, [items]);\n\n const standings = useStandings(competition || undefined);\n const standingRows = standings.data ?? [];\n\n return (\n <div className=\"flex flex-col gap-6\">\n <Card>\n <CardHeader>\n <CardTitle>Fixtures</CardTitle>\n <CardDescription>\n Scheduled, live and completed fixtures across every competition in this workspace.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <FilterSelect\n ariaLabel=\"Filter fixtures by status\"\n allLabel=\"All statuses\"\n value={status}\n onChange={(v) => {\n setStatus(v);\n setPage(0);\n }}\n options={FIXTURE_STATUS_OPTS}\n />\n </div>\n\n {fixtures.isLoading ? (\n <LoadingState label=\"Loading fixtures…\" />\n ) : fixtures.isError ? (\n <ErrorState onRetry={() => void fixtures.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n icon={<CalendarDays className=\"h-6 w-6\" />}\n title=\"No fixtures match these filters\"\n description=\"Fixtures scheduled in this workspace will appear here.\"\n />\n ) : (\n <>\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Home</TableHead>\n <TableHead>Away</TableHead>\n <TableHead>Competition</TableHead>\n <TableHead>Type</TableHead>\n <TableHead>Status</TableHead>\n <TableHead className=\"text-right\">Score</TableHead>\n <TableHead>Scheduled</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {items.map((fixture) => (\n <TableRow key={fixture.id}>\n <TableCell className=\"font-medium text-text-primary\">\n {orDash(fixture.homeTeam?.name)}\n </TableCell>\n <TableCell className=\"font-medium text-text-primary\">\n {orDash(fixture.awayTeam?.name)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {orDash(fixture.competition)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {FIXTURE_TYPE_LABELS[fixture.type]}\n </TableCell>\n <TableCell>\n <StatusBadge status={fixture.status} />\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {fixture.homeScore === null || fixture.awayScore === null\n ? '—'\n : `${fixture.homeScore}–${fixture.awayScore}`}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(fixture.scheduledAt)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n <CursorPager\n page={page}\n hasMore={fixtures.data?.pageInfo.hasMore ?? false}\n onPrev={() => setPage((p) => Math.max(0, p - 1))}\n onNext={() => setPage((p) => p + 1)}\n />\n </>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Standings</CardTitle>\n <CardDescription>\n League table for a competition, recomputed by the service when a fixture result is\n recorded. Pick a competition seen on the fixtures above.\n </CardDescription>\n </CardHeader>\n <CardContent>\n {competitionOpts.length === 0 ? (\n <EmptyState\n icon={<Trophy className=\"h-6 w-6\" />}\n title=\"No competitions to show\"\n description=\"Standings appear once fixtures in this workspace are assigned a competition.\"\n />\n ) : (\n <>\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <FilterSelect\n ariaLabel=\"Select a competition\"\n allLabel=\"Select a competition…\"\n value={competition}\n onChange={setCompetition}\n options={competitionOpts}\n />\n </div>\n\n {!competition ? (\n <EmptyState\n icon={<Trophy className=\"h-6 w-6\" />}\n title=\"Select a competition\"\n description=\"Choose a competition above to load its standings.\"\n />\n ) : standings.isLoading ? (\n <LoadingState label=\"Loading standings…\" />\n ) : standings.isError ? (\n <ErrorState onRetry={() => void standings.refetch()} />\n ) : standingRows.length === 0 ? (\n <EmptyState\n icon={<Trophy className=\"h-6 w-6\" />}\n title=\"No standings recorded yet\"\n description=\"Standings are computed once results are recorded for this competition.\"\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead className=\"text-right\">#</TableHead>\n <TableHead>Team</TableHead>\n <TableHead className=\"text-right\">P</TableHead>\n <TableHead className=\"text-right\">W</TableHead>\n <TableHead className=\"text-right\">D</TableHead>\n <TableHead className=\"text-right\">L</TableHead>\n <TableHead className=\"text-right\">GF</TableHead>\n <TableHead className=\"text-right\">GA</TableHead>\n <TableHead className=\"text-right\">Pts</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {standingRows.map((row) => (\n <TableRow key={row.id}>\n <TableCell className=\"text-right text-text-secondary\">\n {orDash(row.rank)}\n </TableCell>\n <TableCell className=\"font-medium text-text-primary\">\n {orDash(row.team?.name)}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {row.played}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {row.won}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {row.drawn}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {row.lost}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {row.goalsFor}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {row.goalsAgainst}\n </TableCell>\n <TableCell className=\"text-right font-medium text-text-primary\">\n {row.points}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </>\n )}\n </CardContent>\n </Card>\n </div>\n );\n};\n\n// ============================================================================\n// Sessions\n// ============================================================================\n\nconst SessionsTab: FC = () => {\n const [status, setStatus] = useState<AthSessionStatus | ''>('');\n const [page, setPage] = useState(0);\n\n const sessions = useSessions({\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const items = sessions.data?.items ?? [];\n\n return (\n <Card>\n <CardHeader>\n <CardTitle>Coaching sessions</CardTitle>\n <CardDescription>\n Booked, confirmed and completed one-to-one coaching sessions in this workspace.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <FilterSelect\n ariaLabel=\"Filter sessions by status\"\n allLabel=\"All statuses\"\n value={status}\n onChange={(v) => {\n setStatus(v);\n setPage(0);\n }}\n options={SESSION_STATUS_OPTS}\n />\n </div>\n\n {sessions.isLoading ? (\n <LoadingState label=\"Loading sessions…\" />\n ) : sessions.isError ? (\n <ErrorState onRetry={() => void sessions.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n icon={<ClipboardCheck className=\"h-6 w-6\" />}\n title=\"No sessions match these filters\"\n description=\"Coaching sessions booked in this workspace will appear here.\"\n />\n ) : (\n <>\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Title</TableHead>\n <TableHead>Type</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Scheduled</TableHead>\n <TableHead>Duration</TableHead>\n <TableHead className=\"text-right\">Rating</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {items.map((session) => (\n <TableRow key={session.id}>\n <TableCell className=\"font-medium text-text-primary\">\n {session.title}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {SESSION_TYPE_LABELS[session.type]}\n </TableCell>\n <TableCell>\n <StatusBadge status={session.status} />\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(session.scheduledAt)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDuration(session.durationMin)}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {orDash(session.rating)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n <CursorPager\n page={page}\n hasMore={sessions.data?.pageInfo.hasMore ?? false}\n onPrev={() => setPage((p) => Math.max(0, p - 1))}\n onNext={() => setPage((p) => p + 1)}\n />\n </>\n )}\n </CardContent>\n </Card>\n );\n};\n\n// ============================================================================\n// Dashboard\n// ============================================================================\n\nexport const AdminDashboardPage: FC = () => {\n const [activeTab, setActiveTab] = useState('teams');\n\n return (\n <PageLayout\n title=\"AthleteBridge Admin\"\n description=\"Operations oversight for AthleteBridge teams, programs, fixtures and sessions.\"\n hero\n gradientTitle\n >\n <Tabs value={activeTab} onValueChange={setActiveTab} className=\"w-full\">\n <TabsList className=\"mb-4 grid w-full grid-cols-4 sm:w-auto sm:inline-flex\">\n <TabsTrigger value=\"teams\" className=\"gap-2\">\n <Users size={16} />\n Teams\n </TabsTrigger>\n <TabsTrigger value=\"programs\" className=\"gap-2\">\n <Dumbbell size={16} />\n Programs\n </TabsTrigger>\n <TabsTrigger value=\"fixtures\" className=\"gap-2\">\n <CalendarDays size={16} />\n Fixtures\n </TabsTrigger>\n <TabsTrigger value=\"sessions\" className=\"gap-2\">\n <ClipboardCheck size={16} />\n Sessions\n </TabsTrigger>\n </TabsList>\n\n <TabsContent value=\"teams\">\n <TeamsTab />\n </TabsContent>\n <TabsContent value=\"programs\">\n <ProgramsTab />\n </TabsContent>\n <TabsContent value=\"fixtures\">\n <FixturesTab />\n </TabsContent>\n <TabsContent value=\"sessions\">\n <SessionsTab />\n </TabsContent>\n </Tabs>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AdminDashboardPage.js","names":[],"sources":["../../../src/atheletebridge/admin/AdminDashboardPage.tsx"],"sourcesContent":["/**\n * AthleteBridge Admin Dashboard\n * @module atheletebridge/admin\n *\n * Product-specific governance surface mounted inside `admin-app` at\n * `/admin/atheletebridge` (BOFF-3258).\n *\n * Every tab below is backed by a REAL, shipped `wspace-atheletebridge-svc`\n * root field reached through the workspace gateway:\n *\n * Teams → athListTeams (useTeams)\n * Programs → athListPrograms (usePrograms)\n * Fixtures → athListFixtures (useFixtures) + athStandings (useStandings)\n * Sessions → athListSessions (useSessions)\n *\n * DELIBERATE OMISSION (RULINGS R6): governance surfaces that have no backing\n * query are OMITTED ENTIRELY rather than shipped with invented rows. This file\n * intentionally does NOT copy the `PLACEHOLDER_ADAPTERS` / `PLACEHOLDER_LISTINGS`\n * pattern from `microfe-subscriberbot/src/subscriberbot/admin/AdminDashboardPage.tsx`,\n * which renders fabricated data behind a \"backend not implemented\" banner. There\n * are zero hard-coded rows here.\n *\n * READ-ONLY BY DESIGN: this is an operations/support oversight view. Lifecycle\n * mutations (archive team, transition program, cancel session, …) already exist\n * in the product UI behind the same RBAC pairs; duplicating them here would add\n * a second write path to a service already on the duplication watch list\n * (05-OVERLAP-AND-DUPLICATION-REPORT.md:249) without a governance need.\n */\n\nimport { useMemo, useState, type FC } from 'react';\nimport {\n Card,\n CardContent,\n CardDescription,\n CardHeader,\n CardTitle,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from '@burdenoff/fe-libs/ui';\nimport { CalendarDays, ClipboardCheck, Dumbbell, Trophy, Users } from 'lucide-react';\n\nimport { CursorPager } from '../components/CursorPager';\nimport { EmptyState } from '../components/EmptyState';\nimport { FilterSelect } from '../components/FilterSelect';\nimport { LoadingState, ErrorState } from '../components/LoadingState';\nimport { PageLayout } from '../components/PageLayout';\nimport { StatusBadge } from '../components/StatusBadge';\nimport {\n FIXTURE_TYPE_LABELS,\n PROGRAM_LEVEL_LABELS,\n SESSION_TYPE_LABELS,\n SPORT_LABELS,\n} from '../components/domain';\nimport { useFixtures, usePrograms, useSessions, useStandings, useTeams } from '../hooks';\nimport { formatDate, formatDuration } from '../utils/formatters';\nimport type {\n AthFixtureStatus,\n AthProgramStatus,\n AthSessionStatus,\n AthSport,\n AthTeamStatus,\n} from '../types';\n\nconst PAGE_SIZE = 20;\n\nconst SPORT_OPTS = (Object.keys(SPORT_LABELS) as AthSport[]).map((value) => ({\n value,\n label: SPORT_LABELS[value],\n}));\n\nconst TEAM_STATUS_OPTS: ReadonlyArray<{ value: AthTeamStatus; label: string }> = [\n { value: 'FORMING', label: 'Forming' },\n { value: 'ACTIVE', label: 'Active' },\n { value: 'INACTIVE', label: 'Inactive' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nconst PROGRAM_STATUS_OPTS: ReadonlyArray<{ value: AthProgramStatus; label: string }> = [\n { value: 'DRAFT', label: 'Draft' },\n { value: 'PUBLISHED', label: 'Published' },\n { value: 'ARCHIVED', label: 'Archived' },\n];\n\nconst FIXTURE_STATUS_OPTS: ReadonlyArray<{ value: AthFixtureStatus; label: string }> = [\n { value: 'SCHEDULED', label: 'Scheduled' },\n { value: 'LIVE', label: 'Live' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'POSTPONED', label: 'Postponed' },\n { value: 'CANCELLED', label: 'Cancelled' },\n];\n\nconst SESSION_STATUS_OPTS: ReadonlyArray<{ value: AthSessionStatus; label: string }> = [\n { value: 'REQUESTED', label: 'Requested' },\n { value: 'CONFIRMED', label: 'Confirmed' },\n { value: 'COMPLETED', label: 'Completed' },\n { value: 'CANCELLED', label: 'Cancelled' },\n { value: 'NO_SHOW', label: 'No show' },\n];\n\n/** Renders an em dash for absent values so empty cells never read as a bug. */\nconst orDash = (value: string | number | null | undefined): string =>\n value === null || value === undefined || value === '' ? '—' : String(value);\n\n// ============================================================================\n// Teams\n// ============================================================================\n\nconst TeamsTab: FC = () => {\n const [sport, setSport] = useState<AthSport | ''>('');\n const [status, setStatus] = useState<AthTeamStatus | ''>('');\n const [page, setPage] = useState(0);\n\n const teams = useTeams({\n sport: sport || undefined,\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const items = teams.data?.items ?? [];\n\n return (\n <Card>\n <CardHeader>\n <CardTitle>Teams</CardTitle>\n <CardDescription>\n Every squad, club side, academy group and esports roster in the active workspace.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <FilterSelect\n ariaLabel=\"Filter teams by sport\"\n allLabel=\"All sports\"\n value={sport}\n onChange={(v) => {\n setSport(v);\n setPage(0);\n }}\n options={SPORT_OPTS}\n />\n <FilterSelect\n ariaLabel=\"Filter teams by status\"\n allLabel=\"All statuses\"\n value={status}\n onChange={(v) => {\n setStatus(v);\n setPage(0);\n }}\n options={TEAM_STATUS_OPTS}\n />\n </div>\n\n {teams.isLoading ? (\n <LoadingState label=\"Loading teams…\" />\n ) : teams.isError ? (\n <ErrorState onRetry={() => void teams.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n icon={<Users className=\"h-6 w-6\" />}\n title=\"No teams match these filters\"\n description=\"Teams created in this workspace will appear here for oversight.\"\n />\n ) : (\n <>\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Sport</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Age group</TableHead>\n <TableHead>Season</TableHead>\n <TableHead className=\"text-right\">Members</TableHead>\n <TableHead>Created</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {items.map((team) => (\n <TableRow key={team.id}>\n <TableCell className=\"font-medium text-text-primary\">{team.name}</TableCell>\n <TableCell className=\"text-text-secondary\">\n {SPORT_LABELS[team.sport]}\n </TableCell>\n <TableCell>\n <StatusBadge status={team.status} />\n </TableCell>\n <TableCell className=\"text-text-secondary\">{orDash(team.ageGroup)}</TableCell>\n <TableCell className=\"text-text-secondary\">{orDash(team.season)}</TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {team.members?.items.length ?? 0}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(team.createdAt)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n <CursorPager\n page={page}\n hasMore={teams.data?.pageInfo.hasMore ?? false}\n onPrev={() => setPage((p) => Math.max(0, p - 1))}\n onNext={() => setPage((p) => p + 1)}\n />\n </>\n )}\n </CardContent>\n </Card>\n );\n};\n\n// ============================================================================\n// Programs\n// ============================================================================\n\nconst ProgramsTab: FC = () => {\n const [sport, setSport] = useState<AthSport | ''>('');\n const [status, setStatus] = useState<AthProgramStatus | ''>('');\n const [page, setPage] = useState(0);\n\n const programs = usePrograms({\n sport: sport || undefined,\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const items = programs.data?.items ?? [];\n\n return (\n <Card>\n <CardHeader>\n <CardTitle>Training programs</CardTitle>\n <CardDescription>\n Coaching programs and templates, with their drill counts and publication state.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <FilterSelect\n ariaLabel=\"Filter programs by sport\"\n allLabel=\"All sports\"\n value={sport}\n onChange={(v) => {\n setSport(v);\n setPage(0);\n }}\n options={SPORT_OPTS}\n />\n <FilterSelect\n ariaLabel=\"Filter programs by status\"\n allLabel=\"All statuses\"\n value={status}\n onChange={(v) => {\n setStatus(v);\n setPage(0);\n }}\n options={PROGRAM_STATUS_OPTS}\n />\n </div>\n\n {programs.isLoading ? (\n <LoadingState label=\"Loading programs…\" />\n ) : programs.isError ? (\n <ErrorState onRetry={() => void programs.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n icon={<Dumbbell className=\"h-6 w-6\" />}\n title=\"No programs match these filters\"\n description=\"Programs authored by coaches in this workspace will appear here.\"\n />\n ) : (\n <>\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Name</TableHead>\n <TableHead>Sport</TableHead>\n <TableHead>Level</TableHead>\n <TableHead>Status</TableHead>\n <TableHead className=\"text-right\">Weeks</TableHead>\n <TableHead className=\"text-right\">Drills</TableHead>\n <TableHead>Template</TableHead>\n <TableHead>Created</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {items.map((program) => (\n <TableRow key={program.id}>\n <TableCell className=\"font-medium text-text-primary\">\n {program.name}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {SPORT_LABELS[program.sport]}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {PROGRAM_LEVEL_LABELS[program.level]}\n </TableCell>\n <TableCell>\n <StatusBadge status={program.status} />\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {program.weeks}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {program.drills?.items.length ?? 0}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {program.isTemplate ? 'Yes' : 'No'}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(program.createdAt)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n <CursorPager\n page={page}\n hasMore={programs.data?.pageInfo.hasMore ?? false}\n onPrev={() => setPage((p) => Math.max(0, p - 1))}\n onNext={() => setPage((p) => p + 1)}\n />\n </>\n )}\n </CardContent>\n </Card>\n );\n};\n\n// ============================================================================\n// Fixtures + standings\n// ============================================================================\n\nconst FixturesTab: FC = () => {\n const [status, setStatus] = useState<AthFixtureStatus | ''>('');\n const [page, setPage] = useState(0);\n const [competition, setCompetition] = useState<string | ''>('');\n\n const fixtures = useFixtures({\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const items = useMemo(() => fixtures.data?.items ?? [], [fixtures.data]);\n\n // Competition options are derived from the competitions actually present on\n // the loaded fixtures — never a hard-coded league list.\n const competitionOpts = useMemo(() => {\n const names = new Set<string>();\n for (const fixture of items) {\n if (fixture.competition) names.add(fixture.competition);\n }\n return [...names].sort().map((value) => ({ value, label: value }));\n }, [items]);\n\n const standings = useStandings(competition || undefined);\n const standingRows = standings.data ?? [];\n\n return (\n <div className=\"flex flex-col gap-6\">\n <Card>\n <CardHeader>\n <CardTitle>Fixtures</CardTitle>\n <CardDescription>\n Scheduled, live and completed fixtures across every competition in this workspace.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <FilterSelect\n ariaLabel=\"Filter fixtures by status\"\n allLabel=\"All statuses\"\n value={status}\n onChange={(v) => {\n setStatus(v);\n setPage(0);\n }}\n options={FIXTURE_STATUS_OPTS}\n />\n </div>\n\n {fixtures.isLoading ? (\n <LoadingState label=\"Loading fixtures…\" />\n ) : fixtures.isError ? (\n <ErrorState onRetry={() => void fixtures.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n icon={<CalendarDays className=\"h-6 w-6\" />}\n title=\"No fixtures match these filters\"\n description=\"Fixtures scheduled in this workspace will appear here.\"\n />\n ) : (\n <>\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Home</TableHead>\n <TableHead>Away</TableHead>\n <TableHead>Competition</TableHead>\n <TableHead>Type</TableHead>\n <TableHead>Status</TableHead>\n <TableHead className=\"text-right\">Score</TableHead>\n <TableHead>Scheduled</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {items.map((fixture) => (\n <TableRow key={fixture.id}>\n <TableCell className=\"font-medium text-text-primary\">\n {orDash(fixture.homeTeam?.name)}\n </TableCell>\n <TableCell className=\"font-medium text-text-primary\">\n {orDash(fixture.awayTeam?.name)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {orDash(fixture.competition)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {FIXTURE_TYPE_LABELS[fixture.type]}\n </TableCell>\n <TableCell>\n <StatusBadge status={fixture.status} />\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {fixture.homeScore === null || fixture.awayScore === null\n ? '—'\n : `${fixture.homeScore}–${fixture.awayScore}`}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(fixture.scheduledAt)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n <CursorPager\n page={page}\n hasMore={fixtures.data?.pageInfo.hasMore ?? false}\n onPrev={() => setPage((p) => Math.max(0, p - 1))}\n onNext={() => setPage((p) => p + 1)}\n />\n </>\n )}\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Standings</CardTitle>\n <CardDescription>\n League table for a competition, recomputed by the service when a fixture result is\n recorded. Pick a competition seen on the fixtures above.\n </CardDescription>\n </CardHeader>\n <CardContent>\n {competitionOpts.length === 0 ? (\n <EmptyState\n icon={<Trophy className=\"h-6 w-6\" />}\n title=\"No competitions to show\"\n description=\"Standings appear once fixtures in this workspace are assigned a competition.\"\n />\n ) : (\n <>\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <FilterSelect\n ariaLabel=\"Select a competition\"\n allLabel=\"Select a competition…\"\n value={competition}\n onChange={setCompetition}\n options={competitionOpts}\n />\n </div>\n\n {!competition ? (\n <EmptyState\n icon={<Trophy className=\"h-6 w-6\" />}\n title=\"Select a competition\"\n description=\"Choose a competition above to load its standings.\"\n />\n ) : standings.isLoading ? (\n <LoadingState label=\"Loading standings…\" />\n ) : standings.isError ? (\n <ErrorState onRetry={() => void standings.refetch()} />\n ) : standingRows.length === 0 ? (\n <EmptyState\n icon={<Trophy className=\"h-6 w-6\" />}\n title=\"No standings recorded yet\"\n description=\"Standings are computed once results are recorded for this competition.\"\n />\n ) : (\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead className=\"text-right\">#</TableHead>\n <TableHead>Team</TableHead>\n <TableHead className=\"text-right\">P</TableHead>\n <TableHead className=\"text-right\">W</TableHead>\n <TableHead className=\"text-right\">D</TableHead>\n <TableHead className=\"text-right\">L</TableHead>\n <TableHead className=\"text-right\">GF</TableHead>\n <TableHead className=\"text-right\">GA</TableHead>\n <TableHead className=\"text-right\">Pts</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {standingRows.map((row) => (\n <TableRow key={row.id}>\n <TableCell className=\"text-right text-text-secondary\">\n {orDash(row.rank)}\n </TableCell>\n <TableCell className=\"font-medium text-text-primary\">\n {orDash(row.team?.name)}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {row.played}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {row.won}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {row.drawn}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {row.lost}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {row.goalsFor}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {row.goalsAgainst}\n </TableCell>\n <TableCell className=\"text-right font-medium text-text-primary\">\n {row.points}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n )}\n </>\n )}\n </CardContent>\n </Card>\n </div>\n );\n};\n\n// ============================================================================\n// Sessions\n// ============================================================================\n\nconst SessionsTab: FC = () => {\n const [status, setStatus] = useState<AthSessionStatus | ''>('');\n const [page, setPage] = useState(0);\n\n const sessions = useSessions({\n status: status || undefined,\n pagination: { limit: PAGE_SIZE, offset: page * PAGE_SIZE },\n });\n\n const items = sessions.data?.items ?? [];\n\n return (\n <Card>\n <CardHeader>\n <CardTitle>Coaching sessions</CardTitle>\n <CardDescription>\n Booked, confirmed and completed one-to-one coaching sessions in this workspace.\n </CardDescription>\n </CardHeader>\n <CardContent>\n <div className=\"mb-4 flex flex-wrap items-center gap-3\">\n <FilterSelect\n ariaLabel=\"Filter sessions by status\"\n allLabel=\"All statuses\"\n value={status}\n onChange={(v) => {\n setStatus(v);\n setPage(0);\n }}\n options={SESSION_STATUS_OPTS}\n />\n </div>\n\n {sessions.isLoading ? (\n <LoadingState label=\"Loading sessions…\" />\n ) : sessions.isError ? (\n <ErrorState onRetry={() => void sessions.refetch()} />\n ) : items.length === 0 ? (\n <EmptyState\n icon={<ClipboardCheck className=\"h-6 w-6\" />}\n title=\"No sessions match these filters\"\n description=\"Coaching sessions booked in this workspace will appear here.\"\n />\n ) : (\n <>\n <div className=\"overflow-x-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>Title</TableHead>\n <TableHead>Type</TableHead>\n <TableHead>Status</TableHead>\n <TableHead>Scheduled</TableHead>\n <TableHead>Duration</TableHead>\n <TableHead className=\"text-right\">Rating</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {items.map((session) => (\n <TableRow key={session.id}>\n <TableCell className=\"font-medium text-text-primary\">\n {session.title}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {SESSION_TYPE_LABELS[session.type]}\n </TableCell>\n <TableCell>\n <StatusBadge status={session.status} />\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDate(session.scheduledAt)}\n </TableCell>\n <TableCell className=\"text-text-secondary\">\n {formatDuration(session.durationMin)}\n </TableCell>\n <TableCell className=\"text-right text-text-secondary\">\n {orDash(session.rating)}\n </TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n <CursorPager\n page={page}\n hasMore={sessions.data?.pageInfo.hasMore ?? false}\n onPrev={() => setPage((p) => Math.max(0, p - 1))}\n onNext={() => setPage((p) => p + 1)}\n />\n </>\n )}\n </CardContent>\n </Card>\n );\n};\n\n// ============================================================================\n// Dashboard\n// ============================================================================\n\nexport const AdminDashboardPage: FC = () => {\n const [activeTab, setActiveTab] = useState('teams');\n\n return (\n <PageLayout\n title=\"AthleteBridge Admin\"\n description=\"Operations oversight for AthleteBridge teams, programs, fixtures and sessions.\"\n hero\n gradientTitle\n >\n <Tabs value={activeTab} onValueChange={setActiveTab} className=\"w-full\">\n <TabsList className=\"mb-4 grid w-full grid-cols-4 sm:w-auto sm:inline-flex\">\n <TabsTrigger value=\"teams\" className=\"gap-2\">\n <Users size={16} />\n Teams\n </TabsTrigger>\n <TabsTrigger value=\"programs\" className=\"gap-2\">\n <Dumbbell size={16} />\n Programs\n </TabsTrigger>\n <TabsTrigger value=\"fixtures\" className=\"gap-2\">\n <CalendarDays size={16} />\n Fixtures\n </TabsTrigger>\n <TabsTrigger value=\"sessions\" className=\"gap-2\">\n <ClipboardCheck size={16} />\n Sessions\n </TabsTrigger>\n </TabsList>\n\n <TabsContent value=\"teams\">\n <TeamsTab />\n </TabsContent>\n <TabsContent value=\"programs\">\n <ProgramsTab />\n </TabsContent>\n <TabsContent value=\"fixtures\">\n <FixturesTab />\n </TabsContent>\n <TabsContent value=\"sessions\">\n <SessionsTab />\n </TabsContent>\n </Tabs>\n </PageLayout>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminRoot.js","names":[],"sources":["../../../src/atheletebridge/admin/AdminRoot.tsx"],"sourcesContent":["/**\n * AthleteBridge Admin Root\n * @module atheletebridge/admin\n *\n * Host-contract entry point for the AthleteBridge admin surface inside\n * `admin-app` (BOFF-3258). It mirrors the provider/query-client setup of\n * `AthleteBridgeRoot` but deliberately differs in two ways:\n *\n * 1. It does NOT register product-shell nav/footer items — `admin-app` owns\n * its own sidebar via `NavRegistration.tsx`.\n * 2. It does NOT mount `FeatureFlagProvider`. The admin surface is an\n * operations/support view over AthleteBridge's own nouns; per-workspace\n * product gates must not hide rows from an operator investigating them.\n * (No `FeatureGate` is used below this root, so no provider is required.)\n *\n * SCOPE (BOFF-3258 RULINGS R4): this surface covers AthleteBridge's OWN domain\n * entities only — teams, programs, fixtures/standings and sessions. Tenant and\n * workspace administration is product-agnostic, owned by `global-tenant-svc`,\n * and already served generically by `microfe-admin` at the `/admin/*` route.\n * Re-implementing it per product would fork that surface 39 ways.\n */\n\nimport type { FC } from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\n\nimport { AthleteBridgeProvider } from '../context';\nimport type { AthleteBridgeRootProps } from '../types';\nimport { AdminDashboardPage } from './AdminDashboardPage';\n\nconst adminQueryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 60 * 1000,\n retry: 1,\n },\n },\n});\n\nexport interface AthleteBridgeAdminRootProps extends AthleteBridgeRootProps {\n /** Workspace-scoped token supplied by `admin-app` for workspace gateway calls. */\n workspaceToken?: string;\n}\n\nexport const AdminRoot: FC<AthleteBridgeAdminRootProps> = (props) => {\n // `workspaceToken` is consumed by the host shell's `GatewayRoute` / fe-libs\n // graphql transport (`graphqlFetch({ gateway: 'workspace', workspaceToken: true })`\n // reads it from the shared store, not from props). It is intentionally not\n // forwarded to the product context, which only accepts the standard\n // microfrontend root props.\n const { workspaceToken, ...providerProps } = props;\n void workspaceToken;\n\n return (\n <QueryClientProvider client={adminQueryClient}>\n <AthleteBridgeProvider {...providerProps}>\n <AdminDashboardPage />\n </AthleteBridgeProvider>\n </QueryClientProvider>\n );\n};\n"],"mappings":";;;;;;AA6BA,IAAM,IAAmB,IAAI,EAAY,EACvC,gBAAgB,EACd,SAAS;CACP,WAAW,KAAK;CAChB,OAAO;
|
|
1
|
+
{"version":3,"file":"AdminRoot.js","names":[],"sources":["../../../src/atheletebridge/admin/AdminRoot.tsx"],"sourcesContent":["/**\n * AthleteBridge Admin Root\n * @module atheletebridge/admin\n *\n * Host-contract entry point for the AthleteBridge admin surface inside\n * `admin-app` (BOFF-3258). It mirrors the provider/query-client setup of\n * `AthleteBridgeRoot` but deliberately differs in two ways:\n *\n * 1. It does NOT register product-shell nav/footer items — `admin-app` owns\n * its own sidebar via `NavRegistration.tsx`.\n * 2. It does NOT mount `FeatureFlagProvider`. The admin surface is an\n * operations/support view over AthleteBridge's own nouns; per-workspace\n * product gates must not hide rows from an operator investigating them.\n * (No `FeatureGate` is used below this root, so no provider is required.)\n *\n * SCOPE (BOFF-3258 RULINGS R4): this surface covers AthleteBridge's OWN domain\n * entities only — teams, programs, fixtures/standings and sessions. Tenant and\n * workspace administration is product-agnostic, owned by `global-tenant-svc`,\n * and already served generically by `microfe-admin` at the `/admin/*` route.\n * Re-implementing it per product would fork that surface 39 ways.\n */\n\nimport type { FC } from 'react';\nimport { QueryClient, QueryClientProvider } from '@tanstack/react-query';\n\nimport { AthleteBridgeProvider } from '../context';\nimport type { AthleteBridgeRootProps } from '../types';\nimport { AdminDashboardPage } from './AdminDashboardPage';\n\nconst adminQueryClient = new QueryClient({\n defaultOptions: {\n queries: {\n staleTime: 60 * 1000,\n retry: 1,\n },\n },\n});\n\nexport interface AthleteBridgeAdminRootProps extends AthleteBridgeRootProps {\n /** Workspace-scoped token supplied by `admin-app` for workspace gateway calls. */\n workspaceToken?: string;\n}\n\nexport const AdminRoot: FC<AthleteBridgeAdminRootProps> = (props) => {\n // `workspaceToken` is consumed by the host shell's `GatewayRoute` / fe-libs\n // graphql transport (`graphqlFetch({ gateway: 'workspace', workspaceToken: true })`\n // reads it from the shared store, not from props). It is intentionally not\n // forwarded to the product context, which only accepts the standard\n // microfrontend root props.\n const { workspaceToken, ...providerProps } = props;\n void workspaceToken;\n\n return (\n <QueryClientProvider client={adminQueryClient}>\n <AthleteBridgeProvider {...providerProps}>\n <AdminDashboardPage />\n </AthleteBridgeProvider>\n </QueryClientProvider>\n );\n};\n"],"mappings":";;;;;;AA6BA,IAAM,IAAmB,IAAI,EAAY,EACvC,gBAAgB,EACd,SAAS;CACP,WAAW,KAAK;CAChB,OAAO;AACT,EACF,EACF,CAAC,GAOY,KAA8C,MAAU;CAMnE,IAAM,EAAE,mBAAgB,GAAG,MAAkB;CAG7C,OACE,kBAAC,GAAD;EAAqB,QAAQ;YAC3B,kBAAC,GAAD;GAAuB,GAAI;aACzB,kBAAC,GAAD,CAAqB,CAAA;EACA,CAAA;CACJ,CAAA;AAEzB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AthleteSelect.js","names":[],"sources":["../../../src/atheletebridge/components/AthleteSelect.tsx"],"sourcesContent":["/**\n * Athlete picker\n * @module atheletebridge/components\n *\n * Two coupled selects — pick a team, then pick an athlete from that team's live\n * roster (`athListMembers`). Emits the athlete's user id (used everywhere an\n * `athleteId` is required) plus a display name, so a coach never has to paste a\n * raw UUID. Semantic tokens only.\n */\n\nimport { useMemo, useState } from 'react';\nimport { FormField, fieldInputClass } from './form';\nimport { useTeams, useTeamMembers } from '../hooks/useAthleteBridgeApi';\n\nexport interface AthleteSelectValue {\n athleteId: string;\n displayName: string;\n}\n\ninterface AthleteSelectProps {\n /** Currently selected athlete user id. */\n athleteId: string;\n onSelect: (value: AthleteSelectValue) => void;\n required?: boolean;\n teamLabel?: string;\n athleteLabel?: string;\n /** Prefix for the field ids so multiple instances stay unique/accessible. */\n idPrefix?: string;\n}\n\nexport function AthleteSelect({\n athleteId,\n onSelect,\n required = false,\n teamLabel = 'Team',\n athleteLabel = 'Athlete',\n idPrefix = 'athlete',\n}: AthleteSelectProps) {\n const [teamId, setTeamId] = useState('');\n\n const teams = useTeams({ pagination: { limit: 100 } });\n const members = useTeamMembers(teamId || undefined);\n\n const teamOptions = useMemo(() => teams.data?.items ?? [], [teams.data]);\n const memberOptions = useMemo(\n () => (members.data ?? []).filter((m) => Boolean(m.userId)),\n [members.data]\n );\n\n const teamFieldId = `${idPrefix}-team`;\n const athleteFieldId = `${idPrefix}-athlete`;\n\n const memberHint = teamId\n ? members.isLoading\n ? 'Loading roster…'\n : memberOptions.length === 0\n ? 'This team has no members yet — add them from the team page.'\n : undefined\n : 'Choose a team to load its roster.';\n\n return (\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label={teamLabel} htmlFor={teamFieldId} required={required}>\n <select\n id={teamFieldId}\n className={fieldInputClass}\n value={teamId}\n onChange={(e) => {\n setTeamId(e.target.value);\n // Clear any prior athlete selection when the team changes.\n onSelect({ athleteId: '', displayName: '' });\n }}\n >\n <option value=\"\">Select a team…</option>\n {teamOptions.map((team) => (\n <option key={team.id} value={team.id}>\n {team.name}\n </option>\n ))}\n </select>\n </FormField>\n\n <FormField\n label={athleteLabel}\n htmlFor={athleteFieldId}\n required={required}\n hint={memberHint}\n >\n <select\n id={athleteFieldId}\n className={fieldInputClass}\n value={athleteId}\n disabled={!teamId || memberOptions.length === 0}\n onChange={(e) => {\n const picked = memberOptions.find((m) => m.userId === e.target.value);\n onSelect(\n picked\n ? { athleteId: picked.userId, displayName: picked.displayName }\n : { athleteId: '', displayName: '' }\n );\n }}\n >\n <option value=\"\">Select an athlete…</option>\n {memberOptions.map((member) => (\n <option key={member.id} value={member.userId}>\n {member.displayName}\n {member.jerseyNumber != null ? ` · #${member.jerseyNumber}` : ''}\n </option>\n ))}\n </select>\n </FormField>\n </div>\n );\n}\n"],"mappings":";;;;;AA8BA,SAAgB,EAAc,EAC5B,cACA,aACA,cAAW,IACX,eAAY,QACZ,kBAAe,WACf,cAAW,aACU;CACrB,IAAM,CAAC,GAAQ,KAAa,EAAS,
|
|
1
|
+
{"version":3,"file":"AthleteSelect.js","names":[],"sources":["../../../src/atheletebridge/components/AthleteSelect.tsx"],"sourcesContent":["/**\n * Athlete picker\n * @module atheletebridge/components\n *\n * Two coupled selects — pick a team, then pick an athlete from that team's live\n * roster (`athListMembers`). Emits the athlete's user id (used everywhere an\n * `athleteId` is required) plus a display name, so a coach never has to paste a\n * raw UUID. Semantic tokens only.\n */\n\nimport { useMemo, useState } from 'react';\nimport { FormField, fieldInputClass } from './form';\nimport { useTeams, useTeamMembers } from '../hooks/useAthleteBridgeApi';\n\nexport interface AthleteSelectValue {\n athleteId: string;\n displayName: string;\n}\n\ninterface AthleteSelectProps {\n /** Currently selected athlete user id. */\n athleteId: string;\n onSelect: (value: AthleteSelectValue) => void;\n required?: boolean;\n teamLabel?: string;\n athleteLabel?: string;\n /** Prefix for the field ids so multiple instances stay unique/accessible. */\n idPrefix?: string;\n}\n\nexport function AthleteSelect({\n athleteId,\n onSelect,\n required = false,\n teamLabel = 'Team',\n athleteLabel = 'Athlete',\n idPrefix = 'athlete',\n}: AthleteSelectProps) {\n const [teamId, setTeamId] = useState('');\n\n const teams = useTeams({ pagination: { limit: 100 } });\n const members = useTeamMembers(teamId || undefined);\n\n const teamOptions = useMemo(() => teams.data?.items ?? [], [teams.data]);\n const memberOptions = useMemo(\n () => (members.data ?? []).filter((m) => Boolean(m.userId)),\n [members.data]\n );\n\n const teamFieldId = `${idPrefix}-team`;\n const athleteFieldId = `${idPrefix}-athlete`;\n\n const memberHint = teamId\n ? members.isLoading\n ? 'Loading roster…'\n : memberOptions.length === 0\n ? 'This team has no members yet — add them from the team page.'\n : undefined\n : 'Choose a team to load its roster.';\n\n return (\n <div className=\"grid grid-cols-1 gap-5 sm:grid-cols-2\">\n <FormField label={teamLabel} htmlFor={teamFieldId} required={required}>\n <select\n id={teamFieldId}\n className={fieldInputClass}\n value={teamId}\n onChange={(e) => {\n setTeamId(e.target.value);\n // Clear any prior athlete selection when the team changes.\n onSelect({ athleteId: '', displayName: '' });\n }}\n >\n <option value=\"\">Select a team…</option>\n {teamOptions.map((team) => (\n <option key={team.id} value={team.id}>\n {team.name}\n </option>\n ))}\n </select>\n </FormField>\n\n <FormField\n label={athleteLabel}\n htmlFor={athleteFieldId}\n required={required}\n hint={memberHint}\n >\n <select\n id={athleteFieldId}\n className={fieldInputClass}\n value={athleteId}\n disabled={!teamId || memberOptions.length === 0}\n onChange={(e) => {\n const picked = memberOptions.find((m) => m.userId === e.target.value);\n onSelect(\n picked\n ? { athleteId: picked.userId, displayName: picked.displayName }\n : { athleteId: '', displayName: '' }\n );\n }}\n >\n <option value=\"\">Select an athlete…</option>\n {memberOptions.map((member) => (\n <option key={member.id} value={member.userId}>\n {member.displayName}\n {member.jerseyNumber != null ? ` · #${member.jerseyNumber}` : ''}\n </option>\n ))}\n </select>\n </FormField>\n </div>\n );\n}\n"],"mappings":";;;;;AA8BA,SAAgB,EAAc,EAC5B,cACA,aACA,cAAW,IACX,eAAY,QACZ,kBAAe,WACf,cAAW,aACU;CACrB,IAAM,CAAC,GAAQ,KAAa,EAAS,EAAE,GAEjC,IAAQ,EAAS,EAAE,YAAY,EAAE,OAAO,IAAI,EAAE,CAAC,GAC/C,IAAU,EAAe,KAAU,KAAA,CAAS,GAE5C,IAAc,QAAc,EAAM,MAAM,SAAS,CAAC,GAAG,CAAC,EAAM,IAAI,CAAC,GACjE,IAAgB,SACb,EAAQ,QAAQ,CAAC,GAAG,QAAQ,MAAM,EAAQ,EAAE,MAAO,GAC1D,CAAC,EAAQ,IAAI,CACf,GAEM,IAAc,GAAG,EAAS,QAC1B,IAAiB,GAAG,EAAS,WAE7B,IAAa,IACf,EAAQ,YACN,oBACA,EAAc,WAAW,IACvB,gEACA,KAAA,IACJ;CAEJ,OACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD;GAAW,OAAO;GAAW,SAAS;GAAuB;aAC3D,kBAAC,UAAD;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,WAAW,MAAM;KAGf,AAFA,EAAU,EAAE,OAAO,KAAK,GAExB,EAAS;MAAE,WAAW;MAAI,aAAa;KAAG,CAAC;IAC7C;cARF,CAUE,kBAAC,UAAD;KAAQ,OAAM;eAAG;IAAsB,CAAA,GACtC,EAAY,KAAK,MAChB,kBAAC,UAAD;KAAsB,OAAO,EAAK;eAC/B,EAAK;IACA,GAFK,EAAK,EAEV,CACT,CACK;;EACC,CAAA,GAEX,kBAAC,GAAD;GACE,OAAO;GACP,SAAS;GACC;GACV,MAAM;aAEN,kBAAC,UAAD;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,UAAU,CAAC,KAAU,EAAc,WAAW;IAC9C,WAAW,MAAM;KACf,IAAM,IAAS,EAAc,MAAM,MAAM,EAAE,WAAW,EAAE,OAAO,KAAK;KACpE,EACE,IACI;MAAE,WAAW,EAAO;MAAQ,aAAa,EAAO;KAAY,IAC5D;MAAE,WAAW;MAAI,aAAa;KAAG,CACvC;IACF;cAZF,CAcE,kBAAC,UAAD;KAAQ,OAAM;eAAG;IAA0B,CAAA,GAC1C,EAAc,KAAK,MAClB,kBAAC,UAAD;KAAwB,OAAO,EAAO;eAAtC,CACG,EAAO,aACP,EAAO,gBAAgB,OAAsC,KAA/B,OAAO,EAAO,cACvC;OAHK,EAAO,EAGZ,CACT,CACK;;EACC,CAAA,CACR;;AAET"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Crest.js","names":[],"sources":["../../../src/atheletebridge/components/Crest.tsx"],"sourcesContent":["/**\n * Team crest\n * @module atheletebridge/components\n *\n * A deterministic gradient MONOGRAM crest — an honest stand-in for a club logo\n * (the schema has no logo/image field). The gradient + initials are derived\n * stably from the team name, and an optional faint sport glyph reinforces the\n * discipline. Semantic tokens only.\n */\n\nimport { type FC, useId } from 'react';\nimport { cn } from '../utils/cn';\nimport { gradientForSeed, initials } from '../utils/identity';\nimport { SPORT_ICONS } from './domain';\nimport type { AthSport } from '../types';\n\nexport interface CrestProps {\n /** Team name — seeds the gradient and provides the monogram initials. */\n name: string;\n /** Sport — renders a faint watermark glyph and (fallback) seeds the gradient. */\n sport?: AthSport;\n /** Pixel size of the crest (width == height of the shield's bounding box). */\n size?: number;\n /** Show the faint sport glyph watermark (default true when `sport` is set). */\n showSportGlyph?: boolean;\n className?: string;\n}\n\n// Classic shield silhouette in a 40×44 viewBox with a softly-pointed base.\nconst SHIELD_PATH = 'M20 1.5 L37 7.5 L37 22 C37 34 29 40.5 20 42.5 C11 40.5 3 34 3 22 L3 7.5 Z';\n\n/** Deterministic gradient monogram crest for a team (honest logo stand-in). */\nexport const Crest: FC<CrestProps> = ({\n name,\n sport,\n size = 44,\n showSportGlyph = true,\n className,\n}) => {\n const seed = name || sport || 'team';\n const [from, to] = gradientForSeed(seed);\n const mono = initials(name, 3);\n const gradId = `crest-grad-${useId().replace(/:/g, '')}`;\n const SportIcon = sport ? SPORT_ICONS[sport] : undefined;\n\n return (\n <span\n className={cn('relative inline-flex shrink-0 items-center justify-center', className)}\n style={{ width: size, height: (size * 44) / 40 }}\n role=\"img\"\n aria-label={`${name} crest`}\n >\n <svg\n viewBox=\"0 0 40 44\"\n width={size}\n height={(size * 44) / 40}\n aria-hidden=\"true\"\n style={{ display: 'block' }}\n >\n <defs>\n <linearGradient id={gradId} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\">\n <stop offset=\"0%\" style={{ stopColor: from }} />\n <stop offset=\"100%\" style={{ stopColor: to }} />\n </linearGradient>\n </defs>\n <path\n d={SHIELD_PATH}\n fill={`url(#${gradId})`}\n stroke=\"var(--color-action-primary-text)\"\n strokeOpacity={0.22}\n strokeWidth={1}\n />\n </svg>\n\n {SportIcon && showSportGlyph ? (\n <SportIcon\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute\"\n style={{\n width: size * 0.9,\n height: size * 0.9,\n color: 'var(--color-action-primary-text)',\n opacity: 0.12,\n }}\n />\n ) : null}\n\n <span\n aria-hidden=\"true\"\n className=\"absolute font-bold leading-none tracking-tight text-[var(--color-action-primary-text)]\"\n style={{ fontSize: Math.max(10, size * 0.34), letterSpacing: '-0.02em' }}\n >\n {mono}\n </span>\n </span>\n );\n};\n"],"mappings":";;;;;;AA6BA,IAAM,IAAc,6EAGP,KAAyB,EACpC,SACA,UACA,UAAO,IACP,oBAAiB,IACjB,mBACI;CAEJ,IAAM,CAAC,GAAM,KAAM,EADN,KAAQ,KAAS,
|
|
1
|
+
{"version":3,"file":"Crest.js","names":[],"sources":["../../../src/atheletebridge/components/Crest.tsx"],"sourcesContent":["/**\n * Team crest\n * @module atheletebridge/components\n *\n * A deterministic gradient MONOGRAM crest — an honest stand-in for a club logo\n * (the schema has no logo/image field). The gradient + initials are derived\n * stably from the team name, and an optional faint sport glyph reinforces the\n * discipline. Semantic tokens only.\n */\n\nimport { type FC, useId } from 'react';\nimport { cn } from '../utils/cn';\nimport { gradientForSeed, initials } from '../utils/identity';\nimport { SPORT_ICONS } from './domain';\nimport type { AthSport } from '../types';\n\nexport interface CrestProps {\n /** Team name — seeds the gradient and provides the monogram initials. */\n name: string;\n /** Sport — renders a faint watermark glyph and (fallback) seeds the gradient. */\n sport?: AthSport;\n /** Pixel size of the crest (width == height of the shield's bounding box). */\n size?: number;\n /** Show the faint sport glyph watermark (default true when `sport` is set). */\n showSportGlyph?: boolean;\n className?: string;\n}\n\n// Classic shield silhouette in a 40×44 viewBox with a softly-pointed base.\nconst SHIELD_PATH = 'M20 1.5 L37 7.5 L37 22 C37 34 29 40.5 20 42.5 C11 40.5 3 34 3 22 L3 7.5 Z';\n\n/** Deterministic gradient monogram crest for a team (honest logo stand-in). */\nexport const Crest: FC<CrestProps> = ({\n name,\n sport,\n size = 44,\n showSportGlyph = true,\n className,\n}) => {\n const seed = name || sport || 'team';\n const [from, to] = gradientForSeed(seed);\n const mono = initials(name, 3);\n const gradId = `crest-grad-${useId().replace(/:/g, '')}`;\n const SportIcon = sport ? SPORT_ICONS[sport] : undefined;\n\n return (\n <span\n className={cn('relative inline-flex shrink-0 items-center justify-center', className)}\n style={{ width: size, height: (size * 44) / 40 }}\n role=\"img\"\n aria-label={`${name} crest`}\n >\n <svg\n viewBox=\"0 0 40 44\"\n width={size}\n height={(size * 44) / 40}\n aria-hidden=\"true\"\n style={{ display: 'block' }}\n >\n <defs>\n <linearGradient id={gradId} x1=\"0\" y1=\"0\" x2=\"1\" y2=\"1\">\n <stop offset=\"0%\" style={{ stopColor: from }} />\n <stop offset=\"100%\" style={{ stopColor: to }} />\n </linearGradient>\n </defs>\n <path\n d={SHIELD_PATH}\n fill={`url(#${gradId})`}\n stroke=\"var(--color-action-primary-text)\"\n strokeOpacity={0.22}\n strokeWidth={1}\n />\n </svg>\n\n {SportIcon && showSportGlyph ? (\n <SportIcon\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute\"\n style={{\n width: size * 0.9,\n height: size * 0.9,\n color: 'var(--color-action-primary-text)',\n opacity: 0.12,\n }}\n />\n ) : null}\n\n <span\n aria-hidden=\"true\"\n className=\"absolute font-bold leading-none tracking-tight text-[var(--color-action-primary-text)]\"\n style={{ fontSize: Math.max(10, size * 0.34), letterSpacing: '-0.02em' }}\n >\n {mono}\n </span>\n </span>\n );\n};\n"],"mappings":";;;;;;AA6BA,IAAM,IAAc,6EAGP,KAAyB,EACpC,SACA,UACA,UAAO,IACP,oBAAiB,IACjB,mBACI;CAEJ,IAAM,CAAC,GAAM,KAAM,EADN,KAAQ,KAAS,MACS,GACjC,IAAO,EAAS,GAAM,CAAC,GACvB,IAAS,cAAc,EAAM,EAAE,QAAQ,MAAM,EAAE,KAC/C,IAAY,IAAQ,EAAY,KAAS,KAAA;CAE/C,OACE,kBAAC,QAAD;EACE,WAAW,EAAG,6DAA6D,CAAS;EACpF,OAAO;GAAE,OAAO;GAAM,QAAS,IAAO,KAAM;EAAG;EAC/C,MAAK;EACL,cAAY,GAAG,EAAK;YAJtB;GAME,kBAAC,OAAD;IACE,SAAQ;IACR,OAAO;IACP,QAAS,IAAO,KAAM;IACtB,eAAY;IACZ,OAAO,EAAE,SAAS,QAAQ;cAL5B,CAOE,kBAAC,QAAD,EAAA,UACE,kBAAC,kBAAD;KAAgB,IAAI;KAAQ,IAAG;KAAI,IAAG;KAAI,IAAG;KAAI,IAAG;eAApD,CACE,kBAAC,QAAD;MAAM,QAAO;MAAK,OAAO,EAAE,WAAW,EAAK;KAAI,CAAA,GAC/C,kBAAC,QAAD;MAAM,QAAO;MAAO,OAAO,EAAE,WAAW,EAAG;KAAI,CAAA,CACjC;OACZ,CAAA,GACN,kBAAC,QAAD;KACE,GAAG;KACH,MAAM,QAAQ,EAAO;KACrB,QAAO;KACP,eAAe;KACf,aAAa;IACd,CAAA,CACE;;GAEJ,KAAa,IACZ,kBAAC,GAAD;IACE,eAAY;IACZ,WAAU;IACV,OAAO;KACL,OAAO,IAAO;KACd,QAAQ,IAAO;KACf,OAAO;KACP,SAAS;IACX;GACD,CAAA,IACC;GAEJ,kBAAC,QAAD;IACE,eAAY;IACZ,WAAU;IACV,OAAO;KAAE,UAAU,KAAK,IAAI,IAAI,IAAO,GAAI;KAAG,eAAe;IAAU;cAEtE;GACG,CAAA;EACF;;AAEV"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CursorPager.js","names":[],"sources":["../../../src/atheletebridge/components/CursorPager.tsx"],"sourcesContent":["/**\n * Cursor Pager\n * @module atheletebridge/components\n *\n * Offset-based pager driving the svc PaginationInput. The svc connections\n * expose `pageInfo.hasMore`; combined with the page index this yields a calm\n * prev/next control. Tokens only, no movement.\n */\n\nimport type { FC } from 'react';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\nimport { cn } from '../utils/cn';\n\ninterface CursorPagerProps {\n page: number;\n hasMore: boolean;\n onPrev: () => void;\n onNext: () => void;\n className?: string;\n}\n\nexport const CursorPager: FC<CursorPagerProps> = ({ page, hasMore, onPrev, onNext, className }) => {\n const canPrev = page > 0;\n\n if (!canPrev && !hasMore) return null;\n\n return (\n <div className={cn('flex items-center justify-end gap-2', className)}>\n <button\n type=\"button\"\n onClick={onPrev}\n disabled={!canPrev}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <ChevronLeft className=\"h-4 w-4\" /> Previous\n </button>\n <span className=\"px-2 text-sm tabular-nums text-text-secondary\">Page {page + 1}</span>\n <button\n type=\"button\"\n onClick={onNext}\n disabled={!hasMore}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-50\"\n >\n Next <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n );\n};\n"],"mappings":";;;;AAqBA,IAAa,KAAqC,EAAE,SAAM,YAAS,WAAQ,WAAQ,mBAAgB;CACjG,IAAM,IAAU,IAAO;
|
|
1
|
+
{"version":3,"file":"CursorPager.js","names":[],"sources":["../../../src/atheletebridge/components/CursorPager.tsx"],"sourcesContent":["/**\n * Cursor Pager\n * @module atheletebridge/components\n *\n * Offset-based pager driving the svc PaginationInput. The svc connections\n * expose `pageInfo.hasMore`; combined with the page index this yields a calm\n * prev/next control. Tokens only, no movement.\n */\n\nimport type { FC } from 'react';\nimport { ChevronLeft, ChevronRight } from 'lucide-react';\nimport { cn } from '../utils/cn';\n\ninterface CursorPagerProps {\n page: number;\n hasMore: boolean;\n onPrev: () => void;\n onNext: () => void;\n className?: string;\n}\n\nexport const CursorPager: FC<CursorPagerProps> = ({ page, hasMore, onPrev, onNext, className }) => {\n const canPrev = page > 0;\n\n if (!canPrev && !hasMore) return null;\n\n return (\n <div className={cn('flex items-center justify-end gap-2', className)}>\n <button\n type=\"button\"\n onClick={onPrev}\n disabled={!canPrev}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <ChevronLeft className=\"h-4 w-4\" /> Previous\n </button>\n <span className=\"px-2 text-sm tabular-nums text-text-secondary\">Page {page + 1}</span>\n <button\n type=\"button\"\n onClick={onNext}\n disabled={!hasMore}\n className=\"inline-flex items-center gap-1 rounded-lg border border-border-seam px-3 py-1.5 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-50\"\n >\n Next <ChevronRight className=\"h-4 w-4\" />\n </button>\n </div>\n );\n};\n"],"mappings":";;;;AAqBA,IAAa,KAAqC,EAAE,SAAM,YAAS,WAAQ,WAAQ,mBAAgB;CACjG,IAAM,IAAU,IAAO;CAIvB,OAFI,CAAC,KAAW,CAAC,IAAgB,OAG/B,kBAAC,OAAD;EAAK,WAAW,EAAG,uCAAuC,CAAS;YAAnE;GACE,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACT,UAAU,CAAC;IACX,WAAU;cAJZ,CAME,kBAAC,GAAD,EAAa,WAAU,UAAW,CAAA,GAAC,WAC7B;;GACR,kBAAC,QAAD;IAAM,WAAU;cAAhB,CAAgE,SAAM,IAAO,CAAQ;;GACrF,kBAAC,UAAD;IACE,MAAK;IACL,SAAS;IACT,UAAU,CAAC;IACX,WAAU;cAJZ,CAKC,SACM,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA,CAClC;;EACL;;AAET"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DrillVideoLink.js","names":[],"sources":["../../../src/atheletebridge/components/DrillVideoLink.tsx"],"sourcesContent":["/**\n * Drill demo-video reference\n * @module atheletebridge/components\n *\n * A drill's `video` is a reference into wspace-files-svc (blobs live there). We\n * resolve the playable URL lazily and in isolation, so a missing/expired blob\n * degrades to a quiet badge instead of ever breaking the program page.\n */\n\nimport { ExternalLink, FileVideo, Loader2 } from 'lucide-react';\nimport { useFile } from '../hooks/useAthleteBridgeApi';\n\nexport function DrillVideoLink({ fileId }: { fileId: string }) {\n const file = useFile(fileId);\n const url = file.data?.downloadUrl ?? undefined;\n const label = file.data?.name ?? 'Demo video';\n\n if (url) {\n return (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"mt-1.5 inline-flex items-center gap-1 text-xs font-medium text-text-link hover:underline\"\n >\n <FileVideo className=\"h-3.5 w-3.5\" />\n <span className=\"max-w-[16rem] truncate\">{label}</span>\n <ExternalLink className=\"h-3 w-3\" />\n </a>\n );\n }\n\n return (\n <span className=\"mt-1.5 inline-flex items-center gap-1 text-xs text-text-secondary\">\n {file.isLoading ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <FileVideo className=\"h-3.5 w-3.5\" />\n )}\n Demo video\n </span>\n );\n}\n"],"mappings":";;;;AAYA,SAAgB,EAAe,EAAE,aAA8B;CAC7D,IAAM,IAAO,EAAQ,
|
|
1
|
+
{"version":3,"file":"DrillVideoLink.js","names":[],"sources":["../../../src/atheletebridge/components/DrillVideoLink.tsx"],"sourcesContent":["/**\n * Drill demo-video reference\n * @module atheletebridge/components\n *\n * A drill's `video` is a reference into wspace-files-svc (blobs live there). We\n * resolve the playable URL lazily and in isolation, so a missing/expired blob\n * degrades to a quiet badge instead of ever breaking the program page.\n */\n\nimport { ExternalLink, FileVideo, Loader2 } from 'lucide-react';\nimport { useFile } from '../hooks/useAthleteBridgeApi';\n\nexport function DrillVideoLink({ fileId }: { fileId: string }) {\n const file = useFile(fileId);\n const url = file.data?.downloadUrl ?? undefined;\n const label = file.data?.name ?? 'Demo video';\n\n if (url) {\n return (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"mt-1.5 inline-flex items-center gap-1 text-xs font-medium text-text-link hover:underline\"\n >\n <FileVideo className=\"h-3.5 w-3.5\" />\n <span className=\"max-w-[16rem] truncate\">{label}</span>\n <ExternalLink className=\"h-3 w-3\" />\n </a>\n );\n }\n\n return (\n <span className=\"mt-1.5 inline-flex items-center gap-1 text-xs text-text-secondary\">\n {file.isLoading ? (\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n ) : (\n <FileVideo className=\"h-3.5 w-3.5\" />\n )}\n Demo video\n </span>\n );\n}\n"],"mappings":";;;;AAYA,SAAgB,EAAe,EAAE,aAA8B;CAC7D,IAAM,IAAO,EAAQ,CAAM,GACrB,IAAM,EAAK,MAAM,eAAe,KAAA,GAChC,IAAQ,EAAK,MAAM,QAAQ;CAiBjC,OAfI,IAEA,kBAAC,KAAD;EACE,MAAM;EACN,QAAO;EACP,KAAI;EACJ,WAAU;YAJZ;GAME,kBAAC,GAAD,EAAW,WAAU,cAAe,CAAA;GACpC,kBAAC,QAAD;IAAM,WAAU;cAA0B;GAAY,CAAA;GACtD,kBAAC,GAAD,EAAc,WAAU,UAAW,CAAA;EAClC;MAKL,kBAAC,QAAD;EAAM,WAAU;YAAhB,CACG,EAAK,YACJ,kBAAC,GAAD,EAAS,WAAU,2BAA4B,CAAA,IAE/C,kBAAC,GAAD,EAAW,WAAU,cAAe,CAAA,GACpC,YAEE;;AAEV"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmptyState.js","names":[],"sources":["../../../src/atheletebridge/components/EmptyState.tsx"],"sourcesContent":["/**\n * Empty State Component\n * @module atheletebridge/components\n *\n * Boff UI 2.0: when an `illustration` name is provided, renders the on-brand\n * `IllustratedEmptyState` (host serves /illustrations/<name>.webp). Falls back\n * to the calm icon-based layout for callers that pass an `icon`.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\ninterface EmptyStateProps {\n icon?: ReactNode;\n /** On-brand illustration name → /illustrations/<name>.webp (preferred). */\n illustration?: string;\n title: string;\n description?: string;\n action?: ReactNode;\n className?: string;\n}\n\nexport const EmptyState: FC<EmptyStateProps> = ({\n icon,\n illustration,\n title,\n description,\n action,\n className,\n}) => {\n if (illustration) {\n return (\n <IllustratedEmptyState\n illustration={illustration}\n title={title}\n description={description}\n action={action}\n className={className}\n />\n );\n }\n\n return (\n <div\n className={cn('flex flex-col items-center justify-center py-12 px-4 text-center', className)}\n >\n {icon && <div className=\"mb-4 text-text-secondary opacity-50\">{icon}</div>}\n <h3 className=\"text-lg font-semibold tracking-tight text-text-primary\">{title}</h3>\n {description && <p className=\"mt-2 text-sm text-text-secondary max-w-sm\">{description}</p>}\n {action && <div className=\"mt-6\">{action}</div>}\n </div>\n );\n};\n"],"mappings":";;;;AAuBA,IAAa,KAAmC,EAC9C,SACA,iBACA,UACA,gBACA,WACA,mBAEI,IAEA,kBAAC,GAAD;CACgB;CACP;CACM;CACL;CACG;
|
|
1
|
+
{"version":3,"file":"EmptyState.js","names":[],"sources":["../../../src/atheletebridge/components/EmptyState.tsx"],"sourcesContent":["/**\n * Empty State Component\n * @module atheletebridge/components\n *\n * Boff UI 2.0: when an `illustration` name is provided, renders the on-brand\n * `IllustratedEmptyState` (host serves /illustrations/<name>.webp). Falls back\n * to the calm icon-based layout for callers that pass an `icon`.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { IllustratedEmptyState } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\ninterface EmptyStateProps {\n icon?: ReactNode;\n /** On-brand illustration name → /illustrations/<name>.webp (preferred). */\n illustration?: string;\n title: string;\n description?: string;\n action?: ReactNode;\n className?: string;\n}\n\nexport const EmptyState: FC<EmptyStateProps> = ({\n icon,\n illustration,\n title,\n description,\n action,\n className,\n}) => {\n if (illustration) {\n return (\n <IllustratedEmptyState\n illustration={illustration}\n title={title}\n description={description}\n action={action}\n className={className}\n />\n );\n }\n\n return (\n <div\n className={cn('flex flex-col items-center justify-center py-12 px-4 text-center', className)}\n >\n {icon && <div className=\"mb-4 text-text-secondary opacity-50\">{icon}</div>}\n <h3 className=\"text-lg font-semibold tracking-tight text-text-primary\">{title}</h3>\n {description && <p className=\"mt-2 text-sm text-text-secondary max-w-sm\">{description}</p>}\n {action && <div className=\"mt-6\">{action}</div>}\n </div>\n );\n};\n"],"mappings":";;;;AAuBA,IAAa,KAAmC,EAC9C,SACA,iBACA,UACA,gBACA,WACA,mBAEI,IAEA,kBAAC,GAAD;CACgB;CACP;CACM;CACL;CACG;AACZ,CAAA,IAKH,kBAAC,OAAD;CACE,WAAW,EAAG,oEAAoE,CAAS;WAD7F;EAGG,KAAQ,kBAAC,OAAD;GAAK,WAAU;aAAuC;EAAU,CAAA;EACzE,kBAAC,MAAD;GAAI,WAAU;aAA0D;EAAU,CAAA;EACjF,KAAe,kBAAC,KAAD;GAAG,WAAU;aAA6C;EAAe,CAAA;EACxF,KAAU,kBAAC,OAAD;GAAK,WAAU;aAAQ;EAAY,CAAA;CAC3C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FilterSelect.js","names":[],"sources":["../../../src/atheletebridge/components/FilterSelect.tsx"],"sourcesContent":["/**\n * Filter Select\n * @module atheletebridge/components\n *\n * A small, accessible native-select filter styled with semantic tokens.\n * Generic over the option value type so callers stay strictly typed.\n */\n\nimport { cn } from '../utils/cn';\n\nexport interface FilterOption<T extends string> {\n value: T;\n label: string;\n}\n\ninterface FilterSelectProps<T extends string> {\n value: T | '';\n onChange: (value: T | '') => void;\n options: ReadonlyArray<FilterOption<T>>;\n /** Label shown for the \"no filter\" option. */\n allLabel: string;\n ariaLabel: string;\n className?: string;\n}\n\nexport function FilterSelect<T extends string>({\n value,\n onChange,\n options,\n allLabel,\n ariaLabel,\n className,\n}: FilterSelectProps<T>) {\n return (\n <select\n aria-label={ariaLabel}\n value={value}\n onChange={(e) => onChange(e.target.value as T | '')}\n className={cn(\n 'rounded-lg border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary',\n 'focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]',\n className\n )}\n >\n <option value=\"\">{allLabel}</option>\n {options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n );\n}\n"],"mappings":";;;AAyBA,SAAgB,EAA+B,EAC7C,UACA,aACA,YACA,aACA,cACA,gBACuB;
|
|
1
|
+
{"version":3,"file":"FilterSelect.js","names":[],"sources":["../../../src/atheletebridge/components/FilterSelect.tsx"],"sourcesContent":["/**\n * Filter Select\n * @module atheletebridge/components\n *\n * A small, accessible native-select filter styled with semantic tokens.\n * Generic over the option value type so callers stay strictly typed.\n */\n\nimport { cn } from '../utils/cn';\n\nexport interface FilterOption<T extends string> {\n value: T;\n label: string;\n}\n\ninterface FilterSelectProps<T extends string> {\n value: T | '';\n onChange: (value: T | '') => void;\n options: ReadonlyArray<FilterOption<T>>;\n /** Label shown for the \"no filter\" option. */\n allLabel: string;\n ariaLabel: string;\n className?: string;\n}\n\nexport function FilterSelect<T extends string>({\n value,\n onChange,\n options,\n allLabel,\n ariaLabel,\n className,\n}: FilterSelectProps<T>) {\n return (\n <select\n aria-label={ariaLabel}\n value={value}\n onChange={(e) => onChange(e.target.value as T | '')}\n className={cn(\n 'rounded-lg border border-border-seam bg-bg-surface px-3 py-2 text-sm text-text-primary',\n 'focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]',\n className\n )}\n >\n <option value=\"\">{allLabel}</option>\n {options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n );\n}\n"],"mappings":";;;AAyBA,SAAgB,EAA+B,EAC7C,UACA,aACA,YACA,aACA,cACA,gBACuB;CACvB,OACE,kBAAC,UAAD;EACE,cAAY;EACL;EACP,WAAW,MAAM,EAAS,EAAE,OAAO,KAAe;EAClD,WAAW,EACT,0FACA,wEACA,CACF;YARF,CAUE,kBAAC,UAAD;GAAQ,OAAM;aAAI;EAAiB,CAAA,GAClC,EAAQ,KAAK,MACZ,kBAAC,UAAD;GAAwB,OAAO,EAAI;aAChC,EAAI;EACC,GAFK,EAAI,KAET,CACT,CACK;;AAEZ"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FixtureCard.js","names":[],"sources":["../../../src/atheletebridge/components/FixtureCard.tsx"],"sourcesContent":["/**\n * Fixture Card\n * @module atheletebridge/components\n *\n * A fixture tile rendered as a crest-vs-crest Scoreboard (competition strip →\n * home crest / score-or-vs / away crest → date + venue), replacing the former\n * plain card. Semantic tokens only; winner side tinted from the real score.\n */\n\nimport type { FC } from 'react';\nimport { Scoreboard } from './Scoreboard';\nimport type { AthFixture } from '../types';\n\ninterface FixtureCardProps {\n fixture: AthFixture;\n onClick?: () => void;\n}\n\nexport const FixtureCard: FC<FixtureCardProps> = ({ fixture, onClick }) => (\n <Scoreboard fixture={fixture} onClick={onClick} variant=\"card\" />\n);\n"],"mappings":";;;AAkBA,IAAa,KAAqC,EAAE,YAAS,iBAC3D,kBAAC,GAAD;CAAqB;CAAkB;CAAS,SAAQ;
|
|
1
|
+
{"version":3,"file":"FixtureCard.js","names":[],"sources":["../../../src/atheletebridge/components/FixtureCard.tsx"],"sourcesContent":["/**\n * Fixture Card\n * @module atheletebridge/components\n *\n * A fixture tile rendered as a crest-vs-crest Scoreboard (competition strip →\n * home crest / score-or-vs / away crest → date + venue), replacing the former\n * plain card. Semantic tokens only; winner side tinted from the real score.\n */\n\nimport type { FC } from 'react';\nimport { Scoreboard } from './Scoreboard';\nimport type { AthFixture } from '../types';\n\ninterface FixtureCardProps {\n fixture: AthFixture;\n onClick?: () => void;\n}\n\nexport const FixtureCard: FC<FixtureCardProps> = ({ fixture, onClick }) => (\n <Scoreboard fixture={fixture} onClick={onClick} variant=\"card\" />\n);\n"],"mappings":";;;AAkBA,IAAa,KAAqC,EAAE,YAAS,iBAC3D,kBAAC,GAAD;CAAqB;CAAkB;CAAS,SAAQ;AAAQ,CAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LoadingState.js","names":[],"sources":["../../../src/atheletebridge/components/LoadingState.tsx"],"sourcesContent":["/**\n * Loading / Error State Components\n * @module atheletebridge/components\n *\n * Small calm, centered states shared across list and detail pages.\n */\n\nimport type { FC } from 'react';\nimport { AlertCircle, Loader2 } from 'lucide-react';\nimport { cn } from '../utils/cn';\n\ninterface LoadingStateProps {\n label?: string;\n className?: string;\n}\n\nexport const LoadingState: FC<LoadingStateProps> = ({ label = 'Loading…', className }) => (\n <div\n className={cn('flex min-h-[40vh] flex-col items-center justify-center gap-3', className)}\n role=\"status\"\n aria-live=\"polite\"\n >\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n <p className=\"text-sm text-text-secondary\">{label}</p>\n </div>\n);\n\ninterface ErrorStateProps {\n title?: string;\n message?: string;\n onRetry?: () => void;\n className?: string;\n}\n\nexport const ErrorState: FC<ErrorStateProps> = ({\n title = 'Something went wrong',\n message = 'We could not load this data. Please try again.',\n onRetry,\n className,\n}) => (\n <div\n className={cn(\n 'flex min-h-[40vh] flex-col items-center justify-center gap-3 text-center',\n className\n )}\n >\n <AlertCircle className=\"h-10 w-10 text-status-error-text\" />\n <h3 className=\"text-lg font-semibold text-text-primary\">{title}</h3>\n <p className=\"max-w-sm text-sm text-text-secondary\">{message}</p>\n {onRetry && (\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"mt-2 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 Try again\n </button>\n )}\n </div>\n);\n"],"mappings":";;;;AAgBA,IAAa,KAAuC,EAAE,WAAQ,YAAY,mBACxE,kBAAC,OAAD;CACE,WAAW,EAAG,gEAAgE,
|
|
1
|
+
{"version":3,"file":"LoadingState.js","names":[],"sources":["../../../src/atheletebridge/components/LoadingState.tsx"],"sourcesContent":["/**\n * Loading / Error State Components\n * @module atheletebridge/components\n *\n * Small calm, centered states shared across list and detail pages.\n */\n\nimport type { FC } from 'react';\nimport { AlertCircle, Loader2 } from 'lucide-react';\nimport { cn } from '../utils/cn';\n\ninterface LoadingStateProps {\n label?: string;\n className?: string;\n}\n\nexport const LoadingState: FC<LoadingStateProps> = ({ label = 'Loading…', className }) => (\n <div\n className={cn('flex min-h-[40vh] flex-col items-center justify-center gap-3', className)}\n role=\"status\"\n aria-live=\"polite\"\n >\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n <p className=\"text-sm text-text-secondary\">{label}</p>\n </div>\n);\n\ninterface ErrorStateProps {\n title?: string;\n message?: string;\n onRetry?: () => void;\n className?: string;\n}\n\nexport const ErrorState: FC<ErrorStateProps> = ({\n title = 'Something went wrong',\n message = 'We could not load this data. Please try again.',\n onRetry,\n className,\n}) => (\n <div\n className={cn(\n 'flex min-h-[40vh] flex-col items-center justify-center gap-3 text-center',\n className\n )}\n >\n <AlertCircle className=\"h-10 w-10 text-status-error-text\" />\n <h3 className=\"text-lg font-semibold text-text-primary\">{title}</h3>\n <p className=\"max-w-sm text-sm text-text-secondary\">{message}</p>\n {onRetry && (\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"mt-2 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 Try again\n </button>\n )}\n </div>\n);\n"],"mappings":";;;;AAgBA,IAAa,KAAuC,EAAE,WAAQ,YAAY,mBACxE,kBAAC,OAAD;CACE,WAAW,EAAG,gEAAgE,CAAS;CACvF,MAAK;CACL,aAAU;WAHZ,CAKE,kBAAC,GAAD,EAAS,WAAU,2CAA4C,CAAA,GAC/D,kBAAC,KAAD;EAAG,WAAU;YAA+B;CAAS,CAAA,CAClD;IAUM,KAAmC,EAC9C,WAAQ,wBACR,aAAU,kDACV,YACA,mBAEA,kBAAC,OAAD;CACE,WAAW,EACT,4EACA,CACF;WAJF;EAME,kBAAC,GAAD,EAAa,WAAU,mCAAoC,CAAA;EAC3D,kBAAC,MAAD;GAAI,WAAU;aAA2C;EAAU,CAAA;EACnE,kBAAC,KAAD;GAAG,WAAU;aAAwC;EAAW,CAAA;EAC/D,KACC,kBAAC,UAAD;GACE,MAAK;GACL,SAAS;GACT,WAAU;aACX;EAEO,CAAA;CAEP"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MatchupRow.js","names":[],"sources":["../../../src/atheletebridge/components/MatchupRow.tsx"],"sourcesContent":["/**\n * MatchupRow\n * @module atheletebridge/components\n *\n * A compact single-line crest-vs-crest fixture row for the dashboard \"Upcoming\n * fixtures\" feed and team-detail fixture lists. Reuses the Scoreboard building\n * blocks (crest sides + score/vs centerpiece). Semantic tokens only.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { CalendarDays, ChevronRight, Trophy } from 'lucide-react';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { MatchupSide, ScoreCenter, resolveMatchup } from './Scoreboard';\nimport { StatusBadge } from './StatusBadge';\nimport { formatDate } from '../utils/formatters';\nimport { cn } from '../utils/cn';\nimport type { AthFixture } from '../types';\n\nexport interface MatchupRowProps {\n fixture: AthFixture;\n onClick?: () => void;\n /**\n * Right-hand rail content. Defaults to date + competition + status. Pass\n * `false` to hide the rail entirely, or a node to override it.\n */\n rail?: ReactNode | false;\n className?: string;\n}\n\nconst DefaultRail: FC<{ fixture: AthFixture }> = ({ fixture }) => (\n <div className=\"flex flex-col items-end gap-1 text-right\">\n <span className=\"flex items-center gap-1 text-xs text-text-secondary\">\n <CalendarDays className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />\n {formatDate(fixture.scheduledAt)}\n </span>\n <div className=\"flex items-center gap-1.5\">\n {fixture.competition ? (\n <Badge variant=\"secondary\" className=\"gap-1\">\n {fixture.type === 'CUP' || fixture.type === 'TOURNAMENT' ? (\n <Trophy className=\"h-3 w-3\" aria-hidden=\"true\" />\n ) : null}\n {fixture.competition}\n </Badge>\n ) : null}\n <StatusBadge status={fixture.status} />\n </div>\n </div>\n);\n\n/** Compact crest-vs-crest fixture row (dashboard / team-detail feeds). */\nexport const MatchupRow: FC<MatchupRowProps> = ({ fixture, onClick, rail, className }) => {\n const matchup = resolveMatchup(fixture);\n const clickable = Boolean(onClick);\n\n const railNode =\n rail === false ? null : rail !== undefined ? rail : <DefaultRail fixture={fixture} />;\n\n // When there's no rail to carry the date, surface the kickoff under the \"vs\".\n const timeLabel = railNode == null && !matchup.hasScore ? formatDate(fixture.scheduledAt) : null;\n\n const content = (\n <>\n <div className=\"grid flex-1 grid-cols-[1fr_auto_1fr] items-center gap-2 sm:gap-3\">\n <MatchupSide\n name={matchup.homeName}\n sport={matchup.homeSport}\n align=\"end\"\n won={matchup.homeWon}\n dimmed={matchup.awayWon}\n crestSize={36}\n />\n <ScoreCenter matchup={matchup} time={timeLabel} size=\"sm\" />\n <MatchupSide\n name={matchup.awayName}\n sport={matchup.awaySport}\n align=\"start\"\n won={matchup.awayWon}\n dimmed={matchup.homeWon}\n crestSize={36}\n />\n </div>\n {railNode ? (\n <div className=\"hidden items-center gap-3 border-l border-border-seam pl-3 sm:flex md:pl-4\">\n {railNode}\n {clickable ? (\n <ChevronRight className=\"h-4 w-4 shrink-0 text-text-muted\" aria-hidden=\"true\" />\n ) : null}\n </div>\n ) : null}\n </>\n );\n\n if (clickable) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={cn(\n 'flex w-full items-center gap-3 rounded-[var(--radius-card)] px-1 py-3 text-left transition-colors hover:bg-bg-sunken focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] md:gap-4',\n className\n )}\n >\n {content}\n </button>\n );\n }\n\n return (\n <div className={cn('flex w-full items-center gap-3 py-3 md:gap-4', className)}>{content}</div>\n );\n};\n"],"mappings":";;;;;;;;AA6BA,IAAM,KAA4C,EAAE,iBAClD,kBAAC,OAAD;CAAK,WAAU;WAAf,CACE,kBAAC,QAAD;EAAM,WAAU;YAAhB,CACE,kBAAC,GAAD;GAAc,WAAU;GAAuB,eAAY;
|
|
1
|
+
{"version":3,"file":"MatchupRow.js","names":[],"sources":["../../../src/atheletebridge/components/MatchupRow.tsx"],"sourcesContent":["/**\n * MatchupRow\n * @module atheletebridge/components\n *\n * A compact single-line crest-vs-crest fixture row for the dashboard \"Upcoming\n * fixtures\" feed and team-detail fixture lists. Reuses the Scoreboard building\n * blocks (crest sides + score/vs centerpiece). Semantic tokens only.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { CalendarDays, ChevronRight, Trophy } from 'lucide-react';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { MatchupSide, ScoreCenter, resolveMatchup } from './Scoreboard';\nimport { StatusBadge } from './StatusBadge';\nimport { formatDate } from '../utils/formatters';\nimport { cn } from '../utils/cn';\nimport type { AthFixture } from '../types';\n\nexport interface MatchupRowProps {\n fixture: AthFixture;\n onClick?: () => void;\n /**\n * Right-hand rail content. Defaults to date + competition + status. Pass\n * `false` to hide the rail entirely, or a node to override it.\n */\n rail?: ReactNode | false;\n className?: string;\n}\n\nconst DefaultRail: FC<{ fixture: AthFixture }> = ({ fixture }) => (\n <div className=\"flex flex-col items-end gap-1 text-right\">\n <span className=\"flex items-center gap-1 text-xs text-text-secondary\">\n <CalendarDays className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />\n {formatDate(fixture.scheduledAt)}\n </span>\n <div className=\"flex items-center gap-1.5\">\n {fixture.competition ? (\n <Badge variant=\"secondary\" className=\"gap-1\">\n {fixture.type === 'CUP' || fixture.type === 'TOURNAMENT' ? (\n <Trophy className=\"h-3 w-3\" aria-hidden=\"true\" />\n ) : null}\n {fixture.competition}\n </Badge>\n ) : null}\n <StatusBadge status={fixture.status} />\n </div>\n </div>\n);\n\n/** Compact crest-vs-crest fixture row (dashboard / team-detail feeds). */\nexport const MatchupRow: FC<MatchupRowProps> = ({ fixture, onClick, rail, className }) => {\n const matchup = resolveMatchup(fixture);\n const clickable = Boolean(onClick);\n\n const railNode =\n rail === false ? null : rail !== undefined ? rail : <DefaultRail fixture={fixture} />;\n\n // When there's no rail to carry the date, surface the kickoff under the \"vs\".\n const timeLabel = railNode == null && !matchup.hasScore ? formatDate(fixture.scheduledAt) : null;\n\n const content = (\n <>\n <div className=\"grid flex-1 grid-cols-[1fr_auto_1fr] items-center gap-2 sm:gap-3\">\n <MatchupSide\n name={matchup.homeName}\n sport={matchup.homeSport}\n align=\"end\"\n won={matchup.homeWon}\n dimmed={matchup.awayWon}\n crestSize={36}\n />\n <ScoreCenter matchup={matchup} time={timeLabel} size=\"sm\" />\n <MatchupSide\n name={matchup.awayName}\n sport={matchup.awaySport}\n align=\"start\"\n won={matchup.awayWon}\n dimmed={matchup.homeWon}\n crestSize={36}\n />\n </div>\n {railNode ? (\n <div className=\"hidden items-center gap-3 border-l border-border-seam pl-3 sm:flex md:pl-4\">\n {railNode}\n {clickable ? (\n <ChevronRight className=\"h-4 w-4 shrink-0 text-text-muted\" aria-hidden=\"true\" />\n ) : null}\n </div>\n ) : null}\n </>\n );\n\n if (clickable) {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n className={cn(\n 'flex w-full items-center gap-3 rounded-[var(--radius-card)] px-1 py-3 text-left transition-colors hover:bg-bg-sunken focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] md:gap-4',\n className\n )}\n >\n {content}\n </button>\n );\n }\n\n return (\n <div className={cn('flex w-full items-center gap-3 py-3 md:gap-4', className)}>{content}</div>\n );\n};\n"],"mappings":";;;;;;;;AA6BA,IAAM,KAA4C,EAAE,iBAClD,kBAAC,OAAD;CAAK,WAAU;WAAf,CACE,kBAAC,QAAD;EAAM,WAAU;YAAhB,CACE,kBAAC,GAAD;GAAc,WAAU;GAAuB,eAAY;EAAQ,CAAA,GAClE,EAAW,EAAQ,WAAW,CAC3B;KACN,kBAAC,OAAD;EAAK,WAAU;YAAf,CACG,EAAQ,cACP,kBAAC,GAAD;GAAO,SAAQ;GAAY,WAAU;aAArC,CACG,EAAQ,SAAS,SAAS,EAAQ,SAAS,eAC1C,kBAAC,GAAD;IAAQ,WAAU;IAAU,eAAY;GAAQ,CAAA,IAC9C,MACH,EAAQ,WACJ;OACL,MACJ,kBAAC,GAAD,EAAa,QAAQ,EAAQ,OAAS,CAAA,CACnC;GACF;IAIM,KAAmC,EAAE,YAAS,YAAS,SAAM,mBAAgB;CACxF,IAAM,IAAU,EAAe,CAAO,GAChC,IAAY,EAAQ,GAEpB,IACJ,MAAS,KAAQ,OAAO,MAAS,KAAA,IAAmB,kBAAC,GAAD,EAAsB,WAAU,CAAA,IAAvC,GAGzC,IAAY,KAAY,QAAQ,CAAC,EAAQ,WAAW,EAAW,EAAQ,WAAW,IAAI,MAEtF,IACJ,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD;IACE,MAAM,EAAQ;IACd,OAAO,EAAQ;IACf,OAAM;IACN,KAAK,EAAQ;IACb,QAAQ,EAAQ;IAChB,WAAW;GACZ,CAAA;GACD,kBAAC,GAAD;IAAsB;IAAS,MAAM;IAAW,MAAK;GAAM,CAAA;GAC3D,kBAAC,GAAD;IACE,MAAM,EAAQ;IACd,OAAO,EAAQ;IACf,OAAM;IACN,KAAK,EAAQ;IACb,QAAQ,EAAQ;IAChB,WAAW;GACZ,CAAA;EACE;KACJ,IACC,kBAAC,OAAD;EAAK,WAAU;YAAf,CACG,GACA,IACC,kBAAC,GAAD;GAAc,WAAU;GAAmC,eAAY;EAAQ,CAAA,IAC7E,IACD;MACH,IACJ,EAAA,CAAA;CAkBJ,OAfI,IAEA,kBAAC,UAAD;EACE,MAAK;EACI;EACT,WAAW,EACT,8NACA,CACF;YAEC;CACK,CAAA,IAKV,kBAAC,OAAD;EAAK,WAAW,EAAG,gDAAgD,CAAS;YAAI;CAAa,CAAA;AAEjG"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MetricsCard.js","names":[],"sources":["../../../src/atheletebridge/components/MetricsCard.tsx"],"sourcesContent":["/**\n * Metrics Card Component\n * @module atheletebridge/components\n *\n * Boff UI 2.0: KPI tiles use the glassmorphic `GlassCard` \"metric\" treatment\n * (accent wash + seam border + soft pop-lift). The hero metric per page can\n * opt into the brighter `glass` treatment with an accent glow via `featured`.\n *\n * The original API (`icon`, `value`, `label`, `featured`, `className`) is\n * unchanged. Optional props enrich the tile toward the mock's KPI style: a teal\n * `tone`, a signed `delta` pill, and a footer `sparkline` trend — all opt-in, so\n * existing call sites render exactly as before. Numerals are tabular.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { ArrowDownRight, ArrowUpRight } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { Sparkline } from './viz';\nimport { cn } from '../utils/cn';\n\ntype MetricTone = 'default' | 'accent' | 'success';\n\ninterface MetricsCardProps {\n icon: ReactNode;\n value: string | number;\n label: string;\n /** Render as the single featured hero metric (glass treatment + glow). */\n featured?: boolean;\n /** Value color: `default` (primary), `accent` (teal), or `success` (green). */\n tone?: MetricTone;\n /** Signed change; positive is good unless `invertDelta`. Renders a pill. */\n delta?: number;\n /** Unit appended to the delta (default \"%\"). */\n deltaSuffix?: string;\n /** Context line beside the delta, e.g. \"vs last month\". */\n deltaLabel?: string;\n /** When true (e.g. injuries), a negative delta is the good outcome. */\n invertDelta?: boolean;\n /** Optional trend rendered as a footer sparkline. */\n sparkline?: number[];\n className?: string;\n}\n\nconst TONE_CLASS: Record<MetricTone, string> = {\n default: 'text-text-primary',\n accent: 'text-[var(--color-accent-teal)]',\n success: 'text-status-success-text',\n};\n\nexport const MetricsCard: FC<MetricsCardProps> = ({\n icon,\n value,\n label,\n featured = false,\n tone = 'default',\n delta,\n deltaSuffix = '%',\n deltaLabel,\n invertDelta = false,\n sparkline,\n className,\n}) => {\n const hasDelta = typeof delta === 'number';\n const isNeutral = (delta ?? 0) === 0;\n const positiveIsGood = invertDelta ? (delta ?? 0) < 0 : (delta ?? 0) > 0;\n const rising = (delta ?? 0) > 0;\n const deltaTone = isNeutral\n ? 'text-text-muted'\n : positiveIsGood\n ? 'text-status-success-text'\n : 'text-status-error-text';\n\n return (\n <GlassCard\n treatment={featured ? 'glass' : 'metric'}\n glow={featured}\n className={cn('p-6', className)}\n >\n <div className=\"relative text-text-secondary\">{icon}</div>\n <div className=\"relative mt-3\">\n <p className={cn('text-2xl font-bold tabular-nums', TONE_CLASS[tone])}>{value}</p>\n <p className=\"mt-1 text-sm text-text-secondary\">{label}</p>\n </div>\n\n {hasDelta ? (\n <div className=\"relative mt-2 flex items-center gap-1.5 text-sm\">\n <span\n className={cn('inline-flex items-center gap-0.5 font-medium tabular-nums', deltaTone)}\n >\n {isNeutral ? null : rising ? (\n <ArrowUpRight aria-hidden=\"true\" className=\"h-4 w-4\" />\n ) : (\n <ArrowDownRight aria-hidden=\"true\" className=\"h-4 w-4\" />\n )}\n {rising ? '+' : ''}\n {delta}\n {deltaSuffix}\n </span>\n {deltaLabel ? <span className=\"text-text-muted\">{deltaLabel}</span> : null}\n </div>\n ) : null}\n\n {sparkline && sparkline.length > 1 ? (\n <div className=\"relative mt-3\">\n <Sparkline data={sparkline} variant=\"area\" label={`${label} trend`} height={32} />\n </div>\n ) : null}\n </GlassCard>\n );\n};\n"],"mappings":";;;;;;;AA2CA,IAAM,IAAyC;CAC7C,SAAS;CACT,QAAQ;CACR,SAAS;
|
|
1
|
+
{"version":3,"file":"MetricsCard.js","names":[],"sources":["../../../src/atheletebridge/components/MetricsCard.tsx"],"sourcesContent":["/**\n * Metrics Card Component\n * @module atheletebridge/components\n *\n * Boff UI 2.0: KPI tiles use the glassmorphic `GlassCard` \"metric\" treatment\n * (accent wash + seam border + soft pop-lift). The hero metric per page can\n * opt into the brighter `glass` treatment with an accent glow via `featured`.\n *\n * The original API (`icon`, `value`, `label`, `featured`, `className`) is\n * unchanged. Optional props enrich the tile toward the mock's KPI style: a teal\n * `tone`, a signed `delta` pill, and a footer `sparkline` trend — all opt-in, so\n * existing call sites render exactly as before. Numerals are tabular.\n */\n\nimport type { FC, ReactNode } from 'react';\nimport { ArrowDownRight, ArrowUpRight } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { Sparkline } from './viz';\nimport { cn } from '../utils/cn';\n\ntype MetricTone = 'default' | 'accent' | 'success';\n\ninterface MetricsCardProps {\n icon: ReactNode;\n value: string | number;\n label: string;\n /** Render as the single featured hero metric (glass treatment + glow). */\n featured?: boolean;\n /** Value color: `default` (primary), `accent` (teal), or `success` (green). */\n tone?: MetricTone;\n /** Signed change; positive is good unless `invertDelta`. Renders a pill. */\n delta?: number;\n /** Unit appended to the delta (default \"%\"). */\n deltaSuffix?: string;\n /** Context line beside the delta, e.g. \"vs last month\". */\n deltaLabel?: string;\n /** When true (e.g. injuries), a negative delta is the good outcome. */\n invertDelta?: boolean;\n /** Optional trend rendered as a footer sparkline. */\n sparkline?: number[];\n className?: string;\n}\n\nconst TONE_CLASS: Record<MetricTone, string> = {\n default: 'text-text-primary',\n accent: 'text-[var(--color-accent-teal)]',\n success: 'text-status-success-text',\n};\n\nexport const MetricsCard: FC<MetricsCardProps> = ({\n icon,\n value,\n label,\n featured = false,\n tone = 'default',\n delta,\n deltaSuffix = '%',\n deltaLabel,\n invertDelta = false,\n sparkline,\n className,\n}) => {\n const hasDelta = typeof delta === 'number';\n const isNeutral = (delta ?? 0) === 0;\n const positiveIsGood = invertDelta ? (delta ?? 0) < 0 : (delta ?? 0) > 0;\n const rising = (delta ?? 0) > 0;\n const deltaTone = isNeutral\n ? 'text-text-muted'\n : positiveIsGood\n ? 'text-status-success-text'\n : 'text-status-error-text';\n\n return (\n <GlassCard\n treatment={featured ? 'glass' : 'metric'}\n glow={featured}\n className={cn('p-6', className)}\n >\n <div className=\"relative text-text-secondary\">{icon}</div>\n <div className=\"relative mt-3\">\n <p className={cn('text-2xl font-bold tabular-nums', TONE_CLASS[tone])}>{value}</p>\n <p className=\"mt-1 text-sm text-text-secondary\">{label}</p>\n </div>\n\n {hasDelta ? (\n <div className=\"relative mt-2 flex items-center gap-1.5 text-sm\">\n <span\n className={cn('inline-flex items-center gap-0.5 font-medium tabular-nums', deltaTone)}\n >\n {isNeutral ? null : rising ? (\n <ArrowUpRight aria-hidden=\"true\" className=\"h-4 w-4\" />\n ) : (\n <ArrowDownRight aria-hidden=\"true\" className=\"h-4 w-4\" />\n )}\n {rising ? '+' : ''}\n {delta}\n {deltaSuffix}\n </span>\n {deltaLabel ? <span className=\"text-text-muted\">{deltaLabel}</span> : null}\n </div>\n ) : null}\n\n {sparkline && sparkline.length > 1 ? (\n <div className=\"relative mt-3\">\n <Sparkline data={sparkline} variant=\"area\" label={`${label} trend`} height={32} />\n </div>\n ) : null}\n </GlassCard>\n );\n};\n"],"mappings":";;;;;;;AA2CA,IAAM,IAAyC;CAC7C,SAAS;CACT,QAAQ;CACR,SAAS;AACX,GAEa,KAAqC,EAChD,SACA,UACA,UACA,cAAW,IACX,UAAO,WACP,UACA,iBAAc,KACd,eACA,iBAAc,IACd,cACA,mBACI;CACJ,IAAM,IAAW,OAAO,KAAU,UAC5B,KAAa,KAAS,OAAO,GAC7B,IAAiB,KAAe,KAAS,KAAK,KAAK,KAAS,KAAK,GACjE,KAAU,KAAS,KAAK,GACxB,IAAY,IACd,oBACA,IACE,6BACA;CAEN,OACE,kBAAC,GAAD;EACE,WAAW,IAAW,UAAU;EAChC,MAAM;EACN,WAAW,EAAG,OAAO,CAAS;YAHhC;GAKE,kBAAC,OAAD;IAAK,WAAU;cAAgC;GAAU,CAAA;GACzD,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAW,EAAG,mCAAmC,EAAW,EAAK;eAAI;IAAS,CAAA,GACjF,kBAAC,KAAD;KAAG,WAAU;eAAoC;IAAS,CAAA,CACvD;;GAEJ,IACC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KACE,WAAW,EAAG,6DAA6D,CAAS;eADtF;MAGG,IAAY,OACX,EADkB,IACjB,IAEA,GAFD;OAAc,eAAY;OAAO,WAAU;MAAW,CAEE;MAEzD,IAAS,MAAM;MACf;MACA;KACG;QACL,IAAa,kBAAC,QAAD;KAAM,WAAU;eAAmB;IAAiB,CAAA,IAAI,IACnE;QACH;GAEH,KAAa,EAAU,SAAS,IAC/B,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KAAW,MAAM;KAAW,SAAQ;KAAO,OAAO,GAAG,EAAM;KAAS,QAAQ;IAAK,CAAA;GAC9E,CAAA,IACH;EACK;;AAEf"}
|