11import { Cutoff , Round , parseActivityCode } from '@wca/helpers' ;
22import classNames from 'classnames' ;
3- import { useState } from 'react' ;
3+ import { useMemo , useState } from 'react' ;
44import { Trans , useTranslation } from 'react-i18next' ;
55import { Link } from 'react-router-dom' ;
66import { Popover } from 'react-tiny-popover' ;
77import { renderCentiseconds , renderCutoff } from '@/lib/results' ;
8+ import { CompatibleRound , getAdvancementConditionForRound , ResultCondition } from '@/lib/wcif' ;
89import { useWCIF } from '@/providers/WCIFProvider' ;
910
1011export function CutoffTimeLimitPanel ( {
@@ -20,10 +21,20 @@ export function CutoffTimeLimitPanel({
2021 const cutoff = round . cutoff ;
2122 const timeLimit = round . timeLimit ;
2223 const timelimitTime = timeLimit && renderCentiseconds ( timeLimit ?. centiseconds ) ;
24+ const eventRounds = useMemo ( ( ) => {
25+ const { eventId } = parseActivityCode ( round . id ) ;
26+ return (
27+ wcif ?. events
28+ ?. find ( ( event ) => event . id === eventId )
29+ ?. rounds ?. map ( ( candidate ) => candidate as CompatibleRound ) || [ ]
30+ ) ;
31+ } , [ round . id , wcif ?. events ] ) ;
32+ const advancement = useMemo (
33+ ( ) => getAdvancementConditionForRound ( eventRounds , round as CompatibleRound ) ,
34+ [ eventRounds , round ] ,
35+ ) ;
2336
24- if ( ! timeLimit && ! cutoff && ! round . advancementCondition ) return null ;
25-
26- const level = round . advancementCondition ?. level ;
37+ if ( ! timeLimit && ! cutoff && ! advancement ) return null ;
2738
2839 return (
2940 < div className = { classNames ( 'flex w-full' , className ) } >
@@ -86,35 +97,9 @@ export function CutoffTimeLimitPanel({
8697 </ div >
8798 ) }
8899 </ div >
89- { round . advancementCondition && (
90- < div >
91- { round . advancementCondition . type === 'ranking' && (
92- < div className = "px-2" >
93- < Trans
94- i18nKey = { 'common.wca.advancement.ranking' }
95- values = { { level } }
96- components = { { b : < span className = "font-semibold" /> } }
97- />
98- </ div >
99- ) }
100- { round . advancementCondition . type === 'percent' && (
101- < div className = "px-2" >
102- < Trans
103- i18nKey = { 'common.wca.advancement.percent' }
104- values = { { level } }
105- components = { { b : < span className = "font-semibold" /> } }
106- />
107- </ div >
108- ) }
109- { round . advancementCondition . type === 'attemptResult' && (
110- < div className = "px-2" >
111- < Trans
112- i18nKey = { 'common.wca.advancement.attemptResult' }
113- values = { { level } }
114- components = { { b : < span className = "font-semibold" /> } }
115- />
116- </ div >
117- ) }
100+ { advancement && (
101+ < div className = "px-2" >
102+ { renderAdvancementText ( t , advancement . sourceType , advancement ) }
118103 </ div >
119104 ) }
120105 </ div >
@@ -125,6 +110,108 @@ export function CutoffTimeLimitPanel({
125110 ) ;
126111}
127112
113+ function renderAdvancementText (
114+ t : ReturnType < typeof useTranslation > [ 't' ] ,
115+ sourceType : 'registrations' | 'round' | 'linkedRounds' ,
116+ advancement : NonNullable < ReturnType < typeof getAdvancementConditionForRound > > ,
117+ ) {
118+ const isLinkedRounds = sourceType === 'linkedRounds' ;
119+ const { resultCondition } = advancement ;
120+ const sourceRoundNames = advancement . sourceRoundIds . map ( ( roundId ) =>
121+ activityCodeToRoundName ( t , roundId ) ,
122+ ) ;
123+ const sourceRoundsLabel = joinLabels ( sourceRoundNames ) ;
124+
125+ switch ( resultCondition . type ) {
126+ case 'ranking' :
127+ return isLinkedRounds ? (
128+ < >
129+ { t ( 'common.wca.advancement.linkedRanking' , {
130+ defaultValue : 'Top {{level}} combined across {{rounds}} advance to next round' ,
131+ level : resultCondition . value ,
132+ rounds : sourceRoundsLabel ,
133+ } ) }
134+ </ >
135+ ) : (
136+ < Trans
137+ i18nKey = { 'common.wca.advancement.ranking' }
138+ values = { { level : resultCondition . value } }
139+ components = { { b : < span className = "font-semibold" /> } }
140+ />
141+ ) ;
142+ case 'percent' :
143+ return isLinkedRounds ? (
144+ < >
145+ { t ( 'common.wca.advancement.linkedPercent' , {
146+ defaultValue : 'Top {{level}}% combined across {{rounds}} advance to next round' ,
147+ level : resultCondition . value ,
148+ rounds : sourceRoundsLabel ,
149+ } ) }
150+ </ >
151+ ) : (
152+ < Trans
153+ i18nKey = { 'common.wca.advancement.percent' }
154+ values = { { level : resultCondition . value } }
155+ components = { { b : < span className = "font-semibold" /> } }
156+ />
157+ ) ;
158+ case 'resultAchieved' : {
159+ const thresholdCondition = resultCondition as Extract <
160+ ResultCondition ,
161+ { type : 'resultAchieved' }
162+ > ;
163+ const scopeLabel = t ( `common.wca.resultType.${ thresholdCondition . scope } ` , {
164+ defaultValue : thresholdCondition . scope ,
165+ } ) . toLowerCase ( ) ;
166+ const resultValue =
167+ thresholdCondition . value === null
168+ ? t ( 'common.wca.advancement.resultThresholdUnknown' , {
169+ defaultValue : 'an unknown result' ,
170+ } )
171+ : renderCentiseconds ( thresholdCondition . value ) ;
172+
173+ return (
174+ < >
175+ { t (
176+ isLinkedRounds
177+ ? 'common.wca.advancement.linkedResultAchieved'
178+ : 'common.wca.advancement.resultAchieved' ,
179+ {
180+ defaultValue : isLinkedRounds
181+ ? 'Competitors with a {{scope}} better than {{result}} combined across {{rounds}} advance to next round. Minimum of 25% of competitors must be eliminated.'
182+ : 'Competitors with a {{scope}} better than {{result}} advance to next round. Minimum of 25% of competitors must be eliminated.' ,
183+ scope : scopeLabel ,
184+ result : resultValue ,
185+ rounds : sourceRoundsLabel ,
186+ } ,
187+ ) }
188+ </ >
189+ ) ;
190+ }
191+ }
192+ }
193+
194+ function activityCodeToRoundName ( t : ReturnType < typeof useTranslation > [ 't' ] , roundId : string ) {
195+ const { roundNumber } = parseActivityCode ( roundId ) ;
196+
197+ return t ( 'common.activityCodeToName.round' , {
198+ defaultValue : `Round ${ roundNumber } ` ,
199+ roundNumber,
200+ } ) ;
201+ }
202+
203+ function joinLabels ( labels : string [ ] ) {
204+ if ( labels . length <= 1 ) {
205+ return labels [ 0 ] || '' ;
206+ }
207+
208+ if ( labels . length === 2 ) {
209+ return `${ labels [ 0 ] } and ${ labels [ 1 ] } ` ;
210+ }
211+
212+ return `${ labels . slice ( 0 , - 1 ) . join ( ', ' ) } , and ${ labels [ labels . length - 1 ] } ` ;
213+ }
214+
128215function CutoffTimeLimitPopover ( { cutoff } : { cutoff : Cutoff | null } ) {
129216 const { t } = useTranslation ( ) ;
130217 const [ open , setOpen ] = useState ( false ) ;
0 commit comments