Skip to content

Commit

Permalink
Merge pull request #74 from fs-jun24-team-3/feature/search-bar
Browse files Browse the repository at this point in the history
added searc bar, fixed closing dropdowns on side click
  • Loading branch information
k-marchuk authored Sep 25, 2024
2 parents 6ce1604 + 27b98b3 commit 67f057b
Show file tree
Hide file tree
Showing 10 changed files with 318 additions and 128 deletions.
18 changes: 18 additions & 0 deletions src/components/Catalog/Catalog.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,21 @@
}
}
}

.phonesDropdown {
display: flex;

}

.noResults{
margin-top: 20px;
}

.filtration {
display: flex;
flex-direction: column;

@include onTablet {
flex-direction: row;
}
}
101 changes: 75 additions & 26 deletions src/components/Catalog/Catalog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ItemsPerPageOption, SortOption } from '../../utils/types/SortOption';
import { Dropdown } from '../Dropdown';
import { ItemsPerPageDropdown } from '../ItemsPerPageDropdown';
import { useLocation, useNavigate } from 'react-router-dom';
import { SearchBar } from '../SearchBar/SearchBar';
import { sortProducts } from '../../utils/helpers/sortProducts';
import { Breadcrumbs } from '../Breadcrumbs';

Expand All @@ -21,6 +22,7 @@ export const Catalog: React.FC<Props> = ({ items, title, isFiltered }) => {
const [currentPage, setCurrentPage] = useState(1);
const [sortOption, setSortOption] = useState<SortOption>('alphabetical');
const [itemsPerPage, setItemsPerPage] = useState<ItemsPerPageOption>(16);
const [searchQuery, setSearchQuery] = useState('');

useEffect(() => {
const searchParams = new URLSearchParams(location.search);
Expand Down Expand Up @@ -48,22 +50,35 @@ export const Catalog: React.FC<Props> = ({ items, title, isFiltered }) => {
}
}, [location.search]);

const sortedItems = useMemo(() => {
return sortProducts(items, sortOption);
}, [items, sortOption]);
const filteredItems = useMemo(() => {
let filtered = sortProducts(items, sortOption);
if (searchQuery.trim() !== '') {
filtered = filtered.filter(item =>
item.name.toLowerCase().includes(searchQuery.toLowerCase()),
);
}
return filtered;
}, [items, sortOption, searchQuery]);

const paginatedItems = useMemo(() => {
let filtered = filteredItems;
if (searchQuery.trim() !== '') {
filtered = filteredItems.filter(item =>
item.name.toLowerCase().includes(searchQuery.toLowerCase()),
);
}

if (itemsPerPage === 'all') {
return sortedItems;
return filtered;
}

const indexOfLastItem = currentPage * itemsPerPage;
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
return sortedItems.slice(indexOfFirstItem, indexOfLastItem);
}, [sortedItems, currentPage, itemsPerPage]);
return filtered.slice(indexOfFirstItem, indexOfLastItem);
}, [filteredItems, currentPage, itemsPerPage, searchQuery]);

const totalPages =
itemsPerPage === 'all' ? 1 : Math.ceil(sortedItems.length / itemsPerPage);
itemsPerPage === 'all' ? 1 : Math.ceil(filteredItems.length / itemsPerPage);

