import { useEffect, useState, useCallback } from "react"; import LoadingDisplay from "../components/LoadingDisplay"; import { useAuth } from "../contexts/AuthContext"; import { useNavigate } from "react-router"; import { NavBar } from "../components/NavBar"; import SessionExpiredPopup from "../components/SessionExpiredPopup"; import ScoreDisplay from "../components/displays/GenericScoreDisplay"; import DancerushScoreDisplay from "../components/displays/DancerushScoreDisplay"; import DancearoundScoreDisplay from "../components/displays/DancearoundScoreDisplay"; import DivaScoreDisplay from "../components/displays/DivaScoreDisplay"; import MusicDiverScoreDisplay from "../components/displays/MusicDiverScoreDisplay"; import NostalgiaScoreDisplay from "../components/displays/NostalgiaScoreDisplay"; import ReflecBeatScoreDisplay from "../components/displays/ReflecBeatScoreDisplay"; import TaikoScoreDisplay from "../components/displays/TaikoScoreDisplay"; type SortField = string; type SortDirection = "asc" | "desc"; import { getFilterOptions } from "../types/constants"; interface Game { internalName: string; formattedName: string; description: string; } const AllScores = () => { const { user, isLoading, logout } = useAuth(); const navigate = useNavigate(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const [scores, setScores] = useState([]); const [loading, setLoading] = useState(true); const [currentPage, setCurrentPage] = useState(1); const [numPages, setNumPages] = useState(1); const [viewMode, setViewMode] = useState<"cards" | "table">("cards"); const [sortField, setSortField] = useState(""); const [sortDirection, setSortDirection] = useState("asc"); const [requestOrder, setRequestOrder] = useState("timestamp"); const [pbOnly, setPbOnly] = useState(true); const [games, setGames] = useState([]); const [selectedGame, setSelectedGame] = useState( new URLSearchParams(window.location.search).get("game") || "" ); const gameName = selectedGame; const handleLogout = async () => { try { await logout(); navigate("/"); } catch (error) { console.error("Logout failed:", error); alert("Network error during logout. Please try again."); } }; const renderRequestFilterMenu = () => { const filterOptions = getFilterOptions(gameName); return (
{filterOptions.map((option) => ( ))}
); }; const renderPbOnlyToggle = () => { return (
); }; // eslint-disable-next-line @typescript-eslint/no-explicit-any const flattenScoreData = (score: any) => { const flat = { ...score, ...score.data }; delete flat.data; delete flat.gameInternalName; return flat; }; const fetchGames = useCallback(async () => { try { const response = await fetch(import.meta.env.VITE_API_URL + "/supportedGames"); if (!response.ok) throw new Error("Failed to fetch games"); const data = await response.json(); setGames(data); if (!selectedGame && data.length > 0) { setSelectedGame(data[0].internalName); } } catch (error) { console.error("Failed to load games:", error); alert("Failed to load games. Please refresh the page."); } }, [selectedGame]); const fetchScores = useCallback( async (pageNum: number) => { if (!user || !gameName) return; setLoading(true); try { const url = new URL(import.meta.env.VITE_API_URL + "/allScores"); url.searchParams.append("internalGameName", gameName); url.searchParams.append("pageNum", pageNum.toString()); url.searchParams.append("sortKey", requestOrder); // Always sort by timestamp in desc order by default to show most recent first // For other fields, also default to desc to show highest values first url.searchParams.append("direction", "desc"); url.searchParams.append("pbOnly", pbOnly.toString()); const response = await fetch(url.toString(), {credentials: 'include'}); if (!response.ok) throw new Error("Failed to fetch scores"); const data = await response.json(); const flattened = data.scores.map(flattenScoreData); setScores(flattened); setNumPages(data.num_pages); setCurrentPage(pageNum); } catch (error) { console.error("Failed to load scores:", error); alert("Failed to load scores. Please refresh the page."); } finally { setLoading(false); } }, [user, gameName, requestOrder, pbOnly], ); useEffect(() => { fetchGames(); }, [fetchGames]); useEffect(() => { if (user && gameName) { fetchScores(1); } }, [user, fetchScores, gameName]); const handleSort = (field: SortField) => { if (sortField === field) { setSortDirection(sortDirection === "asc" ? "desc" : "asc"); } else { setSortField(field); setSortDirection("desc"); } }; const handleGameChange = (gameInternalName: string) => { setSelectedGame(gameInternalName); setCurrentPage(1); // Reset sort order to timestamp to show most recent scores first setRequestOrder("timestamp"); // Update URL parameter const url = new URL(window.location.href); url.searchParams.set("game", gameInternalName); window.history.replaceState({}, "", url.toString()); }; if (!user) { return ; } if (isLoading) { return ( ); } return (

{gameName ? `${games.find(g => g.internalName === gameName)?.formattedName || gameName} - Community Scores` : "Community Scores" }

{/* Game Selection */}
{/* Filter Menu */}
{gameName && renderRequestFilterMenu()} {renderPbOnlyToggle()}

{pbOnly ? "Showing personal best scores for each chart from all players" : "Showing all recently received scores from all players" }{gameName ? ` for ${games.find(g => g.internalName === gameName)?.formattedName || gameName}` : ""}

{!gameName ? (

Please select a game to view scores

) : loading ? (

Loading community scores...

) : (() => { switch (gameName) { case "dancerush": return ( ); case "dancearound": return ( ); case "diva": return ( ); case "musicdiver": return ( ); case "nostalgia": return ( ); case "reflecbeat": return ( ); case "taiko": return ( ); default: return ( ); } })()} {numPages > 1 && (
{[...Array(numPages)].map((_, i) => ( ))}
)}

{loading ? "Loading..." : `Displaying ${scores.length} scores • Page ${currentPage} of ${numPages}`}

); }; export default AllScores;