summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRafi Zadanly <rafizadanly@gmail.com>2022-11-22 17:09:05 +0700
committerRafi Zadanly <rafizadanly@gmail.com>2022-11-22 17:09:05 +0700
commit930ed6680100e9732157ed1861af3572e36219a0 (patch)
tree2915bd480d315a410028b99d6277e9e381b38e5f
parentfb4b7aea05526e154193d40a0cde6d674be263e7 (diff)
Filter search by brand, category, price
-rw-r--r--src/components/Filter.js57
-rw-r--r--src/icons/close.svg1
-rw-r--r--src/pages/api/shop/search.js11
-rw-r--r--src/pages/login.js4
-rw-r--r--src/pages/register.js6
-rw-r--r--src/pages/shop/search.js67
-rw-r--r--src/styles/globals.css10
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