Spaces:
Running
Running
import React, { useState, useEffect, useMemo } from "react"; | |
import { Box, CircularProgress, Typography } from "@mui/material"; | |
import SearchOffIcon from "@mui/icons-material/SearchOff"; | |
import Logo from "../../components/Logo/Logo"; | |
import PageTitle from "../../components/PageTitle/PageTitle"; | |
import LeaderboardSection from "../../components/LeaderboardSection"; | |
import LeaderboardFilters from "../../components/LeaderboardFilters/LeaderboardFilters"; | |
import API_URLS from "../../config/api"; | |
import { | |
LeaderboardProvider, | |
useLeaderboard, | |
} from "../../context/LeaderboardContext"; | |
const LeaderboardPageContent = () => { | |
const [loading, setLoading] = useState(true); | |
const { | |
setLeaderboards, | |
filterLeaderboards, | |
sections, | |
allSections, | |
sectionsSortedByCount, | |
searchQuery, | |
arenaOnly, | |
selectedCategories, | |
} = useLeaderboard(); | |
// Vérifier si on a uniquement une recherche textuelle active | |
const isOnlyTextSearch = | |
searchQuery && !arenaOnly && selectedCategories.size === 0; | |
// Obtenir tous les leaderboards uniques de toutes les sections | |
const allUniqueLeaderboards = useMemo(() => { | |
if (!allSections) return []; | |
return Array.from( | |
new Set( | |
allSections.reduce((acc, section) => { | |
return [...acc, ...(section.data || [])]; | |
}, []) | |
) | |
); | |
}, [allSections]); | |
// Filtrer tous les leaderboards pour la recherche textuelle | |
const searchResults = useMemo(() => { | |
if (!isOnlyTextSearch) return []; | |
return filterLeaderboards(allUniqueLeaderboards); | |
}, [isOnlyTextSearch, filterLeaderboards, allUniqueLeaderboards]); | |
useEffect(() => { | |
fetch(API_URLS.leaderboards) | |
.then((res) => { | |
if (!res.ok) { | |
throw new Error(`Failed to fetch leaderboards: ${res.status}`); | |
} | |
return res.json(); | |
}) | |
.then((data) => { | |
// Les données sont directement dans le format attendu depuis le dataset HF | |
setLeaderboards(data); | |
setLoading(false); | |
}) | |
.catch((error) => { | |
console.error("Error fetching leaderboards:", error); | |
setLoading(false); | |
}); | |
}, [setLeaderboards]); | |
// Check if any leaderboards are found after filtering | |
const totalFilteredLeaderboards = sections.reduce( | |
(total, { data }) => total + filterLeaderboards(data).length, | |
0 | |
); | |
const hasLeaderboards = totalFilteredLeaderboards > 0; | |
const isFiltering = searchQuery || arenaOnly; | |
return ( | |
<Box | |
sx={{ | |
width: "100%", | |
ph: 2, | |
display: "flex", | |
flexDirection: "column", | |
}} | |
> | |
<Box | |
sx={{ display: "flex", justifyContent: "center", pt: 6, mb: -4, pb: 0 }} | |
> | |
<Logo /> | |
</Box> | |
<Box | |
sx={{ | |
display: "flex", | |
flexDirection: "column", | |
alignItems: "center", | |
textAlign: "center", | |
mb: 0, | |
mt: 6, | |
gap: 2, | |
}} | |
> | |
<PageTitle /> | |
<Typography variant="h6" color="text.secondary"> | |
<span style={{ fontWeight: 600 }}>Discover</span> and{" "} | |
<span style={{ fontWeight: 600 }}>explore</span> all leaderboards from | |
the <span style={{ fontWeight: 600 }}>Hugging Face community</span> | |
</Typography> | |
</Box> | |
{loading ? ( | |
<Box sx={{ display: "flex", justifyContent: "center", mt: 4 }}> | |
<CircularProgress /> | |
</Box> | |
) : ( | |
<Box | |
sx={{ | |
width: "100%", | |
maxWidth: "1200px", | |
mx: "auto", | |
mt: 4, | |
}} | |
> | |
<LeaderboardFilters allSections={allSections} /> | |
{/* Message global "No results" seulement si on n'a pas de résultats, pas de section de recherche, et pas de catégories sélectionnées */} | |
{!hasLeaderboards && | |
isFiltering && | |
!isOnlyTextSearch && | |
selectedCategories.size === 0 && ( | |
<Box | |
sx={{ | |
display: "flex", | |
flexDirection: "column", | |
alignItems: "center", | |
gap: 2, | |
mt: 8, | |
py: 7, | |
}} | |
> | |
<SearchOffIcon | |
sx={{ | |
fontSize: 64, | |
color: "text.secondary", | |
opacity: 0.5, | |
}} | |
/> | |
<Typography variant="h5" color="text.secondary" align="center"> | |
No results found | |
{searchQuery ? ` matching "${searchQuery}"` : ""} | |
</Typography> | |
<Typography | |
variant="body1" | |
color="text.secondary" | |
align="center" | |
> | |
Try adjusting your filters | |
</Typography> | |
</Box> | |
)} | |
{isOnlyTextSearch ? ( | |
// Vue spéciale pour la recherche textuelle | |
searchResults.length > 0 ? ( | |
<Box key="search-results"> | |
<LeaderboardSection | |
id="search-results" | |
title={`All leaderboards matching "${searchQuery}"`} | |
leaderboards={allUniqueLeaderboards} | |
filteredLeaderboards={searchResults} | |
/> | |
</Box> | |
) : ( | |
// Message d'erreur pour la recherche textuelle sans résultats | |
<Box | |
sx={{ | |
display: "flex", | |
flexDirection: "column", | |
alignItems: "center", | |
gap: 2, | |
mt: 8, | |
py: 7, | |
}} | |
> | |
<SearchOffIcon | |
sx={{ | |
fontSize: 64, | |
color: "text.secondary", | |
opacity: 0.5, | |
}} | |
/> | |
<Typography variant="h5" color="text.secondary" align="center"> | |
No results found | |
{searchQuery ? ` matching "${searchQuery}"` : ""} | |
</Typography> | |
<Typography | |
variant="body1" | |
color="text.secondary" | |
align="center" | |
> | |
Try adjusting your filters | |
</Typography> | |
</Box> | |
) | |
) : selectedCategories.size > 0 ? ( | |
// Si des catégories sont sélectionnées | |
selectedCategories.size === 1 ? ( | |
// Si une seule catégorie est sélectionnée, on affiche sa section | |
sections | |
.filter(({ id }) => selectedCategories.has(id)) | |
.map(({ id, title, data }) => { | |
const filteredLeaderboards = filterLeaderboards(data); | |
// Ajouter le terme de recherche au titre si présent | |
const sectionTitle = searchQuery | |
? `${title} matching "${searchQuery}"` | |
: title; | |
// Toujours afficher la section, même si elle est vide | |
return ( | |
<Box key={id} id={id}> | |
<LeaderboardSection | |
id={id} | |
title={sectionTitle} | |
leaderboards={data} | |
filteredLeaderboards={filteredLeaderboards} | |
showEmptyState={true} // Toujours afficher l'état vide sous le header | |
/> | |
</Box> | |
); | |
}) | |
) : ( | |
// Si plusieurs catégories sont sélectionnées, on les agrège | |
(() => { | |
// Agréger les données de toutes les sections sélectionnées | |
const selectedSections = sections.filter(({ id }) => | |
selectedCategories.has(id) | |
); | |
// Créer un titre combiné avec le terme de recherche si présent | |
const combinedTitle = selectedSections | |
.map(({ title }) => title) | |
.join(" + "); | |
const finalTitle = searchQuery | |
? `${combinedTitle} matching "${searchQuery}"` | |
: combinedTitle; | |
// Agréger les leaderboards | |
const combinedData = selectedSections.reduce( | |
(acc, { data }) => [...acc, ...data], | |
[] | |
); | |
// Filtrer les doublons par ID | |
const uniqueData = Array.from( | |
new Map(combinedData.map((item) => [item.id, item])).values() | |
); | |
const filteredLeaderboards = filterLeaderboards(uniqueData); | |
return ( | |
<Box key="combined"> | |
<LeaderboardSection | |
id="combined" | |
title={finalTitle} | |
leaderboards={uniqueData} | |
filteredLeaderboards={filteredLeaderboards} | |
showEmptyState={true} // Toujours afficher l'état vide sous le header | |
/> | |
</Box> | |
); | |
})() | |
) | |
) : ( | |
// Si aucune catégorie n'est sélectionnée, on affiche toutes les sections avec des résultats | |
// triées par nombre de leaderboards | |
(hasLeaderboards || !isFiltering) && | |
sectionsSortedByCount.map(({ id, title, data }) => { | |
const filteredLeaderboards = filterLeaderboards(data); | |
if (filteredLeaderboards.length === 0) return null; | |
return ( | |
<Box key={id} id={id}> | |
<LeaderboardSection | |
id={id} | |
title={title} | |
leaderboards={data} | |
filteredLeaderboards={filteredLeaderboards} | |
/> | |
</Box> | |
); | |
}) | |
)} | |
</Box> | |
)} | |
</Box> | |
); | |
}; | |
const LeaderboardPage = () => { | |
return ( | |
<LeaderboardProvider> | |
<LeaderboardPageContent /> | |
</LeaderboardProvider> | |
); | |
}; | |
export default LeaderboardPage; | |