Skip to content

Commit 0a47ff2

Browse files
committed
add_filter
1 parent a1047f1 commit 0a47ff2

File tree

1 file changed

+150
-2
lines changed

1 file changed

+150
-2
lines changed

frontend/src/pages/Admin/ChallengeManagement.jsx

Lines changed: 150 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,17 @@ import {
1212

1313
export default function ChallengeManagement() {
1414
const [challenges, setChallenges] = useState([]);
15+
const [filteredChallenges, setFilteredChallenges] = useState([]);
1516
const [selectedChallenge, setSelectedChallenge] = useState(null);
1617
const [showModal, setShowModal] = useState(false);
1718
const [showDeleteModal, setShowDeleteModal] = useState(false);
1819
const [error, setError] = useState(null);
1920
const [isLoading, setIsLoading] = useState(true);
21+
22+
// Filter states
23+
const [searchQuery, setSearchQuery] = useState("");
24+
const [tagFilter, setTagFilter] = useState("all");
25+
const [bonusFilter, setBonusFilter] = useState("all");
2026
const [formData, setFormData] = useState({
2127
title: "",
2228
description: "",
@@ -34,6 +40,11 @@ export default function ChallengeManagement() {
3440
useEffect(() => {
3541
fetchChallenges();
3642
}, []);
43+
44+
// Apply filters whenever challenges array or filter settings change
45+
useEffect(() => {
46+
applyFilters();
47+
}, [challenges, searchQuery, tagFilter, bonusFilter]);
3748

3849
const fetchChallenges = async () => {
3950
try {
@@ -173,6 +184,41 @@ export default function ChallengeManagement() {
173184
setSelectedChallenge(null);
174185
};
175186

187+
// Function to apply all filters to the challenges array
188+
const applyFilters = () => {
189+
let result = [...challenges];
190+
191+
// Apply tag filter
192+
if (tagFilter !== "all") {
193+
result = result.filter(challenge => challenge.tag?.toLowerCase() === tagFilter.toLowerCase());
194+
}
195+
196+
// Apply bonus filter
197+
if (bonusFilter === "with-bonus") {
198+
result = result.filter(challenge => challenge.bonusPoints > 0 && challenge.bonusLimit > 0);
199+
} else if (bonusFilter === "no-bonus") {
200+
result = result.filter(challenge => !(challenge.bonusPoints > 0 && challenge.bonusLimit > 0));
201+
}
202+
203+
// Apply search query
204+
if (searchQuery.trim()) {
205+
const query = searchQuery.toLowerCase().trim();
206+
result = result.filter(
207+
challenge =>
208+
challenge.title?.toLowerCase().includes(query) ||
209+
challenge.description?.toLowerCase().includes(query)
210+
);
211+
}
212+
213+
setFilteredChallenges(result);
214+
};
215+
216+
// Extract unique tags from challenges for the tag filter dropdown
217+
const getUniqueTags = () => {
218+
const tags = challenges.map(challenge => challenge.tag).filter(Boolean);
219+
return [...new Set(tags)];
220+
};
221+
176222
const openEditModal = challenge => {
177223
setSelectedChallenge(challenge);
178224
setFormData({
@@ -206,6 +252,93 @@ export default function ChallengeManagement() {
206252
</button>
207253
</div>
208254

255+
{/* Filter Controls */}
256+
<div className="mb-6 bg-gray-800/30 border border-gray-800 rounded-xl p-4">
257+
<div className="flex flex-col md:flex-row gap-4">
258+
{/* Search Input */}
259+
<div className="flex-1">
260+
<label htmlFor="search" className="block text-sm font-medium text-gray-400 mb-1">
261+
Search
262+
</label>
263+
<div className="relative">
264+
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
265+
<svg className="w-4 h-4 text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
266+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
267+
</svg>
268+
</div>
269+
<input
270+
type="text"
271+
id="search"
272+
value={searchQuery}
273+
onChange={(e) => setSearchQuery(e.target.value)}
274+
placeholder="Search by title or description..."
275+
className="w-full pl-10 pr-3 py-2 bg-gray-900/50 border border-gray-800 text-gray-200 text-sm rounded-lg focus:ring-1 focus:ring-purple-500/50 focus:border-purple-500/50 block focus:outline-none transition-colors"
276+
/>
277+
</div>
278+
</div>
279+
280+
{/* Tag Filter */}
281+
<div className="w-full md:w-64">
282+
<label htmlFor="tagFilter" className="block text-sm font-medium text-gray-400 mb-1">
283+
Filter by Tag
284+
</label>
285+
<select
286+
id="tagFilter"
287+
value={tagFilter}
288+
onChange={(e) => setTagFilter(e.target.value)}
289+
className="w-full px-3 py-2 bg-gray-900/50 border border-gray-800 text-gray-200 text-sm rounded-lg focus:ring-1 focus:ring-purple-500/50 focus:border-purple-500/50 block focus:outline-none transition-colors appearance-none"
290+
style={{ backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")`, backgroundPosition: 'right 0.5rem center', backgroundRepeat: 'no-repeat', backgroundSize: '1.5em 1.5em', paddingRight: '2.5rem' }}
291+
>
292+
<option value="all">All Tags</option>
293+
{getUniqueTags().map((tag) => (
294+
<option key={tag} value={tag}>
295+
{tag}
296+
</option>
297+
))}
298+
</select>
299+
</div>
300+
301+
{/* Bonus Points Filter */}
302+
<div className="w-full md:w-64">
303+
<label htmlFor="bonusFilter" className="block text-sm font-medium text-gray-400 mb-1">
304+
Bonus Points
305+
</label>
306+
<select
307+
id="bonusFilter"
308+
value={bonusFilter}
309+
onChange={(e) => setBonusFilter(e.target.value)}
310+
className="w-full px-3 py-2 bg-gray-900/50 border border-gray-800 text-gray-200 text-sm rounded-lg focus:ring-1 focus:ring-purple-500/50 focus:border-purple-500/50 block focus:outline-none transition-colors appearance-none"
311+
style={{ backgroundImage: `url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e")`, backgroundPosition: 'right 0.5rem center', backgroundRepeat: 'no-repeat', backgroundSize: '1.5em 1.5em', paddingRight: '2.5rem' }}
312+
>
313+
<option value="all">All Challenges</option>
314+
<option value="with-bonus">With Bonus Points</option>
315+
<option value="no-bonus">No Bonus Points</option>
316+
</select>
317+
</div>
318+
</div>
319+
320+
{/* Filter Status and Reset */}
321+
<div className="mt-4 flex justify-between items-center">
322+
<div className="text-sm text-gray-400">
323+
Showing {filteredChallenges.length} of {challenges.length} challenges
324+
</div>
325+
326+
{(searchQuery || tagFilter !== "all" || bonusFilter !== "all") && (
327+
<button
328+
onClick={() => {
329+
setSearchQuery("");
330+
setTagFilter("all");
331+
setBonusFilter("all");
332+
}}
333+
className="text-xs px-2 py-1 bg-gray-800 hover:bg-gray-700 text-gray-400 hover:text-white rounded-md transition-colors flex items-center"
334+
>
335+
<X className="w-3.5 h-3.5 mr-1" />
336+
Clear Filters
337+
</button>
338+
)}
339+
</div>
340+
</div>
341+
209342
{/* Challenges Table */}
210343
<div className="border border-gray-800 rounded-xl overflow-hidden">
211344
<table className="w-full">
@@ -219,7 +352,7 @@ export default function ChallengeManagement() {
219352
</tr>
220353
</thead>
221354
<tbody>
222-
{challenges.map(challenge => (
355+
{filteredChallenges.length > 0 ? filteredChallenges.map(challenge => (
223356
<tr key={challenge._id} className="border-t border-gray-800 hover:bg-gray-800/50 transition-colors">
224357
<td className="py-4 px-6">
225358
<span className={`px-3 py-1 rounded-full text-sm ${getTagStyle(challenge.tag)}`}>
@@ -271,7 +404,22 @@ export default function ChallengeManagement() {
271404
</div>
272405
</td>
273406
</tr>
274-
))}
407+
)) : (
408+
<tr>
409+
<td colSpan="5" className="py-8 px-6 text-center text-gray-400">
410+
{isLoading ? (
411+
<div className="flex justify-center">
412+
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-purple-500"></div>
413+
</div>
414+
) : (
415+
<div>
416+
<div className="text-xl mb-2">No challenges found</div>
417+
<div className="text-sm">Try adjusting your filters or add a new challenge</div>
418+
</div>
419+
)}
420+
</td>
421+
</tr>
422+
)}
275423
</tbody>
276424
</table>
277425
</div>

0 commit comments

Comments
 (0)