-
Notifications
You must be signed in to change notification settings - Fork 58
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Enhance Mentor Filter Functionality #226
base: development
Are you sure you want to change the base?
Changes from all commits
e0d2c23
11c4524
10d34a5
9a9eb71
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,10 +5,17 @@ import { usePublicMentors } from '../../hooks/usePublicMentors'; | |
import { type Mentor, type Category } from '../../types'; | ||
import MentorCard from '../../components/MentorCard/MentorCard.component'; | ||
import Loading from '../../assets/svg/Loading'; | ||
import { ApplicationStatus } from '../../enums'; | ||
|
||
const Mentors = () => { | ||
const [selectedCategory, setSelectedCategory] = useState<string | null>(null); | ||
const [selectedCountries, setSelectedCountries] = useState<string[]>([]); | ||
const [selectedAvailableSlots, setSelectedAvailableSlots] = useState<number[]>([]); | ||
const [sortedMentors, setSortedMentors] = useState<Mentor[]>([]); | ||
const [uniqueCountries, setUniqueCountries] = useState<string[]>([]); | ||
const [uniqueAvailableSlots, setUniqueAvailableSlots] = useState<number[]>([]); | ||
const [isCountryDropdownOpen, setIsCountryDropdownOpen] = useState(false); | ||
const [isSlotsDropdownOpen, setIsSlotsDropdownOpen] = useState(false); | ||
const pageSize = 10; | ||
|
||
const { ref, inView } = useInView(); | ||
|
@@ -31,13 +38,62 @@ const Mentors = () => { | |
useEffect(() => { | ||
if (data) { | ||
const allMentors = data.pages.flatMap((page) => page.items); | ||
setSortedMentors(allMentors); | ||
|
||
const countries = allMentors | ||
.map((mentor) => mentor.application?.country) | ||
.filter((country): country is string => !!country); | ||
setUniqueCountries([...new Set(countries)]); | ||
|
||
const availableSlots = allMentors | ||
.map((mentor) => { | ||
const approvedMenteesCount = mentor?.mentees | ||
? mentor.mentees.filter( | ||
(mentee) => mentee.state === ApplicationStatus.APPROVED | ||
).length | ||
: 0; | ||
const availableSlots = mentor?.application.noOfMentees | ||
? Math.max(0, mentor.application.noOfMentees - approvedMenteesCount) | ||
: 0; | ||
return availableSlots; | ||
}) | ||
.filter((slots, index, self) => self.indexOf(slots) === index); | ||
setUniqueAvailableSlots(availableSlots); | ||
} | ||
}, [data]); | ||
|
||
useEffect(() => { | ||
if (data) { | ||
let allMentors = data.pages.flatMap((page) => page.items); | ||
|
||
if (selectedCountries.length > 0) { | ||
allMentors = allMentors.filter((mentor) => | ||
selectedCountries.includes(mentor.application.country) | ||
); | ||
} | ||
|
||
if (selectedAvailableSlots.length > 0) { | ||
allMentors = allMentors.filter((mentor) => { | ||
const approvedMenteesCount = mentor?.mentees | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. |
||
? mentor.mentees.filter( | ||
(mentee) => mentee.state === ApplicationStatus.APPROVED | ||
).length | ||
: 0; | ||
const availableSlots = mentor?.application.noOfMentees | ||
? Math.max(0, mentor.application.noOfMentees - approvedMenteesCount) | ||
: 0; | ||
return selectedAvailableSlots.includes(availableSlots); | ||
}); | ||
} | ||
|
||
setSortedMentors(allMentors); | ||
} | ||
}, [data, selectedCountries, selectedAvailableSlots]); | ||
|
||
const handleSortAZ = () => { | ||
const sorted = [...sortedMentors].sort((a, b) => | ||
a.application.firstName.localeCompare(b.application.firstName) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the issue here? |
||
(a.application?.firstName || '').localeCompare( | ||
b.application?.firstName || '' | ||
) | ||
); | ||
setSortedMentors(sorted); | ||
}; | ||
|
@@ -46,6 +102,34 @@ const Mentors = () => { | |
setSelectedCategory(category); | ||
}; | ||
|
||
const handleCountryChange = (country: string) => { | ||
setSelectedCountries((prevCountries) => { | ||
if (prevCountries.includes(country)) { | ||
return prevCountries.filter((c) => c !== country); | ||
} else { | ||
return [...prevCountries, country]; | ||
} | ||
}); | ||
}; | ||
|
||
const handleAvailableSlotsChange = (slots: number) => { | ||
setSelectedAvailableSlots((prevSlots) => { | ||
if (prevSlots.includes(slots)) { | ||
return prevSlots.filter((s) => s !== slots); | ||
} else { | ||
return [...prevSlots, slots]; | ||
} | ||
}); | ||
}; | ||
|
||
const toggleCountryDropdown = () => { | ||
setIsCountryDropdownOpen(!isCountryDropdownOpen); | ||
}; | ||
|
||
const toggleSlotsDropdown = () => { | ||
setIsSlotsDropdownOpen(!isSlotsDropdownOpen); | ||
}; | ||
|
||
if (status === 'pending' || categoriesLoading) { | ||
return ( | ||
<div className="flex items-center justify-center h-screen"> | ||
|
@@ -59,69 +143,149 @@ const Mentors = () => { | |
} | ||
|
||
return ( | ||
<div className="w-full"> | ||
<div> | ||
<div className="mb-4 w-full flex items-center justify-between"> | ||
<p className="text-2xl font-semibold">Mentors</p> | ||
</div> | ||
<hr className="mb-8" /> | ||
<div className="min-h-screen flex flex-col items-center p-6 bg-white"> | ||
<div className="w-full max-w-7xl flex flex-col lg:flex-row gap-8"> | ||
<div className="w-full lg:w-1/4 bg-white rounded-lg p-6 self-start"> | ||
<p className="text-xl font-semibold mb-4 text-gray-800">Filters</p> | ||
|
||
<div className="mb-4 w-full flex items-center justify-between"> | ||
<div className="flex flex-wrap gap-3 items-center text-sm"> | ||
<button | ||
onClick={() => { | ||
handleCategoryChange(null); | ||
}} | ||
className={`bg-blue text-black px-4 py-1 rounded-full border border-blue-500 ${ | ||
selectedCategory === null ? 'bg-blue-500 text-white' : '' | ||
}`} | ||
> | ||
All | ||
</button> | ||
{allCategories.map((category: Category) => ( | ||
<div className="mb-6"> | ||
<p className="font-medium mb-2 text-gray-700">Categories</p> | ||
<div className="flex flex-col gap-3 text-sm"> | ||
<label className="flex items-center space-x-2"> | ||
<input | ||
type="checkbox" | ||
checked={selectedCategory === null} | ||
onChange={() => handleCategoryChange(null)} | ||
className="form-checkbox h-4 w-4 text-blue-500" | ||
/> | ||
<span className="text-gray-700">All</span> | ||
</label> | ||
|
||
{allCategories.map((category: Category) => ( | ||
<label | ||
key={category.uuid} | ||
className="flex items-center space-x-2" | ||
> | ||
<input | ||
type="checkbox" | ||
checked={selectedCategory === category.uuid} | ||
onChange={() => handleCategoryChange(category.uuid)} | ||
className="form-checkbox h-4 w-4 text-blue-500" | ||
/> | ||
<span className="text-gray-700">{category.category}</span> | ||
</label> | ||
))} | ||
</div> | ||
</div> | ||
|
||
<div className="mb-6"> | ||
<p className="font-medium mb-2 text-gray-700 flex items-center"> | ||
<button | ||
key={category.uuid} | ||
onClick={() => { | ||
handleCategoryChange(category.uuid); | ||
}} | ||
className={`bg-blue text-black px-4 py-1 rounded-full border border-blue-500 ${ | ||
selectedCategory === category.uuid | ||
? 'bg-blue-500 text-white' | ||
: '' | ||
}`} | ||
className=" text-gray-700 hover:text-blue-500" | ||
onClick={toggleCountryDropdown} | ||
> | ||
{category.category} | ||
Countries {isCountryDropdownOpen ? '-' : ' +'} | ||
</button> | ||
))} | ||
</p> | ||
|
||
{isCountryDropdownOpen && ( | ||
<div className="flex flex-col gap-3 text-sm"> | ||
<label className="flex items-center space-x-2"> | ||
<input | ||
type="checkbox" | ||
checked={selectedCountries.length === 0} | ||
onChange={() => setSelectedCountries([])} | ||
className="form-checkbox h-4 w-4 text-blue-500" | ||
/> | ||
<span className="text-gray-700">All</span> | ||
</label> | ||
{uniqueCountries.map((country) => ( | ||
<label key={country} className="flex items-center space-x-2"> | ||
<input | ||
type="checkbox" | ||
checked={selectedCountries.includes(country)} | ||
onChange={() => handleCountryChange(country)} | ||
className="form-checkbox h-4 w-4 text-blue-500" | ||
/> | ||
<span className="text-gray-700">{country}</span> | ||
</label> | ||
))} | ||
</div> | ||
)} | ||
</div> | ||
</div> | ||
|
||
<div className="mb-4 w-full flex justify-end"> | ||
<button | ||
onClick={handleSortAZ} | ||
className="bg-blue-500 text-white px-2 py-1 rounded border border-blue-500 text-xs mr-6" | ||
> | ||
Sort A-Z | ||
</button> | ||
</div> | ||
<div className="mb-6"> | ||
<p className="font-medium mb-2 text-gray-700 flex items-center"> | ||
<button | ||
className=" text-gray-700 hover:text-blue-500" | ||
onClick={toggleSlotsDropdown} | ||
> | ||
Available Slots {isSlotsDropdownOpen ? '-' : ' +'} | ||
</button> | ||
</p> | ||
|
||
{sortedMentors.length > 0 ? ( | ||
<div className="flex-grow grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-5 xl:grid-cols-6 gap-8 items-start"> | ||
{sortedMentors.map((mentor) => ( | ||
<MentorCard key={mentor.uuid} mentor={mentor} /> | ||
))} | ||
{isSlotsDropdownOpen && ( | ||
<div className="flex flex-col gap-3 text-sm"> | ||
<label className="flex items-center space-x-2"> | ||
<input | ||
type="checkbox" | ||
checked={selectedAvailableSlots.length === 0} | ||
onChange={() => setSelectedAvailableSlots([])} | ||
className="form-checkbox h-4 w-4 text-blue-500" | ||
/> | ||
<span className="text-gray-700">All</span> | ||
</label> | ||
{uniqueAvailableSlots.map((slots) => ( | ||
<label key={slots} className="flex items-center space-x-2"> | ||
<input | ||
type="checkbox" | ||
checked={selectedAvailableSlots.includes(slots)} | ||
onChange={() => handleAvailableSlotsChange(slots)} | ||
className="form-checkbox h-4 w-4 text-blue-500" | ||
/> | ||
<span className="text-gray-700"> | ||
{slots} Slots Available | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
</span> | ||
</label> | ||
))} | ||
</div> | ||
)} | ||
</div> | ||
) : ( | ||
<p>No mentors found for this category.</p> | ||
)} | ||
|
||
{isFetchingNextPage && ( | ||
<div className="flex justify-center mt-4"> | ||
<Loading /> | ||
<div> | ||
<button | ||
onClick={handleSortAZ} | ||
className="bg-blue-500 text-white px-2 py-1 rounded border border-blue-500 text-xs mr-6" | ||
> | ||
Sort A-Z | ||
</button> | ||
</div> | ||
)} | ||
</div> | ||
|
||
<div ref={ref} style={{ height: '20px' }} /> | ||
<div className="w-full flex-grow bg-white rounded-lg p-6"> | ||
<div> | ||
<p className="text-2xl font-semibold mb-4">Mentors</p> | ||
<hr className="mb-6" /> | ||
|
||
{sortedMentors.length > 0 ? ( | ||
<div className="grid grid-cols-1 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6"> | ||
{sortedMentors.map((mentor) => ( | ||
<MentorCard key={mentor.uuid} mentor={mentor} /> | ||
))} | ||
</div> | ||
) : ( | ||
<p>No mentors found for this filter.</p> | ||
)} | ||
|
||
{isFetchingNextPage && ( | ||
<div className="flex justify-center mt-6"> | ||
<Loading /> | ||
</div> | ||
)} | ||
|
||
<div ref={ref} style={{ height: '20px' }} /> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you check mentor.application object for the availableSlots and approvedMenteesCount. It should be there. @rdwaynedehoedt