diff options
| author | Rafi Zadanly <rafizadanly@gmail.com> | 2022-11-22 17:09:05 +0700 |
|---|---|---|
| committer | Rafi Zadanly <rafizadanly@gmail.com> | 2022-11-22 17:09:05 +0700 |
| commit | 930ed6680100e9732157ed1861af3572e36219a0 (patch) | |
| tree | 2915bd480d315a410028b99d6277e9e381b38e5f | |
| parent | fb4b7aea05526e154193d40a0cde6d674be263e7 (diff) | |
Filter search by brand, category, price
| -rw-r--r-- | src/components/Filter.js | 57 | ||||
| -rw-r--r-- | src/icons/close.svg | 1 | ||||
| -rw-r--r-- | src/pages/api/shop/search.js | 11 | ||||
| -rw-r--r-- | src/pages/login.js | 4 | ||||
| -rw-r--r-- | src/pages/register.js | 6 | ||||
| -rw-r--r-- | src/pages/shop/search.js | 67 | ||||
| -rw-r--r-- | src/styles/globals.css | 10 |
7 files changed, 138 insertions, 18 deletions
diff --git a/src/components/Filter.js b/src/components/Filter.js new file mode 100644 index 00000000..cb8fd626 --- /dev/null +++ b/src/components/Filter.js @@ -0,0 +1,57 @@ +import CloseIcon from "../icons/close.svg"; + +const Filter = ({ + selectedBrand, + onChangeBrand, + selectedCategory, + onChangeCategory, + brands, + categories, + isActiveFilter, + closeFilter, + onSubmit +}) => { + return ( + <div className={`fixed w-full z-[60] py-8 px-4 ring ring-gray-300 bg-white rounded-t-3xl idt-transition ${isActiveFilter ? 'bottom-0' : 'bottom-[-100%]'}`}> + <div className="flex justify-between items-center mb-5"> + <h2 className="text-xl font-semibold">Filter Produk</h2> + <button onClick={closeFilter}> + <CloseIcon className="w-7" /> + </button> + </div> + <form className="flex flex-col gap-y-4" onSubmit={onSubmit}> + <div> + <label>Kategori</label> + <select className="form-input mt-2" value={selectedCategory} onChange={onChangeCategory}> + <option value="">Pilih kategori...</option> + {categories?.map((category, index) => ( + <option key={index} value={category}>{category}</option> + ))} + </select> + </div> + <div> + <label>Brand</label> + <select className="form-input mt-2" value={selectedBrand} onChange={onChangeBrand}> + <option value="">Pilih brand...</option> + {brands?.map((brand, index) => ( + <option key={index} value={brand}>{brand}</option> + ))} + </select> + </div> + <div> + <label>Harga</label> + <div className="flex gap-x-4 mt-2 items-center"> + <input className="form-input"/> + <span>-</span> + <input className="form-input"/> + </div> + </div> + <button type="submit" className="btn-yellow font-semibold mt-4 w-full"> + Terapkan Filter + </button> + </form> + </div> + ) +}; + +export default Filter;
\ No newline at end of file diff --git a/src/icons/close.svg b/src/icons/close.svg new file mode 100644 index 00000000..50e0589d --- /dev/null +++ b/src/icons/close.svg @@ -0,0 +1 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
\ No newline at end of file diff --git a/src/pages/api/shop/search.js b/src/pages/api/shop/search.js index 72db04fd..3f13f56c 100644 --- a/src/pages/api/shop/search.js +++ b/src/pages/api/shop/search.js @@ -40,7 +40,9 @@ const productResponseMap = (products) => { export default async function handler(req, res) { const { q, - page = 1 + page = 1, + brand = '', + category = '', } = req.query; let limit = 30; @@ -57,9 +59,12 @@ export default async function handler(req, res) { `start=${offset}`, `rows=${limit}`, 'sort=product_rating desc' - ].join('&'); + ]; + + if (brand) parameter.push(`fq=brand:${brand}`); + if (category) parameter.push(`fq=category_name:${category}`); - let result = await axios(process.env.SOLR_HOST + '/solr/products/select?' + parameter); + let result = await axios(process.env.SOLR_HOST + '/solr/products/select?' + parameter.join('&')); try { result.data.response.products = productResponseMap(result.data.response.docs); result.data.responseHeader.params.start = parseInt(result.data.responseHeader.params.start); diff --git a/src/pages/login.js b/src/pages/login.js index cff2e0cd..cb299afa 100644 --- a/src/pages/login.js +++ b/src/pages/login.js @@ -65,14 +65,14 @@ export default function Login() { <form onSubmit={login} className="w-full"> <input type="text" - className="form-input bg-gray-100 mt-4 focus:ring-1 focus:ring-yellow-900" + className="form-input bg-gray-100 mt-4" placeholder="johndoe@gmail.com" value={email} onChange={(e) => setEmail(e.target.value)} /> <input type="password" - className="form-input bg-gray-100 mt-4 focus:ring-1 focus:ring-yellow-900" + className="form-input bg-gray-100 mt-4" placeholder="••••••••" value={password} onChange={(e) => setPassword(e.target.value)} diff --git a/src/pages/register.js b/src/pages/register.js index ff6aa1d8..41bd7c75 100644 --- a/src/pages/register.js +++ b/src/pages/register.js @@ -63,21 +63,21 @@ export default function Login() { <form onSubmit={register} className="w-full"> <input type="text" - className="form-input bg-gray-100 mt-4 focus:ring-1 focus:ring-yellow-900" + className="form-input bg-gray-100 mt-4" placeholder="johndoe@gmail.com" value={email} onChange={(e) => setEmail(e.target.value)} /> <input type="text" - className="form-input bg-gray-100 mt-4 focus:ring-1 focus:ring-yellow-900" + className="form-input bg-gray-100 mt-4" placeholder="John Doe" value={name} onChange={(e) => setName(e.target.value)} /> <input type="password" - className="form-input bg-gray-100 mt-4 focus:ring-1 focus:ring-yellow-900" + className="form-input bg-gray-100 mt-4" placeholder="••••••••" value={password} onChange={(e) => setPassword(e.target.value)} diff --git a/src/pages/shop/search.js b/src/pages/shop/search.js index 21a4ccd9..f41adf3e 100644 --- a/src/pages/shop/search.js +++ b/src/pages/shop/search.js @@ -4,30 +4,77 @@ import Layout from "../../components/Layout"; import Pagination from "../../components/Pagination"; import ProductCard from "../../components/ProductCard"; import FilterIcon from "../../icons/filter.svg"; +import { useEffect, useState } from "react"; +import Filter from "../../components/Filter"; +import { useRouter } from "next/router"; export async function getServerSideProps(context) { - const { q, page = 1 } = context.query; - let searchResults = await axios(`${process.env.SELF_HOST}/api/shop/search?q=${q}&page=${page}`); + const { q, page = 1, brand = '', category = '' } = context.query; + let searchResults = await axios(`${process.env.SELF_HOST}/api/shop/search?q=${q}&page=${page}&brand=${brand}&category=${category}`); searchResults = searchResults.data; - return { props: { searchResults, q, page } }; + return { props: { searchResults, q, page, brand, category } }; } -export default function ShopSearch({ searchResults, q, page }) { +export default function ShopSearch({ searchResults, q, page, brand, category }) { + const router = useRouter(); + const pageCount = Math.ceil(searchResults.response.numFound / searchResults.responseHeader.params.rows); const productStart = searchResults.responseHeader.params.start; const productRows = searchResults.responseHeader.params.rows; const productFound = searchResults.response.numFound; + + const [activeFilter, setActiveFilter] = useState(false); + const [selectedCategory, setSelectedCategory] = useState(category); + const [selectedBrand, setSelectedBrand] = useState(brand); + const [categories, setCategories] = useState([]); + const [brands, setBrands] = useState([]); + + const filterSubmit = (e) => { + e.preventDefault(); + setActiveFilter(false); + let filterRoute = `/shop/search?q=${q}`; + if (selectedBrand) filterRoute += `&brand=${selectedBrand}`; + if (selectedCategory) filterRoute += `&category=${selectedCategory}`; + router.push(filterRoute, undefined, { scroll: false }); + } + + useEffect(() => { + const filterCategory = searchResults.facet_counts.facet_fields.category_name_str.filter((category, index) => { + if (index % 2 == 0) { + const productCountInCategory = searchResults.facet_counts.facet_fields.category_name_str[index + 1]; + if (productCountInCategory > 0) return category; + } + }); + setCategories(filterCategory); + + const filterBrand = searchResults.facet_counts.facet_fields.brand_str.filter((brand, index) => { + if (index % 2 == 0) { + const productCountInBrand = searchResults.facet_counts.facet_fields.brand_str[index + 1]; + if (productCountInBrand > 0) return brand; + } + }); + setBrands(filterBrand); + }, [searchResults]); return ( <> <Header title={`Jual ${q} - Indoteknik`} /> + <Filter + selectedBrand={selectedBrand} + onChangeBrand={(e) => setSelectedBrand(e.target.value)} + selectedCategory={selectedCategory} + onChangeCategory={(e) => setSelectedCategory(e.target.value)} + brands={brands} + categories={categories} + isActiveFilter={activeFilter} + closeFilter={() => setActiveFilter(false)} + onSubmit={filterSubmit} + /> <Layout> <div className="p-4"> - <div className="flex justify-between items-center gap-x-2 mb-1"> - <h1>Produk</h1> - <button className="btn-light py-1 flex items-center gap-x-2"> - <FilterIcon className="w-4 h-4" /> <span>Filter</span> - </button> - </div> + <button className="btn-light py-2 flex items-center gap-x-2 mb-2" onClick={() => setActiveFilter(true)}> + <FilterIcon className="w-4 h-4" /> <span>Filter</span> + </button> + <h1>Produk</h1> <div className="text-sm mb-4"> {productFound > 0 ? ( <> diff --git a/src/styles/globals.css b/src/styles/globals.css index 8e81f7e8..5033a220 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -91,6 +91,8 @@ html, body { w-full leading-none focus:outline-none + focus:ring-1 + focus:ring-yellow-900 ; } @@ -339,4 +341,12 @@ html, body { bg-gray-100 text-sm ; +} + +.idt-transition { + @apply + transition-all + ease-linear + duration-300 + ; }
\ No newline at end of file |
