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

The Search Feature of Skills cirteria implement from one skill to mutliple Skills #414

Merged
merged 4 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 31 additions & 15 deletions src/Homepage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ function App() {
const [currentPage, setCurrentPage] = useState(1);
const [shuffledProfiles, setShuffledProfiles] = useState([]);
const [loadingProfiles, setLoadingProfiles] = useState(false);
const recordsPerPage = 20;

const recordsPerPage = 20;

const currentUrl = window.location.pathname;
useEffect(() => {
Expand Down Expand Up @@ -67,20 +68,35 @@ function App() {
.replace(/\s+/g, ' ')
.trim();

const normalizedValue = normalizeString(value);

const filteredResults = combinedData.filter((user) => {
if (criteria === 'name') {
return normalizeString(user.name).includes(normalizedValue);
} else if (criteria === 'location') {
return normalizeString(user.location).includes(normalizedValue);
} else if (criteria === 'skill') {
return user.skills.some((skill) => normalizeString(skill).includes(normalizedValue));
}
return false;
});

setProfiles(filteredResults);
if (criteria !== 'skill') {
let normalizedValue = normalizeString(value);

const filteredResults = combinedData.filter((user) => {
if (criteria === 'name') {
return normalizeString(user.name).includes(normalizedValue);
} else if (criteria === 'location') {
return normalizeString(user.location).includes(normalizedValue);
}
return false;
});

setProfiles(filteredResults);
} else if(criteria === "skill") {
// if criteria is skill the it will filter the data
if(value.length > 0){
let setOfSearchSkills = new Set(value.map(skill => skill.toLowerCase())); // Convert searchSkills to lowercase for comparison
const filteredUsers = shuffledProfiles.filter(user =>
user.skills.some(skill => setOfSearchSkills.has(skill.toLowerCase())) // Ensure skill is also lowercase
);
setProfiles(filteredUsers);
} else {
//if skills are empty it will reset the data
setProfiles(shuffledProfiles)
}
} else {
setProfiles(false);
}

setSearching(true);
};

Expand Down
133 changes: 96 additions & 37 deletions src/components/Search/Search.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,44 +2,78 @@ import React, { useState, useRef, useEffect } from 'react';
import useDebounce from '../../hooks/useDebouncer';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faMagnifyingGlass, faXmark } from '@fortawesome/free-solid-svg-icons';
import SearchSkillsContainer from './SearchSkillsContainer';

function Search({ onSearch }) {
const [searchValue, setSearchValue] = useState('');
const [prevSearchValue, setPrevSearchValue] = useState('');
const [searchCriteria, setSearchCriteria] = useState('name');
const searchInput = useRef(null);

const [searchSkills, setSearchSkills] = useState([]);

const normalizeString = (str) =>
str
.toLowerCase()
.replace(/\s*,\s*/g, ' ')
.replace(/\s+/g, ' ')
.trim();

const handleInputChange = (event) => {
setSearchValue(event.target.value);
};

const handleCriteriaChange = (event) => {
if(event.target.value !== "skill") {
handleClearSkills();
}
setSearchCriteria(event.target.value);

};

const debouncedValue = useDebounce(searchValue, 500);

useEffect(() => {
if (debouncedValue !== prevSearchValue) {
if (debouncedValue !== prevSearchValue && searchCriteria !== "skill") {
onSearch({ value: debouncedValue, criteria: searchCriteria });
setPrevSearchValue(debouncedValue);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [debouncedValue]);

const handleSearch = () => {
if (searchValue !== prevSearchValue) {
if ((searchValue !== prevSearchValue && searchCriteria !== "skill") || searchValue.trim() === "") {
onSearch({ value: searchValue, criteria: searchCriteria });
setPrevSearchValue(searchValue);
}
};

const handleSearchOnEnter = (e) => {
if (e.keyCode === 13) {
handleSearch();
const handleSearchOnEnter = (event) => {
if (event.keyCode === 13) {
//if searchCriteia is skill then it will add that skill to searchSkills
if (searchCriteria ==='skill') {
let searchvalue = normalizeString(searchValue);
searchvalue = searchvalue.trim();
if (searchvalue.length > 0) {
var set = new Set(searchSkills);
set.add(searchvalue);
setSearchSkills((prev) => [...set]);
}
setSearchValue('');
} else {
handleSearch();
}
}
};

useEffect(() => {
//when new skill is added to searchSkill it will filter the data
if(searchCriteria === "skill") {
onSearch({ value: searchSkills, criteria: searchCriteria });
setPrevSearchValue('');
}
}, [searchSkills]);

const handleSearchButtonClick = () => {
handleSearch();
};
Expand All @@ -53,45 +87,70 @@ function Search({ onSearch }) {
}
};

//Reset the profiles
const handleClearSkills = () => {
setSearchSkills([]);
setSearchValue('');
setPrevSearchValue('');
onSearch({ value: [], criteria: "skill" });
searchInput.current.focus();
};

useEffect(() => {
searchInput.current.focus();
}, []);

return (
<div className="relative flex items-center justify-end space-x-4 pb-6">
<select
className="focus:border-primaryFocus focus:bg-primaryLight dark:focus:border-secondaryFocus dark:focus:bg-secondaryLight h-12 rounded-lg border-2 border-borderSecondary bg-primaryColor px-4 py-3 text-base text-secondaryColor outline-none dark:border-borderColor dark:bg-secondaryColor dark:text-white"
value={searchCriteria}
onChange={handleCriteriaChange}
>
<option value="name">Name</option>
<option value="location">Location</option>
<option value="skill">Skill</option>
</select>
<div className="relative w-full">
<input
className="focus:border-primaryFocus focus:bg-primaryLight dark:focus:border-secondaryFocus dark:focus:bg-secondaryLight h-12 w-full rounded-lg border-2 border-borderSecondary bg-primaryColor px-4 py-3 pr-12 font-spaceMono text-base text-secondaryColor outline-none dark:border-borderColor dark:bg-secondaryColor dark:text-white"
ref={searchInput}
type="text"
onChange={handleInputChange}
value={searchValue}
placeholder={`Search user by ${searchCriteria}`}
onKeyDown={handleSearchOnEnter}
/>
{searchValue ? (
<FontAwesomeIcon
onClick={handleDeleteButtonClick}
className="hover:text-primaryFocus dark:hover:text-secondaryFocus absolute right-4 top-1/2 -translate-y-1/2 scale-125 transform cursor-pointer text-xl text-secondaryColor dark:text-white"
icon={faXmark}
<div className="relative pb-6">
<div className="relative flex items-center justify-end space-x-4 ">
<select
className="focus:border-primaryFocus focus:bg-primaryLight dark:focus:border-secondaryFocus dark:focus:bg-secondaryLight h-12 rounded-lg border-2 border-borderSecondary bg-primaryColor px-4 py-3 text-base text-secondaryColor outline-none dark:border-borderColor dark:bg-secondaryColor dark:text-white"
value={searchCriteria}
onChange={handleCriteriaChange}
>
<option value="name">Name</option>
<option value="location">Location</option>
<option value="skill">Skill</option>
</select>
<div className="relative w-full">
<input
className="focus:border-primaryFocus focus:bg-primaryLight dark:focus:border-secondaryFocus dark:focus:bg-secondaryLight h-12 w-full rounded-lg border-2 border-borderSecondary bg-primaryColor px-4 py-3 pr-12 font-spaceMono text-base text-secondaryColor outline-none dark:border-borderColor dark:bg-secondaryColor dark:text-white"
ref={searchInput}
type="text"
onChange={handleInputChange}
value={searchValue}
placeholder={`Search user by ${searchCriteria}`}
onKeyDown={handleSearchOnEnter}
/>
) : (
<FontAwesomeIcon
onClick={handleSearchButtonClick}
className="hover:text-primaryFocus dark:hover:text-secondaryFocus absolute right-4 top-1/2 -translate-y-1/2 transform cursor-pointer text-xl text-secondaryColor dark:text-white"
icon={faMagnifyingGlass}
/>
)}
{searchValue ? (
<FontAwesomeIcon
onClick={handleDeleteButtonClick}
className="hover:text-primaryFocus dark:hover:text-secondaryFocus absolute right-4 top-1/2 -translate-y-1/2 scale-125 transform cursor-pointer text-xl text-secondaryColor dark:text-white"
icon={faXmark}
/>
) : (
<FontAwesomeIcon
onClick={handleSearchButtonClick}
className="hover:text-primaryFocus dark:hover:text-secondaryFocus absolute right-4 top-1/2 -translate-y-1/2 transform cursor-pointer text-xl text-secondaryColor dark:text-white"
icon={faMagnifyingGlass}
/>
)}
</div>
</div>


{/* This block show the skills the user searched */}
{searchCriteria === 'skill' && searchSkills && searchSkills.length > 0 ? (
<>
<button
onClick={handleClearSkills}
className="m-2 cursor-pointer self-end rounded-md bg-white p-2 font-semibold dark:bg-[#1E2A47] dark:text-white"
>
Clear All
</button>
<SearchSkillsContainer searchSkills={searchSkills} setSearchSkills={setSearchSkills} />
</>
) : null}
</div>
);
}
Expand Down
21 changes: 21 additions & 0 deletions src/components/Search/SearchSkill.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { FaTimes } from 'react-icons/fa';

//it is the design of each skill box
export default function SearchSkill({ skill, setSearchSkills }) {
const handleRemoveSkill = (value) => {
setSearchSkills((prev) => {
return prev.filter((prevSkill) => prevSkill !== skill);
});
};

return (
<div className=" m-2 flex flex-row items-center rounded-md border-2 border-[#00A6FB] bg-[#00A6FB] p-2 text-black transition-colors duration-1000 ease-in-out hover:bg-white hover:text-[#00A6FB] dark:text-white dark:hover:bg-[#141D2F]">
<p className="mr-4">{skill}</p>
<FaTimes
onClick={() => {
handleRemoveSkill(skill);
}}
/>
</div>
);
}
11 changes: 11 additions & 0 deletions src/components/Search/SearchSkillsContainer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import SearchSkill from './SearchSkill';

export default function SearchSkillsContainer({ searchSkills, setSearchSkills }) {
return (
<div className="relative mt-2 flex min-h-20 w-full flex-row flex-wrap rounded-lg bg-white p-2 dark:bg-[#1E2A47]">
{searchSkills.map((skill, idx) => {
return <SearchSkill skill={skill} key={idx} setSearchSkills={setSearchSkills} />;
})}
</div>
);
}
Loading