tfrere commited on
Commit
6103a62
·
1 Parent(s): e812c8c

update filterTag count on show arena toggle | update iflterTag disable state

Browse files
client/src/components/LeaderboardFilters/LeaderboardFilters.jsx CHANGED
@@ -1,9 +1,10 @@
1
  import React, { useState, useMemo } from "react";
2
- import { Box, Stack, Button, useMediaQuery } from "@mui/material";
3
  import { useLeaderboard } from "../../context/LeaderboardContext";
4
  import { useDebounce } from "../../hooks/useDebounce";
5
  import { alpha, lighten, darken } from "@mui/material/styles";
6
  import SearchBar from "./SearchBar";
 
7
 
8
  // Constantes pour les tags de catégorisation
9
  const CATEGORIZATION_TAGS = [
@@ -100,6 +101,18 @@ const LeaderboardFilters = ({ allSections = [] }) => {
100
  const [totalArenaCount, setTotalArenaCount] = useState(0);
101
  const debouncedSearch = useDebounce(inputValue, 200);
102
 
 
 
 
 
 
 
 
 
 
 
 
 
103
  // Update the search query after debounce
104
  React.useEffect(() => {
105
  setSearchQuery(debouncedSearch);
@@ -342,8 +355,8 @@ const LeaderboardFilters = ({ allSections = [] }) => {
342
  justifyContent="center"
343
  sx={{ pb: 2 }}
344
  >
345
- {(allSections || []).map(({ id, title, data = [] }, index) => {
346
- const count = data.length;
347
  const currentGroup = getSectionGroup(id);
348
  const prevGroup =
349
  index > 0 ? getSectionGroup(allSections[index - 1].id) : null;
@@ -355,93 +368,24 @@ const LeaderboardFilters = ({ allSections = [] }) => {
355
  {needsSpacing && index > 0 && (
356
  <Box sx={{ width: "0.5rem", display: "inline-block" }} />
357
  )}
358
- <Button
 
 
 
359
  onClick={() => {
360
  if (isSelected || count > 0) {
361
  setSelectedCategories(id);
362
  }
363
  }}
364
- variant={isSelected ? "contained" : "outlined"}
365
- size="small"
366
- disabled={count === 0 && !isSelected}
367
- sx={{
368
- textTransform: "none",
369
- cursor: count === 0 && !isSelected ? "default" : "pointer",
370
- mb: 1,
371
- backgroundColor: (theme) => {
372
- if (isSelected) {
373
- return groupColors[currentGroup]?.main;
374
- }
375
- return theme.palette.mode === "dark"
376
- ? "background.paper"
377
- : groupColors[currentGroup]?.light;
378
- },
379
- color: (theme) => {
380
- if (isSelected) {
381
- return "white";
382
- }
383
- return groupColors[currentGroup]?.main;
384
- },
385
- borderColor: (theme) => {
386
- return groupColors[currentGroup]?.main;
387
- },
388
- "&:hover": {
389
- backgroundColor: (theme) => {
390
- if (isSelected) {
391
- return groupColors[currentGroup]?.main;
392
- }
393
- return groupColors[currentGroup]?.light;
394
- },
395
- },
396
- "& .MuiTouchRipple-root": {
397
- transition: "none",
398
- },
399
- transition: "none",
400
- }}
401
- >
402
- {title}
403
- <Box
404
- component="span"
405
- sx={{
406
- display: "inline-flex",
407
- alignItems: "center",
408
- gap: 0.75,
409
- color: (theme) => {
410
- if (isSelected) {
411
- return "white";
412
- }
413
- // Assombrir la couleur principale du groupe
414
- return theme.palette.mode === "dark"
415
- ? lighten(groupColors[currentGroup]?.main, 0.2)
416
- : darken(groupColors[currentGroup]?.main, 0.2);
417
- },
418
- ml: 0.75,
419
- }}
420
- >
421
- <Box
422
- component="span"
423
- sx={(theme) => ({
424
- width: "4px",
425
- height: "4px",
426
- borderRadius: "100%",
427
- backgroundColor: isSelected
428
- ? "rgba(255, 255, 255, 0.5)"
429
- : alpha(
430
- groupColors[currentGroup]?.main,
431
- theme.palette.mode === "dark" ? 0.4 : 0.3
432
- ),
433
- })}
434
- />
435
- {count}
436
- </Box>
437
- </Button>
438
  </React.Fragment>
439
  );
440
  })}
441
  </Stack>
442
  </Box>
443
 
444
- {/* Search bar */}
445
  <SearchBar
446
  searchQuery={searchQuery}
447
  setSearchQuery={setSearchQuery}
 
1
  import React, { useState, useMemo } from "react";
2
+ import { Box, Stack, useMediaQuery } from "@mui/material";
3
  import { useLeaderboard } from "../../context/LeaderboardContext";
4
  import { useDebounce } from "../../hooks/useDebounce";
5
  import { alpha, lighten, darken } from "@mui/material/styles";
6
  import SearchBar from "./SearchBar";
7
+ import FilterTag from "../common/FilterTag";
8
 
9
  // Constantes pour les tags de catégorisation
10
  const CATEGORIZATION_TAGS = [
 
101
  const [totalArenaCount, setTotalArenaCount] = useState(0);
102
  const debouncedSearch = useDebounce(inputValue, 200);
103
 
104
+ // Calculate section counts based on arena filter
105
+ const sectionCounts = useMemo(() => {
106
+ const counts = new Map();
107
+ allSections.forEach(({ id, data = [] }) => {
108
+ const filteredData = arenaOnly
109
+ ? data.filter((board) => board.tags?.includes("judge:humans"))
110
+ : data;
111
+ counts.set(id, filteredData.length);
112
+ });
113
+ return counts;
114
+ }, [allSections, arenaOnly]);
115
+
116
  // Update the search query after debounce
117
  React.useEffect(() => {
118
  setSearchQuery(debouncedSearch);
 
355
  justifyContent="center"
356
  sx={{ pb: 2 }}
357
  >
358
+ {(allSections || []).map(({ id, title }, index) => {
359
+ const count = sectionCounts.get(id) || 0;
360
  const currentGroup = getSectionGroup(id);
361
  const prevGroup =
362
  index > 0 ? getSectionGroup(allSections[index - 1].id) : null;
 
368
  {needsSpacing && index > 0 && (
369
  <Box sx={{ width: "0.5rem", display: "inline-block" }} />
370
  )}
371
+ <FilterTag
372
+ label={title}
373
+ count={count}
374
+ isActive={isSelected}
375
  onClick={() => {
376
  if (isSelected || count > 0) {
377
  setSelectedCategories(id);
378
  }
379
  }}
380
+ colors={groupColors[currentGroup]}
381
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
  </React.Fragment>
383
  );
384
  })}
385
  </Stack>
386
  </Box>
387
 
388
+ {/* Search Bar */}
389
  <SearchBar
390
  searchQuery={searchQuery}
391
  setSearchQuery={setSearchQuery}
client/src/components/LeaderboardSection/components/LanguageList.jsx CHANGED
@@ -2,16 +2,15 @@ import React, { useMemo } from "react";
2
  import {
3
  Typography,
4
  Box,
5
- Button,
6
  Accordion,
7
  AccordionSummary,
8
  AccordionDetails,
9
  } from "@mui/material";
10
- import { alpha } from "@mui/material/styles";
11
  import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
12
  import { useLeaderboard } from "../../../context/LeaderboardContext";
 
13
 
14
- const TAG_COLOR = {
15
  main: "#F44336",
16
  light: "#FFEBEE",
17
  };
@@ -24,18 +23,42 @@ const LanguageList = ({
24
  LANGUAGE_FAMILIES,
25
  findLanguageFamily,
26
  }) => {
27
- const { isLanguageExpanded, setIsLanguageExpanded } = useLeaderboard();
 
28
 
29
- // Store initial language stats in a memo to keep them constant
30
- const initialLanguageStats = useMemo(() => new Map(languageStats), []);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
 
32
  // Group languages by family
33
  const groupedLanguages = languages.reduce((acc, lang) => {
34
- const { family } = findLanguageFamily(lang);
35
- if (!acc[family]) {
36
- acc[family] = [];
 
 
 
 
37
  }
38
- acc[family].push(lang);
39
  return acc;
40
  }, {});
41
 
@@ -56,7 +79,6 @@ const LanguageList = ({
56
  "&:before": {
57
  display: "none",
58
  },
59
-
60
  bgcolor: "transparent",
61
  }}
62
  >
@@ -105,76 +127,17 @@ const LanguageList = ({
105
  >
106
  {familyLanguages.map((lang) => {
107
  const isActive = selectedLanguage === lang;
108
- const count = initialLanguageStats.get(lang) || 0;
109
 
110
  return (
111
- <Button
112
  key={lang}
 
 
 
113
  onClick={() => onLanguageSelect(isActive ? null : lang)}
114
- variant={isActive ? "contained" : "outlined"}
115
- size="small"
116
- sx={{
117
- textTransform: "none",
118
- cursor: "pointer",
119
- mb: 0.75,
120
- backgroundColor: (theme) => {
121
- if (isActive) {
122
- return TAG_COLOR.main;
123
- }
124
- return theme.palette.mode === "dark"
125
- ? "background.paper"
126
- : TAG_COLOR.light;
127
- },
128
- color: (theme) => {
129
- if (isActive) {
130
- return "white";
131
- }
132
- return TAG_COLOR.main;
133
- },
134
- borderColor: TAG_COLOR.main,
135
- "&:hover": {
136
- backgroundColor: (theme) => {
137
- if (isActive) {
138
- return TAG_COLOR.main;
139
- }
140
- return TAG_COLOR.light;
141
- },
142
- opacity: 0.8,
143
- },
144
- "& .MuiTouchRipple-root": {
145
- transition: "none",
146
- },
147
- transition: "none",
148
- }}
149
- >
150
- {capitalize(lang)}
151
- <Box
152
- component="span"
153
- sx={{
154
- display: "inline-flex",
155
- alignItems: "center",
156
- gap: 0.75,
157
- color: isActive ? "white" : "inherit",
158
- ml: 0.75,
159
- }}
160
- >
161
- <Box
162
- component="span"
163
- sx={(theme) => ({
164
- width: "4px",
165
- height: "4px",
166
- borderRadius: "100%",
167
- backgroundColor: isActive
168
- ? "rgba(255, 255, 255, 0.5)"
169
- : alpha(
170
- TAG_COLOR.main,
171
- theme.palette.mode === "dark" ? 0.4 : 0.3
172
- ),
173
- })}
174
- />
175
- {count}
176
- </Box>
177
- </Button>
178
  );
179
  })}
180
  </Box>
 
2
  import {
3
  Typography,
4
  Box,
 
5
  Accordion,
6
  AccordionSummary,
7
  AccordionDetails,
8
  } from "@mui/material";
 
9
  import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
10
  import { useLeaderboard } from "../../../context/LeaderboardContext";
11
+ import FilterTag from "../../common/FilterTag";
12
 
13
+ const LANGUAGE_COLORS = {
14
  main: "#F44336",
15
  light: "#FFEBEE",
16
  };
 
23
  LANGUAGE_FAMILIES,
24
  findLanguageFamily,
25
  }) => {
26
+ const { isLanguageExpanded, setIsLanguageExpanded, arenaOnly } =
27
+ useLeaderboard();
28
 
29
+ // Store initial language stats in a memo to keep them constant and filter by arena if needed
30
+ const filteredLanguageStats = useMemo(() => {
31
+ if (!arenaOnly) return languageStats;
32
+
33
+ // Create a new Map with only arena leaderboards
34
+ const filteredStats = new Map();
35
+ languageStats.forEach((count, lang) => {
36
+ // Si nous avons déjà un nombre, nous le gardons tel quel
37
+ if (typeof count === "number") {
38
+ filteredStats.set(lang, count);
39
+ } else {
40
+ // Si nous avons un tableau de leaderboards, nous filtrons par arena
41
+ const arenaCount = count.filter((board) =>
42
+ board.tags?.includes("judge:humans")
43
+ ).length;
44
+ if (arenaCount > 0) {
45
+ filteredStats.set(lang, arenaCount);
46
+ }
47
+ }
48
+ });
49
+ return filteredStats;
50
+ }, [languageStats, arenaOnly]);
51
 
52
  // Group languages by family
53
  const groupedLanguages = languages.reduce((acc, lang) => {
54
+ // Only include languages that have stats after filtering
55
+ if (filteredLanguageStats.has(lang)) {
56
+ const { family } = findLanguageFamily(lang);
57
+ if (!acc[family]) {
58
+ acc[family] = [];
59
+ }
60
+ acc[family].push(lang);
61
  }
 
62
  return acc;
63
  }, {});
64
 
 
79
  "&:before": {
80
  display: "none",
81
  },
 
82
  bgcolor: "transparent",
83
  }}
84
  >
 
127
  >
128
  {familyLanguages.map((lang) => {
129
  const isActive = selectedLanguage === lang;
130
+ const count = filteredLanguageStats.get(lang) || 0;
131
 
132
  return (
133
+ <FilterTag
134
  key={lang}
135
+ label={capitalize(lang)}
136
+ count={count}
137
+ isActive={isActive}
138
  onClick={() => onLanguageSelect(isActive ? null : lang)}
139
+ colors={LANGUAGE_COLORS}
140
+ />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  );
142
  })}
143
  </Box>
client/src/components/LeaderboardSection/hooks/useLanguageStats.js CHANGED
@@ -99,7 +99,9 @@ export const useLanguageStats = (leaderboards, filteredLeaderboards) => {
99
  const statsRef = useRef(null);
100
  const languagesRef = useRef(null);
101
 
102
- if (statsRef.current === null && leaderboards) {
 
 
103
  const langMap = new Map();
104
  const langFamilyMap = new Map();
105
 
@@ -135,10 +137,10 @@ export const useLanguageStats = (leaderboards, filteredLeaderboards) => {
135
  })
136
  .map(([lang]) => lang);
137
 
138
- // Calculer les stats une seule fois
139
  statsRef.current = new Map();
140
  languagesRef.current.forEach((lang) => {
141
- const count = leaderboards.filter((board) =>
142
  board.tags?.some(
143
  (tag) => tag.toLowerCase() === `language:${lang.toLowerCase()}`
144
  )
 
99
  const statsRef = useRef(null);
100
  const languagesRef = useRef(null);
101
 
102
+ // Reset stats when leaderboards or filteredLeaderboards change
103
+ if (leaderboards && filteredLeaderboards) {
104
+ // Calculate unique languages from all leaderboards
105
  const langMap = new Map();
106
  const langFamilyMap = new Map();
107
 
 
137
  })
138
  .map(([lang]) => lang);
139
 
140
+ // Calculate stats based on filtered leaderboards
141
  statsRef.current = new Map();
142
  languagesRef.current.forEach((lang) => {
143
+ const count = filteredLeaderboards.filter((board) =>
144
  board.tags?.some(
145
  (tag) => tag.toLowerCase() === `language:${lang.toLowerCase()}`
146
  )
client/src/components/common/FilterTag.jsx CHANGED
@@ -2,44 +2,53 @@ import React from "react";
2
  import { Button, Box } from "@mui/material";
3
  import { alpha } from "@mui/material/styles";
4
 
5
- const TAG_COLOR = {
6
  main: "#F44336",
7
  light: "#FFEBEE",
8
  };
9
 
10
- const FilterTag = ({ label, count, isActive, onClick }) => {
 
 
 
 
 
 
 
 
11
  return (
12
  <Button
13
  onClick={onClick}
14
  variant={isActive ? "contained" : "outlined"}
15
  size="small"
 
16
  sx={{
17
  textTransform: "none",
18
- cursor: "pointer",
19
  mb: 0.75,
20
  backgroundColor: (theme) => {
21
  if (isActive) {
22
- return TAG_COLOR.main;
23
  }
24
  return theme.palette.mode === "dark"
25
  ? "background.paper"
26
- : TAG_COLOR.light;
27
  },
28
  color: (theme) => {
29
  if (isActive) {
30
  return "white";
31
  }
32
- return TAG_COLOR.main;
33
  },
34
- borderColor: TAG_COLOR.main,
35
  "&:hover": {
36
  backgroundColor: (theme) => {
37
  if (isActive) {
38
- return TAG_COLOR.main;
39
  }
40
- return TAG_COLOR.light;
41
  },
42
- opacity: 0.8,
43
  },
44
  "& .MuiTouchRipple-root": {
45
  transition: "none",
@@ -67,10 +76,7 @@ const FilterTag = ({ label, count, isActive, onClick }) => {
67
  borderRadius: "100%",
68
  backgroundColor: isActive
69
  ? "rgba(255, 255, 255, 0.5)"
70
- : alpha(
71
- TAG_COLOR.main,
72
- theme.palette.mode === "dark" ? 0.4 : 0.3
73
- ),
74
  })}
75
  />
76
  {count}
 
2
  import { Button, Box } from "@mui/material";
3
  import { alpha } from "@mui/material/styles";
4
 
5
+ const DEFAULT_COLORS = {
6
  main: "#F44336",
7
  light: "#FFEBEE",
8
  };
9
 
10
+ const FilterTag = ({
11
+ label,
12
+ count,
13
+ isActive,
14
+ onClick,
15
+ colors = DEFAULT_COLORS,
16
+ }) => {
17
+ const isDisabled = count === 0;
18
+
19
  return (
20
  <Button
21
  onClick={onClick}
22
  variant={isActive ? "contained" : "outlined"}
23
  size="small"
24
+ disabled={isDisabled}
25
  sx={{
26
  textTransform: "none",
27
+ cursor: isDisabled ? "default" : "pointer",
28
  mb: 0.75,
29
  backgroundColor: (theme) => {
30
  if (isActive) {
31
+ return colors.main;
32
  }
33
  return theme.palette.mode === "dark"
34
  ? "background.paper"
35
+ : colors.light;
36
  },
37
  color: (theme) => {
38
  if (isActive) {
39
  return "white";
40
  }
41
+ return colors.main;
42
  },
43
+ borderColor: colors.main,
44
  "&:hover": {
45
  backgroundColor: (theme) => {
46
  if (isActive) {
47
+ return colors.main;
48
  }
49
+ return colors.light;
50
  },
51
+ opacity: isDisabled ? 0.6 : 0.8,
52
  },
53
  "& .MuiTouchRipple-root": {
54
  transition: "none",
 
76
  borderRadius: "100%",
77
  backgroundColor: isActive
78
  ? "rgba(255, 255, 255, 0.5)"
79
+ : alpha(colors.main, theme.palette.mode === "dark" ? 0.4 : 0.3),
 
 
 
80
  })}
81
  />
82
  {count}