Skip to content
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
wants to merge 6 commits into
base: development
Choose a base branch
from
272 changes: 218 additions & 54 deletions src/pages/Mentors/index.tsx
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) => {
Copy link
Member

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

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
Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Slots should be a slider like this,

image

</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>
);
Loading