summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorRafi Zadanly <rafizadanly@gmail.com>2022-12-01 16:26:21 +0700
committerRafi Zadanly <rafizadanly@gmail.com>2022-12-01 16:26:21 +0700
commit0a0c497204acbac562700d80f38e74aa9ffcd94e (patch)
tree3c2387091b0733d33754fbc07d843f2deef2fa9e /src
parent9e1321f7e35a58ba8ce136401a217d835aef15f0 (diff)
dynamic filter, dynamic pagination, detail brand, to title case
Diffstat (limited to 'src')
-rw-r--r--src/components/Filter.js98
-rw-r--r--src/components/ManufactureCard.js15
-rw-r--r--src/components/Pagination.js4
-rw-r--r--src/components/ProductCard.js2
-rw-r--r--src/helpers/slug.js13
-rw-r--r--src/helpers/toTitleCase.js8
-rw-r--r--src/pages/index.js18
-rw-r--r--src/pages/shop/brands.js12
-rw-r--r--src/pages/shop/brands/[slug].js122
-rw-r--r--src/pages/shop/product/[slug].js10
-rw-r--r--src/pages/shop/search.js3
11 files changed, 234 insertions, 71 deletions
diff --git a/src/components/Filter.js b/src/components/Filter.js
index 385c585d..d2a5bf1b 100644
--- a/src/components/Filter.js
+++ b/src/components/Filter.js
@@ -11,7 +11,8 @@ const Filter = ({
defaultCategory,
defaultBrand,
defaultOrderBy,
- searchResults
+ searchResults,
+ disableFilter = []
}) => {
const router = useRouter();
@@ -24,13 +25,22 @@ const Filter = ({
const [brands, setBrands] = useState([]);
const filterRoute = () => {
- let filterRoute = defaultRoute;
- if (selectedBrand) filterRoute += `&brand=${selectedBrand}`;
- if (selectedCategory) filterRoute += `&category=${selectedCategory}`;
- if (priceFrom > 0) filterRoute += `&price_from=${priceFrom}`;
- if (priceTo > 0) filterRoute += `&price_to=${priceTo}`;
- if (orderBy) filterRoute += `&order_by=${orderBy}`;
- return filterRoute;
+ let filterRoute = [];
+ let filterRoutePrefix = '?';
+ if (selectedBrand) filterRoute.push(`brand=${selectedBrand}`);
+ if (selectedCategory) filterRoute.push(`category=${selectedCategory}`);
+ if (priceFrom > 0) filterRoute.push(`price_from=${priceFrom}`);
+ if (priceTo > 0) filterRoute.push(`price_to=${priceTo}`);
+ if (orderBy) filterRoute.push(`order_by=${orderBy}`);
+
+ if (defaultRoute.includes('?')) filterRoutePrefix = '&';
+ if (filterRoute.length > 0) {
+ filterRoute = filterRoutePrefix + filterRoute.join('&');
+ } else {
+ filterRoute = '';
+ }
+
+ return defaultRoute + filterRoute;
}
useEffect(() => {
@@ -82,40 +92,48 @@ const Filter = ({
</button>
</div>
<form className="flex flex-col gap-y-4" onSubmit={submit}>
- <div>
- <label>Urutkan</label>
- <div className="flex flex-wrap gap-2 mt-2">
- <button type="button" className={"p-2 rounded border border-gray-300 text-gray-800" + (orderBy == 'price-asc' ? ' border-yellow-900 text-yellow-900' : '')} onClick={() => changeOrderBy('price-asc')}>Harga Terendah</button>
- <button type="button" className={"p-2 rounded border border-gray-300 text-gray-800" + (orderBy == 'price-desc' ? ' border-yellow-900 text-yellow-900' : '')} onClick={() => changeOrderBy('price-desc')}>Harga Tertinggi</button>
- <button type="button" className={"p-2 rounded border border-gray-300 text-gray-800" + (orderBy == 'popular' ? ' border-yellow-900 text-yellow-900' : '')} onClick={() => changeOrderBy('popular')}>Populer</button>
+ {!disableFilter.includes('orderBy') ? (
+ <div>
+ <label>Urutkan</label>
+ <div className="flex flex-wrap gap-2 mt-2">
+ <button type="button" className={"p-2 rounded border border-gray-300 text-gray-800" + (orderBy == 'price-asc' ? ' border-yellow-900 text-yellow-900' : '')} onClick={() => changeOrderBy('price-asc')}>Harga Terendah</button>
+ <button type="button" className={"p-2 rounded border border-gray-300 text-gray-800" + (orderBy == 'price-desc' ? ' border-yellow-900 text-yellow-900' : '')} onClick={() => changeOrderBy('price-desc')}>Harga Tertinggi</button>
+ <button type="button" className={"p-2 rounded border border-gray-300 text-gray-800" + (orderBy == 'popular' ? ' border-yellow-900 text-yellow-900' : '')} onClick={() => changeOrderBy('popular')}>Populer</button>
+ </div>
+ </div>
+ ) : ''}
+ {!disableFilter.includes('category') ? (
+ <div>
+ <label>Kategori</label>
+ <select className="form-input mt-2" value={selectedCategory} onChange={(e) => setSelectedCategory(e.target.value)}>
+ <option value="">Pilih kategori...</option>
+ {categories?.map((category, index) => (
+ <option key={index} value={category}>{category}</option>
+ ))}
+ </select>
</div>
- </div>
- <div>
- <label>Kategori</label>
- <select className="form-input mt-2" value={selectedCategory} onChange={(e) => setSelectedCategory(e.target.value)}>
- <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={(e) => setSelectedBrand(e.target.value)}>
- <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" value={priceFrom} onChange={(e) => setPriceFrom(e.target.value)}/>
- <span>&mdash;</span>
- <input className="form-input" value={priceTo} onChange={(e) => setPriceTo(e.target.value)}/>
+ ) : ''}
+ {!disableFilter.includes('brand') ? (
+ <div>
+ <label>Brand</label>
+ <select className="form-input mt-2" value={selectedBrand} onChange={(e) => setSelectedBrand(e.target.value)}>
+ <option value="">Pilih brand...</option>
+ {brands?.map((brand, index) => (
+ <option key={index} value={brand}>{brand}</option>
+ ))}
+ </select>
</div>
- </div>
+ ) : ''}
+ {!disableFilter.includes('price') ? (
+ <div>
+ <label>Harga</label>
+ <div className="flex gap-x-4 mt-2 items-center">
+ <input className="form-input" value={priceFrom} onChange={(e) => setPriceFrom(e.target.value)}/>
+ <span>&mdash;</span>
+ <input className="form-input" value={priceTo} onChange={(e) => setPriceTo(e.target.value)}/>
+ </div>
+ </div>
+ ) : ''}
<button type="submit" className="btn-yellow font-semibold mt-2 w-full">
Terapkan Filter
</button>
diff --git a/src/components/ManufactureCard.js b/src/components/ManufactureCard.js
new file mode 100644
index 00000000..6b96a852
--- /dev/null
+++ b/src/components/ManufactureCard.js
@@ -0,0 +1,15 @@
+import { LazyLoadImage } from "react-lazy-load-image-component";
+import "react-lazy-load-image-component/src/effects/blur.css";
+import { createSlug } from "../helpers/slug";
+import Link from "./Link";
+
+export default function ManufactureCard({ data }) {
+ const manufacture = data;
+ return (
+ <Link href={`/shop/brands/${createSlug(manufacture.name, manufacture.id)}`} className="flex justify-center items-center border border-gray-300 p-1 rounded h-16 text-gray-800 text-sm text-center bg-white">
+ {manufacture.logo ? (
+ <LazyLoadImage effect="blur" src={manufacture.logo} alt={manufacture.name || ''} className="w-full max-h-full object-contain object-center" />
+ ) : manufacture.name}
+ </Link>
+ );
+} \ No newline at end of file
diff --git a/src/components/Pagination.js b/src/components/Pagination.js
index 09566a46..1cb1bca0 100644
--- a/src/components/Pagination.js
+++ b/src/components/Pagination.js
@@ -5,13 +5,15 @@ export default function Pagination({ pageCount, currentPage, url }) {
let lastPage = false;
let dotsPrevPage = false;
let dotsNextPage = false;
+ let urlParameterPrefix = url.includes('?') ? '&' : '?';
+
return (
<div className="pagination">
{Array.from(Array(pageCount)).map((v, i) => {
let page = i + 1;
let rangePrevPage = currentPage - 2;
let rangeNextPage = currentPage + 2;
- let PageComponent = <Link key={i} href={`${url}&page=${page}`} className={"pagination-item" + (page == currentPage ? " pagination-item--active " : "")}>{page}</Link>;
+ let PageComponent = <Link key={i} href={`${url + urlParameterPrefix}page=${page}`} className={"pagination-item" + (page == currentPage ? " pagination-item--active " : "")}>{page}</Link>;
let DotsComponent = <div key={i} className="pagination-dots">...</div>;
if (pageCount == 7) {
diff --git a/src/components/ProductCard.js b/src/components/ProductCard.js
index 2299d931..0fe47196 100644
--- a/src/components/ProductCard.js
+++ b/src/components/ProductCard.js
@@ -19,7 +19,7 @@ export default function ProductCard({ data }) {
<div className="product-card__description">
<div>
{typeof product.manufacture.name !== "undefined" ? (
- <a href={'/shop/brands/' + createSlug(product.manufacture.name, product.manufacture.id)} className="product-card__brand">{product.manufacture.name}</a>
+ <Link href={'/shop/brands/' + createSlug(product.manufacture.name, product.manufacture.id)} className="product-card__brand">{product.manufacture.name}</Link>
) : (
<span className="product-card__brand">-</span>
)}
diff --git a/src/helpers/slug.js b/src/helpers/slug.js
index ed1a5b6a..b1e67cdf 100644
--- a/src/helpers/slug.js
+++ b/src/helpers/slug.js
@@ -1,13 +1,22 @@
+import toTitleCase from './toTitleCase';
+
const createSlug = (name, id) => {
return name?.trim().replace(new RegExp(/[^A-Za-z0-9]/, 'g'), '-').toLowerCase() + '-' + id;
}
-const getId = (slug) => {
+const getIdFromSlug = (slug) => {
let id = slug.split('-');
return id[id.length-1];
}
+const getNameFromSlug = (slug) => {
+ let name = slug.split('-');
+ name.pop();
+ return toTitleCase(name.join(' '));
+}
+
export {
createSlug,
- getId
+ getIdFromSlug,
+ getNameFromSlug
}; \ No newline at end of file
diff --git a/src/helpers/toTitleCase.js b/src/helpers/toTitleCase.js
new file mode 100644
index 00000000..5cfd70d0
--- /dev/null
+++ b/src/helpers/toTitleCase.js
@@ -0,0 +1,8 @@
+export default function toTitleCase(str) {
+ return str.replace(
+ /\w\S*/g,
+ function(txt) {
+ return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
+ }
+ );
+} \ No newline at end of file
diff --git a/src/pages/index.js b/src/pages/index.js
index 95d9161f..ee746519 100644
--- a/src/pages/index.js
+++ b/src/pages/index.js
@@ -10,6 +10,8 @@ import "swiper/css/pagination";
import "swiper/css/autoplay";
import ProductSlider from "../components/product/ProductSlider";
import Layout from "../components/Layout";
+import axios from "axios";
+import ManufactureCard from "../components/ManufactureCard";
export default function Home() {
const [heroBanners, setHeroBanners] = useState(null);
@@ -37,8 +39,8 @@ export default function Home() {
getReadyStockProducts();
const getPopularProducts = async () => {
- const dataPopularProducts = await apiOdoo('GET', `/api/v1/product?manufactures=10&limit=30`);
- setPopularProducts(dataPopularProducts);
+ const dataPopularProducts = await axios(`${process.env.SELF_HOST}/api/shop/search?q=*&page=1&order_by=popular`);
+ setPopularProducts(dataPopularProducts.data.response);
}
getPopularProducts();
}, []);
@@ -62,22 +64,20 @@ export default function Home() {
{
manufactures?.manufactures?.map((manufacture, index) => (
<SwiperSlide key={index}>
- <div className="border border-gray-300 p-1 rounded h-full bg-white">
- <LazyLoadImage effect="blur" src={manufacture.logo} alt={manufacture.name} className="w-full h-full object-contain object-center" />
- </div>
+ <ManufactureCard data={manufacture} key={index} />
</SwiperSlide>
))
}
</Swiper>
</div>
<div className="mt-6 px-4 mb-6">
- <h2 className="text-gray-900 font-bold mb-3">Produk Ready Stock</h2>
- <ProductSlider products={readyStockProducts} />
- </div>
- <div className="mt-6 px-4 mb-6">
<h2 className="text-gray-900 font-bold mb-3">Produk Populer</h2>
<ProductSlider products={popularProducts} />
</div>
+ <div className="mt-6 px-4 mb-6">
+ <h2 className="text-gray-900 font-bold mb-3">Produk Ready Stock</h2>
+ <ProductSlider products={readyStockProducts} />
+ </div>
</Layout>
</>
)
diff --git a/src/pages/shop/brands.js b/src/pages/shop/brands.js
index 5d93bf4b..6c1f1816 100644
--- a/src/pages/shop/brands.js
+++ b/src/pages/shop/brands.js
@@ -1,13 +1,10 @@
-import { LazyLoadImage } from "react-lazy-load-image-component";
import Header from "../../components/Header";
import apiOdoo from "../../helpers/apiOdoo";
-import "react-lazy-load-image-component/src/effects/blur.css";
-import Link from "../../components/Link";
-import { createSlug } from "../../helpers/slug";
import InfiniteScroll from "react-infinite-scroll-component";
import { useEffect, useState } from "react";
import Spinner from "../../components/Spinner";
import Layout from "../../components/Layout";
+import ManufactureCard from "../../components/ManufactureCard";
export async function getServerSideProps() {
let initialManufactures = await apiOdoo('GET', '/api/v1/manufacture?limit=31');
@@ -24,7 +21,6 @@ export default function Brands({ initialManufactures }) {
const getMoreManufactures = async () => {
const name = manufactureStartwith != '' ? `${manufactureStartwith}%` : '';
- console.log(manufactures, manufactures.length);
const result = await apiOdoo('GET', `/api/v1/manufacture?limit=30&offset=${manufactures.length}&name=${name}`);
setHasMoreManufacture(manufactures.length + 30 < result.manufacture_total)
setManufactures((manufactures) => [...manufactures, ...result.manufactures]);
@@ -69,11 +65,7 @@ export default function Brands({ initialManufactures }) {
>
{manufactures?.map((manufacture, index) => (
manufacture.name ? (
- <Link href={`/shop/brands/${createSlug(manufacture.name, manufacture.id)}`} className="flex justify-center items-center border border-gray-300 p-1 rounded h-16 text-gray-800 text-sm text-center bg-white" key={index}>
- {manufacture.logo ? (
- <LazyLoadImage effect="blur" src={manufacture.logo} alt={manufacture.name || ''} className="w-full max-h-full object-contain object-center" />
- ) : manufacture.name}
- </Link>
+ <ManufactureCard data={manufacture} key={index} />
) : ''
))}
</InfiniteScroll>
diff --git a/src/pages/shop/brands/[slug].js b/src/pages/shop/brands/[slug].js
new file mode 100644
index 00000000..b532e7a7
--- /dev/null
+++ b/src/pages/shop/brands/[slug].js
@@ -0,0 +1,122 @@
+import axios from "axios";
+import { useState } from "react";
+import Filter from "../../../components/Filter";
+import Header from "../../../components/Header";
+import Layout from "../../../components/Layout";
+import Pagination from "../../../components/Pagination";
+import ProductCard from "../../../components/ProductCard";
+import { getNameFromSlug } from "../../../helpers/slug";
+import FilterIcon from "../../../icons/filter.svg";
+
+export async function getServerSideProps(context) {
+ const {
+ slug,
+ page = 1,
+ category = '',
+ price_from = '0',
+ price_to = '0',
+ order_by = '',
+ } = context.query;
+
+ let urlParameter = [
+ 'q=*',
+ `page=${page}`,
+ `brand=${getNameFromSlug(slug)}`,
+ `category=${category}`,
+ `price_from=${price_from}`,
+ `price_to=${price_to}`,
+ `order_by=${order_by}`
+ ].join('&');
+ let searchResults = await axios(`${process.env.SELF_HOST}/api/shop/search?${urlParameter}`);
+ searchResults = searchResults.data;
+
+ return {
+ props: {
+ searchResults,
+ page,
+ slug,
+ category,
+ price_from,
+ price_to,
+ order_by
+ }
+ };
+}
+
+export default function BrandDetail({
+ searchResults,
+ page,
+ slug,
+ category,
+ price_from,
+ price_to,
+ order_by
+}) {
+ 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 route = () => {
+ let route = `/shop/brands/${slug}`;
+ if (category) route += `&category=${category}`;
+ if (price_from > 0) route += `&price_from=${price_from}`;
+ if (price_to > 0) route += `&price_to=${price_to}`;
+ if (order_by) route += `&order_by=${order_by}`;
+ return route;
+ }
+
+ return (
+ <>
+ <Header title={`Distributor ${getNameFromSlug(slug)} Indonesia Harga Official - Indoteknik`} />
+ <Filter
+ defaultRoute={`/shop/brands/${slug}`}
+ isActive={activeFilter}
+ closeFilter={() => setActiveFilter(false)}
+ defaultPriceFrom={price_from}
+ defaultPriceTo={price_to}
+ defaultBrand=''
+ defaultCategory={category}
+ defaultOrderBy={order_by}
+ searchResults={searchResults}
+ disableFilter={['brand']}
+ />
+ <Layout>
+ <div className="p-4">
+ <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 ? (
+ <>
+ Menampilkan&nbsp;
+ {pageCount > 1 ? (
+ <>
+ {productStart + 1}-{
+ (productStart + productRows) > productFound ? productFound : productStart + productRows
+ }
+ &nbsp;dari&nbsp;
+ </>
+ ) : ''}
+ {searchResults.response.numFound}
+ &nbsp;produk untuk brand <span className="font-semibold">{getNameFromSlug(slug)}</span>
+ </>
+ ) : 'Mungkin yang anda cari'}
+ </div>
+ <div className="grid grid-cols-2 gap-3">
+ {searchResults.response.products.map((product) => (
+ <ProductCard key={product.id} data={product} />
+ ))}
+ </div>
+
+ <div className="mt-4">
+ <Pagination pageCount={pageCount} currentPage={parseInt(page)} url={route()} />
+ </div>
+ </div>
+ </Layout>
+ </>
+ )
+} \ No newline at end of file
diff --git a/src/pages/shop/product/[slug].js b/src/pages/shop/product/[slug].js
index 598330f4..dd554660 100644
--- a/src/pages/shop/product/[slug].js
+++ b/src/pages/shop/product/[slug].js
@@ -3,23 +3,23 @@ import { useRouter } from "next/router";
import { useEffect, useState } from "react";
import Header from "../../../components/Header";
import apiOdoo from "../../../helpers/apiOdoo";
-import { createSlug, getId } from "../../../helpers/slug";
+import { createSlug, getIdFromSlug } from "../../../helpers/slug";
import currencyFormat from "../../../helpers/currencyFormat";
import { LazyLoadImage } from "react-lazy-load-image-component";
import "react-lazy-load-image-component/src/effects/blur.css";
import ProductSlider from "../../../components/product/ProductSlider";
import Layout from "../../../components/Layout";
-export async function getServerSideProps(context) {
+export async function getServerSideProps( context ) {
const { slug } = context.query;
- let product = await apiOdoo('GET', '/api/v1/product/' + getId(slug));
+ let product = await apiOdoo('GET', '/api/v1/product/' + getIdFromSlug(slug));
if (product.length == 1) {
product = product[0];
}
return {props: {product}};
}
-export default function ProductDetail({product}) {
+export default function ProductDetail({ product }) {
const router = useRouter();
const { slug } = router.query;
const [selectedVariant, setSelectedVariant] = useState("");
@@ -43,7 +43,7 @@ export default function ProductDetail({product}) {
useEffect(() => {
setSimilarProducts(null);
const getSimilarProducts = async () => {
- const dataSimilarProducts = await apiOdoo('GET', `/api/v1/product/${getId(slug)}/similar?limit=20`);
+ const dataSimilarProducts = await apiOdoo('GET', `/api/v1/product/${getIdFromSlug(slug)}/similar?limit=20`);
setSimilarProducts(dataSimilarProducts);
}
if (slug) getSimilarProducts();
diff --git a/src/pages/shop/search.js b/src/pages/shop/search.js
index 6b24f2a9..2ae3cca4 100644
--- a/src/pages/shop/search.js
+++ b/src/pages/shop/search.js
@@ -6,7 +6,6 @@ import ProductCard from "../../components/ProductCard";
import FilterIcon from "../../icons/filter.svg";
import { useState } from "react";
import Filter from "../../components/Filter";
-import { useRouter } from "next/router";
export async function getServerSideProps(context) {
const {
@@ -42,8 +41,6 @@ export default function ShopSearch({
price_to,
order_by
}) {
- 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;