const handlePrevPage = () => {
if (currentPage > 1) {
Expand All @@ -79,15 +94,26 @@ export const Catalog: React.FC<Props> = ({ items, title, isFiltered }) => {

const paginate = (pageNumber: number) => setCurrentPage(pageNumber);

const updateURL = (sort: SortOption, perPage: ItemsPerPageOption) => {
const updateURL = (
sort: SortOption,
perPage: ItemsPerPageOption,
searchQuery: string,
) => {
const searchParams = new URLSearchParams(location.search);
searchParams.set('sort', sort);

if (perPage === 8) {
if (perPage === 16) {
searchParams.delete('perPage');
} else {
searchParams.set('perPage', perPage.toString());
}

if (searchQuery.trim() !== '') {
searchParams.set('q', searchQuery);
} else {
searchParams.delete('q');
}

navigate(`${location.pathname}?${searchParams.toString()}`, {
replace: true,
});
Expand All @@ -96,13 +122,19 @@ export const Catalog: React.FC<Props> = ({ items, title, isFiltered }) => {
const handleSort = (option: SortOption) => {
setSortOption(option);
setCurrentPage(1);
updateURL(option, itemsPerPage);
updateURL(option, itemsPerPage, searchQuery);
};

const handleItemsPerPageChange = (option: ItemsPerPageOption) => {
setItemsPerPage(option);
setCurrentPage(1);
updateURL(sortOption, option);
updateURL(sortOption, option, searchQuery);
};

const handleSearch = (query: string) => {
setSearchQuery(query);
setCurrentPage(1);
updateURL(sortOption, itemsPerPage, query);
};

const generatePageNumbers = (currentPage: number, totalPages: number) => {
Expand Down Expand Up @@ -142,26 +174,43 @@ export const Catalog: React.FC<Props> = ({ items, title, isFiltered }) => {
<div className={styles.phones}>
<Breadcrumbs />
<div className={styles.phones__title}>{title}</div>
<div className={styles.phones__countModel}>{items.length} models</div>
<div className={styles.phones__countModel}>
{filteredItems.length} models
</div>
{isFiltered && (
<div className={styles.phonesDropdown}>
<Dropdown handleSort={handleSort} initialSortOption={sortOption} />
<ItemsPerPageDropdown
onSelect={handleItemsPerPageChange}
initialOption={itemsPerPage}
/>
<div className={styles.filtration}>
<div className={styles.phonesDropdown}>
<Dropdown
handleSort={handleSort}
initialSortOption={sortOption}
/>
<ItemsPerPageDropdown
onSelect={handleItemsPerPageChange}
initialOption={itemsPerPage}
/>
</div>
<SearchBar onSearch={handleSearch} />
</div>
)}

<div className={styles.phones__items}>
{paginatedItems.map((item: UnionProduct) => (
<div className={styles.phones__item} key={item.id}>
<PhoneCard item={item} />
</div>
))}
</div>
{paginatedItems.length === 0 && (
<div className={styles.noResults}>
<h2>No results found</h2>
<p>Try searching for something else.</p>
</div>
)}

{paginatedItems.length > 0 && (
<div className={styles.phones__items}>
{paginatedItems.map((item: UnionProduct) => (
<div className={styles.phones__item} key={item.id}>
<PhoneCard item={item} />
</div>
))}
</div>
)}

{itemsPerPage !== 'all' && items.length > itemsPerPage && (
{paginatedItems.length > 0 && itemsPerPage !== 'all' && (
<div className={styles.phones__pagination}>
<button
className={styles.phones__pagination__arr}
Expand Down
5 changes: 4 additions & 1 deletion src/components/Dropdown/Dropdown.module.scss
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
.dropdown {
position: relative;
display: inline-block;
padding-top: 40px;
padding-right: 16px;

@include onDesktop {
padding-top: 40px;
}

&-label {
display: block;
Expand Down
11 changes: 9 additions & 2 deletions src/components/Dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import React, { useEffect, useState } from 'react';

Check failure on line 1 in src/components/Dropdown/Dropdown.tsx

View workflow job for this annotation

GitHub Actions / deploy

Cannot find module 'outsideclick-react' or its corresponding type declarations.
import styles from './Dropdown.module.scss';
import { SortOption } from '../../utils/types/SortOption';
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
import { useOutsideClick } from 'outsideclick-react';

type Props = {
handleSort: (option: SortOption) => void;
initialSortOption: SortOption;
Expand All @@ -27,6 +28,12 @@ export const Dropdown: React.FC<Props> = ({
setIsOpen(false);
};

const handleOutsideClick = () => {
setIsOpen(false);
};

const ref = useOutsideClick(handleOutsideClick);

const options: { value: SortOption; label: string }[] = [
{ value: 'alphabetical', label: 'Alphabetically' },
{ value: 'price_asc', label: 'Price Ascending' },
Expand All @@ -35,7 +42,7 @@ export const Dropdown: React.FC<Props> = ({
];

return (
<div className={styles.dropdown}>
<div ref={ref} className={styles.dropdown}>
<span className={styles['dropdown-label']}>Sort by:</span>
<button
className={`${styles['dropdown-toggle']} ${isOpen ? styles['dropdown-toggle-clicked'] : ''}`}
Expand Down
Loading

0 comments on commit 67f057b

Please sign in to comment.