@burdenoff/microfe-atheletebridge 2026.1031.2 → 2026.1031.3
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/hooks/useAthleteBridgeApi.d.ts +5 -2
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.d.ts.map +1 -1
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js +297 -281
- package/dist/atheletebridge/hooks/useAthleteBridgeApi.js.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.d.ts.map +1 -1
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js +123 -416
- package/dist/atheletebridge/pages/fixtures/FixtureDetailPage.js.map +1 -1
- package/dist/atheletebridge/utils/fixtureCapabilities.d.ts +19 -0
- package/dist/atheletebridge/utils/fixtureCapabilities.d.ts.map +1 -0
- package/dist/atheletebridge/utils/fixtureCapabilities.js +21 -0
- package/dist/atheletebridge/utils/fixtureCapabilities.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FixtureDetailPage.js","names":[],"sources":["../../../../src/atheletebridge/pages/fixtures/FixtureDetailPage.tsx"],"sourcesContent":["/**\n * Fixture Detail\n * @module atheletebridge/pages/fixtures\n *\n * Reads a single fixture (athFixture) and lets the user record a final result\n * (athRecordFixtureResult) which marks it COMPLETED and recomputes standings.\n *\n * The header is a crest-vs-crest Scoreboard hero; the record-result panel shows\n * each team's crest beside its score input. Semantic tokens only, responsive.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router';\nimport { ArrowLeft, Trophy, Loader2, Flag, Minus } from 'lucide-react';\nimport { GlassCard, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { Scoreboard } from '../../components/Scoreboard';\nimport { Crest } from '../../components/Crest';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { FIXTURE_TYPE_LABELS } from '../../components/domain';\nimport { AvailabilityRsvpControl } from '../../components/availability/AvailabilityRsvpControl';\nimport { AvailabilityRosterPanel } from '../../components/availability/AvailabilityRosterPanel';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useScheduledTimeGate } from '../../hooks';\nimport { useFixture, useRecordFixtureResult } from '../../hooks/useAthleteBridgeApi';\nimport {\n buildCricketResultInput,\n buildRecordResultInput,\n parseScoreInput,\n} from '../../utils/recordResult';\nimport { useAbTranslate } from '../../i18n';\n\nexport function FixtureDetailPage() {\n const tr = useAbTranslate();\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 // Knockout outcome (BOFF-7640) — only offered when the fixture is level at\n // full time; a tie still level after extra time must record the shootout.\n const [extraTimeHome, setExtraTimeHome] = useState('');\n const [extraTimeAway, setExtraTimeAway] = useState('');\n const [penaltyHome, setPenaltyHome] = useState('');\n const [penaltyAway, setPenaltyAway] = useState('');\n // Cricket scorecard form (BOFF-7644) — replaces the generic score inputs\n // when the fixture's home team plays cricket.\n const [cricketForm, setCricketForm] = useState({\n homeRuns: '',\n homeWickets: '',\n homeOvers: '',\n homeBalls: '',\n awayRuns: '',\n awayWickets: '',\n awayOvers: '',\n awayBalls: '',\n winner: 'HOME' as 'HOME' | 'AWAY',\n decidedBy: 'RUNS' as 'RUNS' | 'WICKETS',\n margin: '',\n superOver: false,\n dls: false,\n });\n const [error, setError] = useState<string | null>(null);\n // Time gate for the completion action; re-renders when kickoff arrives.\n const fixtureStarted = useScheduledTimeGate(fixture.data?.scheduledAt);\n\n const setCricket = <K extends keyof typeof cricketForm>(\n key: K,\n value: (typeof cricketForm)[K]\n ): void => setCricketForm((prev) => ({ ...prev, [key]: value }));\n\n const resetCricketForm = (): void =>\n setCricketForm({\n homeRuns: '',\n homeWickets: '',\n homeOvers: '',\n homeBalls: '',\n awayRuns: '',\n awayWickets: '',\n awayOvers: '',\n awayBalls: '',\n winner: 'HOME',\n decidedBy: 'RUNS',\n margin: '',\n superOver: false,\n dls: false,\n });\n\n if (fixture.isLoading) {\n return (\n <PageLayout title={tr('atheletebridge.pages.fixtureDetail.title', 'Fixture')}>\n <LoadingState\n label={tr('atheletebridge.pages.fixtureDetail.loading', 'Loading fixture…')}\n />\n </PageLayout>\n );\n }\n\n if (fixture.isError || !fixture.data) {\n return (\n <PageLayout title={tr('atheletebridge.pages.fixtureDetail.title', 'Fixture')}>\n <ErrorState\n title={tr('atheletebridge.pages.fixtureDetail.notFoundTitle', 'Fixture not found')}\n message={tr(\n 'atheletebridge.pages.fixtureDetail.notFoundMessage',\n 'This fixture does not exist or you do not have access to it.'\n )}\n onRetry={() => navigateTo('/fixtures')}\n />\n </PageLayout>\n );\n }\n\n const f = fixture.data;\n const homeName = f.homeTeam?.name ?? tr('atheletebridge.pages.fixtureDetail.home', 'Home');\n const awayName =\n f.awayTeam?.name ??\n (f.awayTeamId ? tr('atheletebridge.pages.fixtureDetail.away', 'Away') : 'TBD');\n // Result entry is the completion transition, gated on kickoff here and\n // enforced server-side too. Both branches share isResultEligible so they\n // cannot drift apart.\n const isResultEligible =\n Boolean(f.awayTeamId) && (f.status === 'SCHEDULED' || f.status === 'LIVE');\n const canRecordResult = isResultEligible && fixtureStarted;\n const awaitingKickoff = isResultEligible && !fixtureStarted;\n\n const isCricket = f.homeTeam?.sport === 'CRICKET';\n\n // Knockout-outcome inputs (BOFF-7640) — offered once the full-time score is\n // entered and level. The shootout appears while the tie is still level\n // (straight from full time, or after a level extra time).\n const parsedHomeScore = parseScoreInput(homeScore);\n const parsedAwayScore = parseScoreInput(awayScore);\n const fullTimeScoresEntered = parsedHomeScore !== null && parsedAwayScore !== null;\n const levelAtFullTime = fullTimeScoresEntered && parsedHomeScore === parsedAwayScore;\n const extraTimeEntered = extraTimeHome !== '' || extraTimeAway !== '';\n const extraTimeParsedHome = parseScoreInput(extraTimeHome);\n const extraTimeParsedAway = parseScoreInput(extraTimeAway);\n const extraTimeComplete = extraTimeParsedHome !== null && extraTimeParsedAway !== null;\n const stillLevelAfterExtraTime = extraTimeComplete && extraTimeParsedHome === extraTimeParsedAway;\n const showPenaltyShootout = levelAtFullTime && (!extraTimeEntered || stillLevelAfterExtraTime);\n\n const handleRecord = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!id) return;\n\n if (isCricket) {\n const draft = buildCricketResultInput(\n { ...cricketForm },\n {\n required: tr(\n 'atheletebridge.pages.fixtureDetail.cricketRequired',\n 'All innings scores, overs and the margin are required.'\n ),\n negative: tr(\n 'atheletebridge.pages.fixtureDetail.cricketNegative',\n 'Scores and overs cannot be negative.'\n ),\n wicketsRange: tr(\n 'atheletebridge.pages.fixtureDetail.cricketWicketsRange',\n 'Wickets must be between 0 and 10.'\n ),\n ballsRange: tr(\n 'atheletebridge.pages.fixtureDetail.cricketBallsRange',\n 'Balls must be between 0 and 5.'\n ),\n marginMinimum: tr(\n 'atheletebridge.pages.fixtureDetail.cricketMarginMinimum',\n 'The margin must be at least 1.'\n ),\n wicketMarginMax: tr(\n 'atheletebridge.pages.fixtureDetail.cricketWicketMarginMax',\n 'A win by wickets cannot exceed a margin of 10.'\n ),\n }\n );\n if (!draft.ok) {\n setError(draft.error);\n return;\n }\n try {\n await recordResult.mutateAsync({ id, input: draft.input });\n resetCricketForm();\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('atheletebridge.pages.fixtureDetail.recordFailed', 'Failed to record result.')\n );\n }\n return;\n }\n\n // Non-cricket fixtures: a single pair of scores, optionally followed by a\n // knockout outcome. All the rules live in `buildRecordResultInput` so the\n // page only renders copy and submits the payload it hands back.\n const draft = buildRecordResultInput(\n { homeScore, awayScore, extraTimeHome, extraTimeAway, penaltyHome, penaltyAway },\n {\n scoresRequired: tr(\n 'atheletebridge.pages.fixtureDetail.scoresRequired',\n 'Both scores are required and must be numbers.'\n ),\n scoresNegative: tr(\n 'atheletebridge.pages.fixtureDetail.scoresNegative',\n 'Scores cannot be negative.'\n ),\n extraTimePair: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimePair',\n 'Enter both extra-time scores.'\n ),\n extraTimeNumeric: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimeNumeric',\n 'Extra-time scores must be numbers.'\n ),\n extraTimeNegative: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimeNegative',\n 'Extra-time scores cannot be negative.'\n ),\n penaltyPair: tr(\n 'atheletebridge.pages.fixtureDetail.penaltyPair',\n 'Enter both penalty scores.'\n ),\n penaltyNumeric: tr(\n 'atheletebridge.pages.fixtureDetail.penaltyNumeric',\n 'Penalty scores must be numbers.'\n ),\n penaltyNegative: tr(\n 'atheletebridge.pages.fixtureDetail.penaltyNegative',\n 'Penalty scores cannot be negative.'\n ),\n extraTimeDecided: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimeDecided',\n 'Extra time decided the tie — leave the penalty shootout empty.'\n ),\n extraTimeLevel: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimeLevel',\n 'Still level after extra time — record the penalty shootout.'\n ),\n penaltyLevel: tr(\n 'atheletebridge.pages.fixtureDetail.penaltyLevel',\n 'Penalty scores must have a winner.'\n ),\n }\n );\n if (!draft.ok) {\n setError(draft.error);\n return;\n }\n try {\n await recordResult.mutateAsync({ id, input: draft.input });\n setHomeScore('');\n setAwayScore('');\n setExtraTimeHome('');\n setExtraTimeAway('');\n setPenaltyHome('');\n setPenaltyAway('');\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('atheletebridge.pages.fixtureDetail.recordFailed', 'Failed to record result.')\n );\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\" /> {tr('atheletebridge.pages.fixtureDetail.back', 'Back')}\n </button>\n }\n >\n {/* Crest-vs-crest hero scoreboard */}\n <Scoreboard fixture={f} variant=\"hero\" className=\"mb-8\" />\n\n {/* Availability / RSVP (BOFF-4134) — athlete's own response + coach roster view */}\n <div className=\"mb-8 grid grid-cols-1 gap-4 lg:grid-cols-[minmax(0,1fr)_2fr]\">\n <AvailabilityRsvpControl teamId={f.homeTeamId} fixtureId={f.id} />\n <AvailabilityRosterPanel teamId={f.homeTeamId} fixtureId={f.id} />\n </div>\n\n {f.notes && (\n <EmphasisPanel className=\"mb-8\">\n <h2 className=\"mb-2 text-sm font-semibold text-text-primary\">\n {tr('atheletebridge.pages.fixtureDetail.notes', 'Notes')}\n </h2>\n <p className=\"text-sm text-text-secondary\">{f.notes}</p>\n </EmphasisPanel>\n )}\n\n {/* Record result */}\n {awaitingKickoff && (\n <EmphasisPanel className=\"mb-8\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'atheletebridge.pages.fixtureDetail.recordResultUnavailable',\n 'A result can be recorded once this fixture has started — after its scheduled date and time.'\n )}\n </p>\n </EmphasisPanel>\n )}\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\" />{' '}\n {tr('atheletebridge.pages.fixtureDetail.recordResult', 'Record result')}\n </h2>\n <GlassCard treatment=\"insight\" className=\"p-5 sm:p-6\">\n <p className=\"mb-5 text-sm text-text-secondary\">\n {tr(\n 'atheletebridge.pages.fixtureDetail.recordResultDescription',\n 'Recording a final score marks this fixture Completed and recomputes the competition standings for both teams.'\n )}\n </p>\n <form onSubmit={handleRecord}>\n {isCricket ? (\n <div className=\"flex flex-col items-center gap-5\">\n <p className=\"text-center text-xs font-medium text-text-secondary\">\n {tr(\n 'atheletebridge.pages.fixtureDetail.cricketScorecardDescription',\n 'Record each innings score and how the match was decided — a cricket result is a margin, not a score comparison.'\n )}\n </p>\n\n {(\n [\n { side: 'home' as const, name: homeName, sport: f.homeTeam?.sport },\n { side: 'away' as const, name: awayName, sport: f.awayTeam?.sport },\n ] as const\n ).map(({ side, name, sport }) => (\n <div key={side} className=\"flex flex-wrap items-end justify-center gap-3\">\n <div className=\"flex flex-col items-center gap-2\">\n <Crest name={name} sport={sport} size={40} />\n <span className=\"max-w-[8rem] truncate text-center text-xs font-medium text-text-secondary\">\n {name}\n </span>\n </div>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.runs', 'Runs')}\n htmlFor={`cricket-${side}-runs`}\n className=\"w-20\"\n >\n <input\n id={`cricket-${side}-runs`}\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm[`${side}Runs`]}\n onChange={(e) => setCricket(`${side}Runs`, e.target.value)}\n placeholder=\"185\"\n />\n </FormField>\n <span className=\"pb-3 text-lg font-bold text-text-muted\">/</span>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.wickets', 'Wkts')}\n htmlFor={`cricket-${side}-wickets`}\n className=\"w-16\"\n >\n <input\n id={`cricket-${side}-wickets`}\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm[`${side}Wickets`]}\n onChange={(e) => setCricket(`${side}Wickets`, e.target.value)}\n placeholder=\"6\"\n />\n </FormField>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.overs', 'Overs')}\n htmlFor={`cricket-${side}-overs`}\n className=\"w-16\"\n >\n <input\n id={`cricket-${side}-overs`}\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm[`${side}Overs`]}\n onChange={(e) => setCricket(`${side}Overs`, e.target.value)}\n placeholder=\"20\"\n />\n </FormField>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.balls', 'Balls')}\n htmlFor={`cricket-${side}-balls`}\n className=\"w-14\"\n >\n <input\n id={`cricket-${side}-balls`}\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm[`${side}Balls`]}\n onChange={(e) => setCricket(`${side}Balls`, e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n </div>\n ))}\n\n <div className=\"flex flex-wrap items-end justify-center gap-4\">\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.winner', 'Winner')}\n htmlFor=\"cricket-winner\"\n >\n <select\n id=\"cricket-winner\"\n className={fieldInputClass}\n value={cricketForm.winner}\n onChange={(e) => setCricket('winner', e.target.value as 'HOME' | 'AWAY')}\n >\n <option value=\"HOME\">{homeName}</option>\n <option value=\"AWAY\">{awayName}</option>\n </select>\n </FormField>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.decidedBy', 'Decided by')}\n htmlFor=\"cricket-decided-by\"\n >\n <select\n id=\"cricket-decided-by\"\n className={fieldInputClass}\n value={cricketForm.decidedBy}\n onChange={(e) =>\n setCricket('decidedBy', e.target.value as 'RUNS' | 'WICKETS')\n }\n >\n <option value=\"RUNS\">\n {tr('atheletebridge.pages.fixtureDetail.runs', 'Runs')}\n </option>\n <option value=\"WICKETS\">\n {tr('atheletebridge.pages.fixtureDetail.wickets', 'Wkts')}\n </option>\n </select>\n </FormField>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.margin', 'Margin')}\n htmlFor=\"cricket-margin\"\n className=\"w-20\"\n >\n <input\n id=\"cricket-margin\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm.margin}\n onChange={(e) => setCricket('margin', e.target.value)}\n placeholder=\"13\"\n />\n </FormField>\n </div>\n\n <div className=\"flex items-center gap-6\">\n <label className=\"flex items-center gap-2.5 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={cricketForm.superOver}\n onChange={(e) => setCricket('superOver', e.target.checked)}\n className=\"h-4 w-4 rounded border-border-seam text-action-primary-bg focus:ring-[var(--color-focus-ring)]\"\n />\n {tr('atheletebridge.pages.fixtureDetail.superOver', 'Super Over')}\n </label>\n <label className=\"flex items-center gap-2.5 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={cricketForm.dls}\n onChange={(e) => setCricket('dls', e.target.checked)}\n className=\"h-4 w-4 rounded border-border-seam text-action-primary-bg focus:ring-[var(--color-focus-ring)]\"\n />\n {tr('atheletebridge.pages.fixtureDetail.dls', 'DLS')}\n </label>\n </div>\n </div>\n ) : (\n <>\n <div className=\"flex flex-wrap items-end justify-center gap-4 sm:gap-6\">\n <div className=\"flex items-end gap-3\">\n <div className=\"flex flex-col items-center gap-2\">\n <Crest name={homeName} sport={f.homeTeam?.sport} size={40} />\n <span className=\"max-w-[8rem] truncate text-center text-xs font-medium text-text-secondary\">\n {homeName}\n </span>\n </div>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.home', 'Home')}\n htmlFor=\"homeScore\"\n className=\"w-24\"\n >\n <input\n id=\"homeScore\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center text-lg font-bold tabular-nums`}\n value={homeScore}\n onChange={(e) => setHomeScore(e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n </div>\n\n <Minus className=\"mb-3 h-5 w-5 shrink-0 text-text-muted\" aria-hidden=\"true\" />\n\n <div className=\"flex items-end gap-3\">\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.away', 'Away')}\n htmlFor=\"awayScore\"\n className=\"w-24\"\n >\n <input\n id=\"awayScore\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center text-lg font-bold tabular-nums`}\n value={awayScore}\n onChange={(e) => setAwayScore(e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n <div className=\"flex flex-col items-center gap-2\">\n <Crest name={awayName} sport={f.awayTeam?.sport} size={40} />\n <span className=\"max-w-[8rem] truncate text-center text-xs font-medium text-text-secondary\">\n {awayName}\n </span>\n </div>\n </div>\n </div>\n\n {levelAtFullTime && (\n <div className=\"mt-6 border-t border-border-seam pt-5\">\n <p className=\"mb-4 text-center text-xs font-medium text-text-secondary\">\n {tr(\n 'atheletebridge.pages.fixtureDetail.levelAtFullTime',\n 'Level at full time — record how the tie was decided (optional).'\n )}\n </p>\n <div className=\"flex flex-col items-center gap-4\">\n <div className=\"flex items-end justify-center gap-3\">\n <span className=\"mb-2.5 w-16 text-right text-xs font-medium text-text-secondary\">\n {homeName}\n </span>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.extraTime', 'Extra time')}\n htmlFor=\"extraTimeHome\"\n className=\"w-20\"\n >\n <input\n id=\"extraTimeHome\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={extraTimeHome}\n onChange={(e) => setExtraTimeHome(e.target.value)}\n placeholder=\"–\"\n />\n </FormField>\n <Minus\n className=\"mb-3 h-4 w-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <FormField label=\" \" htmlFor=\"extraTimeAway\" className=\"w-20\">\n <input\n id=\"extraTimeAway\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={extraTimeAway}\n onChange={(e) => setExtraTimeAway(e.target.value)}\n placeholder=\"–\"\n />\n </FormField>\n <span className=\"mb-2.5 w-16 truncate text-xs font-medium text-text-secondary\">\n {awayName}\n </span>\n </div>\n\n {showPenaltyShootout && (\n <div className=\"flex items-end justify-center gap-3\">\n <span className=\"mb-2.5 w-16 text-right text-xs font-medium text-text-secondary\">\n {homeName}\n </span>\n <FormField\n label={tr(\n 'atheletebridge.pages.fixtureDetail.penaltyShootout',\n 'Penalties'\n )}\n htmlFor=\"penaltyHome\"\n className=\"w-20\"\n >\n <input\n id=\"penaltyHome\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={penaltyHome}\n onChange={(e) => setPenaltyHome(e.target.value)}\n placeholder=\"–\"\n />\n </FormField>\n <Minus\n className=\"mb-3 h-4 w-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <FormField label=\" \" htmlFor=\"penaltyAway\" className=\"w-20\">\n <input\n id=\"penaltyAway\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={penaltyAway}\n onChange={(e) => setPenaltyAway(e.target.value)}\n placeholder=\"–\"\n />\n </FormField>\n <span className=\"mb-2.5 w-16 truncate text-xs font-medium text-text-secondary\">\n {awayName}\n </span>\n </div>\n )}\n </div>\n </div>\n )}\n </>\n )}\n\n <div className=\"mt-6 flex justify-center\">\n <button\n type=\"submit\"\n disabled={recordResult.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-6 py-2.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {recordResult.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trophy className=\"h-4 w-4\" />\n )}\n {tr('atheletebridge.pages.fixtureDetail.recordResultAction', 'Record result')}\n </button>\n </div>\n </form>\n {error && <p className=\"mt-3 text-center text-sm text-status-error-text\">{error}</p>}\n </GlassCard>\n </section>\n )}\n\n {f.competition && (\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(`/standings?competition=${encodeURIComponent(f.competition ?? '')}`)\n }\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Trophy className=\"h-4 w-4\" />{' '}\n {tr(\n 'atheletebridge.pages.fixtureDetail.viewStandings',\n `View ${f.competition} standings`\n )}\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 * The header is a crest-vs-crest Scoreboard hero; the record-result panel shows\n * each team's crest beside its score input. Semantic tokens only, responsive.\n */\n\nimport { useState } from 'react';\nimport { useParams } from 'react-router';\nimport { ArrowLeft, Trophy, Loader2, Flag, Minus } from 'lucide-react';\nimport { GlassCard, EmphasisPanel } from '@burdenoff/fe-libs/ui';\nimport { PageLayout } from '../../components/PageLayout';\nimport { Scoreboard } from '../../components/Scoreboard';\nimport { Crest } from '../../components/Crest';\nimport { LoadingState, ErrorState } from '../../components/LoadingState';\nimport { FormField, fieldInputClass } from '../../components/form';\nimport { FIXTURE_TYPE_LABELS } from '../../components/domain';\nimport { AvailabilityRsvpControl } from '../../components/availability/AvailabilityRsvpControl';\nimport { AvailabilityRosterPanel } from '../../components/availability/AvailabilityRosterPanel';\nimport { useAthleteBridge } from '../../context/AthleteBridgeContext';\nimport { useScheduledTimeGate } from '../../hooks';\nimport { useFixture, useRecordFixtureResult } from '../../hooks/useAthleteBridgeApi';\nimport {\n FIXTURE_CRICKET_SCORECARD_LIVE,\n FIXTURE_KNOCKOUT_SCORES_LIVE,\n} from '../../utils/fixtureCapabilities';\nimport {\n buildCricketResultInput,\n buildRecordResultInput,\n parseScoreInput,\n} from '../../utils/recordResult';\nimport { useAbTranslate } from '../../i18n';\n\nexport function FixtureDetailPage() {\n const tr = useAbTranslate();\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 // Knockout outcome (BOFF-7640) — only offered when the fixture is level at\n // full time; a tie still level after extra time must record the shootout.\n const [extraTimeHome, setExtraTimeHome] = useState('');\n const [extraTimeAway, setExtraTimeAway] = useState('');\n const [penaltyHome, setPenaltyHome] = useState('');\n const [penaltyAway, setPenaltyAway] = useState('');\n // Cricket scorecard form (BOFF-7644) — replaces the generic score inputs\n // when the fixture's home team plays cricket.\n const [cricketForm, setCricketForm] = useState({\n homeRuns: '',\n homeWickets: '',\n homeOvers: '',\n homeBalls: '',\n awayRuns: '',\n awayWickets: '',\n awayOvers: '',\n awayBalls: '',\n winner: 'HOME' as 'HOME' | 'AWAY',\n decidedBy: 'RUNS' as 'RUNS' | 'WICKETS',\n margin: '',\n superOver: false,\n dls: false,\n });\n const [error, setError] = useState<string | null>(null);\n // Time gate for the completion action; re-renders when kickoff arrives.\n const fixtureStarted = useScheduledTimeGate(fixture.data?.scheduledAt);\n\n const setCricket = <K extends keyof typeof cricketForm>(\n key: K,\n value: (typeof cricketForm)[K]\n ): void => setCricketForm((prev) => ({ ...prev, [key]: value }));\n\n const resetCricketForm = (): void =>\n setCricketForm({\n homeRuns: '',\n homeWickets: '',\n homeOvers: '',\n homeBalls: '',\n awayRuns: '',\n awayWickets: '',\n awayOvers: '',\n awayBalls: '',\n winner: 'HOME',\n decidedBy: 'RUNS',\n margin: '',\n superOver: false,\n dls: false,\n });\n\n if (fixture.isLoading) {\n return (\n <PageLayout title={tr('atheletebridge.pages.fixtureDetail.title', 'Fixture')}>\n <LoadingState\n label={tr('atheletebridge.pages.fixtureDetail.loading', 'Loading fixture…')}\n />\n </PageLayout>\n );\n }\n\n if (fixture.isError || !fixture.data) {\n return (\n <PageLayout title={tr('atheletebridge.pages.fixtureDetail.title', 'Fixture')}>\n <ErrorState\n title={tr('atheletebridge.pages.fixtureDetail.notFoundTitle', 'Fixture not found')}\n message={tr(\n 'atheletebridge.pages.fixtureDetail.notFoundMessage',\n 'This fixture does not exist or you do not have access to it.'\n )}\n onRetry={() => navigateTo('/fixtures')}\n />\n </PageLayout>\n );\n }\n\n const f = fixture.data;\n const homeName = f.homeTeam?.name ?? tr('atheletebridge.pages.fixtureDetail.home', 'Home');\n const awayName =\n f.awayTeam?.name ??\n (f.awayTeamId ? tr('atheletebridge.pages.fixtureDetail.away', 'Away') : 'TBD');\n // Result entry is the completion transition, gated on kickoff here and\n // enforced server-side too. Both branches share isResultEligible so they\n // cannot drift apart.\n const isResultEligible =\n Boolean(f.awayTeamId) && (f.status === 'SCHEDULED' || f.status === 'LIVE');\n const canRecordResult = isResultEligible && fixtureStarted;\n const awaitingKickoff = isResultEligible && !fixtureStarted;\n\n // The cricket scorecard and knockout inputs post fields the deployed backend\n // may not accept yet; see utils/fixtureCapabilities.\n const isCricket = FIXTURE_CRICKET_SCORECARD_LIVE && f.homeTeam?.sport === 'CRICKET';\n\n // Knockout-outcome inputs (BOFF-7640) — offered once the full-time score is\n // entered and level. The shootout appears while the tie is still level\n // (straight from full time, or after a level extra time).\n const parsedHomeScore = parseScoreInput(homeScore);\n const parsedAwayScore = parseScoreInput(awayScore);\n const fullTimeScoresEntered = parsedHomeScore !== null && parsedAwayScore !== null;\n const levelAtFullTime =\n FIXTURE_KNOCKOUT_SCORES_LIVE && fullTimeScoresEntered && parsedHomeScore === parsedAwayScore;\n const extraTimeEntered = extraTimeHome !== '' || extraTimeAway !== '';\n const extraTimeParsedHome = parseScoreInput(extraTimeHome);\n const extraTimeParsedAway = parseScoreInput(extraTimeAway);\n const extraTimeComplete = extraTimeParsedHome !== null && extraTimeParsedAway !== null;\n const stillLevelAfterExtraTime = extraTimeComplete && extraTimeParsedHome === extraTimeParsedAway;\n const showPenaltyShootout = levelAtFullTime && (!extraTimeEntered || stillLevelAfterExtraTime);\n\n const handleRecord = async (e: React.FormEvent) => {\n e.preventDefault();\n setError(null);\n if (!id) return;\n\n if (isCricket) {\n const draft = buildCricketResultInput(\n { ...cricketForm },\n {\n required: tr(\n 'atheletebridge.pages.fixtureDetail.cricketRequired',\n 'All innings scores, overs and the margin are required.'\n ),\n negative: tr(\n 'atheletebridge.pages.fixtureDetail.cricketNegative',\n 'Scores and overs cannot be negative.'\n ),\n wicketsRange: tr(\n 'atheletebridge.pages.fixtureDetail.cricketWicketsRange',\n 'Wickets must be between 0 and 10.'\n ),\n ballsRange: tr(\n 'atheletebridge.pages.fixtureDetail.cricketBallsRange',\n 'Balls must be between 0 and 5.'\n ),\n marginMinimum: tr(\n 'atheletebridge.pages.fixtureDetail.cricketMarginMinimum',\n 'The margin must be at least 1.'\n ),\n wicketMarginMax: tr(\n 'atheletebridge.pages.fixtureDetail.cricketWicketMarginMax',\n 'A win by wickets cannot exceed a margin of 10.'\n ),\n }\n );\n if (!draft.ok) {\n setError(draft.error);\n return;\n }\n try {\n await recordResult.mutateAsync({ id, input: draft.input });\n resetCricketForm();\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('atheletebridge.pages.fixtureDetail.recordFailed', 'Failed to record result.')\n );\n }\n return;\n }\n\n // Non-cricket fixtures: a single pair of scores, optionally followed by a\n // knockout outcome. All the rules live in `buildRecordResultInput` so the\n // page only renders copy and submits the payload it hands back.\n const draft = buildRecordResultInput(\n { homeScore, awayScore, extraTimeHome, extraTimeAway, penaltyHome, penaltyAway },\n {\n scoresRequired: tr(\n 'atheletebridge.pages.fixtureDetail.scoresRequired',\n 'Both scores are required and must be numbers.'\n ),\n scoresNegative: tr(\n 'atheletebridge.pages.fixtureDetail.scoresNegative',\n 'Scores cannot be negative.'\n ),\n extraTimePair: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimePair',\n 'Enter both extra-time scores.'\n ),\n extraTimeNumeric: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimeNumeric',\n 'Extra-time scores must be numbers.'\n ),\n extraTimeNegative: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimeNegative',\n 'Extra-time scores cannot be negative.'\n ),\n penaltyPair: tr(\n 'atheletebridge.pages.fixtureDetail.penaltyPair',\n 'Enter both penalty scores.'\n ),\n penaltyNumeric: tr(\n 'atheletebridge.pages.fixtureDetail.penaltyNumeric',\n 'Penalty scores must be numbers.'\n ),\n penaltyNegative: tr(\n 'atheletebridge.pages.fixtureDetail.penaltyNegative',\n 'Penalty scores cannot be negative.'\n ),\n extraTimeDecided: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimeDecided',\n 'Extra time decided the tie — leave the penalty shootout empty.'\n ),\n extraTimeLevel: tr(\n 'atheletebridge.pages.fixtureDetail.extraTimeLevel',\n 'Still level after extra time — record the penalty shootout.'\n ),\n penaltyLevel: tr(\n 'atheletebridge.pages.fixtureDetail.penaltyLevel',\n 'Penalty scores must have a winner.'\n ),\n }\n );\n if (!draft.ok) {\n setError(draft.error);\n return;\n }\n try {\n await recordResult.mutateAsync({ id, input: draft.input });\n setHomeScore('');\n setAwayScore('');\n setExtraTimeHome('');\n setExtraTimeAway('');\n setPenaltyHome('');\n setPenaltyAway('');\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('atheletebridge.pages.fixtureDetail.recordFailed', 'Failed to record result.')\n );\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\" /> {tr('atheletebridge.pages.fixtureDetail.back', 'Back')}\n </button>\n }\n >\n {/* Crest-vs-crest hero scoreboard */}\n <Scoreboard fixture={f} variant=\"hero\" className=\"mb-8\" />\n\n {/* Availability / RSVP (BOFF-4134) — athlete's own response + coach roster view */}\n <div className=\"mb-8 grid grid-cols-1 gap-4 lg:grid-cols-[minmax(0,1fr)_2fr]\">\n <AvailabilityRsvpControl teamId={f.homeTeamId} fixtureId={f.id} />\n <AvailabilityRosterPanel teamId={f.homeTeamId} fixtureId={f.id} />\n </div>\n\n {f.notes && (\n <EmphasisPanel className=\"mb-8\">\n <h2 className=\"mb-2 text-sm font-semibold text-text-primary\">\n {tr('atheletebridge.pages.fixtureDetail.notes', 'Notes')}\n </h2>\n <p className=\"text-sm text-text-secondary\">{f.notes}</p>\n </EmphasisPanel>\n )}\n\n {/* Record result */}\n {awaitingKickoff && (\n <EmphasisPanel className=\"mb-8\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'atheletebridge.pages.fixtureDetail.recordResultUnavailable',\n 'A result can be recorded once this fixture has started — after its scheduled date and time.'\n )}\n </p>\n </EmphasisPanel>\n )}\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\" />{' '}\n {tr('atheletebridge.pages.fixtureDetail.recordResult', 'Record result')}\n </h2>\n <GlassCard treatment=\"insight\" className=\"p-5 sm:p-6\">\n <p className=\"mb-5 text-sm text-text-secondary\">\n {tr(\n 'atheletebridge.pages.fixtureDetail.recordResultDescription',\n 'Recording a final score marks this fixture Completed and recomputes the competition standings for both teams.'\n )}\n </p>\n <form onSubmit={handleRecord}>\n {isCricket ? (\n <div className=\"flex flex-col items-center gap-5\">\n <p className=\"text-center text-xs font-medium text-text-secondary\">\n {tr(\n 'atheletebridge.pages.fixtureDetail.cricketScorecardDescription',\n 'Record each innings score and how the match was decided — a cricket result is a margin, not a score comparison.'\n )}\n </p>\n\n {(\n [\n { side: 'home' as const, name: homeName, sport: f.homeTeam?.sport },\n { side: 'away' as const, name: awayName, sport: f.awayTeam?.sport },\n ] as const\n ).map(({ side, name, sport }) => (\n <div key={side} className=\"flex flex-wrap items-end justify-center gap-3\">\n <div className=\"flex flex-col items-center gap-2\">\n <Crest name={name} sport={sport} size={40} />\n <span className=\"max-w-[8rem] truncate text-center text-xs font-medium text-text-secondary\">\n {name}\n </span>\n </div>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.runs', 'Runs')}\n htmlFor={`cricket-${side}-runs`}\n className=\"w-20\"\n >\n <input\n id={`cricket-${side}-runs`}\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm[`${side}Runs`]}\n onChange={(e) => setCricket(`${side}Runs`, e.target.value)}\n placeholder=\"185\"\n />\n </FormField>\n <span className=\"pb-3 text-lg font-bold text-text-muted\">/</span>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.wickets', 'Wkts')}\n htmlFor={`cricket-${side}-wickets`}\n className=\"w-16\"\n >\n <input\n id={`cricket-${side}-wickets`}\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm[`${side}Wickets`]}\n onChange={(e) => setCricket(`${side}Wickets`, e.target.value)}\n placeholder=\"6\"\n />\n </FormField>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.overs', 'Overs')}\n htmlFor={`cricket-${side}-overs`}\n className=\"w-16\"\n >\n <input\n id={`cricket-${side}-overs`}\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm[`${side}Overs`]}\n onChange={(e) => setCricket(`${side}Overs`, e.target.value)}\n placeholder=\"20\"\n />\n </FormField>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.balls', 'Balls')}\n htmlFor={`cricket-${side}-balls`}\n className=\"w-14\"\n >\n <input\n id={`cricket-${side}-balls`}\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm[`${side}Balls`]}\n onChange={(e) => setCricket(`${side}Balls`, e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n </div>\n ))}\n\n <div className=\"flex flex-wrap items-end justify-center gap-4\">\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.winner', 'Winner')}\n htmlFor=\"cricket-winner\"\n >\n <select\n id=\"cricket-winner\"\n className={fieldInputClass}\n value={cricketForm.winner}\n onChange={(e) => setCricket('winner', e.target.value as 'HOME' | 'AWAY')}\n >\n <option value=\"HOME\">{homeName}</option>\n <option value=\"AWAY\">{awayName}</option>\n </select>\n </FormField>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.decidedBy', 'Decided by')}\n htmlFor=\"cricket-decided-by\"\n >\n <select\n id=\"cricket-decided-by\"\n className={fieldInputClass}\n value={cricketForm.decidedBy}\n onChange={(e) =>\n setCricket('decidedBy', e.target.value as 'RUNS' | 'WICKETS')\n }\n >\n <option value=\"RUNS\">\n {tr('atheletebridge.pages.fixtureDetail.runs', 'Runs')}\n </option>\n <option value=\"WICKETS\">\n {tr('atheletebridge.pages.fixtureDetail.wickets', 'Wkts')}\n </option>\n </select>\n </FormField>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.margin', 'Margin')}\n htmlFor=\"cricket-margin\"\n className=\"w-20\"\n >\n <input\n id=\"cricket-margin\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={cricketForm.margin}\n onChange={(e) => setCricket('margin', e.target.value)}\n placeholder=\"13\"\n />\n </FormField>\n </div>\n\n <div className=\"flex items-center gap-6\">\n <label className=\"flex items-center gap-2.5 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={cricketForm.superOver}\n onChange={(e) => setCricket('superOver', e.target.checked)}\n className=\"h-4 w-4 rounded border-border-seam text-action-primary-bg focus:ring-[var(--color-focus-ring)]\"\n />\n {tr('atheletebridge.pages.fixtureDetail.superOver', 'Super Over')}\n </label>\n <label className=\"flex items-center gap-2.5 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={cricketForm.dls}\n onChange={(e) => setCricket('dls', e.target.checked)}\n className=\"h-4 w-4 rounded border-border-seam text-action-primary-bg focus:ring-[var(--color-focus-ring)]\"\n />\n {tr('atheletebridge.pages.fixtureDetail.dls', 'DLS')}\n </label>\n </div>\n </div>\n ) : (\n <>\n <div className=\"flex flex-wrap items-end justify-center gap-4 sm:gap-6\">\n <div className=\"flex items-end gap-3\">\n <div className=\"flex flex-col items-center gap-2\">\n <Crest name={homeName} sport={f.homeTeam?.sport} size={40} />\n <span className=\"max-w-[8rem] truncate text-center text-xs font-medium text-text-secondary\">\n {homeName}\n </span>\n </div>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.home', 'Home')}\n htmlFor=\"homeScore\"\n className=\"w-24\"\n >\n <input\n id=\"homeScore\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center text-lg font-bold tabular-nums`}\n value={homeScore}\n onChange={(e) => setHomeScore(e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n </div>\n\n <Minus className=\"mb-3 h-5 w-5 shrink-0 text-text-muted\" aria-hidden=\"true\" />\n\n <div className=\"flex items-end gap-3\">\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.away', 'Away')}\n htmlFor=\"awayScore\"\n className=\"w-24\"\n >\n <input\n id=\"awayScore\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center text-lg font-bold tabular-nums`}\n value={awayScore}\n onChange={(e) => setAwayScore(e.target.value)}\n placeholder=\"0\"\n />\n </FormField>\n <div className=\"flex flex-col items-center gap-2\">\n <Crest name={awayName} sport={f.awayTeam?.sport} size={40} />\n <span className=\"max-w-[8rem] truncate text-center text-xs font-medium text-text-secondary\">\n {awayName}\n </span>\n </div>\n </div>\n </div>\n\n {levelAtFullTime && (\n <div className=\"mt-6 border-t border-border-seam pt-5\">\n <p className=\"mb-4 text-center text-xs font-medium text-text-secondary\">\n {tr(\n 'atheletebridge.pages.fixtureDetail.levelAtFullTime',\n 'Level at full time — record how the tie was decided (optional).'\n )}\n </p>\n <div className=\"flex flex-col items-center gap-4\">\n <div className=\"flex items-end justify-center gap-3\">\n <span className=\"mb-2.5 w-16 text-right text-xs font-medium text-text-secondary\">\n {homeName}\n </span>\n <FormField\n label={tr('atheletebridge.pages.fixtureDetail.extraTime', 'Extra time')}\n htmlFor=\"extraTimeHome\"\n className=\"w-20\"\n >\n <input\n id=\"extraTimeHome\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={extraTimeHome}\n onChange={(e) => setExtraTimeHome(e.target.value)}\n placeholder=\"–\"\n />\n </FormField>\n <Minus\n className=\"mb-3 h-4 w-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <FormField label=\" \" htmlFor=\"extraTimeAway\" className=\"w-20\">\n <input\n id=\"extraTimeAway\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={extraTimeAway}\n onChange={(e) => setExtraTimeAway(e.target.value)}\n placeholder=\"–\"\n />\n </FormField>\n <span className=\"mb-2.5 w-16 truncate text-xs font-medium text-text-secondary\">\n {awayName}\n </span>\n </div>\n\n {showPenaltyShootout && (\n <div className=\"flex items-end justify-center gap-3\">\n <span className=\"mb-2.5 w-16 text-right text-xs font-medium text-text-secondary\">\n {homeName}\n </span>\n <FormField\n label={tr(\n 'atheletebridge.pages.fixtureDetail.penaltyShootout',\n 'Penalties'\n )}\n htmlFor=\"penaltyHome\"\n className=\"w-20\"\n >\n <input\n id=\"penaltyHome\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={penaltyHome}\n onChange={(e) => setPenaltyHome(e.target.value)}\n placeholder=\"–\"\n />\n </FormField>\n <Minus\n className=\"mb-3 h-4 w-4 shrink-0 text-text-muted\"\n aria-hidden=\"true\"\n />\n <FormField label=\" \" htmlFor=\"penaltyAway\" className=\"w-20\">\n <input\n id=\"penaltyAway\"\n inputMode=\"numeric\"\n className={`${fieldInputClass} text-center tabular-nums`}\n value={penaltyAway}\n onChange={(e) => setPenaltyAway(e.target.value)}\n placeholder=\"–\"\n />\n </FormField>\n <span className=\"mb-2.5 w-16 truncate text-xs font-medium text-text-secondary\">\n {awayName}\n </span>\n </div>\n )}\n </div>\n </div>\n )}\n </>\n )}\n\n <div className=\"mt-6 flex justify-center\">\n <button\n type=\"submit\"\n disabled={recordResult.isPending}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-action-primary-bg px-6 py-2.5 text-sm font-medium text-action-primary-text transition-colors hover:bg-action-primary-bg/90 disabled:opacity-60\"\n >\n {recordResult.isPending ? (\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n ) : (\n <Trophy className=\"h-4 w-4\" />\n )}\n {tr('atheletebridge.pages.fixtureDetail.recordResultAction', 'Record result')}\n </button>\n </div>\n </form>\n {error && <p className=\"mt-3 text-center text-sm text-status-error-text\">{error}</p>}\n </GlassCard>\n </section>\n )}\n\n {f.competition && (\n <button\n type=\"button\"\n onClick={() =>\n navigateTo(`/standings?competition=${encodeURIComponent(f.competition ?? '')}`)\n }\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-border-seam px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken\"\n >\n <Trophy className=\"h-4 w-4\" />{' '}\n {tr(\n 'atheletebridge.pages.fixtureDetail.viewStandings',\n `View ${f.competition} standings`\n )}\n </button>\n )}\n </PageLayout>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { AthFixture } from '../types';
|
|
2
|
+
|
|
3
|
+
/** `extraTime*Score` / `penalty*Score` on AthFixture and AthRecordResultInput. */
|
|
4
|
+
export declare const FIXTURE_KNOCKOUT_SCORES_LIVE = false;
|
|
5
|
+
/** `cricketScorecard` on AthFixture and AthRecordResultInput. */
|
|
6
|
+
export declare const FIXTURE_CRICKET_SCORECARD_LIVE = false;
|
|
7
|
+
/**
|
|
8
|
+
* The optional part of a fixture selection, built from the flags above.
|
|
9
|
+
* `cricketScorecardSelection` is the scorecard's own sub-selection.
|
|
10
|
+
*/
|
|
11
|
+
export declare function fixtureOutcomeSelection(cricketScorecardSelection: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Fill the fields that were not selected with `null`, so a reader that checks
|
|
14
|
+
* `!== null` never mistakes an absent value for a recorded one.
|
|
15
|
+
*/
|
|
16
|
+
export declare function normalizeFixture<T extends Partial<AthFixture>>(fixture: T): T;
|
|
17
|
+
/** `normalizeFixture` over a nullable value. */
|
|
18
|
+
export declare function normalizeFixtureOrNull<T extends Partial<AthFixture>>(fixture: T | null): T | null;
|
|
19
|
+
//# sourceMappingURL=fixtureCapabilities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixtureCapabilities.d.ts","sourceRoot":"","sources":["../../../src/atheletebridge/utils/fixtureCapabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAE3C,kFAAkF;AAClF,eAAO,MAAM,4BAA4B,QAAQ,CAAC;AAElD,iEAAiE;AACjE,eAAO,MAAM,8BAA8B,QAAQ,CAAC;AASpD;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,yBAAyB,EAAE,MAAM,GAAG,MAAM,CAMjF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,OAAO,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,CAAC,CAS7E;AAED,gDAAgD;AAChD,wBAAgB,sBAAsB,CAAC,CAAC,SAAS,OAAO,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI,CAEjG"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
//#region src/atheletebridge/utils/fixtureCapabilities.ts
|
|
2
|
+
function e(e) {
|
|
3
|
+
return [].join("\n");
|
|
4
|
+
}
|
|
5
|
+
function t(e) {
|
|
6
|
+
return {
|
|
7
|
+
...e,
|
|
8
|
+
extraTimeHomeScore: e.extraTimeHomeScore ?? null,
|
|
9
|
+
extraTimeAwayScore: e.extraTimeAwayScore ?? null,
|
|
10
|
+
penaltyHomeScore: e.penaltyHomeScore ?? null,
|
|
11
|
+
penaltyAwayScore: e.penaltyAwayScore ?? null,
|
|
12
|
+
cricketScorecard: e.cricketScorecard ?? null
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function n(e) {
|
|
16
|
+
return e ? t(e) : null;
|
|
17
|
+
}
|
|
18
|
+
//#endregion
|
|
19
|
+
export { e as fixtureOutcomeSelection, t as normalizeFixture, n as normalizeFixtureOrNull };
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=fixtureCapabilities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixtureCapabilities.js","names":[],"sources":["../../../src/atheletebridge/utils/fixtureCapabilities.ts"],"sourcesContent":["/**\n * Fixture fields the deployed backend can serve.\n * @module atheletebridge/utils/fixtureCapabilities\n *\n * The knockout-outcome scores (BOFF-7640: extra time and penalties) and the\n * cricket scorecard (BOFF-7644) shipped in this MFE before the matching\n * `wspace-atheletebridge-svc` changes (svc PRs #9 and #10, still in review).\n * The gateway validates every operation against the composed supergraph, so\n * selecting a field the subgraph does not publish fails the WHOLE operation\n * with GRAPHQL_VALIDATION_FAILED. In production that took down the fixture\n * list, the fixture detail page and the team detail page (\"Team not found\").\n *\n * Until the backend publishes those fields, they are left out of every\n * selection and normalised to `null`, which every reader already treats as\n * \"not recorded\". The UI for entering them stays hidden for the same reason.\n * Flip the matching flag once the svc release that adds the field is live in\n * both alpha and production.\n */\n\nimport type { AthFixture } from '../types';\n\n/** `extraTime*Score` / `penalty*Score` on AthFixture and AthRecordResultInput. */\nexport const FIXTURE_KNOCKOUT_SCORES_LIVE = false;\n\n/** `cricketScorecard` on AthFixture and AthRecordResultInput. */\nexport const FIXTURE_CRICKET_SCORECARD_LIVE = false;\n\nconst KNOCKOUT_SCORE_FIELDS = [\n 'extraTimeHomeScore',\n 'extraTimeAwayScore',\n 'penaltyHomeScore',\n 'penaltyAwayScore',\n] as const;\n\n/**\n * The optional part of a fixture selection, built from the flags above.\n * `cricketScorecardSelection` is the scorecard's own sub-selection.\n */\nexport function fixtureOutcomeSelection(cricketScorecardSelection: string): string {\n const parts: string[] = [];\n if (FIXTURE_KNOCKOUT_SCORES_LIVE) parts.push(KNOCKOUT_SCORE_FIELDS.join(' '));\n if (FIXTURE_CRICKET_SCORECARD_LIVE)\n parts.push(`cricketScorecard { ${cricketScorecardSelection} }`);\n return parts.join('\\n');\n}\n\n/**\n * Fill the fields that were not selected with `null`, so a reader that checks\n * `!== null` never mistakes an absent value for a recorded one.\n */\nexport function normalizeFixture<T extends Partial<AthFixture>>(fixture: T): T {\n return {\n ...fixture,\n extraTimeHomeScore: fixture.extraTimeHomeScore ?? null,\n extraTimeAwayScore: fixture.extraTimeAwayScore ?? null,\n penaltyHomeScore: fixture.penaltyHomeScore ?? null,\n penaltyAwayScore: fixture.penaltyAwayScore ?? null,\n cricketScorecard: fixture.cricketScorecard ?? null,\n };\n}\n\n/** `normalizeFixture` over a nullable value. */\nexport function normalizeFixtureOrNull<T extends Partial<AthFixture>>(fixture: T | null): T | null {\n return fixture ? normalizeFixture(fixture) : null;\n}\n"],"mappings":";AAsCA,SAAgB,EAAwB,GAA2C;CAKjF,OAAO,CAAA,EAAM,KAAK,IAAI;AACxB;AAMA,SAAgB,EAAgD,GAAe;CAC7E,OAAO;EACL,GAAG;EACH,oBAAoB,EAAQ,sBAAsB;EAClD,oBAAoB,EAAQ,sBAAsB;EAClD,kBAAkB,EAAQ,oBAAoB;EAC9C,kBAAkB,EAAQ,oBAAoB;EAC9C,kBAAkB,EAAQ,oBAAoB;CAChD;AACF;AAGA,SAAgB,EAAsD,GAA6B;CACjG,OAAO,IAAU,EAAiB,CAAO,IAAI;AAC/C"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@burdenoff/microfe-atheletebridge",
|
|
3
|
-
"version": "2026.1031.
|
|
3
|
+
"version": "2026.1031.3",
|
|
4
4
|
"description": "Burdenoff AthleteBridge Microfrontend - Sports, coaching & training platform for teams, fixtures, programs, sessions and athlete performance",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|