11import type { IChallengeDTO } from "@cgg/utils/challenges" ;
2+ import type { IApiChallenge } from "@cgg/utils/endpoints/types" ;
3+ import { getNextTier , getTierIndex } from "@cgg/utils/getTier" ;
24
3- export type SortMode = "Name-ASC" | "Name-DESC" ;
5+ export type SortMode =
6+ | "Name-ASC"
7+ | "Name-DESC"
8+ | "Rank"
9+ | "Last Updated"
10+ | "Position"
11+ | "Levelup" ;
412
13+ // TODO: Maps would probably be more efficient here
514export function sortChallenges (
615 challenges : IChallengeDTO [ ] ,
716 mode : SortMode ,
17+ userChallenges ?: IApiChallenge [ ] ,
818) : IChallengeDTO [ ] {
919 switch ( mode ) {
1020 case "Name-ASC" :
@@ -16,7 +26,127 @@ export function sortChallenges(
1626 } ) ;
1727 break ;
1828 case "Name-DESC" :
19- challenges = challenges . sort ( ( a , b ) => b . name . localeCompare ( a . name ) ) ;
29+ challenges = challenges . sort ( ( a , b ) => {
30+ if ( a . retired && ! b . retired ) return 1 ;
31+ if ( ! a . retired && b . retired ) return - 1 ;
32+
33+ return b . name . localeCompare ( a . name ) ;
34+ } ) ;
35+ break ;
36+
37+ case "Rank" :
38+ // When this filter - for whatever reason - gets selected on a non-user page
39+ if ( ! userChallenges ) {
40+ return sortChallenges ( challenges , "Name-ASC" ) ;
41+ }
42+ challenges = challenges . sort ( ( a , b ) => {
43+ if ( a . retired && ! b . retired ) return 1 ;
44+ if ( ! a . retired && b . retired ) return - 1 ;
45+
46+ const userChallengeA = userChallenges . find ( ( uc ) => uc . challengeId === a . id ) ;
47+ const userChallengeB = userChallenges . find ( ( uc ) => uc . challengeId === b . id ) ;
48+ const tierA = getTierIndex ( userChallengeA ?. tier ) ;
49+ const tierB = getTierIndex ( userChallengeB ?. tier ) ;
50+
51+ // sort by tier first, then by percentile
52+ if ( tierA !== tierB ) return tierB - tierA ;
53+
54+ return ( userChallengeA ?. percentile || 0 ) - ( userChallengeB ?. percentile || 0 ) ;
55+ } ) ;
56+ break ;
57+
58+ case "Last Updated" :
59+ // When this filter - for whatever reason - gets selected on a non-user page
60+ if ( ! userChallenges ) {
61+ return sortChallenges ( challenges , "Name-ASC" ) ;
62+ }
63+ challenges = challenges . sort ( ( a , b ) => {
64+ if ( a . retired && ! b . retired ) return 1 ;
65+ if ( ! a . retired && b . retired ) return - 1 ;
66+
67+ const userChallengeA = userChallenges . find ( ( uc ) => uc . challengeId === a . id ) ;
68+ const userChallengeB = userChallenges . find ( ( uc ) => uc . challengeId === b . id ) ;
69+
70+ if ( userChallengeA && userChallengeB ) {
71+ return (
72+ new Date ( userChallengeB . achievedTime || 0 ) . getTime ( ) -
73+ new Date ( userChallengeA . achievedTime || 0 ) . getTime ( )
74+ ) ;
75+ }
76+
77+ if ( userChallengeA && ! userChallengeB ) return - 1 ;
78+ if ( ! userChallengeA && userChallengeB ) return 1 ;
79+ return 0 ;
80+ } ) ;
81+ break ;
82+
83+ case "Position" :
84+ // When this filter - for whatever reason - gets selected on a non-user page
85+ if ( ! userChallenges ) {
86+ return sortChallenges ( challenges , "Name-ASC" ) ;
87+ }
88+ challenges = challenges . sort ( ( a , b ) => {
89+ if ( a . retired && ! b . retired ) return 1 ;
90+ if ( ! a . retired && b . retired ) return - 1 ;
91+
92+ const userChallengeA = userChallenges . find ( ( uc ) => uc . challengeId === a . id ) ;
93+ const userChallengeB = userChallenges . find ( ( uc ) => uc . challengeId === b . id ) ;
94+
95+ if ( userChallengeA && userChallengeB ) {
96+ const positionA = userChallengeA . position || 0 ;
97+ const positionB = userChallengeB . position || 0 ;
98+
99+ // A validation check with <= 0 is necessary, as it is not uncommon for the API to return negative positions
100+ if ( positionA > 0 && positionB <= 0 ) return - 1 ;
101+ if ( positionA <= 0 && positionB > 0 ) return 1 ;
102+ if ( positionA <= 0 && positionB <= 0 ) return 0 ;
103+ return ( positionA as number ) - ( positionB as number ) ;
104+ }
105+
106+ if ( userChallengeA && ! userChallengeB ) return - 1 ;
107+ if ( ! userChallengeA && userChallengeB ) return 1 ;
108+ return 0 ;
109+ } ) ;
110+
111+ break ;
112+
113+ case "Levelup" :
114+ // When this filter - for whatever reason - gets selected on a non-user page
115+ if ( ! userChallenges ) {
116+ return sortChallenges ( challenges , "Name-ASC" ) ;
117+ }
118+ challenges = challenges . sort ( ( a , b ) => {
119+ if ( a . retired && ! b . retired ) return 1 ;
120+ if ( ! a . retired && b . retired ) return - 1 ;
121+
122+ const userChallengeA = userChallenges . find ( ( uc ) => uc . challengeId === a . id ) ;
123+ const userChallengeB = userChallenges . find ( ( uc ) => uc . challengeId === b . id ) ;
124+ if ( userChallengeA && ! userChallengeB ) return - 1 ;
125+ if ( ! userChallengeA && userChallengeB ) return 1 ;
126+ if ( ! userChallengeA && ! userChallengeB ) return 0 ;
127+
128+ const currentValueA = userChallengeA ! . value ;
129+ const nextValueA =
130+ a . thresholds [ getNextTier ( userChallengeA ! . tier , a , false ) ] . points ;
131+ const currentValueB = userChallengeB ! . value ;
132+ const nextValueB =
133+ b . thresholds [ getNextTier ( userChallengeB ! . tier , b , false ) ] . points ;
134+
135+ if ( nextValueA === 0 && nextValueB === 0 ) return 0 ;
136+ if ( nextValueA === 0 ) return - 1 ;
137+ if ( nextValueB === 0 ) return 1 ;
138+
139+ const progressA = Math . min ( currentValueA / nextValueA , 1 ) ;
140+ const progressB = Math . min ( currentValueB / nextValueB , 1 ) ;
141+
142+ // Already "maxed challenges" (progress >= 1) can be put at the back of the list
143+ if ( progressA >= 1 && progressA === progressB ) return 0 ;
144+ if ( progressA >= 1 && progressB < 1 ) return 1 ;
145+ if ( progressA < 1 && progressB >= 1 ) return - 1 ;
146+
147+ return progressB - progressA ;
148+ } ) ;
149+
20150 break ;
21151 default :
22152 break ;
0 commit comments