11import { mdiCalendarCheck , mdiCalendarHeart , mdiCalendarMonth , mdiCalendarRange , mdiCalendarWeek } from "@mdi/js" ;
22import Icon from "@mdi/react" ;
3- import { useEffect } from "react" ;
3+ import { useEffect , useState } from "react" ;
44import { Badge , Button , Card , Form , ListGroup } from "react-bootstrap" ;
55import { useDispatch , useSelector } from "react-redux" ;
6- import { Link , useLocation , useNavigate , useParams } from "react-router" ;
6+ import { Link , useLocation , useNavigate , useParams , useSearchParams } from "react-router" ;
77
88import VertItem from "../components/vertitem.jsx" ;
99import { useRetrieveRankingsQuery } from "../features/call.js" ;
@@ -18,9 +18,20 @@ export default function Rankings() {
1818 const thisdate = new Date ( ) ;
1919 const { y, m, d } = useParams ( ) ;
2020 const isweekly = location . pathname . endsWith ( "/week" ) ;
21+ const isCustomRange = location . pathname === "/rankings/range" ;
22+ const [ searchParams , setSearchParams ] = useSearchParams ( ) ;
23+ const rangeStart = searchParams . get ( "start" ) ;
24+ const rangeEnd = searchParams . get ( "end" ) ;
25+ const [ startField , setStartField ] = useState ( rangeStart || "" ) ;
26+ const [ endField , setEndField ] = useState ( rangeEnd || "" ) ;
2127 const pickdate = useSelector ( ( state ) => state . area . date ) ;
2228 const vibe = useSelector ( ( data ) => data . area . vibe ) ;
2329
30+ useEffect ( ( ) => {
31+ setStartField ( rangeStart || "" ) ;
32+ setEndField ( rangeEnd || "" ) ;
33+ } , [ rangeStart , rangeEnd ] ) ;
34+
2435 const readDate = ( ) => {
2536 if ( y && m && d ) {
2637 return `${ y } -${ m . toString ( ) . padStart ( 2 , "0" ) } -${ d . toString ( ) . padStart ( 2 , "0" ) } ` ;
@@ -41,14 +52,21 @@ export default function Rankings() {
4152 }
4253 } ;
4354
44- const params = {
55+ const stdParams = {
4556 ...( y && { y : parseInt ( y ) } ) ,
4657 ...( m && { m : parseInt ( m ) } ) ,
4758 ...( d && { d : parseInt ( d ) } ) ,
4859 ...( isweekly && { w : true } ) ,
4960 } ;
5061
51- const { data : dict , isLoading, error } = useRetrieveRankingsQuery ( params , { skip : false } ) ;
62+ const apiParams =
63+ isCustomRange && rangeStart && rangeEnd
64+ ? { start : rangeStart , end : rangeEnd }
65+ : stdParams ;
66+
67+ const skipRetrieve = isCustomRange && ( ! rangeStart || ! rangeEnd ) ;
68+
69+ const { data : dict , isLoading, error } = useRetrieveRankingsQuery ( apiParams , { skip : skipRetrieve } ) ;
5270
5371 // Show or Hide LoadNote
5472 useEffect ( ( ) => {
@@ -63,32 +81,41 @@ export default function Rankings() {
6381 return < Mistaken /> ;
6482 }
6583
66- if ( isLoading || ! dict ) {
84+ const loadingBlocked =
85+ ( ! isCustomRange && ( isLoading || ! dict ) ) ||
86+ ( isCustomRange && rangeStart && rangeEnd && ( isLoading || ! dict ) ) ;
87+
88+ if ( loadingBlocked ) {
6789 return null ;
6890 }
6991
7092 const showDate = ( ) => {
71- if ( Object . keys ( params ) . length === 0 ) return "All time" ;
93+ if ( isCustomRange ) {
94+ if ( rangeStart && rangeEnd ) return `${ rangeStart } to ${ rangeEnd } ` ;
95+ return "Pick start and end dates" ;
96+ }
97+ if ( Object . keys ( stdParams ) . length === 0 ) return "All time" ;
7298
7399 const date = new Date ( ) ;
74- if ( params . y ) date . setFullYear ( params . y ) ;
75- if ( params . m ) date . setMonth ( params . m - 1 ) ;
76- if ( params . d ) date . setDate ( params . d ) ;
100+ if ( stdParams . y ) date . setFullYear ( stdParams . y ) ;
101+ if ( stdParams . m ) date . setMonth ( stdParams . m - 1 ) ;
102+ if ( stdParams . d ) date . setDate ( stdParams . d ) ;
77103
78104 const option = { } ;
79- if ( params . y ) option . year = "numeric" ;
80- if ( params . m ) option . month = "long" ;
81- if ( params . d ) option . day = "numeric" ;
105+ if ( stdParams . y ) option . year = "numeric" ;
106+ if ( stdParams . m ) option . month = "long" ;
107+ if ( stdParams . d ) option . day = "numeric" ;
82108
83109 return date . toLocaleDateString ( "en-US" , option ) ;
84110 } ;
85111
86112 const showHead = ( ) => {
113+ if ( isCustomRange ) return "Custom range" ;
87114 let name = "" ;
88- if ( params . w ) name = "Weekly" ;
89- else if ( params . d ) name = "Daily" ;
90- else if ( params . m ) name = "Monthly" ;
91- else if ( params . y ) name = "Yearly" ;
115+ if ( stdParams . w ) name = "Weekly" ;
116+ else if ( stdParams . d ) name = "Daily" ;
117+ else if ( stdParams . m ) name = "Monthly" ;
118+ else if ( stdParams . y ) name = "Yearly" ;
92119 else name = "All time" ;
93120 return name ;
94121 } ;
@@ -108,27 +135,27 @@ export default function Rankings() {
108135 const scanDate = ( conf = "" ) => {
109136 if ( conf === "d" )
110137 return (
111- params . y &&
112- params . m &&
113- params . d &&
114- ( params . y !== thisdate . getFullYear ( ) ||
115- params . m !== thisdate . getMonth ( ) + 1 ||
116- params . d !== thisdate . getDate ( ) ) &&
138+ stdParams . y &&
139+ stdParams . m &&
140+ stdParams . d &&
141+ ( stdParams . y !== thisdate . getFullYear ( ) ||
142+ stdParams . m !== thisdate . getMonth ( ) + 1 ||
143+ stdParams . d !== thisdate . getDate ( ) ) &&
117144 isweekly
118145 ) ;
119146 else if ( conf === "w" )
120147 return (
121- params . y &&
122- params . m &&
123- params . d &&
124- ( params . y !== thisdate . getFullYear ( ) ||
125- params . m !== thisdate . getMonth ( ) + 1 ||
126- params . d !== thisdate . getDate ( ) ) &&
148+ stdParams . y &&
149+ stdParams . m &&
150+ stdParams . d &&
151+ ( stdParams . y !== thisdate . getFullYear ( ) ||
152+ stdParams . m !== thisdate . getMonth ( ) + 1 ||
153+ stdParams . d !== thisdate . getDate ( ) ) &&
127154 ! isweekly
128155 ) ;
129156 else if ( conf === "m" )
130- return params . y && params . m && ( params . y !== thisdate . getFullYear ( ) || params . m !== thisdate . getMonth ( ) + 1 ) ;
131- else if ( conf === "y" ) return params . y && params . y !== thisdate . getFullYear ( ) ;
157+ return stdParams . y && stdParams . m && ( stdParams . y !== thisdate . getFullYear ( ) || stdParams . m !== thisdate . getMonth ( ) + 1 ) ;
158+ else if ( conf === "y" ) return stdParams . y && stdParams . y !== thisdate . getFullYear ( ) ;
132159 return false ;
133160 } ;
134161
@@ -139,9 +166,43 @@ export default function Rankings() {
139166 < Card . Body className = "p-2" >
140167 < Card . Title className = "dataelem text-truncate" > Rankings</ Card . Title >
141168 < Card . Text className = "small" > { showDate ( ) } </ Card . Text >
142- < Card . Text >
143- < Form . Control type = "date" value = { readDate ( ) } onChange = { handleChange } size = "sm" autoComplete = "off" />
144- </ Card . Text >
169+ { isCustomRange ? (
170+ < Card . Text >
171+ < Form
172+ onSubmit = { ( e ) => {
173+ e . preventDefault ( ) ;
174+ if ( ! startField || ! endField ) return ;
175+ setSearchParams ( { start : startField , end : endField } ) ;
176+ } }
177+ >
178+ < Form . Label className = "small mb-0" > Start</ Form . Label >
179+ < Form . Control
180+ type = "date"
181+ value = { startField }
182+ onChange = { ( e ) => setStartField ( e . target . value ) }
183+ className = "mb-2"
184+ size = "sm"
185+ autoComplete = "off"
186+ />
187+ < Form . Label className = "small mb-0" > End</ Form . Label >
188+ < Form . Control
189+ type = "date"
190+ value = { endField }
191+ onChange = { ( e ) => setEndField ( e . target . value ) }
192+ className = "mb-2"
193+ size = "sm"
194+ autoComplete = "off"
195+ />
196+ < Button type = "submit" size = "sm" variant = "outline-secondary" className = "w-100 vibe-border" style = { { "--vibe" : vibe } } >
197+ Show rankings
198+ </ Button >
199+ </ Form >
200+ </ Card . Text >
201+ ) : (
202+ < Card . Text >
203+ < Form . Control type = "date" value = { readDate ( ) } onChange = { handleChange } size = "sm" autoComplete = "off" />
204+ </ Card . Text >
205+ ) }
145206 </ Card . Body >
146207 </ Card >
147208 < div className = "d-grid gap-2" >
@@ -200,11 +261,22 @@ export default function Rankings() {
200261 < Icon path = { mdiCalendarHeart } size = { 0.875 } className = "me-1" />
201262 All time
202263 </ Button >
264+ < Button
265+ as = { Link }
266+ to = "/rankings/range"
267+ variant = "outline-secondary"
268+ className = "d-grid d-inline-flex align-items-center ps-1 vibe-border"
269+ size = "sm"
270+ style = { { "--vibe" : vibe } }
271+ >
272+ < Icon path = { mdiCalendarRange } size = { 0.875 } className = "me-1" />
273+ Custom date range
274+ </ Button >
203275 { scanDate ( "y" ) || scanDate ( "m" ) || scanDate ( "w" ) || scanDate ( "d" ) ? < hr className = "m-0" /> : null }
204276 { scanDate ( "w" ) ? (
205277 < Button
206278 as = { Link }
207- to = { `/rankings/y/${ params . y } /m/${ params . m } /d/${ params . d } /week` }
279+ to = { `/rankings/y/${ stdParams . y } /m/${ stdParams . m } /d/${ stdParams . d } /week` }
208280 variant = "outline-secondary"
209281 className = "d-grid d-inline-flex align-items-center ps-1 vibe-border"
210282 size = "sm"
@@ -217,7 +289,7 @@ export default function Rankings() {
217289 { scanDate ( "d" ) ? (
218290 < Button
219291 as = { Link }
220- to = { `/rankings/y/${ params . y } /m/${ params . m } /d/${ params . d } ` }
292+ to = { `/rankings/y/${ stdParams . y } /m/${ stdParams . m } /d/${ stdParams . d } ` }
221293 variant = "outline-secondary"
222294 className = "d-grid d-inline-flex align-items-center ps-1 vibe-border"
223295 size = "sm"
@@ -230,27 +302,27 @@ export default function Rankings() {
230302 { scanDate ( "m" ) ? (
231303 < Button
232304 as = { Link }
233- to = { `/rankings/y/${ params . y } /m/${ params . m } ` }
305+ to = { `/rankings/y/${ stdParams . y } /m/${ stdParams . m } ` }
234306 variant = "outline-secondary"
235307 className = "d-grid d-inline-flex align-items-center ps-1 vibe-border"
236308 size = "sm"
237309 style = { { "--vibe" : vibe } }
238310 >
239311 < Icon path = { mdiCalendarMonth } size = { 0.875 } className = "me-1" />
240- For { new Date ( params . y , params . m - 1 ) . toLocaleDateString ( "en-US" , { month : "long" } ) }
312+ For { new Date ( stdParams . y , stdParams . m - 1 ) . toLocaleDateString ( "en-US" , { month : "long" } ) }
241313 </ Button >
242314 ) : null }
243315 { scanDate ( "y" ) ? (
244316 < Button
245317 as = { Link }
246- to = { `/rankings/y/${ params . y } ` }
318+ to = { `/rankings/y/${ stdParams . y } ` }
247319 variant = "outline-secondary"
248320 className = "d-grid d-inline-flex align-items-center ps-1 vibe-border"
249321 size = "sm"
250322 style = { { "--vibe" : vibe } }
251323 >
252324 < Icon path = { mdiCalendarCheck } size = { 0.875 } className = "me-1" />
253- For { params . y }
325+ For { stdParams . y }
254326 </ Button >
255327 ) : null }
256328 </ div >
@@ -271,7 +343,7 @@ export default function Rankings() {
271343 link = { `/identity/${ item . nickname } ` }
272344 shot = { portraitProvider ( item . mail ) }
273345 head = { item . nickname }
274- body = { `Collected ${ item . badges } badge(s) ${ y || m || d || isweekly ? `during this period • Global rank #${ item . rank . global } ` : "" } ` }
346+ body = { `Collected ${ item . badges } badge(s) ${ y || m || d || isweekly || ( isCustomRange && rangeStart && rangeEnd ) ? `during this period • Global rank #${ item . rank . global } ` : "" } ` }
275347 hand = {
276348 < Badge className = "monoelem vibe-badge" style = { { "--vibe" : vibe } } >
277349 #{ item . rank . period }
0 commit comments