Skip to content

Commit

Permalink
Refactor FiltersSection component and add domain filter functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
teetangh committed Dec 12, 2024
1 parent a770445 commit c0f6933
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 28 deletions.
41 changes: 30 additions & 11 deletions app/explore/experts/components/FiltersSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
SelectValue,
} from "@/components/ui/select";
import { Domain, SubDomain, Tag } from "@prisma/client";
import { useState } from "react";
import { useState, useEffect } from "react";

interface FiltersSectionProps {
metadata: {
Expand Down Expand Up @@ -65,6 +65,19 @@ export function FiltersSection({
const [searchTerm, setSearchTerm] = useState("");
const [isDropdownOpen, setIsDropdownOpen] = useState(false);

// Reset subdomain and tags when domain changes
useEffect(() => {
setSelectedSubdomain(null);
setSelectedTags([]);
}, [selectedDomain, setSelectedSubdomain, setSelectedTags]);

const handleDomainChange = (value: string) => {
setSelectedDomain(value);
setSelectedSubdomain(null);
setSelectedTags([]);
setSearchTerm("");
};

const handleTagSelect = (tag: string) => {
if (!selectedTags.includes(tag)) {
setSelectedTags([...selectedTags, tag]);
Expand All @@ -82,12 +95,13 @@ export function FiltersSection({
setIsDropdownOpen(true);
};

const filteredTags =
metadata?.tags.filter(
(tag) =>
tag.name.toLowerCase().includes(searchTerm.toLowerCase()) &&
!selectedTags.includes(tag.name)
) || [];
// Filter tags based on selected domain and search term
const filteredTags = metadata?.tags.filter((tag) => {
if (selectedDomain && tag.domainId !== selectedDomain) return false;
if (!searchTerm) return true;
if (selectedTags.includes(tag.name)) return false;
return tag.name.toLowerCase().includes(searchTerm.toLowerCase());
}) || [];

return (
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
Expand All @@ -99,7 +113,10 @@ export function FiltersSection({
>
Domain
</label>
<Select onValueChange={(value) => setSelectedDomain(value)}>
<Select
value={selectedDomain || undefined}
onValueChange={handleDomainChange}
>
<SelectTrigger id="domain" aria-label="Select domain">
<SelectValue placeholder="Select domain" />
</SelectTrigger>
Expand All @@ -125,7 +142,8 @@ export function FiltersSection({
</label>
<Select
disabled={!selectedDomain}
onValueChange={(value) => setSelectedSubdomain(value)}
value={selectedSubdomain || undefined}
onValueChange={setSelectedSubdomain}
>
<SelectTrigger id="subdomain" aria-label="Select subdomain">
<SelectValue placeholder="Select subdomain" />
Expand Down Expand Up @@ -158,13 +176,14 @@ export function FiltersSection({
<input
className="bg-white border border-gray-300 text-black text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5"
id="tags"
placeholder="Search tags..."
placeholder={selectedDomain ? "Search tags..." : "Select a domain first"}
type="text"
value={searchTerm}
onChange={handleInputChange}
onFocus={() => setIsDropdownOpen(true)}
disabled={!selectedDomain}
/>
{isDropdownOpen && (
{isDropdownOpen && filteredTags.length > 0 && (
<div className="absolute z-10 w-full mt-1 bg-white border border-gray-300 rounded-lg shadow-lg">
<ul className="py-1 overflow-auto max-h-60">
{filteredTags.map((tag) => (
Expand Down
145 changes: 128 additions & 17 deletions app/explore/experts/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { useState, useEffect } from "react";
import { useState, useEffect, useMemo } from "react";
import { Domain, SubDomain, Tag } from "@prisma/client";
import { TConsultantProfile } from "@/types/consultant";
import { FiltersSection } from "./components/FiltersSection";
Expand All @@ -13,6 +13,15 @@ interface MetaData {
domains: Domain[];
subdomains: SubDomain[];
tags: Tag[];
consultantMetadata: {
totalConsultants: number;
consultantsByDomain: {
id: string;
name: string;
consultantCount: number;
}[];
averageRating: number;
};
}

function FindExperts() {
Expand Down Expand Up @@ -48,6 +57,106 @@ function FindExperts() {
fetchData();
}, []);

// Filter functions
const filterBySearch = (consultant: TConsultantProfile) => {
if (!searchTerm) return true;
const searchLower = searchTerm.toLowerCase();

// Search in user details
if (consultant.user.name?.toLowerCase().includes(searchLower)) return true;
if (consultant.user.email?.toLowerCase().includes(searchLower)) return true;

// Search in consultant details
if (consultant.description?.toLowerCase().includes(searchLower)) return true;
if (consultant.specialization?.toLowerCase().includes(searchLower)) return true;
if (consultant.qualifications?.toLowerCase().includes(searchLower)) return true;

// Search in domain and subdomain names
if (consultant.domain.name.toLowerCase().includes(searchLower)) return true;
if (consultant.subDomains.some(sd =>
sd.name.toLowerCase().includes(searchLower)
)) return true;

// Search in tags
if (consultant.tags.some(tag =>
tag.name.toLowerCase().includes(searchLower)
)) return true;

return false;
};

const filterByDomain = (consultant: TConsultantProfile) => {
if (!selectedDomain) return true;
return consultant.domainId === selectedDomain;
};

const filterBySubdomain = (consultant: TConsultantProfile) => {
if (!selectedSubdomain) return true;
return consultant.subDomains.some((sd) => sd.id === selectedSubdomain);
};

const filterByTags = (consultant: TConsultantProfile) => {
if (selectedTags.length === 0) return true;
return selectedTags.every((tagName) =>
consultant.tags.some((t) => t.name === tagName)
);
};

const filterByExperience = (consultant: TConsultantProfile) => {
if (experienceYears === 0) return true;
if (!consultant.experience) return false;

// Parse experience range (e.g., "5-10 years" -> 5)
const match = consultant.experience.match(/\d+/);
if (!match) return false;

const years = parseInt(match[0]);
return years >= experienceYears;
};

const filterByPrice = (consultant: TConsultantProfile) => {
if (pricing === 0) return true;
// Check if any subscription plan's price is less than or equal to the filter price
return consultant.subscriptionPlans.some(
(plan) => plan.price / 100 <= pricing
);
};

// Apply all filters using useMemo to optimize performance
const filteredConsultants = useMemo(() => {
return consultants.filter(
(consultant) =>
filterBySearch(consultant) &&
filterByDomain(consultant) &&
filterBySubdomain(consultant) &&
filterByTags(consultant) &&
filterByExperience(consultant) &&
filterByPrice(consultant)
);
}, [
consultants,
searchTerm,
selectedDomain,
selectedSubdomain,
selectedTags,
experienceYears,
pricing,
]);

// Group consultants by domain
const groupedConsultants = useMemo(() => {
const grouped = new Map<string, TConsultantProfile[]>();

filteredConsultants.forEach((consultant) => {
if (!grouped.has(consultant.domain.id)) {
grouped.set(consultant.domain.id, []);
}
grouped.get(consultant.domain.id)?.push(consultant);
});

return grouped;
}, [filteredConsultants]);

if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
Expand Down Expand Up @@ -86,29 +195,31 @@ function FindExperts() {

<div className="space-y-4">
{metadata?.domains.map((domain) => {
const domainConsultants = consultants?.filter(
(consultant) => consultant.domainId === domain.id,
) ?? [];
const domainConsultants = groupedConsultants.get(domain.id) || [];

if (domainConsultants.length === 0) return null;

return (
<div key={domain.id} className="space-y-4">
<h2 className="text-2xl font-bold">{domain.name}</h2>
{domainConsultants.length > 0 ? (
domainConsultants.map((consultant) => (
<ConsultantCard
key={consultant.id}
consultant={consultant}
metadata={metadata}
/>
))
) : (
<p className="text-gray-500">
No consultants available in this domain at the moment.
</p>
)}
{domainConsultants.map((consultant) => (
<ConsultantCard
key={consultant.id}
consultant={consultant}
metadata={metadata}
/>
))}
</div>
);
})}

{filteredConsultants.length === 0 && (
<div className="text-center py-8">
<p className="text-gray-500 text-lg">
No consultants found matching your criteria. Try adjusting your filters.
</p>
</div>
)}
</div>
</div>
);
Expand Down

0 comments on commit c0f6933

Please sign in to comment.