@burdenoff/microfe-atheletebridge 2026.629.1 → 2026.705.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/atheletebridge/AthleteBridgeRoot.js +45 -37
  2. package/dist/atheletebridge/AthleteBridgeRoot.js.map +1 -1
  3. package/dist/atheletebridge/AthleteBridgeRoutes.js +10 -6
  4. package/dist/atheletebridge/AthleteBridgeRoutes.js.map +1 -1
  5. package/dist/atheletebridge/components/AthleteSelect.js +66 -0
  6. package/dist/atheletebridge/components/AthleteSelect.js.map +1 -0
  7. package/dist/atheletebridge/components/DrillVideoLink.js +28 -0
  8. package/dist/atheletebridge/components/DrillVideoLink.js.map +1 -0
  9. package/dist/atheletebridge/components/MetricsCard.js +20 -46
  10. package/dist/atheletebridge/components/MetricsCard.js.map +1 -1
  11. package/dist/atheletebridge/components/UserSearchSelect.js +129 -0
  12. package/dist/atheletebridge/components/UserSearchSelect.js.map +1 -0
  13. package/dist/atheletebridge/components/domain.js +10 -1
  14. package/dist/atheletebridge/components/domain.js.map +1 -1
  15. package/dist/atheletebridge/hooks/useAthleteBridgeApi.js +313 -180
  16. package/dist/atheletebridge/hooks/useAthleteBridgeApi.js.map +1 -1
  17. package/dist/atheletebridge/pages/DashboardPage.js +77 -69
  18. package/dist/atheletebridge/pages/DashboardPage.js.map +1 -1
  19. package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +4 -4
  20. package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
  21. package/dist/atheletebridge/pages/performance/PerformancePage.js +496 -0
  22. package/dist/atheletebridge/pages/performance/PerformancePage.js.map +1 -0
  23. package/dist/atheletebridge/pages/programs/ProgramDetailPage.js +176 -142
  24. package/dist/atheletebridge/pages/programs/ProgramDetailPage.js.map +1 -1
  25. package/dist/atheletebridge/pages/sessions/SessionDetailPage.js +86 -85
  26. package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -1
  27. package/dist/atheletebridge/pages/sessions/SessionFormPage.js +92 -98
  28. package/dist/atheletebridge/pages/sessions/SessionFormPage.js.map +1 -1
  29. package/dist/atheletebridge/pages/teams/TeamDetailPage.js +132 -137
  30. package/dist/atheletebridge/pages/teams/TeamDetailPage.js.map +1 -1
  31. package/dist/atheletebridge/utils/formatters.js +2 -25
  32. package/dist/atheletebridge/utils/formatters.js.map +1 -1
  33. package/dist/atheletebridge/utils/url.js +17 -0
  34. package/dist/atheletebridge/utils/url.js.map +1 -0
  35. package/dist/index.js +4 -4
  36. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"file":"useAthleteBridgeApi.js","names":[],"sources":["../../../src/atheletebridge/hooks/useAthleteBridgeApi.ts"],"sourcesContent":["/**\n * AthleteBridge API hooks\n * @module atheletebridge/hooks\n *\n * Thin react-query wrappers over the real `wspace-atheletebridge-svc` federated\n * GraphQL contract, reached through the workspace gateway with the workspace\n * dual-token. Every query/mutation here maps 1:1 to an `ath*` root field in the\n * service's `.hive-schema.graphql`. NO MOCKS — all data is live.\n */\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\n\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useAthleteBridge } from '../context';\nimport type {\n AthCompleteSessionInput,\n AthCreateFixtureInput,\n AthCreateProgramInput,\n AthCreateTeamInput,\n AthAddMemberInput,\n AthAddDrillInput,\n AthBookSessionInput,\n AthDrill,\n AthFixture,\n AthFixtureConnection,\n AthFixtureStatus,\n AthMember,\n AthMemberStatus,\n AthPerformanceMetric,\n AthProgram,\n AthProgramConnection,\n AthProgramLevel,\n AthProgramStatus,\n AthRecordResultInput,\n AthSession,\n AthSessionConnection,\n AthSessionStatus,\n AthSport,\n AthStanding,\n AthTeam,\n AthTeamConnection,\n AthTeamStatus,\n AthUpdateProgramInput,\n AthUpdateTeamInput,\n AthWorkoutLogConnection,\n PaginationInput,\n} from '../types';\n\n// ============================================================================\n// Low-level query wrapper\n// ============================================================================\n\nconst athQuery = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n\n return response.data;\n};\n\n// ============================================================================\n// GraphQL fragments (inline strings — exact fields from the svc schema)\n// ============================================================================\n\nconst TEAM_FIELDS = `\n id workspaceId name sport status description ageGroup season homeVenue\n headCoachId tags createdAt updatedAt\n`;\n\nconst MEMBER_FIELDS = `\n id workspaceId teamId userId displayName role status position jerseyNumber\n height weight dominantSide notes joinedAt createdAt updatedAt\n`;\n\nconst FIXTURE_FIELDS = `\n id workspaceId type status competition round venue homeTeamId awayTeamId\n homeScore awayScore scheduledAt kickoffAt notes createdAt updatedAt\n homeTeam { id name sport }\n awayTeam { id name sport }\n`;\n\nconst STANDING_FIELDS = `\n id workspaceId competition teamId played won drawn lost goalsFor goalsAgainst\n points rank createdAt updatedAt\n team { id name sport }\n`;\n\nconst DRILL_FIELDS = `\n id workspaceId programId name description weekNumber orderIndex sets reps\n durationSec restSec createdAt updatedAt\n video { id }\n`;\n\nconst PROGRAM_FIELDS = `\n id workspaceId coachId name sport level status summary weeks isTemplate tags\n createdAt updatedAt\n`;\n\nconst SESSION_FIELDS = `\n id workspaceId coachId athleteId programId title type status location\n meetingUrl scheduledAt durationMin completedAt feedback rating\n createdAt updatedAt\n`;\n\nconst WORKOUT_LOG_FIELDS = `\n id workspaceId athleteId programId title status performedAt durationMin rpe\n totalLoad metrics notes createdAt updatedAt\n`;\n\nconst METRIC_FIELDS = `\n id workspaceId athleteId type label value unit source recordedAt\n createdAt updatedAt\n`;\n\n// ============================================================================\n// Teams\n// ============================================================================\n\nexport interface TeamFilters {\n sport?: AthSport;\n status?: AthTeamStatus;\n pagination?: PaginationInput;\n}\n\nexport const useTeams = (filters: TeamFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'teams', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListTeams: AthTeamConnection }>(\n `query AthListTeams($sport: AthSport, $status: AthTeamStatus, $pagination: PaginationInput) {\n athListTeams(sport: $sport, status: $status, pagination: $pagination) {\n items { ${TEAM_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n sport: filters.sport,\n status: filters.status,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListTeams;\n },\n });\n};\n\nexport const useTeam = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'team', id],\n queryFn: async () => {\n const result = await athQuery<{ athTeam: AthTeam | null }>(\n `query AthTeam($id: ID!) {\n athTeam(id: $id) {\n ${TEAM_FIELDS}\n members { ${MEMBER_FIELDS} }\n fixtures { ${FIXTURE_FIELDS} }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athTeam;\n },\n enabled: Boolean(id),\n });\n};\n\nexport const useTeamMembers = (teamId: string | undefined, status?: AthMemberStatus) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'members', teamId, status],\n queryFn: async () => {\n const result = await athQuery<{ athListMembers: AthMember[] }>(\n `query AthListMembers($teamId: ID!, $status: AthMemberStatus) {\n athListMembers(teamId: $teamId, status: $status) { ${MEMBER_FIELDS} }\n }`,\n { teamId, status },\n apiGatewayUrl\n );\n return result.athListMembers;\n },\n enabled: Boolean(teamId),\n });\n};\n\nexport const useCreateTeam = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthCreateTeamInput) => {\n const result = await athQuery<{ athCreateTeam: AthTeam }>(\n `mutation AthCreateTeam($input: AthCreateTeamInput!) {\n athCreateTeam(input: $input) { ${TEAM_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athCreateTeam;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'teams'] });\n },\n });\n};\n\nexport const useUpdateTeam = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input: AthUpdateTeamInput }) => {\n const result = await athQuery<{ athUpdateTeam: AthTeam }>(\n `mutation AthUpdateTeam($id: ID!, $input: AthUpdateTeamInput!) {\n athUpdateTeam(id: $id, input: $input) { ${TEAM_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.athUpdateTeam;\n },\n onSuccess: (team) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'teams'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'team', team.id] });\n },\n });\n};\n\nexport const useArchiveTeam = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athArchiveTeam: boolean }>(\n `mutation AthArchiveTeam($id: ID!) { athArchiveTeam(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.athArchiveTeam;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'teams'] });\n },\n });\n};\n\nexport const useAddMember = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthAddMemberInput) => {\n const result = await athQuery<{ athAddMember: AthMember }>(\n `mutation AthAddMember($input: AthAddMemberInput!) {\n athAddMember(input: $input) { ${MEMBER_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athAddMember;\n },\n onSuccess: (member) => {\n void queryClient.invalidateQueries({\n queryKey: ['atheletebridge', 'members', member.teamId],\n });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'team', member.teamId] });\n },\n });\n};\n\nexport const useRemoveMember = (teamId?: string) => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athRemoveMember: boolean }>(\n `mutation AthRemoveMember($id: ID!) { athRemoveMember(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.athRemoveMember;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'members', teamId] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'team', teamId] });\n },\n });\n};\n\n// ============================================================================\n// Fixtures & standings\n// ============================================================================\n\nexport interface FixtureFilters {\n status?: AthFixtureStatus;\n teamId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useFixtures = (filters: FixtureFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'fixtures', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListFixtures: AthFixtureConnection }>(\n `query AthListFixtures($status: AthFixtureStatus, $teamId: ID, $pagination: PaginationInput) {\n athListFixtures(status: $status, teamId: $teamId, pagination: $pagination) {\n items { ${FIXTURE_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n status: filters.status,\n teamId: filters.teamId,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListFixtures;\n },\n });\n};\n\nexport const useFixture = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'fixture', id],\n queryFn: async () => {\n const result = await athQuery<{ athFixture: AthFixture | null }>(\n `query AthFixture($id: ID!) {\n athFixture(id: $id) { ${FIXTURE_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athFixture;\n },\n enabled: Boolean(id),\n });\n};\n\nexport const useStandings = (competition: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'standings', competition],\n queryFn: async () => {\n const result = await athQuery<{ athStandings: AthStanding[] }>(\n `query AthStandings($competition: String!) {\n athStandings(competition: $competition) { ${STANDING_FIELDS} }\n }`,\n { competition },\n apiGatewayUrl\n );\n return result.athStandings;\n },\n enabled: Boolean(competition),\n });\n};\n\nexport const useScheduleFixture = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthCreateFixtureInput) => {\n const result = await athQuery<{ athScheduleFixture: AthFixture }>(\n `mutation AthScheduleFixture($input: AthCreateFixtureInput!) {\n athScheduleFixture(input: $input) { ${FIXTURE_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athScheduleFixture;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'fixtures'] });\n },\n });\n};\n\nexport const useRecordFixtureResult = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input: AthRecordResultInput }) => {\n const result = await athQuery<{ athRecordFixtureResult: AthFixture }>(\n `mutation AthRecordFixtureResult($id: ID!, $input: AthRecordResultInput!) {\n athRecordFixtureResult(id: $id, input: $input) { ${FIXTURE_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.athRecordFixtureResult;\n },\n onSuccess: (fixture) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'fixtures'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'fixture', fixture.id] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'standings'] });\n },\n });\n};\n\n// ============================================================================\n// Programs & drills\n// ============================================================================\n\nexport interface ProgramFilters {\n sport?: AthSport;\n level?: AthProgramLevel;\n status?: AthProgramStatus;\n pagination?: PaginationInput;\n}\n\nexport const usePrograms = (filters: ProgramFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'programs', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListPrograms: AthProgramConnection }>(\n `query AthListPrograms($sport: AthSport, $level: AthProgramLevel, $status: AthProgramStatus, $pagination: PaginationInput) {\n athListPrograms(sport: $sport, level: $level, status: $status, pagination: $pagination) {\n items { ${PROGRAM_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n sport: filters.sport,\n level: filters.level,\n status: filters.status,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListPrograms;\n },\n });\n};\n\nexport const useProgram = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'program', id],\n queryFn: async () => {\n const result = await athQuery<{ athProgram: AthProgram | null }>(\n `query AthProgram($id: ID!) {\n athProgram(id: $id) {\n ${PROGRAM_FIELDS}\n drills { ${DRILL_FIELDS} }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athProgram;\n },\n enabled: Boolean(id),\n });\n};\n\nexport const useCreateProgram = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthCreateProgramInput) => {\n const result = await athQuery<{ athCreateProgram: AthProgram }>(\n `mutation AthCreateProgram($input: AthCreateProgramInput!) {\n athCreateProgram(input: $input) { ${PROGRAM_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athCreateProgram;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'programs'] });\n },\n });\n};\n\nexport const useUpdateProgram = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input: AthUpdateProgramInput }) => {\n const result = await athQuery<{ athUpdateProgram: AthProgram }>(\n `mutation AthUpdateProgram($id: ID!, $input: AthUpdateProgramInput!) {\n athUpdateProgram(id: $id, input: $input) { ${PROGRAM_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.athUpdateProgram;\n },\n onSuccess: (program) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'programs'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'program', program.id] });\n },\n });\n};\n\nexport const useTransitionProgram = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, status }: { id: string; status: AthProgramStatus }) => {\n const result = await athQuery<{ athTransitionProgram: AthProgram }>(\n `mutation AthTransitionProgram($id: ID!, $status: AthProgramStatus!) {\n athTransitionProgram(id: $id, status: $status) { ${PROGRAM_FIELDS} }\n }`,\n { id, status },\n apiGatewayUrl\n );\n return result.athTransitionProgram;\n },\n onSuccess: (program) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'programs'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'program', program.id] });\n },\n });\n};\n\nexport const useArchiveProgram = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athArchiveProgram: boolean }>(\n `mutation AthArchiveProgram($id: ID!) { athArchiveProgram(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.athArchiveProgram;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'programs'] });\n },\n });\n};\n\nexport const useAddDrill = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthAddDrillInput) => {\n const result = await athQuery<{ athAddDrill: AthDrill }>(\n `mutation AthAddDrill($input: AthAddDrillInput!) {\n athAddDrill(input: $input) { ${DRILL_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athAddDrill;\n },\n onSuccess: (drill) => {\n void queryClient.invalidateQueries({\n queryKey: ['atheletebridge', 'program', drill.programId],\n });\n },\n });\n};\n\nexport const useRemoveDrill = (programId?: string) => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athRemoveDrill: boolean }>(\n `mutation AthRemoveDrill($id: ID!) { athRemoveDrill(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.athRemoveDrill;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'program', programId] });\n },\n });\n};\n\n// ============================================================================\n// Coaching sessions\n// ============================================================================\n\nexport interface SessionFilters {\n athleteId?: string;\n coachId?: string;\n status?: AthSessionStatus;\n pagination?: PaginationInput;\n}\n\nexport const useSessions = (filters: SessionFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'sessions', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListSessions: AthSessionConnection }>(\n `query AthListSessions($athleteId: ID, $coachId: ID, $status: AthSessionStatus, $pagination: PaginationInput) {\n athListSessions(athleteId: $athleteId, coachId: $coachId, status: $status, pagination: $pagination) {\n items { ${SESSION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n athleteId: filters.athleteId,\n coachId: filters.coachId,\n status: filters.status,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListSessions;\n },\n });\n};\n\nexport const useSession = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'session', id],\n queryFn: async () => {\n const result = await athQuery<{ athSession: AthSession | null }>(\n `query AthSession($id: ID!) {\n athSession(id: $id) { ${SESSION_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athSession;\n },\n enabled: Boolean(id),\n });\n};\n\nexport const useBookSession = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthBookSessionInput) => {\n const result = await athQuery<{ athBookSession: AthSession }>(\n `mutation AthBookSession($input: AthBookSessionInput!) {\n athBookSession(input: $input) { ${SESSION_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athBookSession;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'sessions'] });\n },\n });\n};\n\nexport const useConfirmSession = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athConfirmSession: AthSession }>(\n `mutation AthConfirmSession($id: ID!) {\n athConfirmSession(id: $id) { ${SESSION_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athConfirmSession;\n },\n onSuccess: (session) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'sessions'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'session', session.id] });\n },\n });\n};\n\nexport const useCompleteSession = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input?: AthCompleteSessionInput }) => {\n const result = await athQuery<{ athCompleteSession: AthSession }>(\n `mutation AthCompleteSession($id: ID!, $input: AthCompleteSessionInput) {\n athCompleteSession(id: $id, input: $input) { ${SESSION_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.athCompleteSession;\n },\n onSuccess: (session) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'sessions'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'session', session.id] });\n },\n });\n};\n\nexport const useCancelSession = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athCancelSession: AthSession }>(\n `mutation AthCancelSession($id: ID!) {\n athCancelSession(id: $id) { ${SESSION_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athCancelSession;\n },\n onSuccess: (session) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'sessions'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'session', session.id] });\n },\n });\n};\n\n// ============================================================================\n// Workout logs & performance metrics\n// ============================================================================\n\nexport interface WorkoutLogFilters {\n athleteId?: string;\n programId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useWorkoutLogs = (filters: WorkoutLogFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'workout-logs', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListWorkoutLogs: AthWorkoutLogConnection }>(\n `query AthListWorkoutLogs($athleteId: ID, $programId: ID, $pagination: PaginationInput) {\n athListWorkoutLogs(athleteId: $athleteId, programId: $programId, pagination: $pagination) {\n items { ${WORKOUT_LOG_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n athleteId: filters.athleteId,\n programId: filters.programId,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListWorkoutLogs;\n },\n });\n};\n\nexport const usePerformanceMetrics = (athleteId: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'metrics', athleteId],\n queryFn: async () => {\n const result = await athQuery<{ athPerformanceMetrics: AthPerformanceMetric[] }>(\n `query AthPerformanceMetrics($athleteId: ID!) {\n athPerformanceMetrics(athleteId: $athleteId) { ${METRIC_FIELDS} }\n }`,\n { athleteId },\n apiGatewayUrl\n );\n return result.athPerformanceMetrics;\n },\n enabled: Boolean(athleteId),\n });\n};\n\n// ============================================================================\n// Health probe\n// ============================================================================\n\nexport const useHealth = () => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'health'],\n queryFn: async () => {\n const result = await athQuery<{ athHealth: string }>(\n `query AthHealth { athHealth }`,\n {},\n apiGatewayUrl\n );\n return result.athHealth;\n },\n });\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"useAthleteBridgeApi.js","names":[],"sources":["../../../src/atheletebridge/hooks/useAthleteBridgeApi.ts"],"sourcesContent":["/**\n * AthleteBridge API hooks\n * @module atheletebridge/hooks\n *\n * Thin react-query wrappers over the real `wspace-atheletebridge-svc` federated\n * GraphQL contract, reached through the workspace gateway with the workspace\n * dual-token. Every query/mutation here maps 1:1 to an `ath*` root field in the\n * service's `.hive-schema.graphql`. NO MOCKS — all data is live.\n */\n\nimport { useCallback } from 'react';\n\nimport { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';\n\nimport { graphqlFetch, clearGraphqlFetchResponseCache } from '@burdenoff/fe-libs/shared/graphql';\n\nimport { useAthleteBridge } from '../context';\nimport type {\n AthCompleteSessionInput,\n AthCreateFixtureInput,\n AthCreateProgramInput,\n AthCreateTeamInput,\n AthAddMemberInput,\n AthAddDrillInput,\n AthBookSessionInput,\n AthDrill,\n AthFixture,\n AthFixtureConnection,\n AthFixtureStatus,\n AthLogWorkoutInput,\n AthMember,\n AthMemberStatus,\n AthPerformanceMetric,\n AthProgram,\n AthProgramConnection,\n AthProgramLevel,\n AthProgramStatus,\n AthRecordMetricInput,\n AthRecordResultInput,\n AthSession,\n AthSessionConnection,\n AthSessionStatus,\n AthSport,\n AthStanding,\n AthTeam,\n AthTeamConnection,\n AthTeamStatus,\n AthUpdateProgramInput,\n AthUpdateTeamInput,\n AthWorkoutLog,\n AthWorkoutLogConnection,\n PaginationInput,\n} from '../types';\n\n// ============================================================================\n// Low-level query wrapper\n// ============================================================================\n\nconst athQuery = async <TData>(\n query: string,\n variables: Record<string, unknown>,\n baseUrl?: string\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'workspace',\n baseUrl,\n query,\n variables,\n workspaceToken: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n\n // This MFE bypasses Apollo, so fe-libs' short-lived graphqlFetch read cache\n // would otherwise serve pre-write data to the very next refetch issued by a\n // mutation's onSuccess invalidation. Clear it after any successful mutation so\n // every downstream read is fresh (fleet-wide stale-read-after-write fix).\n if (query.trimStart().startsWith('mutation')) {\n clearGraphqlFetchResponseCache();\n }\n\n return response.data;\n};\n\n// Global-gateway read wrapper — the workspace-scoped MFE reaches the tenant user\n// directory (for real user pickers) through the global gateway.\nconst athGlobalQuery = async <TData>(\n query: string,\n variables: Record<string, unknown>\n): Promise<TData> => {\n const response = await graphqlFetch<TData>({\n gateway: 'global',\n query,\n variables,\n suppressGlobalErrorEvent: true,\n });\n\n if (response.errors?.length) {\n throw new Error(response.errors[0]?.message ?? 'GraphQL request failed');\n }\n\n if (!response.data) {\n throw new Error('GraphQL response did not include data');\n }\n\n return response.data;\n};\n\n// ============================================================================\n// GraphQL fragments (inline strings — exact fields from the svc schema)\n// ============================================================================\n\nconst TEAM_FIELDS = `\n id workspaceId name sport status description ageGroup season homeVenue\n headCoachId tags createdAt updatedAt\n`;\n\nconst MEMBER_FIELDS = `\n id workspaceId teamId userId displayName role status position jerseyNumber\n height weight dominantSide notes joinedAt createdAt updatedAt\n`;\n\nconst FIXTURE_FIELDS = `\n id workspaceId type status competition round venue homeTeamId awayTeamId\n homeScore awayScore scheduledAt kickoffAt notes createdAt updatedAt\n homeTeam { id name sport }\n awayTeam { id name sport }\n`;\n\nconst STANDING_FIELDS = `\n id workspaceId competition teamId played won drawn lost goalsFor goalsAgainst\n points rank createdAt updatedAt\n team { id name sport }\n`;\n\nconst DRILL_FIELDS = `\n id workspaceId programId name description weekNumber orderIndex sets reps\n durationSec restSec createdAt updatedAt\n video { id }\n`;\n\nconst PROGRAM_FIELDS = `\n id workspaceId coachId name sport level status summary weeks isTemplate tags\n createdAt updatedAt\n`;\n\nconst SESSION_FIELDS = `\n id workspaceId coachId athleteId programId title type status location\n meetingUrl scheduledAt durationMin completedAt feedback rating\n createdAt updatedAt\n`;\n\nconst WORKOUT_LOG_FIELDS = `\n id workspaceId athleteId programId title status performedAt durationMin rpe\n totalLoad metrics notes createdAt updatedAt\n`;\n\nconst METRIC_FIELDS = `\n id workspaceId athleteId type label value unit source recordedAt\n createdAt updatedAt\n`;\n\n// ============================================================================\n// Teams\n// ============================================================================\n\nexport interface TeamFilters {\n sport?: AthSport;\n status?: AthTeamStatus;\n pagination?: PaginationInput;\n}\n\nexport const useTeams = (filters: TeamFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'teams', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListTeams: AthTeamConnection }>(\n `query AthListTeams($sport: AthSport, $status: AthTeamStatus, $pagination: PaginationInput) {\n athListTeams(sport: $sport, status: $status, pagination: $pagination) {\n items { ${TEAM_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n sport: filters.sport,\n status: filters.status,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListTeams;\n },\n });\n};\n\nexport const useTeam = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'team', id],\n queryFn: async () => {\n const result = await athQuery<{ athTeam: AthTeam | null }>(\n `query AthTeam($id: ID!) {\n athTeam(id: $id) {\n ${TEAM_FIELDS}\n members { ${MEMBER_FIELDS} }\n fixtures { ${FIXTURE_FIELDS} }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athTeam;\n },\n enabled: Boolean(id),\n });\n};\n\nexport const useTeamMembers = (teamId: string | undefined, status?: AthMemberStatus) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'members', teamId, status],\n queryFn: async () => {\n const result = await athQuery<{ athListMembers: AthMember[] }>(\n `query AthListMembers($teamId: ID!, $status: AthMemberStatus) {\n athListMembers(teamId: $teamId, status: $status) { ${MEMBER_FIELDS} }\n }`,\n { teamId, status },\n apiGatewayUrl\n );\n return result.athListMembers;\n },\n enabled: Boolean(teamId),\n });\n};\n\nexport const useCreateTeam = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthCreateTeamInput) => {\n const result = await athQuery<{ athCreateTeam: AthTeam }>(\n `mutation AthCreateTeam($input: AthCreateTeamInput!) {\n athCreateTeam(input: $input) { ${TEAM_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athCreateTeam;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'teams'] });\n },\n });\n};\n\nexport const useUpdateTeam = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input: AthUpdateTeamInput }) => {\n const result = await athQuery<{ athUpdateTeam: AthTeam }>(\n `mutation AthUpdateTeam($id: ID!, $input: AthUpdateTeamInput!) {\n athUpdateTeam(id: $id, input: $input) { ${TEAM_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.athUpdateTeam;\n },\n onSuccess: (team) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'teams'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'team', team.id] });\n },\n });\n};\n\nexport const useArchiveTeam = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athArchiveTeam: boolean }>(\n `mutation AthArchiveTeam($id: ID!) { athArchiveTeam(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.athArchiveTeam;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'teams'] });\n },\n });\n};\n\nexport const useAddMember = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthAddMemberInput) => {\n const result = await athQuery<{ athAddMember: AthMember }>(\n `mutation AthAddMember($input: AthAddMemberInput!) {\n athAddMember(input: $input) { ${MEMBER_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athAddMember;\n },\n onSuccess: (member) => {\n void queryClient.invalidateQueries({\n queryKey: ['atheletebridge', 'members', member.teamId],\n });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'team', member.teamId] });\n },\n });\n};\n\nexport const useRemoveMember = (teamId?: string) => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athRemoveMember: boolean }>(\n `mutation AthRemoveMember($id: ID!) { athRemoveMember(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.athRemoveMember;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'members', teamId] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'team', teamId] });\n },\n });\n};\n\n// ============================================================================\n// Fixtures & standings\n// ============================================================================\n\nexport interface FixtureFilters {\n status?: AthFixtureStatus;\n teamId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useFixtures = (filters: FixtureFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'fixtures', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListFixtures: AthFixtureConnection }>(\n `query AthListFixtures($status: AthFixtureStatus, $teamId: ID, $pagination: PaginationInput) {\n athListFixtures(status: $status, teamId: $teamId, pagination: $pagination) {\n items { ${FIXTURE_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n status: filters.status,\n teamId: filters.teamId,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListFixtures;\n },\n });\n};\n\nexport const useFixture = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'fixture', id],\n queryFn: async () => {\n const result = await athQuery<{ athFixture: AthFixture | null }>(\n `query AthFixture($id: ID!) {\n athFixture(id: $id) { ${FIXTURE_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athFixture;\n },\n enabled: Boolean(id),\n });\n};\n\nexport const useStandings = (competition: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'standings', competition],\n queryFn: async () => {\n const result = await athQuery<{ athStandings: AthStanding[] }>(\n `query AthStandings($competition: String!) {\n athStandings(competition: $competition) { ${STANDING_FIELDS} }\n }`,\n { competition },\n apiGatewayUrl\n );\n return result.athStandings;\n },\n enabled: Boolean(competition),\n });\n};\n\nexport const useScheduleFixture = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthCreateFixtureInput) => {\n const result = await athQuery<{ athScheduleFixture: AthFixture }>(\n `mutation AthScheduleFixture($input: AthCreateFixtureInput!) {\n athScheduleFixture(input: $input) { ${FIXTURE_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athScheduleFixture;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'fixtures'] });\n },\n });\n};\n\nexport const useRecordFixtureResult = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input: AthRecordResultInput }) => {\n const result = await athQuery<{ athRecordFixtureResult: AthFixture }>(\n `mutation AthRecordFixtureResult($id: ID!, $input: AthRecordResultInput!) {\n athRecordFixtureResult(id: $id, input: $input) { ${FIXTURE_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.athRecordFixtureResult;\n },\n onSuccess: (fixture) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'fixtures'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'fixture', fixture.id] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'standings'] });\n },\n });\n};\n\n// ============================================================================\n// Programs & drills\n// ============================================================================\n\nexport interface ProgramFilters {\n sport?: AthSport;\n level?: AthProgramLevel;\n status?: AthProgramStatus;\n pagination?: PaginationInput;\n}\n\nexport const usePrograms = (filters: ProgramFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'programs', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListPrograms: AthProgramConnection }>(\n `query AthListPrograms($sport: AthSport, $level: AthProgramLevel, $status: AthProgramStatus, $pagination: PaginationInput) {\n athListPrograms(sport: $sport, level: $level, status: $status, pagination: $pagination) {\n items { ${PROGRAM_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n sport: filters.sport,\n level: filters.level,\n status: filters.status,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListPrograms;\n },\n });\n};\n\nexport const useProgram = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'program', id],\n queryFn: async () => {\n const result = await athQuery<{ athProgram: AthProgram | null }>(\n `query AthProgram($id: ID!) {\n athProgram(id: $id) {\n ${PROGRAM_FIELDS}\n drills { ${DRILL_FIELDS} }\n }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athProgram;\n },\n enabled: Boolean(id),\n });\n};\n\nexport const useCreateProgram = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthCreateProgramInput) => {\n const result = await athQuery<{ athCreateProgram: AthProgram }>(\n `mutation AthCreateProgram($input: AthCreateProgramInput!) {\n athCreateProgram(input: $input) { ${PROGRAM_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athCreateProgram;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'programs'] });\n },\n });\n};\n\nexport const useUpdateProgram = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input: AthUpdateProgramInput }) => {\n const result = await athQuery<{ athUpdateProgram: AthProgram }>(\n `mutation AthUpdateProgram($id: ID!, $input: AthUpdateProgramInput!) {\n athUpdateProgram(id: $id, input: $input) { ${PROGRAM_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.athUpdateProgram;\n },\n onSuccess: (program) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'programs'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'program', program.id] });\n },\n });\n};\n\nexport const useTransitionProgram = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, status }: { id: string; status: AthProgramStatus }) => {\n const result = await athQuery<{ athTransitionProgram: AthProgram }>(\n `mutation AthTransitionProgram($id: ID!, $status: AthProgramStatus!) {\n athTransitionProgram(id: $id, status: $status) { ${PROGRAM_FIELDS} }\n }`,\n { id, status },\n apiGatewayUrl\n );\n return result.athTransitionProgram;\n },\n onSuccess: (program) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'programs'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'program', program.id] });\n },\n });\n};\n\nexport const useArchiveProgram = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athArchiveProgram: boolean }>(\n `mutation AthArchiveProgram($id: ID!) { athArchiveProgram(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.athArchiveProgram;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'programs'] });\n },\n });\n};\n\nexport const useAddDrill = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthAddDrillInput) => {\n const result = await athQuery<{ athAddDrill: AthDrill }>(\n `mutation AthAddDrill($input: AthAddDrillInput!) {\n athAddDrill(input: $input) { ${DRILL_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athAddDrill;\n },\n onSuccess: (drill) => {\n void queryClient.invalidateQueries({\n queryKey: ['atheletebridge', 'program', drill.programId],\n });\n },\n });\n};\n\nexport const useRemoveDrill = (programId?: string) => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athRemoveDrill: boolean }>(\n `mutation AthRemoveDrill($id: ID!) { athRemoveDrill(id: $id) }`,\n { id },\n apiGatewayUrl\n );\n return result.athRemoveDrill;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'program', programId] });\n },\n });\n};\n\n// ============================================================================\n// Coaching sessions\n// ============================================================================\n\nexport interface SessionFilters {\n athleteId?: string;\n coachId?: string;\n status?: AthSessionStatus;\n pagination?: PaginationInput;\n}\n\nexport const useSessions = (filters: SessionFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'sessions', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListSessions: AthSessionConnection }>(\n `query AthListSessions($athleteId: ID, $coachId: ID, $status: AthSessionStatus, $pagination: PaginationInput) {\n athListSessions(athleteId: $athleteId, coachId: $coachId, status: $status, pagination: $pagination) {\n items { ${SESSION_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n athleteId: filters.athleteId,\n coachId: filters.coachId,\n status: filters.status,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListSessions;\n },\n });\n};\n\nexport const useSession = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'session', id],\n queryFn: async () => {\n const result = await athQuery<{ athSession: AthSession | null }>(\n `query AthSession($id: ID!) {\n athSession(id: $id) { ${SESSION_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athSession;\n },\n enabled: Boolean(id),\n });\n};\n\nexport const useBookSession = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthBookSessionInput) => {\n const result = await athQuery<{ athBookSession: AthSession }>(\n `mutation AthBookSession($input: AthBookSessionInput!) {\n athBookSession(input: $input) { ${SESSION_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athBookSession;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'sessions'] });\n },\n });\n};\n\nexport const useConfirmSession = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athConfirmSession: AthSession }>(\n `mutation AthConfirmSession($id: ID!) {\n athConfirmSession(id: $id) { ${SESSION_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athConfirmSession;\n },\n onSuccess: (session) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'sessions'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'session', session.id] });\n },\n });\n};\n\nexport const useCompleteSession = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async ({ id, input }: { id: string; input?: AthCompleteSessionInput }) => {\n const result = await athQuery<{ athCompleteSession: AthSession }>(\n `mutation AthCompleteSession($id: ID!, $input: AthCompleteSessionInput) {\n athCompleteSession(id: $id, input: $input) { ${SESSION_FIELDS} }\n }`,\n { id, input },\n apiGatewayUrl\n );\n return result.athCompleteSession;\n },\n onSuccess: (session) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'sessions'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'session', session.id] });\n },\n });\n};\n\nexport const useCancelSession = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (id: string) => {\n const result = await athQuery<{ athCancelSession: AthSession }>(\n `mutation AthCancelSession($id: ID!) {\n athCancelSession(id: $id) { ${SESSION_FIELDS} }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.athCancelSession;\n },\n onSuccess: (session) => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'sessions'] });\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'session', session.id] });\n },\n });\n};\n\n// ============================================================================\n// Workout logs & performance metrics\n// ============================================================================\n\nexport interface WorkoutLogFilters {\n athleteId?: string;\n programId?: string;\n pagination?: PaginationInput;\n}\n\nexport const useWorkoutLogs = (filters: WorkoutLogFilters = {}) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'workout-logs', filters],\n queryFn: async () => {\n const result = await athQuery<{ athListWorkoutLogs: AthWorkoutLogConnection }>(\n `query AthListWorkoutLogs($athleteId: ID, $programId: ID, $pagination: PaginationInput) {\n athListWorkoutLogs(athleteId: $athleteId, programId: $programId, pagination: $pagination) {\n items { ${WORKOUT_LOG_FIELDS} }\n pageInfo { cursor hasMore }\n }\n }`,\n {\n athleteId: filters.athleteId,\n programId: filters.programId,\n pagination: filters.pagination,\n },\n apiGatewayUrl\n );\n return result.athListWorkoutLogs;\n },\n });\n};\n\nexport const usePerformanceMetrics = (athleteId: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'metrics', athleteId],\n queryFn: async () => {\n const result = await athQuery<{ athPerformanceMetrics: AthPerformanceMetric[] }>(\n `query AthPerformanceMetrics($athleteId: ID!) {\n athPerformanceMetrics(athleteId: $athleteId) { ${METRIC_FIELDS} }\n }`,\n { athleteId },\n apiGatewayUrl\n );\n return result.athPerformanceMetrics;\n },\n enabled: Boolean(athleteId),\n });\n};\n\nexport const useLogWorkout = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthLogWorkoutInput) => {\n const result = await athQuery<{ athLogWorkout: AthWorkoutLog }>(\n `mutation AthLogWorkout($input: AthLogWorkoutInput!) {\n athLogWorkout(input: $input) { ${WORKOUT_LOG_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athLogWorkout;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'workout-logs'] });\n },\n });\n};\n\nexport const useRecordMetric = () => {\n const queryClient = useQueryClient();\n const { apiGatewayUrl } = useAthleteBridge();\n return useMutation({\n mutationFn: async (input: AthRecordMetricInput) => {\n const result = await athQuery<{ athRecordMetric: AthPerformanceMetric }>(\n `mutation AthRecordMetric($input: AthRecordMetricInput!) {\n athRecordMetric(input: $input) { ${METRIC_FIELDS} }\n }`,\n { input },\n apiGatewayUrl\n );\n return result.athRecordMetric;\n },\n onSuccess: () => {\n void queryClient.invalidateQueries({ queryKey: ['atheletebridge', 'metrics'] });\n },\n });\n};\n\n// ============================================================================\n// Workspace files — drill demo videos live in wspace-files-svc, reached through\n// the same workspace gateway. Blobs never touch the atheletebridge subgraph.\n// ============================================================================\n\nconst fileExtensionOf = (fileName: string): string => {\n const dot = fileName.lastIndexOf('.');\n return dot >= 0 ? fileName.slice(dot + 1).toLowerCase() : 'bin';\n};\n\n/**\n * Upload a file to wspace-files-svc via the standard three-step flow\n * (initiateUpload → presigned PUT → completeUpload) and resolve the new file id,\n * ready to pass as `AthAddDrillInput.videoFileId`.\n */\nexport const useUploadFile = () => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useCallback(\n async (file: File): Promise<string> => {\n const initiated = await athQuery<{\n initiateUpload: { uploadUrl: string | null; session: { id: string } };\n }>(\n `mutation AthInitiateUpload($input: InitiateUploadInput!) {\n initiateUpload(input: $input) { uploadUrl session { id } }\n }`,\n {\n input: {\n fileName: file.name,\n extension: fileExtensionOf(file.name),\n fileSize: file.size,\n mimeType: file.type || 'application/octet-stream',\n visibility: 'WORKSPACE',\n },\n },\n apiGatewayUrl\n );\n\n const uploadUrl = initiated.initiateUpload.uploadUrl;\n const sessionId = initiated.initiateUpload.session.id;\n if (!uploadUrl || !sessionId) {\n throw new Error('Upload could not be initialised.');\n }\n\n const put = await fetch(uploadUrl, {\n method: 'PUT',\n body: file,\n headers: { 'Content-Type': file.type || 'application/octet-stream' },\n });\n if (!put.ok) {\n throw new Error(`Storage rejected the upload (HTTP ${put.status}).`);\n }\n\n const completed = await athQuery<{ completeUpload: { fileId: string } }>(\n `mutation AthCompleteUpload($input: CompleteUploadInput!) {\n completeUpload(input: $input) { fileId }\n }`,\n { input: { sessionId } },\n apiGatewayUrl\n );\n return completed.completeUpload.fileId;\n },\n [apiGatewayUrl]\n );\n};\n\nexport interface WorkspaceFileRef {\n id: string;\n name: string;\n downloadUrl: string | null;\n}\n\n/**\n * Resolve a wspace-files File (a drill's demo video) by id, on demand. Kept as a\n * separate lazy read so a missing/expired blob can never break the program page.\n */\nexport const useFile = (id: string | undefined) => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'file', id],\n queryFn: async () => {\n const result = await athQuery<{ getFile: WorkspaceFileRef | null }>(\n `query AthGetFile($id: ID!) {\n getFile(id: $id) { id name downloadUrl }\n }`,\n { id },\n apiGatewayUrl\n );\n return result.getFile;\n },\n enabled: Boolean(id),\n staleTime: 60_000,\n });\n};\n\n// ============================================================================\n// User directory (global gateway) — resolve real users for team member add so a\n// coach never has to paste a raw UUID.\n// ============================================================================\n\nexport interface DirectoryUser {\n id: string;\n name: string | null;\n firstName: string | null;\n lastName: string | null;\n email: string;\n username: string;\n}\n\n/** Best display name for a directory user (name → first+last → username → email). */\nexport const directoryUserName = (user: DirectoryUser): string => {\n if (user.name?.trim()) return user.name.trim();\n const full = [user.firstName, user.lastName].filter(Boolean).join(' ').trim();\n if (full) return full;\n if (user.username?.trim()) return user.username.trim();\n return user.email;\n};\n\n/**\n * Search the tenant user directory by name/email. Enabled once the term is at\n * least two characters; failures are surfaced to the caller (which offers a\n * manual-id fallback) rather than as a global error toast.\n */\nexport const useUserSearch = (search: string) => {\n const term = search.trim();\n return useQuery({\n queryKey: ['atheletebridge', 'user-search', term],\n queryFn: async () => {\n const result = await athGlobalQuery<{\n users: { users: DirectoryUser[]; hasMore: boolean };\n }>(\n `query AthUserSearch($filter: UserFilterInput, $pagination: PaginationInput) {\n users(filter: $filter, pagination: $pagination) {\n users { id name firstName lastName email username }\n hasMore\n }\n }`,\n { filter: { search: term }, pagination: { limit: 8, offset: 0 } }\n );\n return result.users.users;\n },\n enabled: term.length >= 2,\n staleTime: 30_000,\n retry: false,\n });\n};\n\n// ============================================================================\n// Health probe\n// ============================================================================\n\nexport const useHealth = () => {\n const { apiGatewayUrl } = useAthleteBridge();\n return useQuery({\n queryKey: ['atheletebridge', 'health'],\n queryFn: async () => {\n const result = await athQuery<{ athHealth: string }>(\n `query AthHealth { athHealth }`,\n {},\n apiGatewayUrl\n );\n return result.athHealth;\n },\n });\n};\n"],"mappings":"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"}
@@ -8,162 +8,170 @@ import { EmptyState as o } from "../components/EmptyState.js";
8
8
  import { LoadingState as s } from "../components/LoadingState.js";
9
9
  import { useFixtures as c, usePrograms as l, useSessions as u, useTeams as d } from "../hooks/useAthleteBridgeApi.js";
10
10
  import { useMemo as f } from "react";
11
- import { ArrowRight as p, CalendarDays as m, ClipboardCheck as h, Dumbbell as g, Plus as _, Trophy as v, Users as y } from "lucide-react";
12
- import { jsx as b, jsxs as x } from "react/jsx-runtime";
13
- import { EmphasisPanel as S, PagePurpose as C } from "@burdenoff/fe-libs/ui";
11
+ import { ArrowRight as p, CalendarDays as m, ClipboardCheck as h, Dumbbell as g, Gauge as _, Plus as v, Trophy as y, Users as b } from "lucide-react";
12
+ import { jsx as x, jsxs as S } from "react/jsx-runtime";
13
+ import { EmphasisPanel as C, PagePurpose as w } from "@burdenoff/fe-libs/ui";
14
14
  //#region src/atheletebridge/pages/DashboardPage.tsx
15
- function w() {
16
- let { navigateTo: w, currentUser: T } = e(), E = d({ pagination: { limit: 50 } }), D = c({ pagination: { limit: 50 } }), O = l({ pagination: { limit: 50 } }), k = u({ pagination: { limit: 50 } }), A = T?.firstName ?? T?.name ?? "coach", j = f(() => E.data?.items ?? [], [E.data]), M = f(() => D.data?.items ?? [], [D.data]), N = f(() => O.data?.items ?? [], [O.data]), P = f(() => k.data?.items ?? [], [k.data]), F = f(() => [...M].filter((e) => e.status === "SCHEDULED" || e.status === "LIVE").sort((e, t) => new Date(e.scheduledAt).getTime() - new Date(t.scheduledAt).getTime()).slice(0, 3), [M]), I = f(() => [...P].filter((e) => e.status === "REQUESTED" || e.status === "CONFIRMED").sort((e, t) => new Date(e.scheduledAt).getTime() - new Date(t.scheduledAt).getTime()).slice(0, 3), [P]), L = f(() => j.slice(0, 6), [j]), R = N.filter((e) => e.status === "PUBLISHED").length;
17
- return E.isLoading || D.isLoading || O.isLoading || k.isLoading ? /* @__PURE__ */ b(t, {
15
+ function T() {
16
+ let { navigateTo: T, currentUser: E } = e(), D = d({ pagination: { limit: 50 } }), O = c({ pagination: { limit: 50 } }), k = l({ pagination: { limit: 50 } }), A = u({ pagination: { limit: 50 } }), j = E?.firstName ?? E?.name ?? "coach", M = f(() => D.data?.items ?? [], [D.data]), N = f(() => O.data?.items ?? [], [O.data]), P = f(() => k.data?.items ?? [], [k.data]), F = f(() => A.data?.items ?? [], [A.data]), I = f(() => [...N].filter((e) => e.status === "SCHEDULED" || e.status === "LIVE").sort((e, t) => new Date(e.scheduledAt).getTime() - new Date(t.scheduledAt).getTime()).slice(0, 3), [N]), L = f(() => [...F].filter((e) => e.status === "REQUESTED" || e.status === "CONFIRMED").sort((e, t) => new Date(e.scheduledAt).getTime() - new Date(t.scheduledAt).getTime()).slice(0, 3), [F]), R = f(() => M.slice(0, 6), [M]), z = P.filter((e) => e.status === "PUBLISHED").length;
17
+ return D.isLoading || O.isLoading || k.isLoading || A.isLoading ? /* @__PURE__ */ x(t, {
18
18
  title: "AthleteBridge",
19
19
  hero: !0,
20
20
  gradientTitle: !0,
21
- children: /* @__PURE__ */ b(s, { label: "Loading your sporting operation…" })
22
- }) : /* @__PURE__ */ x(t, {
23
- title: `Welcome back, ${A}`,
21
+ children: /* @__PURE__ */ x(s, { label: "Loading your sporting operation…" })
22
+ }) : /* @__PURE__ */ S(t, {
23
+ title: `Welcome back, ${j}`,
24
24
  description: "Your teams, fixtures, training and coaching — one connected record.",
25
25
  hero: !0,
26
26
  gradientTitle: !0,
27
- actions: /* @__PURE__ */ x("button", {
28
- type: "button",
29
- onClick: () => w("/teams/new"),
30
- className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90",
31
- children: [/* @__PURE__ */ b(_, { className: "h-4 w-4" }), " New team"]
27
+ actions: /* @__PURE__ */ S("div", {
28
+ className: "flex flex-wrap items-center gap-2",
29
+ children: [/* @__PURE__ */ S("button", {
30
+ type: "button",
31
+ onClick: () => T("/performance"),
32
+ className: "inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken",
33
+ children: [/* @__PURE__ */ x(_, { className: "h-4 w-4" }), " Track performance"]
34
+ }), /* @__PURE__ */ S("button", {
35
+ type: "button",
36
+ onClick: () => T("/teams/new"),
37
+ className: "inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90",
38
+ children: [/* @__PURE__ */ x(v, { className: "h-4 w-4" }), " New team"]
39
+ })]
32
40
  }),
33
41
  children: [
34
- /* @__PURE__ */ b(C, {
42
+ /* @__PURE__ */ x(w, {
35
43
  className: "mb-6",
36
44
  children: "AthleteBridge is the operating system for sports, coaching and training. From here you can assemble your roster, schedule fixtures, build training programs, run coaching sessions and track athlete performance — all reading and writing the same workspace-scoped source of truth."
37
45
  }),
38
- /* @__PURE__ */ x(S, {
46
+ /* @__PURE__ */ S(C, {
39
47
  className: "mb-8",
40
- children: [/* @__PURE__ */ b("h2", {
48
+ children: [/* @__PURE__ */ x("h2", {
41
49
  className: "mb-4 text-lg font-semibold text-text-primary",
42
50
  children: "At a glance"
43
- }), /* @__PURE__ */ x("div", {
51
+ }), /* @__PURE__ */ S("div", {
44
52
  className: "grid grid-cols-2 gap-4 lg:grid-cols-4",
45
53
  children: [
46
- /* @__PURE__ */ b(n, {
47
- icon: /* @__PURE__ */ b(y, { className: "h-5 w-5" }),
48
- value: j.length,
54
+ /* @__PURE__ */ x(n, {
55
+ icon: /* @__PURE__ */ x(b, { className: "h-5 w-5" }),
56
+ value: M.length,
49
57
  label: "Teams",
50
58
  featured: !0
51
59
  }),
52
- /* @__PURE__ */ b(n, {
53
- icon: /* @__PURE__ */ b(m, { className: "h-5 w-5" }),
54
- value: M.length,
60
+ /* @__PURE__ */ x(n, {
61
+ icon: /* @__PURE__ */ x(m, { className: "h-5 w-5" }),
62
+ value: N.length,
55
63
  label: "Fixtures"
56
64
  }),
57
- /* @__PURE__ */ b(n, {
58
- icon: /* @__PURE__ */ b(g, { className: "h-5 w-5" }),
59
- value: `${R}/${N.length}`,
65
+ /* @__PURE__ */ x(n, {
66
+ icon: /* @__PURE__ */ x(g, { className: "h-5 w-5" }),
67
+ value: `${z}/${P.length}`,
60
68
  label: "Programs published"
61
69
  }),
62
- /* @__PURE__ */ b(n, {
63
- icon: /* @__PURE__ */ b(h, { className: "h-5 w-5" }),
64
- value: P.length,
70
+ /* @__PURE__ */ x(n, {
71
+ icon: /* @__PURE__ */ x(h, { className: "h-5 w-5" }),
72
+ value: F.length,
65
73
  label: "Sessions"
66
74
  })
67
75
  ]
68
76
  })]
69
77
  }),
70
- /* @__PURE__ */ x("section", {
78
+ /* @__PURE__ */ S("section", {
71
79
  className: "mb-8",
72
- children: [/* @__PURE__ */ x("div", {
80
+ children: [/* @__PURE__ */ S("div", {
73
81
  className: "mb-4 flex items-center justify-between",
74
- children: [/* @__PURE__ */ b("h2", {
82
+ children: [/* @__PURE__ */ x("h2", {
75
83
  className: "text-lg font-semibold text-text-primary",
76
84
  children: "Upcoming fixtures"
77
- }), /* @__PURE__ */ x("button", {
85
+ }), /* @__PURE__ */ S("button", {
78
86
  type: "button",
79
- onClick: () => w("/fixtures"),
87
+ onClick: () => T("/fixtures"),
80
88
  className: "flex items-center gap-1 text-sm font-medium text-primary hover:underline",
81
- children: ["View all ", /* @__PURE__ */ b(p, { className: "h-3.5 w-3.5" })]
89
+ children: ["View all ", /* @__PURE__ */ x(p, { className: "h-3.5 w-3.5" })]
82
90
  })]
83
- }), F.length === 0 ? /* @__PURE__ */ b(o, {
91
+ }), I.length === 0 ? /* @__PURE__ */ x(o, {
84
92
  illustration: "empty-calendar",
85
- icon: /* @__PURE__ */ b(m, { className: "h-10 w-10" }),
93
+ icon: /* @__PURE__ */ x(m, { className: "h-10 w-10" }),
86
94
  title: "No upcoming fixtures",
87
95
  description: "Schedule a match to start tracking results and standings.",
88
- action: /* @__PURE__ */ b("button", {
96
+ action: /* @__PURE__ */ x("button", {
89
97
  type: "button",
90
- onClick: () => w("/fixtures/new"),
98
+ onClick: () => T("/fixtures/new"),
91
99
  className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90",
92
100
  children: "Schedule fixture"
93
101
  })
94
- }) : /* @__PURE__ */ b("div", {
102
+ }) : /* @__PURE__ */ x("div", {
95
103
  className: "grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3",
96
- children: F.map((e) => /* @__PURE__ */ b(i, {
104
+ children: I.map((e) => /* @__PURE__ */ x(i, {
97
105
  fixture: e,
98
- onClick: () => w(`/fixtures/${e.id}`)
106
+ onClick: () => T(`/fixtures/${e.id}`)
99
107
  }, e.id))
100
108
  })]
101
109
  }),
102
- /* @__PURE__ */ x("section", {
110
+ /* @__PURE__ */ S("section", {
103
111
  className: "mb-8",
104
- children: [/* @__PURE__ */ x("div", {
112
+ children: [/* @__PURE__ */ S("div", {
105
113
  className: "mb-4 flex items-center justify-between",
106
- children: [/* @__PURE__ */ b("h2", {
114
+ children: [/* @__PURE__ */ x("h2", {
107
115
  className: "text-lg font-semibold text-text-primary",
108
116
  children: "Upcoming sessions"
109
- }), /* @__PURE__ */ x("button", {
117
+ }), /* @__PURE__ */ S("button", {
110
118
  type: "button",
111
- onClick: () => w("/sessions"),
119
+ onClick: () => T("/sessions"),
112
120
  className: "flex items-center gap-1 text-sm font-medium text-primary hover:underline",
113
- children: ["View all ", /* @__PURE__ */ b(p, { className: "h-3.5 w-3.5" })]
121
+ children: ["View all ", /* @__PURE__ */ x(p, { className: "h-3.5 w-3.5" })]
114
122
  })]
115
- }), I.length === 0 ? /* @__PURE__ */ b(o, {
123
+ }), L.length === 0 ? /* @__PURE__ */ x(o, {
116
124
  illustration: "empty-calendar",
117
- icon: /* @__PURE__ */ b(h, { className: "h-10 w-10" }),
125
+ icon: /* @__PURE__ */ x(h, { className: "h-10 w-10" }),
118
126
  title: "No upcoming sessions",
119
127
  description: "Book a coaching session with an athlete to get started.",
120
- action: /* @__PURE__ */ b("button", {
128
+ action: /* @__PURE__ */ x("button", {
121
129
  type: "button",
122
- onClick: () => w("/sessions/new"),
130
+ onClick: () => T("/sessions/new"),
123
131
  className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90",
124
132
  children: "Book session"
125
133
  })
126
- }) : /* @__PURE__ */ b("div", {
134
+ }) : /* @__PURE__ */ x("div", {
127
135
  className: "grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3",
128
- children: I.map((e) => /* @__PURE__ */ b(a, {
136
+ children: L.map((e) => /* @__PURE__ */ x(a, {
129
137
  session: e,
130
- onClick: () => w(`/sessions/${e.id}`)
138
+ onClick: () => T(`/sessions/${e.id}`)
131
139
  }, e.id))
132
140
  })]
133
141
  }),
134
- /* @__PURE__ */ x("section", { children: [/* @__PURE__ */ x("div", {
142
+ /* @__PURE__ */ S("section", { children: [/* @__PURE__ */ S("div", {
135
143
  className: "mb-4 flex items-center justify-between",
136
- children: [/* @__PURE__ */ b("h2", {
144
+ children: [/* @__PURE__ */ x("h2", {
137
145
  className: "text-lg font-semibold text-text-primary",
138
146
  children: "Your teams"
139
- }), /* @__PURE__ */ x("button", {
147
+ }), /* @__PURE__ */ S("button", {
140
148
  type: "button",
141
- onClick: () => w("/teams"),
149
+ onClick: () => T("/teams"),
142
150
  className: "flex items-center gap-1 text-sm font-medium text-primary hover:underline",
143
- children: ["View all ", /* @__PURE__ */ b(p, { className: "h-3.5 w-3.5" })]
151
+ children: ["View all ", /* @__PURE__ */ x(p, { className: "h-3.5 w-3.5" })]
144
152
  })]
145
- }), L.length === 0 ? /* @__PURE__ */ b(o, {
153
+ }), R.length === 0 ? /* @__PURE__ */ x(o, {
146
154
  illustration: "empty-data",
147
- icon: /* @__PURE__ */ b(v, { className: "h-10 w-10" }),
155
+ icon: /* @__PURE__ */ x(y, { className: "h-10 w-10" }),
148
156
  title: "No teams yet",
149
157
  description: "Create your first team to build a roster and schedule fixtures.",
150
- action: /* @__PURE__ */ b("button", {
158
+ action: /* @__PURE__ */ x("button", {
151
159
  type: "button",
152
- onClick: () => w("/teams/new"),
160
+ onClick: () => T("/teams/new"),
153
161
  className: "rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90",
154
162
  children: "Create team"
155
163
  })
156
- }) : /* @__PURE__ */ b("div", {
164
+ }) : /* @__PURE__ */ x("div", {
157
165
  className: "grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3",
158
- children: L.map((e) => /* @__PURE__ */ b(r, {
166
+ children: R.map((e) => /* @__PURE__ */ x(r, {
159
167
  team: e,
160
- onClick: () => w(`/teams/${e.id}`)
168
+ onClick: () => T(`/teams/${e.id}`)
161
169
  }, e.id))
162
170
  })] })
163
171
  ]
164
172
  });
165
173
  }
166
174
  //#endregion
167
- export { w as DashboardPage };
175
+ export { T as DashboardPage };
168
176
 
169
177
  //# sourceMappingURL=DashboardPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/atheletebridge/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * Dashboard / Overview\n * @module atheletebridge/pages\n *\n * The AthleteBridge landing page: a workspace-wide command center that reads\n * live counts and recent items across teams, fixtures, programs and sessions\n * from the real `ath*` API.\n */\n\nimport { useMemo } from 'react';\nimport {\n Users,\n CalendarDays,\n Dumbbell,\n ClipboardCheck,\n ArrowRight,\n Plus,\n Trophy,\n} from 'lucide-react';\nimport { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { MetricsCard } from '../components/MetricsCard';\nimport { TeamCard } from '../components/TeamCard';\nimport { FixtureCard } from '../components/FixtureCard';\nimport { SessionCard } from '../components/SessionCard';\nimport { EmptyState } from '../components/EmptyState';\nimport { LoadingState } from '../components/LoadingState';\nimport { useAthleteBridge } from '../context/AthleteBridgeContext';\nimport { useTeams, useFixtures, usePrograms, useSessions } from '../hooks/useAthleteBridgeApi';\n\nexport function DashboardPage() {\n const { navigateTo, currentUser } = useAthleteBridge();\n\n const teams = useTeams({ pagination: { limit: 50 } });\n const fixtures = useFixtures({ pagination: { limit: 50 } });\n const programs = usePrograms({ pagination: { limit: 50 } });\n const sessions = useSessions({ pagination: { limit: 50 } });\n\n const firstName = currentUser?.firstName ?? currentUser?.name ?? 'coach';\n\n const teamItems = useMemo(() => teams.data?.items ?? [], [teams.data]);\n const fixtureItems = useMemo(() => fixtures.data?.items ?? [], [fixtures.data]);\n const programItems = useMemo(() => programs.data?.items ?? [], [programs.data]);\n const sessionItems = useMemo(() => sessions.data?.items ?? [], [sessions.data]);\n\n const upcomingFixtures = useMemo(\n () =>\n [...fixtureItems]\n .filter((f) => f.status === 'SCHEDULED' || f.status === 'LIVE')\n .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime())\n .slice(0, 3),\n [fixtureItems]\n );\n\n const upcomingSessions = useMemo(\n () =>\n [...sessionItems]\n .filter((s) => s.status === 'REQUESTED' || s.status === 'CONFIRMED')\n .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime())\n .slice(0, 3),\n [sessionItems]\n );\n\n const recentTeams = useMemo(() => teamItems.slice(0, 6), [teamItems]);\n\n const publishedPrograms = programItems.filter((p) => p.status === 'PUBLISHED').length;\n\n const isLoading =\n teams.isLoading || fixtures.isLoading || programs.isLoading || sessions.isLoading;\n\n if (isLoading) {\n return (\n <PageLayout title=\"AthleteBridge\" hero gradientTitle>\n <LoadingState label=\"Loading your sporting operation…\" />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={`Welcome back, ${firstName}`}\n description=\"Your teams, fixtures, training and coaching — one connected record.\"\n hero\n gradientTitle\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" /> New team\n </button>\n }\n >\n <PagePurpose className=\"mb-6\">\n AthleteBridge is the operating system for sports, coaching and training. From here you can\n assemble your roster, schedule fixtures, build training programs, run coaching sessions and\n track athlete performance — all reading and writing the same workspace-scoped source of\n truth.\n </PagePurpose>\n\n {/* KPIs */}\n <EmphasisPanel className=\"mb-8\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">At a glance</h2>\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <MetricsCard\n icon={<Users className=\"h-5 w-5\" />}\n value={teamItems.length}\n label=\"Teams\"\n featured\n />\n <MetricsCard\n icon={<CalendarDays className=\"h-5 w-5\" />}\n value={fixtureItems.length}\n label=\"Fixtures\"\n />\n <MetricsCard\n icon={<Dumbbell className=\"h-5 w-5\" />}\n value={`${publishedPrograms}/${programItems.length}`}\n label=\"Programs published\"\n />\n <MetricsCard\n icon={<ClipboardCheck className=\"h-5 w-5\" />}\n value={sessionItems.length}\n label=\"Sessions\"\n />\n </div>\n </EmphasisPanel>\n\n {/* Upcoming fixtures */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Upcoming fixtures</h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures')}\n className=\"flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n View all <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n {upcomingFixtures.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<CalendarDays className=\"h-10 w-10\" />}\n title=\"No upcoming fixtures\"\n description=\"Schedule a match to start tracking results and standings.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n Schedule fixture\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {upcomingFixtures.map((fixture) => (\n <FixtureCard\n key={fixture.id}\n fixture={fixture}\n onClick={() => navigateTo(`/fixtures/${fixture.id}`)}\n />\n ))}\n </div>\n )}\n </section>\n\n {/* Upcoming sessions */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Upcoming sessions</h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions')}\n className=\"flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n View all <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n {upcomingSessions.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<ClipboardCheck className=\"h-10 w-10\" />}\n title=\"No upcoming sessions\"\n description=\"Book a coaching session with an athlete to get started.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n Book session\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {upcomingSessions.map((session) => (\n <SessionCard\n key={session.id}\n session={session}\n onClick={() => navigateTo(`/sessions/${session.id}`)}\n />\n ))}\n </div>\n )}\n </section>\n\n {/* Teams */}\n <section>\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Your teams</h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams')}\n className=\"flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n View all <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n {recentTeams.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Trophy className=\"h-10 w-10\" />}\n title=\"No teams yet\"\n description=\"Create your first team to build a roster and schedule fixtures.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n Create team\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {recentTeams.map((team) => (\n <TeamCard key={team.id} team={team} onClick={() => navigateTo(`/teams/${team.id}`)} />\n ))}\n </div>\n )}\n </section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/atheletebridge/pages/DashboardPage.tsx"],"sourcesContent":["/**\n * Dashboard / Overview\n * @module atheletebridge/pages\n *\n * The AthleteBridge landing page: a workspace-wide command center that reads\n * live counts and recent items across teams, fixtures, programs and sessions\n * from the real `ath*` API.\n */\n\nimport { useMemo } from 'react';\nimport {\n Users,\n CalendarDays,\n Dumbbell,\n ClipboardCheck,\n ArrowRight,\n Plus,\n Trophy,\n Gauge,\n} from 'lucide-react';\nimport { EmphasisPanel, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../components/PageLayout';\nimport { MetricsCard } from '../components/MetricsCard';\nimport { TeamCard } from '../components/TeamCard';\nimport { FixtureCard } from '../components/FixtureCard';\nimport { SessionCard } from '../components/SessionCard';\nimport { EmptyState } from '../components/EmptyState';\nimport { LoadingState } from '../components/LoadingState';\nimport { useAthleteBridge } from '../context/AthleteBridgeContext';\nimport { useTeams, useFixtures, usePrograms, useSessions } from '../hooks/useAthleteBridgeApi';\n\nexport function DashboardPage() {\n const { navigateTo, currentUser } = useAthleteBridge();\n\n const teams = useTeams({ pagination: { limit: 50 } });\n const fixtures = useFixtures({ pagination: { limit: 50 } });\n const programs = usePrograms({ pagination: { limit: 50 } });\n const sessions = useSessions({ pagination: { limit: 50 } });\n\n const firstName = currentUser?.firstName ?? currentUser?.name ?? 'coach';\n\n const teamItems = useMemo(() => teams.data?.items ?? [], [teams.data]);\n const fixtureItems = useMemo(() => fixtures.data?.items ?? [], [fixtures.data]);\n const programItems = useMemo(() => programs.data?.items ?? [], [programs.data]);\n const sessionItems = useMemo(() => sessions.data?.items ?? [], [sessions.data]);\n\n const upcomingFixtures = useMemo(\n () =>\n [...fixtureItems]\n .filter((f) => f.status === 'SCHEDULED' || f.status === 'LIVE')\n .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime())\n .slice(0, 3),\n [fixtureItems]\n );\n\n const upcomingSessions = useMemo(\n () =>\n [...sessionItems]\n .filter((s) => s.status === 'REQUESTED' || s.status === 'CONFIRMED')\n .sort((a, b) => new Date(a.scheduledAt).getTime() - new Date(b.scheduledAt).getTime())\n .slice(0, 3),\n [sessionItems]\n );\n\n const recentTeams = useMemo(() => teamItems.slice(0, 6), [teamItems]);\n\n const publishedPrograms = programItems.filter((p) => p.status === 'PUBLISHED').length;\n\n const isLoading =\n teams.isLoading || fixtures.isLoading || programs.isLoading || sessions.isLoading;\n\n if (isLoading) {\n return (\n <PageLayout title=\"AthleteBridge\" hero gradientTitle>\n <LoadingState label=\"Loading your sporting operation…\" />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={`Welcome back, ${firstName}`}\n description=\"Your teams, fixtures, training and coaching — one connected record.\"\n hero\n gradientTitle\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/performance')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Gauge className=\"h-4 w-4\" /> Track performance\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams/new')}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n <Plus className=\"h-4 w-4\" /> New team\n </button>\n </div>\n }\n >\n <PagePurpose className=\"mb-6\">\n AthleteBridge is the operating system for sports, coaching and training. From here you can\n assemble your roster, schedule fixtures, build training programs, run coaching sessions and\n track athlete performance — all reading and writing the same workspace-scoped source of\n truth.\n </PagePurpose>\n\n {/* KPIs */}\n <EmphasisPanel className=\"mb-8\">\n <h2 className=\"mb-4 text-lg font-semibold text-text-primary\">At a glance</h2>\n <div className=\"grid grid-cols-2 gap-4 lg:grid-cols-4\">\n <MetricsCard\n icon={<Users className=\"h-5 w-5\" />}\n value={teamItems.length}\n label=\"Teams\"\n featured\n />\n <MetricsCard\n icon={<CalendarDays className=\"h-5 w-5\" />}\n value={fixtureItems.length}\n label=\"Fixtures\"\n />\n <MetricsCard\n icon={<Dumbbell className=\"h-5 w-5\" />}\n value={`${publishedPrograms}/${programItems.length}`}\n label=\"Programs published\"\n />\n <MetricsCard\n icon={<ClipboardCheck className=\"h-5 w-5\" />}\n value={sessionItems.length}\n label=\"Sessions\"\n />\n </div>\n </EmphasisPanel>\n\n {/* Upcoming fixtures */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Upcoming fixtures</h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures')}\n className=\"flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n View all <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n {upcomingFixtures.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<CalendarDays className=\"h-10 w-10\" />}\n title=\"No upcoming fixtures\"\n description=\"Schedule a match to start tracking results and standings.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n Schedule fixture\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {upcomingFixtures.map((fixture) => (\n <FixtureCard\n key={fixture.id}\n fixture={fixture}\n onClick={() => navigateTo(`/fixtures/${fixture.id}`)}\n />\n ))}\n </div>\n )}\n </section>\n\n {/* Upcoming sessions */}\n <section className=\"mb-8\">\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Upcoming sessions</h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions')}\n className=\"flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n View all <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n {upcomingSessions.length === 0 ? (\n <EmptyState\n illustration=\"empty-calendar\"\n icon={<ClipboardCheck className=\"h-10 w-10\" />}\n title=\"No upcoming sessions\"\n description=\"Book a coaching session with an athlete to get started.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n Book session\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {upcomingSessions.map((session) => (\n <SessionCard\n key={session.id}\n session={session}\n onClick={() => navigateTo(`/sessions/${session.id}`)}\n />\n ))}\n </div>\n )}\n </section>\n\n {/* Teams */}\n <section>\n <div className=\"mb-4 flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold text-text-primary\">Your teams</h2>\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams')}\n className=\"flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n View all <ArrowRight className=\"h-3.5 w-3.5\" />\n </button>\n </div>\n {recentTeams.length === 0 ? (\n <EmptyState\n illustration=\"empty-data\"\n icon={<Trophy className=\"h-10 w-10\" />}\n title=\"No teams yet\"\n description=\"Create your first team to build a roster and schedule fixtures.\"\n action={\n <button\n type=\"button\"\n onClick={() => navigateTo('/teams/new')}\n className=\"rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90\"\n >\n Create team\n </button>\n }\n />\n ) : (\n <div className=\"grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3\">\n {recentTeams.map((team) => (\n <TeamCard key={team.id} team={team} onClick={() => navigateTo(`/teams/${team.id}`)} />\n ))}\n </div>\n )}\n </section>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
@@ -47,18 +47,18 @@ function C() {
47
47
  children: /* @__PURE__ */ y(n, { status: N.status })
48
48
  }),
49
49
  /* @__PURE__ */ b("div", {
50
- className: "flex items-center justify-center gap-6",
50
+ className: "flex items-center justify-center gap-3 sm:gap-6",
51
51
  children: [
52
52
  /* @__PURE__ */ y("span", {
53
- className: "flex-1 text-right text-lg font-semibold text-text-primary",
53
+ className: "min-w-0 flex-1 break-words text-right text-lg font-semibold text-text-primary",
54
54
  children: F
55
55
  }),
56
56
  /* @__PURE__ */ y("span", {
57
- className: "rounded-xl bg-bg-sunken px-5 py-2 text-3xl font-bold tabular-nums text-text-primary",
57
+ className: "shrink-0 rounded-xl bg-bg-sunken px-4 py-2 text-2xl font-bold tabular-nums text-text-primary sm:px-5 sm:text-3xl",
58
58
  children: P ? `${N.homeScore} : ${N.awayScore}` : "vs"
59
59
  }),
60
60
  /* @__PURE__ */ y("span", {
61
- className: "flex-1 text-left text-lg font-semibold text-text-primary",
61
+ className: "min-w-0 flex-1 break-words text-left text-lg font-semibold text-text-primary",
62
62
  children: I
63
63
  })
64
64
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"FixtureDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/fixtures/FixtureDetailPage.tsx"],"sourcesContent":["/**\n * Fixture Detail\n * @module atheletebridge/pages/fixtures\n *\n * Reads a single fixture (athFixture) and lets the user record a final result\n * (athRecordFixtureResult) which marks it COMPLETED and recomputes standings.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, CalendarClock, MapPin, Trophy, Loader2, Flag } from 'lucide-react';\nimport { GlassCard, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { FIXTURE_TYPE_LABELS } from '../../components/domain';\nimport { formatDate } from '../../utils/formatters';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useFixture, useRecordFixtureResult } from '../../hooks/useAthleteBridgeApi';\n\nexport function FixtureDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const fixture = useFixture(id);\n const recordResult = useRecordFixtureResult();\n\n const [homeScore, setHomeScore] = useState('');\n const [awayScore, setAwayScore] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n if (fixture.isLoading) {\n return (\n <PageLayout title=\"Fixture\">\n <LoadingState label=\"Loading fixture…\" />\n </PageLayout>\n );\n }\n\n if (fixture.isError || !fixture.data) {\n return (\n <PageLayout title=\"Fixture\">\n <ErrorState\n title=\"Fixture not found\"\n message=\"This fixture does not exist or you do not have access to it.\"\n onRetry={() => navigateTo('/fixtures')}\n />\n </PageLayout>\n );\n }\n\n const f = fixture.data;\n const hasScore = f.homeScore !== null && f.awayScore !== null;\n const homeName = f.homeTeam?.name ?? 'Home';\n const awayName = f.awayTeam?.name ?? (f.awayTeamId ? 'Away' : 'TBD');\n const canRecordResult =\n Boolean(f.awayTeamId) && (f.status === 'SCHEDULED' || f.status === 'LIVE');\n\n const handleRecord = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!id) return;\n const home = Number.parseInt(homeScore, 10);\n const away = Number.parseInt(awayScore, 10);\n if (Number.isNaN(home) || Number.isNaN(away)) {\n setError('Both scores are required and must be numbers.');\n return;\n }\n if (home < 0 || away < 0) {\n setError('Scores cannot be negative.');\n return;\n }\n try {\n await recordResult.mutateAsync({ id, input: { homeScore: home, awayScore: away } });\n setHomeScore('');\n setAwayScore('');\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to record result.');\n }\n };\n\n return (\n <PageLayout\n title={`${homeName} vs ${awayName}`}\n description={`${FIXTURE_TYPE_LABELS[f.type]}${f.competition ? ` · ${f.competition}` : ''}`}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n }\n >\n {/* Scoreboard */}\n <GlassCard treatment=\"glass\" glow className=\"mb-8 p-8\">\n <div className=\"mb-4 flex items-center justify-center\">\n <StatusBadge status={f.status} />\n </div>\n <div className=\"flex items-center justify-center gap-6\">\n <span className=\"flex-1 text-right text-lg font-semibold text-text-primary\">\n {homeName}\n </span>\n <span className=\"rounded-xl bg-bg-sunken px-5 py-2 text-3xl font-bold tabular-nums text-text-primary\">\n {hasScore ? `${f.homeScore} : ${f.awayScore}` : 'vs'}\n </span>\n <span className=\"flex-1 text-left text-lg font-semibold text-text-primary\">\n {awayName}\n </span>\n </div>\n <div className=\"mt-6 flex flex-wrap items-center justify-center gap-x-6 gap-y-2 text-sm text-text-secondary\">\n <span className=\"flex items-center gap-1.5\">\n <CalendarClock className=\"h-4 w-4\" /> {formatDate(f.scheduledAt)}\n </span>\n {f.venue && (\n <span className=\"flex items-center gap-1.5\">\n <MapPin className=\"h-4 w-4\" /> {f.venue}\n </span>\n )}\n {f.round && <span>{f.round}</span>}\n </div>\n </GlassCard>\n\n {f.notes && (\n <EmphasisPanel className=\"mb-8\">\n <h2 className=\"mb-2 text-sm font-semibold text-text-primary\">Notes</h2>\n <p className=\"text-sm text-text-secondary\">{f.notes}</p>\n </EmphasisPanel>\n )}\n\n {/* Record result */}\n {canRecordResult && (\n <section className=\"mb-8\">\n <h2 className=\"mb-4 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Flag className=\"h-5 w-5\" /> Record result\n </h2>\n <GlassCard treatment=\"insight\" className=\"p-6\">\n <p className=\"mb-4 text-sm text-text-secondary\">\n Recording a final score marks this fixture <strong>Completed</strong> and recomputes\n the competition standings for both teams.\n </p>\n <form onSubmit={handleRecord} className=\"flex flex-wrap items-end gap-4\">\n <FormField label={`${homeName} score`} htmlFor=\"homeScore\" className=\"w-32\">\n <input\n id=\"homeScore\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={homeScore}\n onChange={(e) => setHomeScore(e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n <FormField label={`${awayName} score`} htmlFor=\"awayScore\" className=\"w-32\">\n <input\n id=\"awayScore\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={awayScore}\n onChange={(e) => setAwayScore(e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n <button\n type=\"submit\"\n disabled={recordResult.isPending}\n className=\"inline-flex h-[38px] items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {recordResult.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trophy className=\"h-4 w-4\" />\n )}\n Record\n </button>\n </form>\n {error && <p className=\"mt-3 text-sm text-status-error-text\">{error}</p>}\n </GlassCard>\n </section>\n )}\n\n {f.competition && (\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(`/standings?competition=${encodeURIComponent(f.competition ?? '')}`)\n }\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Trophy className=\"h-4 w-4\" /> View {f.competition} standings\n </button>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"FixtureDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/fixtures/FixtureDetailPage.tsx"],"sourcesContent":["/**\n * Fixture Detail\n * @module atheletebridge/pages/fixtures\n *\n * Reads a single fixture (athFixture) and lets the user record a final result\n * (athRecordFixtureResult) which marks it COMPLETED and recomputes standings.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, CalendarClock, MapPin, Trophy, Loader2, Flag } from 'lucide-react';\nimport { GlassCard, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { FIXTURE_TYPE_LABELS } from '../../components/domain';\nimport { formatDate } from '../../utils/formatters';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useFixture, useRecordFixtureResult } from '../../hooks/useAthleteBridgeApi';\n\nexport function FixtureDetailPage() {\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const fixture = useFixture(id);\n const recordResult = useRecordFixtureResult();\n\n const [homeScore, setHomeScore] = useState('');\n const [awayScore, setAwayScore] = useState('');\n const [error, setError] = useState<string | null>(null);\n\n if (fixture.isLoading) {\n return (\n <PageLayout title=\"Fixture\">\n <LoadingState label=\"Loading fixture…\" />\n </PageLayout>\n );\n }\n\n if (fixture.isError || !fixture.data) {\n return (\n <PageLayout title=\"Fixture\">\n <ErrorState\n title=\"Fixture not found\"\n message=\"This fixture does not exist or you do not have access to it.\"\n onRetry={() => navigateTo('/fixtures')}\n />\n </PageLayout>\n );\n }\n\n const f = fixture.data;\n const hasScore = f.homeScore !== null && f.awayScore !== null;\n const homeName = f.homeTeam?.name ?? 'Home';\n const awayName = f.awayTeam?.name ?? (f.awayTeamId ? 'Away' : 'TBD');\n const canRecordResult =\n Boolean(f.awayTeamId) && (f.status === 'SCHEDULED' || f.status === 'LIVE');\n\n const handleRecord = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!id) return;\n const home = Number.parseInt(homeScore, 10);\n const away = Number.parseInt(awayScore, 10);\n if (Number.isNaN(home) || Number.isNaN(away)) {\n setError('Both scores are required and must be numbers.');\n return;\n }\n if (home < 0 || away < 0) {\n setError('Scores cannot be negative.');\n return;\n }\n try {\n await recordResult.mutateAsync({ id, input: { homeScore: home, awayScore: away } });\n setHomeScore('');\n setAwayScore('');\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to record result.');\n }\n };\n\n return (\n <PageLayout\n title={`${homeName} vs ${awayName}`}\n description={`${FIXTURE_TYPE_LABELS[f.type]}${f.competition ? ` · ${f.competition}` : ''}`}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/fixtures')}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <ArrowLeft className=\"h-4 w-4\" /> Back\n </button>\n }\n >\n {/* Scoreboard */}\n <GlassCard treatment=\"glass\" glow className=\"mb-8 p-8\">\n <div className=\"mb-4 flex items-center justify-center\">\n <StatusBadge status={f.status} />\n </div>\n <div className=\"flex items-center justify-center gap-3 sm:gap-6\">\n <span className=\"min-w-0 flex-1 break-words text-right text-lg font-semibold text-text-primary\">\n {homeName}\n </span>\n <span className=\"shrink-0 rounded-xl bg-bg-sunken px-4 py-2 text-2xl font-bold tabular-nums text-text-primary sm:px-5 sm:text-3xl\">\n {hasScore ? `${f.homeScore} : ${f.awayScore}` : 'vs'}\n </span>\n <span className=\"min-w-0 flex-1 break-words text-left text-lg font-semibold text-text-primary\">\n {awayName}\n </span>\n </div>\n <div className=\"mt-6 flex flex-wrap items-center justify-center gap-x-6 gap-y-2 text-sm text-text-secondary\">\n <span className=\"flex items-center gap-1.5\">\n <CalendarClock className=\"h-4 w-4\" /> {formatDate(f.scheduledAt)}\n </span>\n {f.venue && (\n <span className=\"flex items-center gap-1.5\">\n <MapPin className=\"h-4 w-4\" /> {f.venue}\n </span>\n )}\n {f.round && <span>{f.round}</span>}\n </div>\n </GlassCard>\n\n {f.notes && (\n <EmphasisPanel className=\"mb-8\">\n <h2 className=\"mb-2 text-sm font-semibold text-text-primary\">Notes</h2>\n <p className=\"text-sm text-text-secondary\">{f.notes}</p>\n </EmphasisPanel>\n )}\n\n {/* Record result */}\n {canRecordResult && (\n <section className=\"mb-8\">\n <h2 className=\"mb-4 flex items-center gap-2 text-lg font-semibold text-text-primary\">\n <Flag className=\"h-5 w-5\" /> Record result\n </h2>\n <GlassCard treatment=\"insight\" className=\"p-6\">\n <p className=\"mb-4 text-sm text-text-secondary\">\n Recording a final score marks this fixture <strong>Completed</strong> and recomputes\n the competition standings for both teams.\n </p>\n <form onSubmit={handleRecord} className=\"flex flex-wrap items-end gap-4\">\n <FormField label={`${homeName} score`} htmlFor=\"homeScore\" className=\"w-32\">\n <input\n id=\"homeScore\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={homeScore}\n onChange={(e) => setHomeScore(e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n <FormField label={`${awayName} score`} htmlFor=\"awayScore\" className=\"w-32\">\n <input\n id=\"awayScore\"\n inputMode=\"numeric\"\n className={fieldInputClass}\n value={awayScore}\n onChange={(e) => setAwayScore(e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n <button\n type=\"submit\"\n disabled={recordResult.isPending}\n className=\"inline-flex h-[38px] items-center gap-1.5 rounded-lg bg-action-primary-bg px-4 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {recordResult.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trophy className=\"h-4 w-4\" />\n )}\n Record\n </button>\n </form>\n {error && <p className=\"mt-3 text-sm text-status-error-text\">{error}</p>}\n </GlassCard>\n </section>\n )}\n\n {f.competition && (\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(`/standings?competition=${encodeURIComponent(f.competition ?? '')}`)\n }\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Trophy className=\"h-4 w-4\" /> View {f.competition} standings\n </button>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}