@@ -10,6 +10,14 @@ import React, {
1010import { motion } from 'framer-motion'
1111import { Tournament } from 'src/components'
1212import { AnalysisListContext } from 'src/contexts'
13+ import { getAnalysisGameList } from 'src/api'
14+
15+ interface GameData {
16+ game_id : string
17+ maia_name : string
18+ result : string
19+ player_color : 'white' | 'black'
20+ }
1321
1422interface AnalysisGameListProps {
1523 currentId : string [ ] | null
@@ -43,6 +51,13 @@ export const AnalysisGameList: React.FC<AnalysisGameListProps> = ({
4351 analysisTournamentList,
4452 } = useContext ( AnalysisListContext )
4553
54+ const [ currentPage , setCurrentPage ] = useState ( 1 )
55+ const [ totalPages , setTotalPages ] = useState ( 1 )
56+ const [ loading , setLoading ] = useState ( false )
57+ const [ localPlayGames , setLocalPlayGames ] = useState ( analysisPlayList )
58+ const [ localHandGames , setLocalHandGames ] = useState ( analysisHandList )
59+ const [ localBrainGames , setLocalBrainGames ] = useState ( analysisBrainList )
60+
4661 const listKeys = useMemo ( ( ) => {
4762 return analysisTournamentList
4863 ? Array . from ( analysisTournamentList . keys ( ) ) . sort (
@@ -78,6 +93,58 @@ export const AnalysisGameList: React.FC<AnalysisGameListProps> = ({
7893 setLoadingIndex ( null )
7994 } , [ selected ] )
8095
96+ useEffect ( ( ) => {
97+ if ( selected !== 'tournament' && selected !== 'pgn' ) {
98+ setLoading ( true )
99+ getAnalysisGameList ( selected , currentPage ) . then ( ( data ) => {
100+ const parse = (
101+ game : {
102+ game_id : string
103+ maia_name : string
104+ result : string
105+ player_color : 'white' | 'black'
106+ } ,
107+ type : string ,
108+ ) => {
109+ const raw = game . maia_name . replace ( '_kdd_' , ' ' )
110+ const maia = raw . charAt ( 0 ) . toUpperCase ( ) + raw . slice ( 1 )
111+
112+ return {
113+ id : game . game_id ,
114+ label :
115+ game . player_color === 'white'
116+ ? `You vs. ${ maia } `
117+ : `${ maia } vs. You` ,
118+ result : game . result ,
119+ type,
120+ }
121+ }
122+
123+ if ( selected === 'play' ) {
124+ setLocalPlayGames (
125+ data . games . map ( ( game : GameData ) => parse ( game , 'play' ) ) ,
126+ )
127+ } else if ( selected === 'hand' ) {
128+ setLocalHandGames (
129+ data . games . map ( ( game : GameData ) => parse ( game , 'hand' ) ) ,
130+ )
131+ } else if ( selected === 'brain' ) {
132+ setLocalBrainGames (
133+ data . games . map ( ( game : GameData ) => parse ( game , 'brain' ) ) ,
134+ )
135+ }
136+ setTotalPages ( Math . ceil ( data . total / 100 ) )
137+ setLoading ( false )
138+ } )
139+ }
140+ } , [ selected , currentPage ] )
141+
142+ const handlePageChange = ( newPage : number ) => {
143+ if ( newPage >= 1 && newPage <= totalPages ) {
144+ setCurrentPage ( newPage )
145+ }
146+ }
147+
81148 return analysisTournamentList ? (
82149 < div className = "flex h-full flex-col items-start justify-start overflow-hidden bg-background-1 md:rounded" >
83150 < div className = "flex h-full w-full flex-col" >
@@ -114,71 +181,127 @@ export const AnalysisGameList: React.FC<AnalysisGameListProps> = ({
114181 />
115182 </ div >
116183 < div className = "red-scrollbar flex h-full flex-col overflow-y-scroll" >
117- { selected === 'tournament' ? (
118- < >
119- { listKeys . map ( ( id , i ) => (
120- < Tournament
121- key = { i }
122- id = { id }
123- index = { i }
124- openIndex = { openIndex }
125- currentId = { currentId }
126- openElement = { openElement as React . RefObject < HTMLDivElement > }
127- setOpenIndex = { setOpenIndex }
128- loadingIndex = { loadingIndex }
129- setLoadingIndex = { setLoadingIndex }
130- selectedGameElement = {
131- selectedGameElement as React . RefObject < HTMLButtonElement >
132- }
133- loadNewTournamentGame = { loadNewTournamentGame }
134- analysisTournamentList = { analysisTournamentList }
135- />
136- ) ) }
137- </ >
184+ { loading ? (
185+ < div className = "flex h-full items-center justify-center" >
186+ < div className = "h-8 w-8 animate-spin rounded-full border-b-2 border-white" > </ div >
187+ </ div >
138188 ) : (
139189 < >
140- { ( selected === 'play'
141- ? analysisPlayList
142- : selected === 'hand'
143- ? analysisHandList
144- : selected === 'brain'
145- ? analysisBrainList
146- : analysisLichessList
147- ) . map ( ( game , index ) => {
148- const selectedGame = currentId && currentId [ 0 ] === game . id
149- return (
150- < button
151- key = { index }
152- onClick = { async ( ) => {
153- setLoadingIndex ( index )
154- if ( game . type === 'pgn' ) {
155- await loadNewLichessGames ( game . id , game . pgn as string )
156- } else {
157- await loadNewUserGames (
158- game . id ,
159- game . type as 'play' | 'hand' | 'brain' ,
160- )
190+ { selected === 'tournament' ? (
191+ < >
192+ { listKeys . map ( ( id , i ) => (
193+ < Tournament
194+ key = { i }
195+ id = { id }
196+ index = { i }
197+ openIndex = { openIndex }
198+ currentId = { currentId }
199+ openElement = {
200+ openElement as React . RefObject < HTMLDivElement >
161201 }
162- setLoadingIndex ( null )
163- } }
164- className = { `group flex w-full cursor-pointer items-center gap-2 pr-1 ${ selectedGame ? 'bg-background-2 font-bold' : index % 2 === 0 ? 'bg-background-1/30 hover:bg-background-2' : 'bg-background-1/10 hover:bg-background-2' } ` }
165- >
166- < div
167- className = { `flex h-full w-9 items-center justify-center ${ selectedGame ? 'bg-background-3' : 'bg-background-2 group-hover:bg-white/5' } ` }
168- >
169- < p className = "text-sm text-secondary" > { index + 1 } </ p >
170- </ div >
171- < div className = "flex flex-1 items-center justify-between overflow-hidden py-1" >
172- < p className = "overflow-hidden text-ellipsis whitespace-nowrap text-sm text-primary" >
173- { game . label }
174- </ p >
175- < p className = "whitespace-nowrap text-sm font-light text-secondary" >
176- { game . result }
177- </ p >
202+ setOpenIndex = { setOpenIndex }
203+ loadingIndex = { loadingIndex }
204+ setLoadingIndex = { setLoadingIndex }
205+ selectedGameElement = {
206+ selectedGameElement as React . RefObject < HTMLButtonElement >
207+ }
208+ loadNewTournamentGame = { loadNewTournamentGame }
209+ analysisTournamentList = { analysisTournamentList }
210+ />
211+ ) ) }
212+ </ >
213+ ) : (
214+ < >
215+ { ( selected === 'play'
216+ ? localPlayGames
217+ : selected === 'hand'
218+ ? localHandGames
219+ : selected === 'brain'
220+ ? localBrainGames
221+ : analysisLichessList
222+ ) . map ( ( game , index ) => {
223+ const selectedGame = currentId && currentId [ 0 ] === game . id
224+ return (
225+ < button
226+ key = { index }
227+ onClick = { async ( ) => {
228+ setLoadingIndex ( index )
229+ if ( game . type === 'pgn' ) {
230+ await loadNewLichessGames (
231+ game . id ,
232+ game . pgn as string ,
233+ )
234+ } else {
235+ await loadNewUserGames (
236+ game . id ,
237+ game . type as 'play' | 'hand' | 'brain' ,
238+ )
239+ }
240+ setLoadingIndex ( null )
241+ } }
242+ className = { `group flex w-full cursor-pointer items-center gap-2 pr-1 ${ selectedGame ? 'bg-background-2 font-bold' : index % 2 === 0 ? 'bg-background-1/30 hover:bg-background-2' : 'bg-background-1/10 hover:bg-background-2' } ` }
243+ >
244+ < div
245+ className = { `flex h-full w-9 items-center justify-center ${ selectedGame ? 'bg-background-3' : 'bg-background-2 group-hover:bg-white/5' } ` }
246+ >
247+ < p className = "text-sm text-secondary" > { index + 1 } </ p >
248+ </ div >
249+ < div className = "flex flex-1 items-center justify-between overflow-hidden py-1" >
250+ < p className = "overflow-hidden text-ellipsis whitespace-nowrap text-sm text-primary" >
251+ { game . label }
252+ </ p >
253+ < p className = "whitespace-nowrap text-sm font-light text-secondary" >
254+ { game . result }
255+ </ p >
256+ </ div >
257+ </ button >
258+ )
259+ } ) }
260+ { selected !== 'pgn' && totalPages > 1 && (
261+ < div className = "flex items-center justify-center gap-2 py-2" >
262+ < button
263+ onClick = { ( ) => handlePageChange ( 1 ) }
264+ disabled = { currentPage === 1 }
265+ className = "flex items-center justify-center text-secondary hover:text-primary disabled:opacity-50"
266+ >
267+ < span className = "material-symbols-outlined" >
268+ first_page
269+ </ span >
270+ </ button >
271+ < button
272+ onClick = { ( ) => handlePageChange ( currentPage - 1 ) }
273+ disabled = { currentPage === 1 }
274+ className = "flex items-center justify-center text-secondary hover:text-primary disabled:opacity-50"
275+ >
276+ < span className = "material-symbols-outlined" >
277+ arrow_back_ios
278+ </ span >
279+ </ button >
280+ < span className = "text-sm text-secondary" >
281+ Page { currentPage } of { totalPages }
282+ </ span >
283+ < button
284+ onClick = { ( ) => handlePageChange ( currentPage + 1 ) }
285+ disabled = { currentPage === totalPages }
286+ className = "flex items-center justify-center text-secondary hover:text-primary disabled:opacity-50"
287+ >
288+ < span className = "material-symbols-outlined" >
289+ arrow_forward_ios
290+ </ span >
291+ </ button >
292+ < button
293+ onClick = { ( ) => handlePageChange ( totalPages ) }
294+ disabled = { currentPage === totalPages }
295+ className = "flex items-center justify-center text-secondary hover:text-primary disabled:opacity-50"
296+ >
297+ < span className = "material-symbols-outlined" >
298+ last_page
299+ </ span >
300+ </ button >
178301 </ div >
179- </ button >
180- )
181- } ) }
302+ ) }
303+ </ >
304+ ) }
182305 </ >
183306 ) }
184307 < div className = "flex flex-1 items-start justify-center gap-1 py-2 md:items-center" >
0 commit comments