tfrere commited on
Commit
2405b72
·
1 Parent(s): 8d4d46d

update empty states and header section

Browse files
.gitignore CHANGED
@@ -30,6 +30,7 @@ client/*.local
30
 
31
  client/.env
32
  server/.env
 
33
  server/data/leaderboards_discussions.json
34
  server/data/leaderboards_list.json
35
  server/data/leaderboards_results.json
 
30
 
31
  client/.env
32
  server/.env
33
+ server/data/leaderboards.json
34
  server/data/leaderboards_discussions.json
35
  server/data/leaderboards_list.json
36
  server/data/leaderboards_results.json
client/src/components/LeaderboardSection/components/EmptyState.jsx CHANGED
@@ -1,18 +1,51 @@
1
  import React from "react";
2
- import { Box, Typography } from "@mui/material";
 
3
  import SearchOffIcon from "@mui/icons-material/SearchOff";
4
  import { useLeaderboard } from "../../../context/LeaderboardContext";
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  const EmptyState = ({ title, searchQuery }) => {
7
  const { selectedCategories, selectedLanguage, arenaOnly } = useLeaderboard();
8
 
9
- // Construire un message plus explicite
10
- const getDetailedMessage = () => {
11
- // Approche simplifiée : construire le message à partir des éléments de base
 
 
 
 
 
 
12
  const parts = [];
13
 
14
- // 1. Ajouter le titre de base (catégorie)
15
- if (title) {
16
  // Extraire le titre de base sans "matching" ni "language:"
17
  let baseTitle = title;
18
 
@@ -21,55 +54,124 @@ const EmptyState = ({ title, searchQuery }) => {
21
  baseTitle = baseTitle.split("matching")[0].trim();
22
  }
23
 
24
- // Si le titre contient "Language:" avec des langues spécifiques, le garder tel quel
25
- if (baseTitle.toLowerCase().includes("language:")) {
26
- parts.push(baseTitle.toLowerCase());
27
- }
28
- // Si le titre est "Language Specific" et qu'on a des langues sélectionnées, ne pas l'ajouter
29
- else if (
30
- baseTitle.toLowerCase().includes("language specific") &&
31
- selectedLanguage.size > 0
32
- ) {
33
- // Ne pas ajouter "Language Specific" car on va ajouter les langues spécifiques plus tard
34
- }
35
- // Sinon, ajouter le titre de base
36
- else {
37
- parts.push(baseTitle.toLowerCase());
38
- }
39
  }
40
 
41
  // 2. Ajouter les langues sélectionnées si elles ne sont pas déjà dans le titre
42
  if (selectedLanguage && selectedLanguage.size > 0) {
43
- const languages = Array.from(selectedLanguage).join(", ");
 
 
44
 
45
- // Vérifier si les langues sont déjà dans le titre
 
 
46
  if (
47
  !parts.some((part) =>
48
- part.toLowerCase().includes(`language: ${languages.toLowerCase()}`)
49
  )
50
  ) {
51
- parts.push(`language: ${languages}`);
52
  }
53
  }
54
 
55
  // 3. Ajouter le filtre Arena
56
  if (arenaOnly) {
57
- parts.push("arena only");
58
  }
59
 
60
- // 4. Ajouter le terme de recherche
61
- if (searchQuery) {
62
- parts.push(`matching "${searchQuery}"`);
63
- }
64
 
65
- // Si aucun filtre n'est actif, afficher un message simple
66
- if (parts.length === 0) {
67
- return "No results found";
68
- }
69
 
70
- // Construire le message final
71
- return `No ${parts.join(" + ")} leaderboards found`;
72
- };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
 
74
  return (
75
  <Box
@@ -79,6 +181,7 @@ const EmptyState = ({ title, searchQuery }) => {
79
  alignItems: "center",
80
  gap: 2,
81
  py: 7,
 
82
  bgcolor: (theme) =>
83
  theme.palette.mode === "dark"
84
  ? "background.paper"
@@ -93,9 +196,55 @@ const EmptyState = ({ title, searchQuery }) => {
93
  opacity: 0.5,
94
  }}
95
  />
96
- <Typography variant="h5" color="text.secondary" align="center">
97
- {getDetailedMessage()}
98
- </Typography>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  <Typography variant="body1" color="text.secondary" align="center">
100
  Try adjusting your search filters
101
  </Typography>
 
1
  import React from "react";
2
+ import { Box, Typography, Chip } from "@mui/material";
3
+ import { alpha } from "@mui/material/styles";
4
  import SearchOffIcon from "@mui/icons-material/SearchOff";
5
  import { useLeaderboard } from "../../../context/LeaderboardContext";
6
 
7
+ // Composant pour les chips (repris de SectionHeader mais sans couleur)
8
+ const StyledChip = ({ label, sx = {} }) => (
9
+ <Chip
10
+ label={label}
11
+ size="small"
12
+ sx={{
13
+ height: "24px",
14
+ backgroundColor: (theme) =>
15
+ alpha(
16
+ theme.palette.text.primary,
17
+ theme.palette.mode === "dark" ? 0.1 : 0.05
18
+ ),
19
+ color: "text.secondary",
20
+ fontSize: "0.75rem",
21
+ fontWeight: 500,
22
+ mx: 0.5,
23
+ "& .MuiChip-label": {
24
+ px: 1,
25
+ lineHeight: 1,
26
+ paddingTop: "1px", // Ajustement pour le centrage vertical
27
+ },
28
+ ...sx,
29
+ }}
30
+ />
31
+ );
32
+
33
  const EmptyState = ({ title, searchQuery }) => {
34
  const { selectedCategories, selectedLanguage, arenaOnly } = useLeaderboard();
35
 
36
+ // Vérifier si des filtres sont actifs
37
+ const hasActiveFilters =
38
+ searchQuery ||
39
+ arenaOnly ||
40
+ selectedCategories.size > 0 ||
41
+ selectedLanguage.size > 0;
42
+
43
+ // Construire les parties du message pour l'affichage formaté
44
+ const getMessageParts = () => {
45
  const parts = [];
46
 
47
+ // 1. Ajouter le titre de base (catégorie) s'il existe et n'est pas "All leaderboards"
48
+ if (title && !title.toLowerCase().includes("all leaderboards")) {
49
  // Extraire le titre de base sans "matching" ni "language:"
50
  let baseTitle = title;
51
 
 
54
  baseTitle = baseTitle.split("matching")[0].trim();
55
  }
56
 
57
+ // Traiter les parties du titre séparées par "+"
58
+ const titleParts = baseTitle.split(" + ");
59
+ titleParts.forEach((part) => {
60
+ if (part.trim()) {
61
+ parts.push(part.trim());
62
+ }
63
+ });
 
 
 
 
 
 
 
 
64
  }
65
 
66
  // 2. Ajouter les langues sélectionnées si elles ne sont pas déjà dans le titre
67
  if (selectedLanguage && selectedLanguage.size > 0) {
68
+ const languages = Array.from(selectedLanguage)
69
+ .map((lang) => lang.charAt(0).toUpperCase() + lang.slice(1))
70
+ .join(", ");
71
 
72
+ const languageLabel = `Language: ${languages}`;
73
+
74
+ // Vérifier si les langues sont déjà dans les parties
75
  if (
76
  !parts.some((part) =>
77
+ part.toLowerCase().includes(languages.toLowerCase())
78
  )
79
  ) {
80
+ parts.push(languageLabel);
81
  }
82
  }
83
 
84
  // 3. Ajouter le filtre Arena
85
  if (arenaOnly) {
86
+ parts.push("Arena only");
87
  }
88
 
89
+ return parts;
90
+ };
 
 
91
 
92
+ // Obtenir les parties du message
93
+ const messageParts = getMessageParts();
 
 
94
 
95
+ // Si on a uniquement un terme de recherche sans autres filtres
96
+ if (searchQuery && messageParts.length === 0) {
97
+ return (
98
+ <Box
99
+ sx={{
100
+ display: "flex",
101
+ flexDirection: "column",
102
+ alignItems: "center",
103
+ gap: 2,
104
+ py: 7,
105
+ mt: 0,
106
+ bgcolor: (theme) =>
107
+ theme.palette.mode === "dark"
108
+ ? "background.paper"
109
+ : "background.default",
110
+ borderRadius: 2,
111
+ }}
112
+ >
113
+ <SearchOffIcon
114
+ sx={{
115
+ fontSize: 64,
116
+ color: "text.secondary",
117
+ opacity: 0.5,
118
+ }}
119
+ />
120
+ <Box
121
+ sx={{
122
+ display: "flex",
123
+ flexWrap: "wrap",
124
+ justifyContent: "center",
125
+ alignItems: "center",
126
+ gap: 0.5,
127
+ }}
128
+ >
129
+ <Typography variant="h5" component="span" color="text.secondary">
130
+ No results matching
131
+ </Typography>
132
+ <StyledChip label={`"${searchQuery}"`} />
133
+ </Box>
134
+ <Typography variant="body1" color="text.secondary" align="center">
135
+ Try adjusting your search filters
136
+ </Typography>
137
+ </Box>
138
+ );
139
+ }
140
+
141
+ // Message simple si aucun filtre n'est actif ou si aucune partie n'est générée
142
+ if (!hasActiveFilters || (messageParts.length === 0 && !searchQuery)) {
143
+ return (
144
+ <Box
145
+ sx={{
146
+ display: "flex",
147
+ flexDirection: "column",
148
+ alignItems: "center",
149
+ gap: 2,
150
+ py: 7,
151
+ mt: hasActiveFilters ? 0 : 8,
152
+ bgcolor: (theme) =>
153
+ theme.palette.mode === "dark"
154
+ ? "background.paper"
155
+ : "background.default",
156
+ borderRadius: 2,
157
+ }}
158
+ >
159
+ <SearchOffIcon
160
+ sx={{
161
+ fontSize: 64,
162
+ color: "text.secondary",
163
+ opacity: 0.5,
164
+ }}
165
+ />
166
+ <Typography variant="h5" color="text.secondary" align="center">
167
+ No results found
168
+ </Typography>
169
+ <Typography variant="body1" color="text.secondary" align="center">
170
+ Try adjusting your search filters
171
+ </Typography>
172
+ </Box>
173
+ );
174
+ }
175
 
176
  return (
177
  <Box
 
181
  alignItems: "center",
182
  gap: 2,
183
  py: 7,
184
+ mt: 0,
185
  bgcolor: (theme) =>
186
  theme.palette.mode === "dark"
187
  ? "background.paper"
 
196
  opacity: 0.5,
197
  }}
198
  />
199
+
200
+ <Box
201
+ sx={{
202
+ display: "flex",
203
+ flexDirection: "column",
204
+ alignItems: "center",
205
+ gap: 1,
206
+ }}
207
+ >
208
+ {/* Phrase en langage naturel avec chips intégrées */}
209
+ <Box
210
+ sx={{
211
+ display: "flex",
212
+ flexWrap: "wrap",
213
+ justifyContent: "center",
214
+ alignItems: "center",
215
+ gap: 0.5,
216
+ }}
217
+ >
218
+ <Typography variant="h5" component="span" color="text.secondary">
219
+ No results found for
220
+ </Typography>
221
+
222
+ {messageParts.map((part, index) => (
223
+ <React.Fragment key={index}>
224
+ {index > 0 && (
225
+ <Typography
226
+ variant="h5"
227
+ component="span"
228
+ color="text.secondary"
229
+ >
230
+ {index === messageParts.length - 1 ? " and" : ","}
231
+ </Typography>
232
+ )}
233
+ <StyledChip label={part} />
234
+ </React.Fragment>
235
+ ))}
236
+
237
+ {searchQuery && (
238
+ <>
239
+ <Typography variant="h5" component="span" color="text.secondary">
240
+ {messageParts.length > 0 ? " matching" : "matching"}
241
+ </Typography>
242
+ <StyledChip label={`"${searchQuery}"`} />
243
+ </>
244
+ )}
245
+ </Box>
246
+ </Box>
247
+
248
  <Typography variant="body1" color="text.secondary" align="center">
