Skip to content

Commit 4197ec2

Browse files
feat: add pagination to game lists
1 parent a08d024 commit 4197ec2

File tree

4 files changed

+290
-101
lines changed

4 files changed

+290
-101
lines changed

src/components/Analysis/AnalysisGameList.tsx

Lines changed: 184 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ import React, {
1010
import { motion } from 'framer-motion'
1111
import { Tournament } from 'src/components'
1212
import { 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

1422
interface 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

Comments
 (0)