@burdenoff/microfe-atheletebridge 2026.1013.1 → 2026.1024.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/atheletebridge/components/MatchupRow.d.ts.map +1 -1
- package/dist/atheletebridge/components/MatchupRow.js +36 -36
- package/dist/atheletebridge/components/MatchupRow.js.map +1 -1
- package/dist/atheletebridge/components/Scoreboard.d.ts +71 -2
- package/dist/atheletebridge/components/Scoreboard.d.ts.map +1 -1
- package/dist/atheletebridge/components/Scoreboard.js +221 -96
- package/dist/atheletebridge/components/Scoreboard.js.map +1 -1
- package/dist/atheletebridge/hooks/index.d.ts +1 -0
- package/dist/atheletebridge/hooks/index.d.ts.map +1 -1
- package/dist/atheletebridge/hooks/index.js +1 -0
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.d.ts.map +1 -1
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js +93 -82
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js.map +1 -1
- package/dist/atheletebridge/hooks/useScheduledTimeGate.d.ts +18 -0
- package/dist/atheletebridge/hooks/useScheduledTimeGate.d.ts.map +1 -0
- package/dist/atheletebridge/hooks/useScheduledTimeGate.js +24 -0
- package/dist/atheletebridge/hooks/useScheduledTimeGate.js.map +1 -0
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +436 -107
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js +164 -159
- package/dist/atheletebridge/pages/sessions/SessionDetailPage.js.map +1 -1
- package/dist/atheletebridge/pages/tournaments/TournamentBracketPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/tournaments/TournamentBracketPage.js +104 -103
- package/dist/atheletebridge/pages/tournaments/TournamentBracketPage.js.map +1 -1
- package/dist/atheletebridge/types/index.d.ts +74 -1
- package/dist/atheletebridge/types/index.d.ts.map +1 -1
- package/dist/atheletebridge/utils/index.d.ts +2 -0
- package/dist/atheletebridge/utils/index.d.ts.map +1 -1
- package/dist/atheletebridge/utils/recordResult.d.ts +92 -0
- package/dist/atheletebridge/utils/recordResult.d.ts.map +1 -0
- package/dist/atheletebridge/utils/recordResult.js +114 -0
- package/dist/atheletebridge/utils/recordResult.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SessionDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/sessions/SessionDetailPage.tsx"],"sourcesContent":["/**\n * Session Detail\n * @module atheletebridge/pages/sessions\n *\n * Reads a single session (athSession) and drives its lifecycle:\n * confirm (athConfirmSession), complete with feedback + rating\n * (athCompleteSession) and cancel (athCancelSession).\n *\n * The layout mirrors the concept mock: a lifecycle timeline, paired\n * Coach / Athlete / Schedule cards (identities resolved via the platform User\n * federation lookup), an iconized meeting-link card, and an Actions panel with\n * an interactive 5-star rating in the Complete flow. Semantic tokens only,\n * responsive down to a single mobile column (with bottom-sheet dialogs).\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport {\n ArrowLeft,\n CalendarClock,\n Clock,\n MapPin,\n Video,\n CheckCircle2,\n XCircle,\n Loader2,\n Mail,\n ExternalLink,\n} from 'lucide-react';\nimport {\n GlassCard,\n EmphasisPanel,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { UserAvatar } from '../../components/UserAvatar';\nimport { SessionStatusTimeline } from '../../components/SessionStatusTimeline';\nimport { RatingStars } from '../../components/viz';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { SESSION_TYPE_LABELS } from '../../components/domain';\nimport { formatDate, formatDuration } from '../../utils/formatters';\nimport { safeExternalHref } from '../../utils/url';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport {\n useSession,\n useUserDirectory,\n useConfirmSession,\n useCompleteSession,\n useCancelSession,\n} from '../../hooks/useAthleteBridgeApi';\nimport { useAbTranslate } from '../../i18n';\n\n/** A labelled identity tile (Coach / Athlete) — monogram avatar + name + email. */\nfunction PartyCard({\n role,\n name,\n email,\n id,\n}: {\n role: string;\n name?: string | null;\n email?: string | null;\n id: string;\n}) {\n const displayName = name?.trim() || `User ${id.slice(0, 6)}`;\n return (\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-muted\">{role}</p>\n <div className=\"mt-3 flex items-center gap-3\">\n <UserAvatar name={name} id={id} size=\"lg\" />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">{displayName}</p>\n <p className=\"truncate text-xs text-text-secondary\">{role}</p>\n </div>\n </div>\n {email ? (\n <a\n href={`mailto:${email}`}\n className=\"mt-3 flex items-center gap-1.5 text-xs text-text-secondary hover:text-text-primary hover:underline\"\n >\n <Mail className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />\n <span className=\"truncate\">{email}</span>\n </a>\n ) : null}\n </GlassCard>\n );\n}\n\nexport function SessionDetailPage() {\n const tr = useAbTranslate();\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const session = useSession(id);\n const confirmSession = useConfirmSession();\n const completeSession = useCompleteSession();\n const cancelSession = useCancelSession();\n\n const [completeOpen, setCompleteOpen] = useState(false);\n const [feedback, setFeedback] = useState('');\n const [rating, setRating] = useState<number>(0);\n const [error, setError] = useState<string | null>(null);\n\n // Resolve coach + athlete identities in one batched federation call.\n const directoryIds = useMemo(\n () => (session.data ? [session.data.coachId, session.data.athleteId] : []),\n [session.data]\n );\n const directory = useUserDirectory(directoryIds);\n\n if (session.isLoading) {\n return (\n <PageLayout title={tr('atheletebridge.pages.sessionDetail.title', 'Session')}>\n <LoadingState\n label={tr('atheletebridge.pages.sessionDetail.loading', 'Loading session…')}\n />\n </PageLayout>\n );\n }\n\n if (session.isError || !session.data) {\n return (\n <PageLayout title={tr('atheletebridge.pages.sessionDetail.title', 'Session')}>\n <ErrorState\n title={tr('atheletebridge.pages.sessionDetail.notFoundTitle', 'Session not found')}\n message={tr(\n 'atheletebridge.pages.sessionDetail.notFoundMessage',\n 'This session does not exist or you do not have access to it.'\n )}\n onRetry={() => navigateTo('/sessions')}\n />\n </PageLayout>\n );\n }\n\n const s = session.data;\n const canConfirm = s.status === 'REQUESTED';\n const canComplete = s.status === 'CONFIRMED';\n const canCancel = s.status === 'REQUESTED' || s.status === 'CONFIRMED';\n // Sanitize the user-controlled meeting URL before it becomes a clickable href.\n const meetingHref = safeExternalHref(s.meetingUrl);\n\n const handleComplete = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!id) return;\n const ratingNum = rating > 0 ? rating : undefined;\n if (ratingNum !== undefined && (ratingNum < 1 || ratingNum > 5)) {\n setError('Rating must be between 1 and 5.');\n return;\n }\n try {\n await completeSession.mutateAsync({\n id,\n input: {\n feedback: feedback.trim() || undefined,\n rating: ratingNum,\n },\n });\n setCompleteOpen(false);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to complete session.');\n }\n };\n\n return (\n <PageLayout\n title={s.title}\n description={SESSION_TYPE_LABELS[s.type]}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions')}\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\" /> {tr('atheletebridge.pages.sessionDetail.back', 'Back')}\n </button>\n }\n >\n {/* Lifecycle timeline + prominent status */}\n <GlassCard treatment=\"insight\" className=\"mb-6 p-5 sm:p-6\">\n <div className=\"mb-5 flex items-center justify-between gap-3\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('atheletebridge.pages.sessionDetail.lifecycle', 'Lifecycle')}\n </span>\n <StatusBadge status={s.status} />\n </div>\n <SessionStatusTimeline\n status={s.status}\n createdAt={s.createdAt}\n completedAt={s.completedAt}\n />\n </GlassCard>\n\n {/* Coach / Athlete / Schedule — three-up on desktop, stacked on mobile */}\n <div className=\"mb-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <PartyCard\n role={tr('atheletebridge.pages.sessionDetail.coach', 'Coach')}\n id={s.coachId}\n name={directory.nameOf(s.coachId)}\n email={directory.emailOf(s.coachId)}\n />\n <PartyCard\n role={tr('atheletebridge.pages.sessionDetail.athlete', 'Athlete')}\n id={s.athleteId}\n name={directory.nameOf(s.athleteId)}\n email={directory.emailOf(s.athleteId)}\n />\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('atheletebridge.pages.sessionDetail.schedule', 'Schedule')}\n </p>\n <div className=\"mt-3 space-y-2.5 text-sm text-text-primary\">\n <span className=\"flex items-center gap-2\">\n <CalendarClock className=\"h-4 w-4 shrink-0 text-text-secondary\" aria-hidden=\"true\" />\n {formatDate(s.scheduledAt)}\n </span>\n <span className=\"flex items-center gap-2 text-text-secondary\">\n <Clock className=\"h-4 w-4 shrink-0\" aria-hidden=\"true\" />\n {formatDuration(s.durationMin)}\n </span>\n {s.type === 'IN_PERSON' && s.location ? (\n <span className=\"flex items-center gap-2 text-text-secondary\">\n <MapPin className=\"h-4 w-4 shrink-0\" aria-hidden=\"true\" /> {s.location}\n </span>\n ) : null}\n </div>\n </GlassCard>\n </div>\n\n {/* Details + Actions */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n {/* Left: session details, meeting link, outcome */}\n <div className=\"space-y-6\">\n <GlassCard treatment=\"glass\" className=\"p-5 sm:p-6\">\n <h2 className=\"text-sm font-semibold text-text-primary\">\n {tr('atheletebridge.pages.sessionDetail.sessionDetails', 'Session details')}\n </h2>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {SESSION_TYPE_LABELS[s.type]}{' '}\n {tr('atheletebridge.pages.sessionDetail.sessionTypeSuffix', 'session')}\n {s.location ? ` · ${s.location}` : ''}.\n </p>\n\n {meetingHref ? (\n <a\n href={meetingHref}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"mt-4 flex items-center gap-3 rounded-xl border border-border-seam bg-bg-sunken p-3 transition-colors hover:bg-bg-surface focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n <Video className=\"h-5 w-5\" aria-hidden=\"true\" />\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block text-sm font-medium text-text-primary\">\n {tr('atheletebridge.pages.sessionDetail.meetingLink', 'Meeting link')}\n </span>\n <span className=\"block truncate text-xs text-text-secondary\">\n {tr(\n 'atheletebridge.pages.sessionDetail.onlineSessionHint',\n 'Online session — tap to join'\n )}\n </span>\n </span>\n <span className=\"inline-flex items-center gap-1 text-sm font-medium text-text-link\">\n {tr('atheletebridge.pages.sessionDetail.join', 'Join')}{' '}\n <ExternalLink className=\"h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </a>\n ) : null}\n\n {s.completedAt ? (\n <div className=\"mt-5 border-t border-border-seam pt-5\">\n <h3 className=\"mb-2 text-sm font-semibold text-text-primary\">\n {tr('atheletebridge.pages.sessionDetail.outcome', 'Outcome')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr('atheletebridge.pages.sessionDetail.completed', 'Completed')}{' '}\n {formatDate(s.completedAt)}\n </p>\n {s.rating !== null ? (\n <div className=\"mt-2\">\n <RatingStars value={s.rating} size={18} showValue />\n </div>\n ) : null}\n {s.feedback ? (\n <p className=\"mt-3 rounded-lg bg-bg-sunken p-3 text-sm italic text-text-secondary\">\n “{s.feedback}”\n </p>\n ) : null}\n </div>\n ) : null}\n </GlassCard>\n </div>\n\n {/* Right: lifecycle actions */}\n {canConfirm || canComplete || canCancel ? (\n <EmphasisPanel className=\"h-fit\">\n <h2 className=\"mb-4 text-sm font-semibold text-text-primary\">\n {tr('atheletebridge.pages.sessionDetail.actions', 'Actions')}\n </h2>\n <div className=\"flex flex-col gap-2.5\">\n {canConfirm && (\n <button\n type=\"button\"\n onClick={() => id && confirmSession.mutate(id)}\n disabled={confirmSession.isPending}\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {confirmSession.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"h-4 w-4\" />\n )}\n {tr('atheletebridge.pages.sessionDetail.confirm', 'Confirm')}\n </button>\n )}\n {canComplete && (\n <button\n type=\"button\"\n onClick={() => {\n setError(null);\n setFeedback(s.feedback ?? '');\n setRating(s.rating ?? 0);\n setCompleteOpen(true);\n }}\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lg bg-status-success-bg-subtle px-4 py-2.5 text-sm font-medium text-status-success-text ring-1 ring-inset ring-status-success-border transition-colors hover:bg-status-success-bg-subtle/70\"\n >\n <CheckCircle2 className=\"h-4 w-4\" />{' '}\n {tr('atheletebridge.pages.sessionDetail.complete', 'Complete')}\n </button>\n )}\n {canCancel && (\n <button\n type=\"button\"\n onClick={() => id && cancelSession.mutate(id)}\n disabled={cancelSession.isPending}\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lg border border-status-error-border px-4 py-2.5 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-60\"\n >\n {cancelSession.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <XCircle className=\"h-4 w-4\" />\n )}\n {tr('atheletebridge.pages.sessionDetail.cancelSession', 'Cancel session')}\n </button>\n )}\n </div>\n {canComplete ? (\n <p className=\"mt-3 text-xs text-text-muted\">\n {tr(\n 'atheletebridge.pages.sessionDetail.completeTooltip',\n 'Completing lets you leave feedback and a 1–5 star rating for the athlete.'\n )}\n </p>\n ) : null}\n </EmphasisPanel>\n ) : null}\n </div>\n\n {/* Complete dialog */}\n <ResponsiveDialog open={completeOpen} onOpenChange={setCompleteOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('atheletebridge.pages.sessionDetail.completeDialogTitle', 'Complete session')}\n </ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n <form onSubmit={handleComplete} className=\"space-y-4 px-1\">\n <FormField\n label={tr('atheletebridge.pages.sessionDetail.feedbackLabel', 'Feedback')}\n htmlFor=\"feedback\"\n hint={tr(\n 'atheletebridge.pages.sessionDetail.feedbackHint',\n 'Optional post-session notes.'\n )}\n >\n <textarea\n id=\"feedback\"\n className={fieldInputClass}\n rows={3}\n value={feedback}\n onChange={(e) => setFeedback(e.target.value)}\n placeholder={tr(\n 'atheletebridge.pages.sessionDetail.feedbackPlaceholder',\n 'How did the session go?'\n )}\n />\n </FormField>\n <FormField\n label={tr('atheletebridge.pages.sessionDetail.ratingLabel', 'Rating')}\n hint={tr('atheletebridge.pages.sessionDetail.ratingHint', 'Optional — tap a star.')}\n >\n <RatingStars\n value={rating}\n onChange={setRating}\n size={30}\n showLabels\n ariaLabel={tr(\n 'atheletebridge.pages.sessionDetail.ratingAria',\n 'Rate this session out of 5'\n )}\n />\n </FormField>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={() => setCompleteOpen(false)}\n className=\"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 {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={completeSession.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-status-success-bg-subtle px-4 py-2 text-sm font-medium text-status-success-text ring-1 ring-inset ring-status-success-border transition-colors hover:bg-status-success-bg-subtle/70 disabled:opacity-60\"\n >\n {completeSession.isPending && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n {tr('atheletebridge.pages.sessionDetail.completeAction', 'Complete session')}\n </button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SessionDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/sessions/SessionDetailPage.tsx"],"sourcesContent":["/**\n * Session Detail\n * @module atheletebridge/pages/sessions\n *\n * Reads a single session (athSession) and drives its lifecycle:\n * confirm (athConfirmSession), complete with feedback + rating\n * (athCompleteSession) and cancel (athCancelSession).\n *\n * The layout mirrors the concept mock: a lifecycle timeline, paired\n * Coach / Athlete / Schedule cards (identities resolved via the platform User\n * federation lookup), an iconized meeting-link card, and an Actions panel with\n * an interactive 5-star rating in the Complete flow. Semantic tokens only,\n * responsive down to a single mobile column (with bottom-sheet dialogs).\n */\n\nimport { useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport {\n ArrowLeft,\n CalendarClock,\n Clock,\n MapPin,\n Video,\n CheckCircle2,\n XCircle,\n Loader2,\n Mail,\n ExternalLink,\n} from 'lucide-react';\nimport {\n GlassCard,\n EmphasisPanel,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogFooter,\n} from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { UserAvatar } from '../../components/UserAvatar';\nimport { SessionStatusTimeline } from '../../components/SessionStatusTimeline';\nimport { RatingStars } from '../../components/viz';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { SESSION_TYPE_LABELS } from '../../components/domain';\nimport { formatDate, formatDuration } from '../../utils/formatters';\nimport { safeExternalHref } from '../../utils/url';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport {\n useSession,\n useUserDirectory,\n useConfirmSession,\n useCompleteSession,\n useCancelSession,\n} from '../../hooks/useAthleteBridgeApi';\nimport { useScheduledTimeGate } from '../../hooks';\nimport { useAbTranslate } from '../../i18n';\n\n/** A labelled identity tile (Coach / Athlete) — monogram avatar + name + email. */\nfunction PartyCard({\n role,\n name,\n email,\n id,\n}: {\n role: string;\n name?: string | null;\n email?: string | null;\n id: string;\n}) {\n const displayName = name?.trim() || `User ${id.slice(0, 6)}`;\n return (\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-muted\">{role}</p>\n <div className=\"mt-3 flex items-center gap-3\">\n <UserAvatar name={name} id={id} size=\"lg\" />\n <div className=\"min-w-0\">\n <p className=\"truncate text-sm font-semibold text-text-primary\">{displayName}</p>\n <p className=\"truncate text-xs text-text-secondary\">{role}</p>\n </div>\n </div>\n {email ? (\n <a\n href={`mailto:${email}`}\n className=\"mt-3 flex items-center gap-1.5 text-xs text-text-secondary hover:text-text-primary hover:underline\"\n >\n <Mail className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />\n <span className=\"truncate\">{email}</span>\n </a>\n ) : null}\n </GlassCard>\n );\n}\n\nexport function SessionDetailPage() {\n const tr = useAbTranslate();\n const { id } = useParams<{ id: string }>();\n const { navigateTo } = useAthleteBridge();\n\n const session = useSession(id);\n const confirmSession = useConfirmSession();\n const completeSession = useCompleteSession();\n const cancelSession = useCancelSession();\n\n const [completeOpen, setCompleteOpen] = useState(false);\n const [feedback, setFeedback] = useState('');\n const [rating, setRating] = useState<number>(0);\n const [error, setError] = useState<string | null>(null);\n // Time gate for the complete action; re-renders when the session starts.\n const sessionStarted = useScheduledTimeGate(session.data?.scheduledAt);\n\n // Resolve coach + athlete identities in one batched federation call.\n const directoryIds = useMemo(\n () => (session.data ? [session.data.coachId, session.data.athleteId] : []),\n [session.data]\n );\n const directory = useUserDirectory(directoryIds);\n\n if (session.isLoading) {\n return (\n <PageLayout title={tr('atheletebridge.pages.sessionDetail.title', 'Session')}>\n <LoadingState\n label={tr('atheletebridge.pages.sessionDetail.loading', 'Loading session…')}\n />\n </PageLayout>\n );\n }\n\n if (session.isError || !session.data) {\n return (\n <PageLayout title={tr('atheletebridge.pages.sessionDetail.title', 'Session')}>\n <ErrorState\n title={tr('atheletebridge.pages.sessionDetail.notFoundTitle', 'Session not found')}\n message={tr(\n 'atheletebridge.pages.sessionDetail.notFoundMessage',\n 'This session does not exist or you do not have access to it.'\n )}\n onRetry={() => navigateTo('/sessions')}\n />\n </PageLayout>\n );\n }\n\n const s = session.data;\n const canConfirm = s.status === 'REQUESTED';\n // Completion is gated on kickoff here and enforced server-side too. Both\n // branches share isCompletable so they cannot drift apart.\n const isCompletable = s.status === 'CONFIRMED';\n const canComplete = isCompletable && sessionStarted;\n const awaitingStart = isCompletable && !sessionStarted;\n const canCancel = s.status === 'REQUESTED' || s.status === 'CONFIRMED';\n // Sanitize the user-controlled meeting URL before it becomes a clickable href.\n const meetingHref = safeExternalHref(s.meetingUrl);\n\n const handleComplete = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!id) return;\n const ratingNum = rating > 0 ? rating : undefined;\n if (ratingNum !== undefined && (ratingNum < 1 || ratingNum > 5)) {\n setError('Rating must be between 1 and 5.');\n return;\n }\n try {\n await completeSession.mutateAsync({\n id,\n input: {\n feedback: feedback.trim() || undefined,\n rating: ratingNum,\n },\n });\n setCompleteOpen(false);\n } catch (err) {\n setError(err instanceof Error ? err.message : 'Failed to complete session.');\n }\n };\n\n return (\n <PageLayout\n title={s.title}\n description={SESSION_TYPE_LABELS[s.type]}\n actions={\n <button\n type=\"button\"\n onClick={() => navigateTo('/sessions')}\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\" /> {tr('atheletebridge.pages.sessionDetail.back', 'Back')}\n </button>\n }\n >\n {/* Lifecycle timeline + prominent status */}\n <GlassCard treatment=\"insight\" className=\"mb-6 p-5 sm:p-6\">\n <div className=\"mb-5 flex items-center justify-between gap-3\">\n <span className=\"text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('atheletebridge.pages.sessionDetail.lifecycle', 'Lifecycle')}\n </span>\n <StatusBadge status={s.status} />\n </div>\n <SessionStatusTimeline\n status={s.status}\n createdAt={s.createdAt}\n completedAt={s.completedAt}\n />\n </GlassCard>\n\n {/* Coach / Athlete / Schedule — three-up on desktop, stacked on mobile */}\n <div className=\"mb-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <PartyCard\n role={tr('atheletebridge.pages.sessionDetail.coach', 'Coach')}\n id={s.coachId}\n name={directory.nameOf(s.coachId)}\n email={directory.emailOf(s.coachId)}\n />\n <PartyCard\n role={tr('atheletebridge.pages.sessionDetail.athlete', 'Athlete')}\n id={s.athleteId}\n name={directory.nameOf(s.athleteId)}\n email={directory.emailOf(s.athleteId)}\n />\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-xs font-medium uppercase tracking-wide text-text-muted\">\n {tr('atheletebridge.pages.sessionDetail.schedule', 'Schedule')}\n </p>\n <div className=\"mt-3 space-y-2.5 text-sm text-text-primary\">\n <span className=\"flex items-center gap-2\">\n <CalendarClock className=\"h-4 w-4 shrink-0 text-text-secondary\" aria-hidden=\"true\" />\n {formatDate(s.scheduledAt)}\n </span>\n <span className=\"flex items-center gap-2 text-text-secondary\">\n <Clock className=\"h-4 w-4 shrink-0\" aria-hidden=\"true\" />\n {formatDuration(s.durationMin)}\n </span>\n {s.type === 'IN_PERSON' && s.location ? (\n <span className=\"flex items-center gap-2 text-text-secondary\">\n <MapPin className=\"h-4 w-4 shrink-0\" aria-hidden=\"true\" /> {s.location}\n </span>\n ) : null}\n </div>\n </GlassCard>\n </div>\n\n {/* Details + Actions */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n {/* Left: session details, meeting link, outcome */}\n <div className=\"space-y-6\">\n <GlassCard treatment=\"glass\" className=\"p-5 sm:p-6\">\n <h2 className=\"text-sm font-semibold text-text-primary\">\n {tr('atheletebridge.pages.sessionDetail.sessionDetails', 'Session details')}\n </h2>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {SESSION_TYPE_LABELS[s.type]}{' '}\n {tr('atheletebridge.pages.sessionDetail.sessionTypeSuffix', 'session')}\n {s.location ? ` · ${s.location}` : ''}.\n </p>\n\n {meetingHref ? (\n <a\n href={meetingHref}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"mt-4 flex items-center gap-3 rounded-xl border border-border-seam bg-bg-sunken p-3 transition-colors hover:bg-bg-surface focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]\"\n >\n <span className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\">\n <Video className=\"h-5 w-5\" aria-hidden=\"true\" />\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block text-sm font-medium text-text-primary\">\n {tr('atheletebridge.pages.sessionDetail.meetingLink', 'Meeting link')}\n </span>\n <span className=\"block truncate text-xs text-text-secondary\">\n {tr(\n 'atheletebridge.pages.sessionDetail.onlineSessionHint',\n 'Online session — tap to join'\n )}\n </span>\n </span>\n <span className=\"inline-flex items-center gap-1 text-sm font-medium text-text-link\">\n {tr('atheletebridge.pages.sessionDetail.join', 'Join')}{' '}\n <ExternalLink className=\"h-4 w-4\" aria-hidden=\"true\" />\n </span>\n </a>\n ) : null}\n\n {s.completedAt ? (\n <div className=\"mt-5 border-t border-border-seam pt-5\">\n <h3 className=\"mb-2 text-sm font-semibold text-text-primary\">\n {tr('atheletebridge.pages.sessionDetail.outcome', 'Outcome')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr('atheletebridge.pages.sessionDetail.completed', 'Completed')}{' '}\n {formatDate(s.completedAt)}\n </p>\n {s.rating !== null ? (\n <div className=\"mt-2\">\n <RatingStars value={s.rating} size={18} showValue />\n </div>\n ) : null}\n {s.feedback ? (\n <p className=\"mt-3 rounded-lg bg-bg-sunken p-3 text-sm italic text-text-secondary\">\n “{s.feedback}”\n </p>\n ) : null}\n </div>\n ) : null}\n </GlassCard>\n </div>\n\n {/* Right: lifecycle actions */}\n {canConfirm || canComplete || canCancel ? (\n <EmphasisPanel className=\"h-fit\">\n <h2 className=\"mb-4 text-sm font-semibold text-text-primary\">\n {tr('atheletebridge.pages.sessionDetail.actions', 'Actions')}\n </h2>\n <div className=\"flex flex-col gap-2.5\">\n {canConfirm && (\n <button\n type=\"button\"\n onClick={() => id && confirmSession.mutate(id)}\n disabled={confirmSession.isPending}\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lg bg-action-primary-bg px-4 py-2.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {confirmSession.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <CheckCircle2 className=\"h-4 w-4\" />\n )}\n {tr('atheletebridge.pages.sessionDetail.confirm', 'Confirm')}\n </button>\n )}\n {canComplete && (\n <button\n type=\"button\"\n onClick={() => {\n setError(null);\n setFeedback(s.feedback ?? '');\n setRating(s.rating ?? 0);\n setCompleteOpen(true);\n }}\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lg bg-status-success-bg-subtle px-4 py-2.5 text-sm font-medium text-status-success-text ring-1 ring-inset ring-status-success-border transition-colors hover:bg-status-success-bg-subtle/70\"\n >\n <CheckCircle2 className=\"h-4 w-4\" />{' '}\n {tr('atheletebridge.pages.sessionDetail.complete', 'Complete')}\n </button>\n )}\n {canCancel && (\n <button\n type=\"button\"\n onClick={() => id && cancelSession.mutate(id)}\n disabled={cancelSession.isPending}\n className=\"inline-flex w-full items-center justify-center gap-2 rounded-lg border border-status-error-border px-4 py-2.5 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:opacity-60\"\n >\n {cancelSession.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <XCircle className=\"h-4 w-4\" />\n )}\n {tr('atheletebridge.pages.sessionDetail.cancelSession', 'Cancel session')}\n </button>\n )}\n </div>\n {canComplete ? (\n <p className=\"mt-3 text-xs text-text-muted\">\n {tr(\n 'atheletebridge.pages.sessionDetail.completeTooltip',\n 'Completing lets you leave feedback and a 1–5 star rating for the athlete.'\n )}\n </p>\n ) : awaitingStart ? (\n <p className=\"mt-3 text-xs text-text-muted\">\n {tr(\n 'atheletebridge.pages.sessionDetail.completeUnavailable',\n 'This session can be completed once its scheduled date and time arrives.'\n )}\n </p>\n ) : null}\n </EmphasisPanel>\n ) : null}\n </div>\n\n {/* Complete dialog */}\n <ResponsiveDialog open={completeOpen} onOpenChange={setCompleteOpen}>\n <ResponsiveDialogContent>\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('atheletebridge.pages.sessionDetail.completeDialogTitle', 'Complete session')}\n </ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n <form onSubmit={handleComplete} className=\"space-y-4 px-1\">\n <FormField\n label={tr('atheletebridge.pages.sessionDetail.feedbackLabel', 'Feedback')}\n htmlFor=\"feedback\"\n hint={tr(\n 'atheletebridge.pages.sessionDetail.feedbackHint',\n 'Optional post-session notes.'\n )}\n >\n <textarea\n id=\"feedback\"\n className={fieldInputClass}\n rows={3}\n value={feedback}\n onChange={(e) => setFeedback(e.target.value)}\n placeholder={tr(\n 'atheletebridge.pages.sessionDetail.feedbackPlaceholder',\n 'How did the session go?'\n )}\n />\n </FormField>\n <FormField\n label={tr('atheletebridge.pages.sessionDetail.ratingLabel', 'Rating')}\n hint={tr('atheletebridge.pages.sessionDetail.ratingHint', 'Optional — tap a star.')}\n >\n <RatingStars\n value={rating}\n onChange={setRating}\n size={30}\n showLabels\n ariaLabel={tr(\n 'atheletebridge.pages.sessionDetail.ratingAria',\n 'Rate this session out of 5'\n )}\n />\n </FormField>\n {error && <p className=\"text-sm text-status-error-text\">{error}</p>}\n <ResponsiveDialogFooter>\n <button\n type=\"button\"\n onClick={() => setCompleteOpen(false)}\n className=\"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 {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n disabled={completeSession.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-status-success-bg-subtle px-4 py-2 text-sm font-medium text-status-success-text ring-1 ring-inset ring-status-success-border transition-colors hover:bg-status-success-bg-subtle/70 disabled:opacity-60\"\n >\n {completeSession.isPending && <Loader2 className=\"h-4 w-4 animate-spin\" />}\n {tr('atheletebridge.pages.sessionDetail.completeAction', 'Complete session')}\n </button>\n </ResponsiveDialogFooter>\n </form>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TournamentBracketPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/tournaments/TournamentBracketPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AA4FH,wBAAgB,qBAAqB,
|
|
1
|
+
{"version":3,"file":"TournamentBracketPage.d.ts","sourceRoot":"","sources":["../../../../src/atheletebridge/pages/tournaments/TournamentBracketPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AA4FH,wBAAgB,qBAAqB,gCA6MpC"}
|
|
@@ -4,29 +4,29 @@ import { STATUS as n } from "../../components/viz/lib/palette.js";
|
|
|
4
4
|
import "../../components/viz/index.js";
|
|
5
5
|
import { Crest as r } from "../../components/Crest.js";
|
|
6
6
|
import { formatDate as i } from "../../utils/formatters.js";
|
|
7
|
-
import { resolveMatchup as a } from "../../components/Scoreboard.js";
|
|
8
|
-
import { EmptyState as
|
|
9
|
-
import { ErrorState as
|
|
10
|
-
import { useFixtures as
|
|
11
|
-
import { FilterSelect as
|
|
12
|
-
import { useMemo as
|
|
13
|
-
import { Gamepad2 as
|
|
14
|
-
import { jsx as
|
|
15
|
-
import { Badge as
|
|
7
|
+
import { resolveMatchup as a, useMatchupLabels as o } from "../../components/Scoreboard.js";
|
|
8
|
+
import { EmptyState as s } from "../../components/EmptyState.js";
|
|
9
|
+
import { ErrorState as c, LoadingState as l } from "../../components/LoadingState.js";
|
|
10
|
+
import { useFixtures as u } from "../../hooks/useAthleteBridgeApi.js";
|
|
11
|
+
import { FilterSelect as d } from "../../components/FilterSelect.js";
|
|
12
|
+
import { useMemo as f, useState as p } from "react";
|
|
13
|
+
import { Gamepad2 as m, Trophy as h } from "lucide-react";
|
|
14
|
+
import { jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
15
|
+
import { Badge as v, GlassCard as y } from "@burdenoff/fe-libs/ui";
|
|
16
16
|
//#region src/atheletebridge/pages/tournaments/TournamentBracketPage.tsx
|
|
17
|
-
var
|
|
17
|
+
var b = {
|
|
18
18
|
COMPLETED: n.success,
|
|
19
19
|
LIVE: "var(--color-accent-primary)",
|
|
20
20
|
SCHEDULED: n.warning,
|
|
21
21
|
POSTPONED: "var(--color-text-muted)",
|
|
22
22
|
CANCELLED: "var(--color-text-muted)"
|
|
23
23
|
};
|
|
24
|
-
function
|
|
24
|
+
function x(e) {
|
|
25
25
|
if (!e) return 9999;
|
|
26
26
|
let t = e.toLowerCase(), n = t.match(/round of\s*(\d+)/);
|
|
27
27
|
return n ? 1e3 - Number(n[1]) : /third|3rd/.test(t) ? 1095 : /(grand\s*)?final/.test(t) && !/semi|quarter/.test(t) ? 1100 : /semi/.test(t) ? 1050 : /quarter/.test(t) ? 1020 : 9999;
|
|
28
28
|
}
|
|
29
|
-
function
|
|
29
|
+
function S(e) {
|
|
30
30
|
let t = /* @__PURE__ */ new Map();
|
|
31
31
|
for (let n of e) {
|
|
32
32
|
let e = n.round?.trim() || "Unscheduled round", r = t.get(e) ?? [];
|
|
@@ -37,111 +37,111 @@ function x(e) {
|
|
|
37
37
|
return {
|
|
38
38
|
key: e,
|
|
39
39
|
label: e,
|
|
40
|
-
rank:
|
|
40
|
+
rank: x(t[0]?.round ?? null),
|
|
41
41
|
earliest: Number.isFinite(r) ? r : 0,
|
|
42
42
|
fixtures: n
|
|
43
43
|
};
|
|
44
44
|
}).sort((e, t) => e.rank === t.rank ? e.earliest - t.earliest : e.rank - t.rank);
|
|
45
45
|
}
|
|
46
|
-
function
|
|
47
|
-
let { navigateTo:
|
|
46
|
+
function C() {
|
|
47
|
+
let { navigateTo: b } = e(), x = u({ pagination: { limit: 200 } }), C = f(() => (x.data?.items ?? []).filter((e) => e.type === "TOURNAMENT"), [x.data]), T = f(() => {
|
|
48
48
|
let e = /* @__PURE__ */ new Set();
|
|
49
|
-
for (let t of
|
|
49
|
+
for (let t of C) e.add(t.competition?.trim() || "Tournament");
|
|
50
50
|
return Array.from(e).sort();
|
|
51
|
-
}, [
|
|
52
|
-
let e =
|
|
51
|
+
}, [C]), [D, O] = p(""), k = D || T[0] || "", A = f(() => S(C.filter((e) => (e.competition?.trim() || "Tournament") === k)), [C, k]), j = A.reduce((e, t) => e + t.fixtures.length, 0), M = o(), N = f(() => {
|
|
52
|
+
let e = A[A.length - 1];
|
|
53
53
|
if (!e) return null;
|
|
54
54
|
for (let t of e.fixtures) {
|
|
55
55
|
if (t.status !== "COMPLETED") continue;
|
|
56
|
-
let e = a(t);
|
|
56
|
+
let e = a(t, M), n = e.cricket ? e.homeWon ? `${e.cricket.homeLabel} v ${e.cricket.awayLabel}` : `${e.cricket.awayLabel} v ${e.cricket.homeLabel}` : e.homeWon ? `${t.homeScore}–${t.awayScore}` : `${t.awayScore}–${t.homeScore}`, r = e.decidedBy && e.outcomeNote ? `${n} (${e.outcomeNote})` : n;
|
|
57
57
|
if (e.homeWon) return {
|
|
58
58
|
name: e.homeName,
|
|
59
59
|
sport: e.homeSport,
|
|
60
|
-
score:
|
|
60
|
+
score: r
|
|
61
61
|
};
|
|
62
62
|
if (e.awayWon) return {
|
|
63
63
|
name: e.awayName,
|
|
64
64
|
sport: e.awaySport,
|
|
65
|
-
score:
|
|
65
|
+
score: r
|
|
66
66
|
};
|
|
67
67
|
}
|
|
68
68
|
return null;
|
|
69
|
-
}, [
|
|
70
|
-
return
|
|
69
|
+
}, [A, M]);
|
|
70
|
+
return x.isLoading ? /* @__PURE__ */ g(t, {
|
|
71
71
|
title: "Tournaments",
|
|
72
|
-
children: /* @__PURE__ */
|
|
73
|
-
}) :
|
|
72
|
+
children: /* @__PURE__ */ g(l, { label: "Loading tournament fixtures…" })
|
|
73
|
+
}) : x.isError ? /* @__PURE__ */ g(t, {
|
|
74
74
|
title: "Tournaments",
|
|
75
|
-
children: /* @__PURE__ */
|
|
76
|
-
}) : /* @__PURE__ */
|
|
75
|
+
children: /* @__PURE__ */ g(c, { onRetry: () => void x.refetch() })
|
|
76
|
+
}) : /* @__PURE__ */ g(t, {
|
|
77
77
|
title: "Tournaments",
|
|
78
78
|
description: "Round-grouped brackets for your knockout competitions — every match is a real fixture.",
|
|
79
|
-
actions:
|
|
80
|
-
value:
|
|
81
|
-
onChange: (e) =>
|
|
82
|
-
options:
|
|
79
|
+
actions: T.length > 1 ? /* @__PURE__ */ g(d, {
|
|
80
|
+
value: k,
|
|
81
|
+
onChange: (e) => O(e),
|
|
82
|
+
options: T.map((e) => ({
|
|
83
83
|
value: e,
|
|
84
84
|
label: e
|
|
85
85
|
})),
|
|
86
86
|
allLabel: "All competitions",
|
|
87
87
|
ariaLabel: "Select competition"
|
|
88
88
|
}) : void 0,
|
|
89
|
-
children:
|
|
89
|
+
children: C.length === 0 ? /* @__PURE__ */ g(s, {
|
|
90
90
|
illustration: "empty-calendar",
|
|
91
|
-
icon: /* @__PURE__ */ h
|
|
91
|
+
icon: /* @__PURE__ */ g(h, { className: "h-10 w-10" }),
|
|
92
92
|
title: "No tournament fixtures yet",
|
|
93
93
|
description: "Schedule fixtures with the Tournament type and a round label (e.g. “Quarter Finals”) to build a bracket.",
|
|
94
|
-
action: /* @__PURE__ */
|
|
94
|
+
action: /* @__PURE__ */ g("button", {
|
|
95
95
|
type: "button",
|
|
96
|
-
onClick: () =>
|
|
96
|
+
onClick: () => b("/fixtures/new"),
|
|
97
97
|
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",
|
|
98
98
|
children: "Schedule a fixture"
|
|
99
99
|
})
|
|
100
|
-
}) : /* @__PURE__ */
|
|
100
|
+
}) : /* @__PURE__ */ _("div", {
|
|
101
101
|
className: "space-y-6",
|
|
102
102
|
children: [
|
|
103
|
-
/* @__PURE__ */
|
|
103
|
+
/* @__PURE__ */ _(y, {
|
|
104
104
|
treatment: "insight",
|
|
105
105
|
className: "flex flex-wrap items-center gap-x-6 gap-y-3 p-5",
|
|
106
106
|
children: [
|
|
107
|
-
/* @__PURE__ */
|
|
107
|
+
/* @__PURE__ */ _("span", {
|
|
108
108
|
className: "flex items-center gap-2 text-lg font-semibold text-text-primary",
|
|
109
|
-
children: [/* @__PURE__ */ h
|
|
109
|
+
children: [/* @__PURE__ */ g(h, { className: "h-5 w-5 text-[var(--color-accent-teal)]" }), k]
|
|
110
110
|
}),
|
|
111
|
-
/* @__PURE__ */
|
|
111
|
+
/* @__PURE__ */ _("span", {
|
|
112
112
|
className: "text-sm text-text-secondary",
|
|
113
113
|
children: [
|
|
114
|
-
/* @__PURE__ */
|
|
114
|
+
/* @__PURE__ */ g("span", {
|
|
115
115
|
className: "font-semibold tabular-nums text-text-primary",
|
|
116
|
-
children:
|
|
116
|
+
children: A.length
|
|
117
117
|
}),
|
|
118
118
|
" ",
|
|
119
|
-
|
|
119
|
+
A.length === 1 ? "round" : "rounds"
|
|
120
120
|
]
|
|
121
121
|
}),
|
|
122
|
-
/* @__PURE__ */
|
|
122
|
+
/* @__PURE__ */ _("span", {
|
|
123
123
|
className: "text-sm text-text-secondary",
|
|
124
124
|
children: [
|
|
125
|
-
/* @__PURE__ */
|
|
125
|
+
/* @__PURE__ */ g("span", {
|
|
126
126
|
className: "font-semibold tabular-nums text-text-primary",
|
|
127
|
-
children:
|
|
127
|
+
children: j
|
|
128
128
|
}),
|
|
129
129
|
" ",
|
|
130
|
-
|
|
130
|
+
j === 1 ? "match" : "matches"
|
|
131
131
|
]
|
|
132
132
|
}),
|
|
133
|
-
/* @__PURE__ */
|
|
133
|
+
/* @__PURE__ */ _("div", {
|
|
134
134
|
className: "ml-auto flex flex-wrap items-center gap-x-4 gap-y-1 text-xs text-text-secondary",
|
|
135
135
|
children: [
|
|
136
|
-
/* @__PURE__ */
|
|
136
|
+
/* @__PURE__ */ g(E, {
|
|
137
137
|
color: n.success,
|
|
138
138
|
label: "Completed"
|
|
139
139
|
}),
|
|
140
|
-
/* @__PURE__ */
|
|
140
|
+
/* @__PURE__ */ g(E, {
|
|
141
141
|
color: "var(--color-accent-primary)",
|
|
142
142
|
label: "Live"
|
|
143
143
|
}),
|
|
144
|
-
/* @__PURE__ */
|
|
144
|
+
/* @__PURE__ */ g(E, {
|
|
145
145
|
color: n.warning,
|
|
146
146
|
label: "Scheduled"
|
|
147
147
|
})
|
|
@@ -149,54 +149,55 @@ function S() {
|
|
|
149
149
|
})
|
|
150
150
|
]
|
|
151
151
|
}),
|
|
152
|
-
/* @__PURE__ */
|
|
152
|
+
/* @__PURE__ */ g("div", {
|
|
153
153
|
className: "overflow-x-auto pb-2",
|
|
154
|
-
children: /* @__PURE__ */
|
|
154
|
+
children: /* @__PURE__ */ _("div", {
|
|
155
155
|
className: "flex min-w-max items-stretch gap-4",
|
|
156
|
-
children: [
|
|
156
|
+
children: [A.map((e) => /* @__PURE__ */ _("div", {
|
|
157
157
|
className: "flex w-72 shrink-0 flex-col gap-3",
|
|
158
|
-
children: [/* @__PURE__ */
|
|
158
|
+
children: [/* @__PURE__ */ _("div", {
|
|
159
159
|
className: "px-1",
|
|
160
|
-
children: [/* @__PURE__ */
|
|
160
|
+
children: [/* @__PURE__ */ g("h2", {
|
|
161
161
|
className: "text-xs font-semibold uppercase tracking-wide text-text-secondary",
|
|
162
162
|
children: e.label
|
|
163
|
-
}), e.earliest > 0 ? /* @__PURE__ */
|
|
163
|
+
}), e.earliest > 0 ? /* @__PURE__ */ g("p", {
|
|
164
164
|
className: "text-xs text-text-muted",
|
|
165
165
|
children: i(new Date(e.earliest).toISOString())
|
|
166
166
|
}) : null]
|
|
167
|
-
}), /* @__PURE__ */
|
|
167
|
+
}), /* @__PURE__ */ g("div", {
|
|
168
168
|
className: "flex flex-1 flex-col justify-around gap-3",
|
|
169
|
-
children: e.fixtures.map((e) => /* @__PURE__ */
|
|
169
|
+
children: e.fixtures.map((e) => /* @__PURE__ */ g(w, {
|
|
170
170
|
fixture: e,
|
|
171
|
-
|
|
171
|
+
labels: M,
|
|
172
|
+
onClick: () => b(`/fixtures/${e.id}`)
|
|
172
173
|
}, e.id))
|
|
173
174
|
})]
|
|
174
|
-
}, e.key)),
|
|
175
|
+
}, e.key)), N ? /* @__PURE__ */ _("div", {
|
|
175
176
|
className: "flex w-72 shrink-0 flex-col justify-center gap-3",
|
|
176
|
-
children: [/* @__PURE__ */
|
|
177
|
+
children: [/* @__PURE__ */ g("div", {
|
|
177
178
|
className: "px-1",
|
|
178
|
-
children: /* @__PURE__ */
|
|
179
|
+
children: /* @__PURE__ */ g("h2", {
|
|
179
180
|
className: "text-xs font-semibold uppercase tracking-wide text-text-secondary",
|
|
180
181
|
children: "Champion"
|
|
181
182
|
})
|
|
182
|
-
}), /* @__PURE__ */
|
|
183
|
+
}), /* @__PURE__ */ _(y, {
|
|
183
184
|
treatment: "glass",
|
|
184
185
|
className: "flex flex-col items-center gap-3 p-6 text-center",
|
|
185
186
|
children: [
|
|
186
|
-
/* @__PURE__ */ h
|
|
187
|
-
/* @__PURE__ */
|
|
188
|
-
name:
|
|
189
|
-
sport:
|
|
187
|
+
/* @__PURE__ */ g(h, { className: "h-8 w-8 text-[var(--color-status-warning-text)]" }),
|
|
188
|
+
/* @__PURE__ */ g(r, {
|
|
189
|
+
name: N.name,
|
|
190
|
+
sport: N.sport,
|
|
190
191
|
size: 56
|
|
191
192
|
}),
|
|
192
|
-
/* @__PURE__ */
|
|
193
|
+
/* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("p", {
|
|
193
194
|
className: "text-lg font-semibold text-text-primary",
|
|
194
|
-
children:
|
|
195
|
-
}), /* @__PURE__ */
|
|
195
|
+
children: N.name
|
|
196
|
+
}), /* @__PURE__ */ _("p", {
|
|
196
197
|
className: "mt-0.5 text-sm tabular-nums text-text-secondary",
|
|
197
|
-
children: ["Won ",
|
|
198
|
+
children: ["Won ", N.score]
|
|
198
199
|
})] }),
|
|
199
|
-
/* @__PURE__ */
|
|
200
|
+
/* @__PURE__ */ g(v, {
|
|
200
201
|
variant: "default",
|
|
201
202
|
children: "Champions"
|
|
202
203
|
})
|
|
@@ -205,9 +206,9 @@ function S() {
|
|
|
205
206
|
}) : null]
|
|
206
207
|
})
|
|
207
208
|
}),
|
|
208
|
-
|
|
209
|
+
A.length === 0 ? /* @__PURE__ */ g(s, {
|
|
209
210
|
illustration: "empty-calendar",
|
|
210
|
-
icon: /* @__PURE__ */
|
|
211
|
+
icon: /* @__PURE__ */ g(m, { className: "h-10 w-10" }),
|
|
211
212
|
title: "No matches in this competition",
|
|
212
213
|
description: "Add round labels to this competition's tournament fixtures to populate the bracket."
|
|
213
214
|
}) : null
|
|
@@ -215,44 +216,44 @@ function S() {
|
|
|
215
216
|
})
|
|
216
217
|
});
|
|
217
218
|
}
|
|
218
|
-
function
|
|
219
|
-
let
|
|
220
|
-
return /* @__PURE__ */
|
|
219
|
+
function w({ fixture: e, onClick: t, labels: n }) {
|
|
220
|
+
let r = a(e, n), i = b[e.status];
|
|
221
|
+
return /* @__PURE__ */ _("button", {
|
|
221
222
|
type: "button",
|
|
222
223
|
onClick: t,
|
|
223
224
|
className: "group w-full rounded-[var(--radius-card)] border border-border-seam bg-bg-surface p-3 text-left transition-all duration-200 hover:-translate-y-0.5 hover:border-border-strong hover:shadow-elevation-2 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]",
|
|
224
225
|
children: [
|
|
225
|
-
/* @__PURE__ */
|
|
226
|
-
name:
|
|
227
|
-
sport:
|
|
226
|
+
/* @__PURE__ */ g(T, {
|
|
227
|
+
name: r.homeName,
|
|
228
|
+
sport: r.homeSport,
|
|
228
229
|
score: e.homeScore,
|
|
229
|
-
won:
|
|
230
|
-
dimmed:
|
|
230
|
+
won: r.homeWon,
|
|
231
|
+
dimmed: r.awayWon
|
|
231
232
|
}),
|
|
232
|
-
/* @__PURE__ */
|
|
233
|
+
/* @__PURE__ */ g("div", {
|
|
233
234
|
className: "my-1 h-px bg-border-seam",
|
|
234
235
|
"aria-hidden": "true"
|
|
235
236
|
}),
|
|
236
|
-
/* @__PURE__ */
|
|
237
|
-
name:
|
|
238
|
-
sport:
|
|
237
|
+
/* @__PURE__ */ g(T, {
|
|
238
|
+
name: r.awayName,
|
|
239
|
+
sport: r.awaySport,
|
|
239
240
|
score: e.awayScore,
|
|
240
|
-
won:
|
|
241
|
-
dimmed:
|
|
241
|
+
won: r.awayWon,
|
|
242
|
+
dimmed: r.homeWon
|
|
242
243
|
}),
|
|
243
|
-
/* @__PURE__ */
|
|
244
|
+
/* @__PURE__ */ _("div", {
|
|
244
245
|
className: "mt-2 flex items-center gap-1.5 text-xs text-text-muted",
|
|
245
246
|
children: [
|
|
246
|
-
/* @__PURE__ */
|
|
247
|
+
/* @__PURE__ */ g("span", {
|
|
247
248
|
className: "h-1.5 w-1.5 rounded-full",
|
|
248
|
-
style: { backgroundColor:
|
|
249
|
+
style: { backgroundColor: i },
|
|
249
250
|
"aria-hidden": "true"
|
|
250
251
|
}),
|
|
251
|
-
/* @__PURE__ */
|
|
252
|
+
/* @__PURE__ */ g("span", {
|
|
252
253
|
className: "capitalize",
|
|
253
254
|
children: e.status.toLowerCase().replace(/_/g, " ")
|
|
254
255
|
}),
|
|
255
|
-
e.venue ? /* @__PURE__ */
|
|
256
|
+
e.venue ? /* @__PURE__ */ _("span", {
|
|
256
257
|
className: "truncate",
|
|
257
258
|
children: ["· ", e.venue]
|
|
258
259
|
}) : null
|
|
@@ -261,31 +262,31 @@ function C({ fixture: e, onClick: t }) {
|
|
|
261
262
|
]
|
|
262
263
|
});
|
|
263
264
|
}
|
|
264
|
-
function
|
|
265
|
-
return /* @__PURE__ */
|
|
265
|
+
function T({ name: e, sport: t, score: n, won: i, dimmed: a }) {
|
|
266
|
+
return /* @__PURE__ */ _("div", {
|
|
266
267
|
className: "flex items-center gap-2",
|
|
267
268
|
children: [
|
|
268
|
-
/* @__PURE__ */
|
|
269
|
+
/* @__PURE__ */ g(r, {
|
|
269
270
|
name: e,
|
|
270
271
|
sport: t,
|
|
271
272
|
size: 22,
|
|
272
273
|
showSportGlyph: !1
|
|
273
274
|
}),
|
|
274
|
-
/* @__PURE__ */
|
|
275
|
+
/* @__PURE__ */ g("span", {
|
|
275
276
|
className: i ? "min-w-0 flex-1 truncate text-sm font-semibold text-status-success-text" : a ? "min-w-0 flex-1 truncate text-sm text-text-muted" : "min-w-0 flex-1 truncate text-sm font-medium text-text-primary",
|
|
276
277
|
children: e
|
|
277
278
|
}),
|
|
278
|
-
/* @__PURE__ */
|
|
279
|
+
/* @__PURE__ */ g("span", {
|
|
279
280
|
className: i ? "shrink-0 text-sm font-bold tabular-nums text-status-success-text" : "shrink-0 text-sm font-semibold tabular-nums text-text-secondary",
|
|
280
281
|
children: n ?? "–"
|
|
281
282
|
})
|
|
282
283
|
]
|
|
283
284
|
});
|
|
284
285
|
}
|
|
285
|
-
function
|
|
286
|
-
return /* @__PURE__ */
|
|
286
|
+
function E({ color: e, label: t }) {
|
|
287
|
+
return /* @__PURE__ */ _("span", {
|
|
287
288
|
className: "flex items-center gap-1.5",
|
|
288
|
-
children: [/* @__PURE__ */
|
|
289
|
+
children: [/* @__PURE__ */ g("span", {
|
|
289
290
|
className: "h-2 w-2 rounded-full",
|
|
290
291
|
style: { backgroundColor: e },
|
|
291
292
|
"aria-hidden": "true"
|
|
@@ -293,6 +294,6 @@ function T({ color: e, label: t }) {
|
|
|
293
294
|
});
|
|
294
295
|
}
|
|
295
296
|
//#endregion
|
|
296
|
-
export {
|
|
297
|
+
export { C as TournamentBracketPage };
|
|
297
298
|
|
|
298
299
|
//# sourceMappingURL=TournamentBracketPage.js.map
|