249
  Try adjusting your search filters
250
  </Typography>
client/src/components/LeaderboardSection/components/SectionHeader.jsx CHANGED
@@ -18,7 +18,7 @@ const StyledChip = ({ label, sx = {} }) => (
18
  color: "text.secondary",
19
  fontSize: "0.75rem",
20
  fontWeight: 500,
21
- mx: 1,
22
  "& .MuiChip-label": {
23
  px: 1,
24
  lineHeight: 1,
@@ -29,12 +29,6 @@ const StyledChip = ({ label, sx = {} }) => (
29
  />
30
  );
31
 
32
- // Composant pour le chip AND
33
- const AndChip = () => <StyledChip label="AND" />;
34
-
35
- // Composant pour le chip matching
36
- const MatchingChip = () => <StyledChip label="MATCHING" />;
37
-
38
  const SectionHeader = ({
39
  title,
40
  count,
@@ -46,10 +40,9 @@ const SectionHeader = ({
46
  // Séparer le titre en parties si c'est un titre combiné
47
  const titleParts = title.split(" matching ");
48
  const categories = titleParts[0].split(" + ");
 
 
49
  const hasSearchQuery = titleParts.length > 1;
50
- const searchQuery = hasSearchQuery
51
- ? titleParts[1].replace(/['"]/g, "")
52
- : null;
53
 
54
  return (
55
  <Box
@@ -85,26 +78,20 @@ const SectionHeader = ({
85
  >
86
  {categories.map((category, index) => (
87
  <React.Fragment key={index}>
88
- {index > 0 && <AndChip />}
 
 
 
 
 
 
 
 
 
 
89
  {category}
90
  </React.Fragment>
91
  ))}
92
- {hasSearchQuery && (
93
- <>
94
- <MatchingChip />
95
- <Typography
96
- component="span"
97
- sx={{
98
- color: "text.primary",
99
- fontWeight: 600,
100
- fontSize: "inherit",
101
- lineHeight: "inherit",
102
- }}
103
- >
104
- "{searchQuery}"
105
- </Typography>
106
- </>
107
- )}
108
  </Typography>
109
  </Box>
110
  <Box
@@ -116,7 +103,7 @@ const SectionHeader = ({
116
  theme.palette.text.primary,
117
  theme.palette.mode === "dark" ? 0.2 : 0.15
118
  ),
119
- mx: 1,
120
  })}
121
  />
122
  <Typography
 
18
  color: "text.secondary",
19
  fontSize: "0.75rem",
20
  fontWeight: 500,
21
+ mx: 0.5,
22
  "& .MuiChip-label": {
23
  px: 1,
24
  lineHeight: 1,
 
29
  />
30
  );
31
 
 
 
 
 
 
 
32
  const SectionHeader = ({
33
  title,
34
  count,
 
40
  // Séparer le titre en parties si c'est un titre combiné
41
  const titleParts = title.split(" matching ");
42
  const categories = titleParts[0].split(" + ");
43
+
44
+ // On garde la référence au terme de recherche mais on ne l'affiche pas dans le header
45
  const hasSearchQuery = titleParts.length > 1;
 
 
 
46
 
47
  return (
48
  <Box
 
78
  >
79
  {categories.map((category, index) => (
80
  <React.Fragment key={index}>
81
+ {index > 0 && (
82
+ <span
83
+ style={{
84
+ opacity: 0.4,
85
+ marginLeft: "0.5rem",
86
+ marginRight: "0.5rem",
87
+ }}
88
+ >
89
+ {index === categories.length - 1 ? " and " : ", "}
90
+ </span>
91
+ )}
92
  {category}
93
  </React.Fragment>
94
  ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
  </Typography>
96
  </Box>
97
  <Box
 
103
  theme.palette.text.primary,
104
  theme.palette.mode === "dark" ? 0.2 : 0.15
105
  ),
106
+ mx: 0.25,
107
  })}
108
  />
109
  <Typography
client/src/components/LeaderboardSection/index.jsx CHANGED
@@ -155,10 +155,8 @@ const LeaderboardSection = ({
155
  )}
156
 
157
  {approvedLeaderboards.length === 0 ? (
158
- // Afficher EmptyState uniquement si showEmptyState est true
159
- showEmptyState ? (
160
- <EmptyState title={enrichedTitle} searchQuery={searchQuery} />
161
- ) : null
162
  ) : (
163
  <LeaderboardGrid
164
  displayedLeaderboards={displayedLeaderboards}
 
155
  )}
156
 
157
  {approvedLeaderboards.length === 0 ? (
158
+ // Toujours afficher EmptyState, que showEmptyState soit true ou non
159
+ <EmptyState title={enrichedTitle} searchQuery={searchQuery} />
 
 
160
  ) : (
161
  <LeaderboardGrid
162
  displayedLeaderboards={displayedLeaderboards}
client/src/pages/LeaderboardPage/LeaderboardPage.jsx CHANGED
@@ -17,6 +17,7 @@ import {
17
  LeaderboardProvider,
18
  useLeaderboard,
19
  } from "../../context/LeaderboardContext";
 
20
 
21
  const LeaderboardPageContent = () => {
22
  const [loading, setLoading] = useState(true);
@@ -197,42 +198,6 @@ const LeaderboardPageContent = () => {
197
  </Box>
198
  )}
199
 
200
- {/* 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 */}
201
- {!hasLeaderboards &&
202
- isFiltering &&
203
- !isOnlyTextSearch &&
204
- selectedCategories.size === 0 && (
205
- <Box
206
- sx={{
207
- display: "flex",
208
- flexDirection: "column",
209
- alignItems: "center",
210
- gap: 2,
211
- mt: 8,
212
- py: 7,
213
- }}
214
- >
215
- <SearchOffIcon
216
- sx={{
217
- fontSize: 64,
218
- color: "text.secondary",
219
- opacity: 0.5,
220
- }}
221
- />
222
- <Typography variant="h5" color="text.secondary" align="center">
223
- No results found
224
- {searchQuery ? ` matching "${searchQuery}"` : ""}
225
- </Typography>
226
- <Typography
227
- variant="body1"
228
- color="text.secondary"
229
- align="center"
230
- >
231
- Try adjusting your filters
232
- </Typography>
233
- </Box>
234
- )}
235
-
236
  {isOnlyTextSearch ? (
237
  // Vue spéciale pour la recherche textuelle
238
  searchResults.length > 0 ? (
@@ -246,34 +211,14 @@ const LeaderboardPageContent = () => {
246
  </Box>
247
  ) : (
248
  // Message d'erreur pour la recherche textuelle sans résultats
249
- <Box
250
- sx={{
251
- display: "flex",
252
- flexDirection: "column",
253
- alignItems: "center",
254
- gap: 2,
255
- mt: 8,
256
- py: 7,
257
- }}
258
- >
259
- <SearchOffIcon
260
- sx={{
261
- fontSize: 64,
262
- color: "text.secondary",
263
- opacity: 0.5,
264
- }}
265
  />
266
- <Typography variant="h5" color="text.secondary" align="center">
267
- No results found
268
- {searchQuery ? ` matching "${searchQuery}"` : ""}
269
- </Typography>
270
- <Typography
271
- variant="body1"
272
- color="text.secondary"
273
- align="center"
274
- >
275
- Try adjusting your filters
276
- </Typography>
277
  </Box>
278
  )
279
  ) : selectedCategories.size > 0 ? (
 
17
  LeaderboardProvider,
18
  useLeaderboard,
19
  } from "../../context/LeaderboardContext";
20
+ import EmptyState from "../../components/LeaderboardSection/components/EmptyState";
21
 
22
  const LeaderboardPageContent = () => {
23
  const [loading, setLoading] = useState(true);
 
198
  </Box>
199
  )}
200
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  {isOnlyTextSearch ? (
202
  // Vue spéciale pour la recherche textuelle
203
  searchResults.length > 0 ? (
 
211
  </Box>
212
  ) : (
213
  // Message d'erreur pour la recherche textuelle sans résultats
214
+ <Box key="search-results">
215
+ <LeaderboardSection
216
+ id="search-results"
217
+ title={`All leaderboards matching "${searchQuery}"`}
218
+ leaderboards={allUniqueLeaderboards}
219
+ filteredLeaderboards={[]}
220
+ showEmptyState={true}
 
 
 
 
 
 
 
 
 
221
  />
 
 
 
 
 
 
 
 
 
 
 
222
  </Box>
223
  )
224
  ) : selectedCategories.size > 0 ? (