diff --git a/src/components/ClubList.tsx b/src/components/ClubList.tsx index aaf3812..171ef71 100644 --- a/src/components/ClubList.tsx +++ b/src/components/ClubList.tsx @@ -11,7 +11,7 @@ import { import { debounce } from '@utils/debounce'; import ClubCard from './ClubCard'; import SearchBar from './SearchBar'; -import ClubDetailModal from './ClubDetailModal'; // Import the modal +import ClubDetailModal from './ClubDetailModal'; const ClubList: React.FC = () => { const [allClubsData, setAllClubsData] = useState([]); @@ -22,6 +22,7 @@ const ClubList: React.FC = () => { const [selectedCategory, setSelectedCategory] = useState(''); const [selectedAcademicYear, setSelectedAcademicYear] = useState(''); const [selectedGrade, setSelectedGrade] = useState(''); + const [selectedStudentLed, setSelectedStudentLed] = useState(''); const [availableCategories, setAvailableCategories] = useState({}); const [availableAcademicYears, setAvailableAcademicYears] = useState({}); @@ -79,15 +80,19 @@ const ClubList: React.FC = () => { return; } + const studentLedParam = selectedStudentLed === "" ? undefined : selectedStudentLed === "true"; + let clubs; if ( selectedCategory || selectedAcademicYear || - (gradeNumber && gradeNumber >= 1 && gradeNumber <= 12) + (gradeNumber && gradeNumber >= 1 && gradeNumber <= 12) || + selectedStudentLed !== "" ) { clubs = await filterClubs({ category: selectedCategory || undefined, academicYear: selectedAcademicYear || undefined, grade: gradeNumber, + isStudentLed: studentLedParam, }); } else { clubs = await listAllClubs(); @@ -101,7 +106,7 @@ const ClubList: React.FC = () => { } finally { setIsLoading(false); } - }, [selectedCategory, selectedAcademicYear, selectedGrade, sortClubsDesc]); + }, [selectedCategory, selectedAcademicYear, selectedGrade, selectedStudentLed, sortClubsDesc]); useEffect(() => { fetchFilteredClubsFromAPI(); @@ -159,6 +164,8 @@ const ClubList: React.FC = () => { availableAcademicYears={availableAcademicYears} selectedGrade={selectedGrade} onGradeChange={setSelectedGrade} + selectedStudentLed={selectedStudentLed} + onStudentLedChange={setSelectedStudentLed} /> {isLoading &&

Filtering clubs...

} {!isLoading && displayedClubs.length === 0 && ( @@ -169,9 +176,9 @@ const ClubList: React.FC = () => { ))} diff --git a/src/components/SearchBar.tsx b/src/components/SearchBar.tsx index 15a60f8..574c23f 100644 --- a/src/components/SearchBar.tsx +++ b/src/components/SearchBar.tsx @@ -1,9 +1,9 @@ -import React, { useState } from 'react'; // Removed useEffect as it's not needed here anymore +// src/components/SearchBar.tsx +import React, { useState } from 'react'; import type { CategoryCount, AcademicYearCount } from '@types'; interface SearchBarProps { - // initialSearchTerm?: string; // Could be used if you want to pre-fill from URL params etc. - onSearchTermChange: (term: string) => void; // This is the debounced function from parent + onSearchTermChange: (term: string) => void; selectedCategory: string; onCategoryChange: (category: string) => void; availableCategories: CategoryCount; @@ -12,10 +12,12 @@ interface SearchBarProps { availableAcademicYears: AcademicYearCount; selectedGrade: string; onGradeChange: (grade: string) => void; + selectedStudentLed: string; + onStudentLedChange: (value: string) => void; } const SearchBar: React.FC = ({ - onSearchTermChange, // This is the debounced function from ClubList + onSearchTermChange, selectedCategory, onCategoryChange, availableCategories, @@ -24,13 +26,15 @@ const SearchBar: React.FC = ({ availableAcademicYears, selectedGrade, onGradeChange, + selectedStudentLed, + onStudentLedChange, }) => { - const [inputValue, setInputValue] = useState(''); // Local state for immediate input update + const [inputValue, setInputValue] = useState(''); const handleInputChange = (e: React.ChangeEvent) => { const newValue = e.target.value; - setInputValue(newValue); // Update local state immediately for responsive input field - onSearchTermChange(newValue); // Call the debounced function passed from the parent + setInputValue(newValue); + onSearchTermChange(newValue); }; return ( @@ -38,10 +42,31 @@ const SearchBar: React.FC = ({ + onGradeChange(e.target.value)} + min="1" + max="12" + aria-label="Filter by grade" + /> + - onGradeChange(e.target.value)} - min="1" - max="12" - aria-label="Filter by grade" - /> ); }; diff --git a/src/styles/global.css b/src/styles/global.css index ec3a662..0f75e31 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -162,6 +162,8 @@ footer { line-height: 1.4; flex-grow: 1; flex-shrink: 1; + height: 2.75rem; + vertical-align: middle; } /* Handle select dropdown arrow color for dark mode (can be tricky cross-browser) */ /* For modern browsers, accent-color can influence form controls */ @@ -179,10 +181,14 @@ footer { min-width: 250px; } -.search-bar-container select, +.search-bar-container select { + flex-basis: 160px; + min-width: 120px; +} + .search-bar-container input[type="number"] { - flex-basis: 200px; - min-width: 150px; + flex-basis: 120px; + min-width: 80px; } @@ -228,6 +234,7 @@ footer { min-width: 0; flex-grow: 0; flex-shrink: 0; + height: 2.5rem; } } diff --git a/src/utils/apiService.ts b/src/utils/apiService.ts index bbbb3f8..e5d2217 100644 --- a/src/utils/apiService.ts +++ b/src/utils/apiService.ts @@ -15,8 +15,8 @@ const BASE_URL = 'https://dsas-cca.jamesflare.com/v1'; const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); async function fetchWithErrorHandling(url: string, isRetryable: boolean = false, attempt: number = 1): Promise { - const MAX_RETRY_ATTEMPTS_FOR_NON_PERSISTENT = 3; // Max retries for general errors if not persistent - const RETRY_DELAY_MS = 1000; // 1 second for persistent retry + const MAX_RETRY_ATTEMPTS_FOR_NON_PERSISTENT = 3; + const RETRY_DELAY_MS = 1000; try { const response = await fetch(url); @@ -59,11 +59,13 @@ export async function filterClubs(params: { category?: string; academicYear?: string; grade?: number; + isStudentLed?: boolean | undefined; }): Promise { const queryParams = new URLSearchParams(); if (params.category) queryParams.append('category', params.category); if (params.academicYear) queryParams.append('academicYear', params.academicYear); if (params.grade !== undefined) queryParams.append('grade', String(params.grade)); + if (params.isStudentLed !== undefined) queryParams.append('isStudentLed', String(params.isStudentLed)); const url = `${BASE_URL}/activity/list?${queryParams.toString()}`; const data = await fetchWithErrorHandling(url);