-
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
Open
rdwaynedehoedt
wants to merge
6
commits into
sef-global:development
Choose a base branch
from
rdwaynedehoedt:development
base: development
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
e0d2c23
Fix issue #200: Prevent website link from appearing if not provided
rdwaynedehoedt 11c4524
Fix issue #200: Prevent website link from appearing if not provided
rdwaynedehoedt 10d34a5
Merge branch 'sef-global:development' into development
rdwaynedehoedt 9a9eb71
Fix Mentor Filtration #223: Add filters and sorting
rdwaynedehoedt 2d8b5d6
Merge branch 'sef-global:development' into development
rdwaynedehoedt b17a524
Merge branch 'sef-global:development' into development
rdwaynedehoedt File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
rdwaynedehoedt marked this conversation as resolved.
Show resolved
Hide resolved
|
||
(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> | ||
); | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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