import React, { createContext, useContext, useState, useCallback, useMemo, useEffect, } from "react"; import { normalizeTags } from "../utils/tagFilters"; import { useUrlState } from "../hooks/useUrlState"; import { CATEGORIZATION_TAGS, isUncategorized, filterLeaderboards as filterLeaderboardsUtil, generateSections, } from "../utils/filterUtils"; const LeaderboardContext = createContext(); // Helper pour déterminer si un leaderboard est non catégorisé const isUncategorizedBoard = isUncategorized; export const LeaderboardProvider = ({ children }) => { const { params, updateParams } = useUrlState(); const [leaderboards, setLeaderboards] = useState([]); const [searchQuery, setSearchQuery] = useState(params.search || ""); const [arenaOnly, setArenaOnly] = useState(params.arena === "true"); const [selectedCategories, setSelectedCategories] = useState( new Set(params.categories || []) ); const [selectedLanguage, setSelectedLanguage] = useState( params.language ? new Set(params.language.split(",")) : new Set() ); const [expandedSections, setExpandedSections] = useState( new Set(params.categories || []) ); const [isLanguageExpanded, setIsLanguageExpanded] = useState( params.languageExpanded === "true" ); useEffect(() => { updateParams({ categories: Array.from(selectedCategories), search: searchQuery, arena: arenaOnly ? "true" : null, language: selectedLanguage.size > 0 ? Array.from(selectedLanguage).join(",") : null, languageExpanded: isLanguageExpanded ? "true" : null, }); }, [ selectedCategories, searchQuery, arenaOnly, selectedLanguage, isLanguageExpanded, updateParams, ]); useEffect(() => { if (leaderboards.length > 0) { const uncategorizedLeaderboards = leaderboards.filter( (board) => board.approval_status === "approved" && isUncategorizedBoard(board) ); } }, [leaderboards]); const handleCategorySelection = useCallback((categoryId) => { setSelectedCategories((prev) => { const newCategories = new Set(prev); if (newCategories.has(categoryId)) { newCategories.delete(categoryId); setExpandedSections((prev) => { const newExpanded = new Set(prev); newExpanded.delete(categoryId); return newExpanded; }); } else { newCategories.add(categoryId); // Si on sélectionne, on déploie la section setExpandedSections((prev) => { const newExpanded = new Set(prev); newExpanded.add(categoryId); return newExpanded; }); } return newCategories; }); // On réinitialise la langue sélectionnée seulement si on désélectionne "language" if (categoryId === "language") { setSelectedLanguage(new Set()); } }, []); // Wrapper pour la sélection de langue const handleLanguageSelection = useCallback((language) => { setSelectedLanguage((prev) => { const newSet = new Set(prev); if (newSet.has(language)) { newSet.delete(language); } else { newSet.add(language); } return newSet; }); }, []); // Filter leaderboards based on search query and arena toggle, ignoring category selection const filterLeaderboardsForCount = useCallback( (boards) => { return filterLeaderboardsUtil({ boards, searchQuery, arenaOnly, }); }, [searchQuery, arenaOnly] ); // Filter leaderboards based on all criteria including categories and language selection const filterLeaderboards = useCallback( (boards) => { return filterLeaderboardsUtil({ boards, searchQuery, arenaOnly, selectedCategories, selectedLanguage, }); }, [searchQuery, arenaOnly, selectedCategories, selectedLanguage] ); // Fonction pour obtenir les leaderboards bruts d'une section const getSectionLeaderboards = useCallback((boards) => { if (!boards) return []; return [...boards]; }, []); // Filter functions for categories const filterByTag = useCallback((tag, boards) => { const searchTag = tag.toLowerCase(); return ( boards?.filter((board) => board.tags?.some((t) => t.toLowerCase() === searchTag) ) || [] ); }, []); const filterByLanguage = useCallback((boards) => { return ( boards?.filter((board) => board.tags?.some((tag) => tag.startsWith("language:")) ) || [] ); }, []); // Define sections with raw data const allSections = useMemo(() => { if (!leaderboards) return []; return generateSections(leaderboards, filterByTag, filterByLanguage); }, [leaderboards, filterByTag, filterByLanguage]); // Get sections with data const sections = useMemo(() => { return allSections.filter((section) => section.data.length > 0); }, [allSections]); // Sections triées par nombre de leaderboards (pour l'affichage sur la page d'accueil) const sectionsSortedByCount = useMemo(() => { return [...sections].sort((a, b) => { // Filtrer pour n'avoir que les leaderboards approuvés const approvedA = a.data.filter( (board) => board.approval_status === "approved" ); const approvedB = b.data.filter( (board) => board.approval_status === "approved" ); return approvedB.length - approvedA.length; // Tri décroissant }); }, [sections]); // Calculate total number of unique leaderboards (excluding duplicates) const totalLeaderboards = useMemo(() => { const uniqueIds = new Set( leaderboards .filter((board) => board.approval_status === "approved") .map((board) => board.id) ); return uniqueIds.size; }, [leaderboards]); // Get filtered count const filteredCount = useMemo(() => { return filterLeaderboards(leaderboards).length; }, [filterLeaderboards, leaderboards]); // Function to get highlighted parts of text const getHighlightedText = useCallback((text, searchTerm) => { if (!searchTerm || !text) return { text, shouldHighlight: false }; const query = searchTerm.toLowerCase(); const searchableTagPrefixes = [ "domain:", "language:", "judge:", "test:", "modality:", "submission:", "eval:", ]; // Si c'est une recherche par tag, on ne highlight rien if (searchableTagPrefixes.some((prefix) => query.startsWith(prefix))) { return { text, shouldHighlight: false }; } // Sinon on highlight les parties qui matchent const index = text.toLowerCase().indexOf(query); if (index === -1) return { text, shouldHighlight: false }; return { text, shouldHighlight: true, highlightStart: index, highlightEnd: index + query.length, }; }, []); // Wrapper pour setLeaderboards qui normalise les tags const setNormalizedLeaderboards = useCallback((boards) => { const normalizedBoards = boards.map((board) => ({ ...board, tags: normalizeTags(board.tags), })); setLeaderboards(normalizedBoards); }, []); const resetState = useCallback(() => { setSearchQuery(""); setArenaOnly(false); setSelectedCategories(new Set()); setSelectedLanguage(new Set()); setExpandedSections(new Set()); setIsLanguageExpanded(false); }, []); const value = { leaderboards, setLeaderboards: setNormalizedLeaderboards, searchQuery, setSearchQuery, arenaOnly, setArenaOnly, totalLeaderboards, filteredCount, filterLeaderboards, filterLeaderboardsForCount, sections, sectionsSortedByCount, allSections, getHighlightedText, selectedCategories, setSelectedCategories: handleCategorySelection, selectedLanguage, setSelectedLanguage: handleLanguageSelection, expandedSections, setExpandedSections, resetState, isLanguageExpanded, setIsLanguageExpanded, }; return ( {children} ); }; export const useLeaderboard = () => { const context = useContext(LeaderboardContext); if (!context) { throw new Error("useLeaderboard must be used within a LeaderboardProvider"); } return context; };