summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package.json2
-rw-r--r--src/lib/brand/components/Brand.jsx128
-rw-r--r--src/lib/product/api/productSearchApi.js2
-rw-r--r--src/lib/product/components/ProductFilterDesktop.jsx147
-rw-r--r--src/lib/product/components/ProductSearch.jsx258
-rw-r--r--tailwind.config.js12
6 files changed, 447 insertions, 102 deletions
diff --git a/package.json b/package.json
index 3f9ef1a7..0d51e896 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,8 @@
"axios": "^1.1.3",
"camelcase-object-deep": "^1.1.7",
"cookies-next": "^2.1.1",
+ "flowbite": "^1.6.4",
+ "flowbite-react": "^0.4.2",
"framer-motion": "^7.6.7",
"lodash-contrib": "^4.1200.1",
"midtrans-client": "^1.3.1",
diff --git a/src/lib/brand/components/Brand.jsx b/src/lib/brand/components/Brand.jsx
index c338c4c4..db4e81da 100644
--- a/src/lib/brand/components/Brand.jsx
+++ b/src/lib/brand/components/Brand.jsx
@@ -8,6 +8,8 @@ import 'swiper/css/pagination'
import 'swiper/css/autoplay'
import Divider from '@/core/components/elements/Divider/Divider'
import ImageSkeleton from '@/core/components/elements/Skeleton/ImageSkeleton'
+import MobileView from '@/core/components/views/MobileView'
+import DesktopView from '@/core/components/views/DesktopView'
const swiperBanner = {
pagination: { dynamicBullets: true },
@@ -23,46 +25,94 @@ const Brand = ({ id }) => {
return (
<>
- <div className='min-h-[150px]'>
- {brand.isLoading && <ImageSkeleton />}
- {brand.data && (
- <>
- <Swiper
- slidesPerView={1}
- pagination={swiperBanner.pagination}
- modules={swiperBanner.modules}
- autoplay={swiperBanner.autoplay}
- className='border-b border-gray_r-6'
- >
- {brand.data?.banners?.map((banner, index) => (
- <SwiperSlide key={index}>
- <Image
- src={banner}
- alt={`Brand ${brand.data?.name} - Indoteknik`}
- className='w-full h-auto'
- />
- </SwiperSlide>
- ))}
- </Swiper>
- <div className='p-4'>
- <div className='text-caption-1 text-gray_r-11 mb-2'>Produk dari brand:</div>
- {brand?.data?.logo && (
- <Image
- src={brand?.data?.logo}
- alt={brand?.data?.name}
- className='w-32 p-2 border borde-gray_r-6 rounded'
- />
- )}
- {!brand?.data?.logo && (
- <div className='bg-red_r-10 text-white text-center text-caption-1 py-2 px-4 rounded w-fit'>
- {brand?.data?.name}
+ <MobileView>
+ <>
+ <div className='min-h-[150px]'>
+ {brand.isLoading && <ImageSkeleton />}
+ {brand.data && (
+ <>
+ <Swiper
+ slidesPerView={1}
+ pagination={swiperBanner.pagination}
+ modules={swiperBanner.modules}
+ autoplay={swiperBanner.autoplay}
+ className='border-b border-gray_r-6'
+ >
+ {brand.data?.banners?.map((banner, index) => (
+ <SwiperSlide key={index}>
+ <Image
+ src={banner}
+ alt={`Brand ${brand.data?.name} - Indoteknik`}
+ className='w-full h-auto'
+ />
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ <div className='p-4'>
+ <div className='text-caption-1 text-gray_r-11 mb-2'>Produk dari brand:</div>
+ {brand?.data?.logo && (
+ <Image
+ src={brand?.data?.logo}
+ alt={brand?.data?.name}
+ className='w-32 p-2 border borde-gray_r-6 rounded'
+ />
+ )}
+ {!brand?.data?.logo && (
+ <div className='bg-red_r-10 text-white text-center text-caption-1 py-2 px-4 rounded w-fit'>
+ {brand?.data?.name}
+ </div>
+ )}
</div>
- )}
- </div>
- </>
- )}
- </div>
- <Divider />
+ </>
+ )}
+ </div>
+ <Divider />
+ </>
+ </MobileView>
+ <DesktopView>
+ <div className='container mx-auto mt-10 mb-3'>
+ <div className='min-h-[150px]'>
+ {brand.isLoading && <ImageSkeleton />}
+ {brand.data && (
+ <>
+ <Swiper
+ slidesPerView={1}
+ pagination={swiperBanner.pagination}
+ modules={swiperBanner.modules}
+ autoplay={swiperBanner.autoplay}
+ className='border-b border-gray_r-6'
+ >
+ {brand.data?.banners?.map((banner, index) => (
+ <SwiperSlide key={index}>
+ <Image
+ src={banner}
+ alt={`Brand ${brand.data?.name} - Indoteknik`}
+ className='w-full h-auto'
+ />
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ <div className='p-4'>
+ <div className='text-caption-1 text-gray_r-11 mb-2'>Produk dari brand:</div>
+ {brand?.data?.logo && (
+ <Image
+ src={brand?.data?.logo}
+ alt={brand?.data?.name}
+ className='w-32 p-2 border borde-gray_r-6 rounded'
+ />
+ )}
+ {!brand?.data?.logo && (
+ <div className='bg-red_r-10 text-white text-center text-caption-1 py-2 px-4 rounded w-fit'>
+ {brand?.data?.name}
+ </div>
+ )}
+ </div>
+ </>
+ )}
+ </div>
+ <Divider />
+ </div>
+ </DesktopView>
</>
)
}
diff --git a/src/lib/product/api/productSearchApi.js b/src/lib/product/api/productSearchApi.js
index e7ad49a6..f626e8cc 100644
--- a/src/lib/product/api/productSearchApi.js
+++ b/src/lib/product/api/productSearchApi.js
@@ -2,7 +2,7 @@ import _ from 'lodash-contrib'
import axios from 'axios'
const productSearchApi = async ({ query }) => {
- const dataProductSearch = await axios(`${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/search?${query}`)
+ const dataProductSearch = await axios(`${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/search?${query}&operation=OR`)
return dataProductSearch.data
}
diff --git a/src/lib/product/components/ProductFilterDesktop.jsx b/src/lib/product/components/ProductFilterDesktop.jsx
new file mode 100644
index 00000000..276a7cc9
--- /dev/null
+++ b/src/lib/product/components/ProductFilterDesktop.jsx
@@ -0,0 +1,147 @@
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
+import { useRouter } from 'next/router'
+import { useState } from 'react'
+import _ from 'lodash'
+import { toQuery } from 'lodash-contrib'
+import { Accordion, Badge, Checkbox, Label, TextInput } from 'flowbite-react'
+
+const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = null }) => {
+ const router = useRouter()
+ const { query } = router
+ const [order, setOrder] = useState(query?.orderBy)
+ const [brandValues, setBrand] = useState(query?.brand?.split(',') || [])
+ const [categoryValues, setCategory] = useState(query?.category?.split(',') || [])
+ const [priceFrom, setPriceFrom] = useState(query?.priceFrom)
+ const [priceTo, setPriceTo] = useState(query?.priceTo)
+
+ const handleCategorysChange = (event) => {
+ const value = event.target.value
+ const isChecked = event.target.checked
+ if (isChecked) {
+ setCategory([...categoryValues, value])
+ } else {
+ setCategory(categoryValues.filter((val) => val !== value))
+ }
+ }
+ const handleBrandsChange = (event) => {
+ const value = event.target.value
+ const isChecked = event.target.checked
+ if (isChecked) {
+ setBrand([...brandValues, value])
+ } else {
+ setBrand(brandValues.filter((val) => val !== value))
+ }
+ }
+
+ console.log('branddddd', defaultBrand)
+
+ const handleSubmit = () => {
+ let params = {
+ q: router.query.q,
+ orderBy: order,
+ brand: brandValues.join(','),
+ category: categoryValues.join(','),
+ priceFrom,
+ priceTo
+ }
+ params = _.pickBy(params, _.identity)
+ params = toQuery(params)
+ router.push(`${prefixUrl}?${params}`)
+ }
+
+ return (
+ <>
+ <Accordion
+ flush={true}
+ alwaysOpen={true}
+ >
+ <Accordion.Panel>
+ <Accordion.Title>Kategori</Accordion.Title>
+ <Accordion.Content className='overflow-auto max-h-[150px]'>
+ <div
+ className='flex flex-col gap-4 scroll-snap'
+ id='checkbox'
+ >
+ {categories.map((category, index) => (
+ <div
+ className='flex items-center gap-2'
+ key={index}
+ >
+ <Checkbox
+ checked={categoryValues.includes(category)}
+ onChange={handleCategorysChange}
+ value={category}
+ />
+ <Label htmlFor='accept'> {category} </Label>
+ {/* <div className='badge-solid-red'>250</div> */}
+ </div>
+ ))}
+ </div>
+ </Accordion.Content>
+ </Accordion.Panel>
+ <Accordion.Panel>
+ {!defaultBrand && (
+ <>
+ <Accordion.Title >Brand</Accordion.Title>
+ <Accordion.Content className='overflow-auto max-h-[150px]'>
+ <div
+ className='flex flex-col gap-4 scroll-snap'
+ id='checkbox'
+ >
+ {brands.map((brand, index) => (
+ <div
+ className='flex items-center gap-2'
+ key={index}
+ >
+ <Checkbox
+ checked={brandValues.includes(brand)}
+ onChange={handleBrandsChange}
+ value={brand}
+ />
+ <Label htmlFor='accept'> {brand} </Label>
+ {/* <div className='badge-solid-red'>250</div> */}
+ </div>
+ ))}
+ </div>
+ </Accordion.Content>
+ </>
+ )}
+ </Accordion.Panel>
+ <Accordion.Panel>
+ <Accordion.Title> Harga </Accordion.Title>
+ <Accordion.Content>
+ <div className='mb-3'>
+ <TextInput
+ placeholder='Harga Minimum'
+ addon='Rp'
+ type='number'
+ value={priceFrom}
+ onChange={(e) => setPriceFrom(e.target.value)}
+ />
+ </div>
+ <div className='mb-3'>
+ <TextInput
+ placeholder='Harga Maximum'
+ addon='Rp'
+ type='number'
+ value={priceTo}
+ onChange={(e) => setPriceTo(e.target.value)}
+ />
+ </div>
+ </Accordion.Content>
+ </Accordion.Panel>
+ </Accordion>
+ <div className='p-5'>
+ <button
+ type='button'
+ className='btn-solid-red w-full mt-6'
+ onClick={handleSubmit}
+ >
+ Terapkan
+ </button>
+ </div>
+ </>
+ )
+}
+
+export default ProductFilterDesktop
diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx
index 52bd5119..3078eac5 100644
--- a/src/lib/product/components/ProductSearch.jsx
+++ b/src/lib/product/components/ProductSearch.jsx
@@ -7,8 +7,15 @@ import _ from 'lodash'
import ProductSearchSkeleton from './Skeleton/ProductSearchSkeleton'
import ProductFilter from './ProductFilter'
import useActive from '@/core/hooks/useActive'
+import MobileView from '@/core/components/views/MobileView'
+import DesktopView from '@/core/components/views/DesktopView'
+import NextImage from 'next/image'
+import { ChevronDownIcon } from '@heroicons/react/24/outline'
+import ProductFilterDesktop from './ProductFilterDesktop'
+import { useRouter } from 'next/router'
const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => {
+ const router = useRouter()
const { page = 1 } = query
if (defaultBrand) query.brand = defaultBrand.toLowerCase()
const { productSearch } = useProductSearch({ query })
@@ -35,6 +42,29 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => {
}
)
+ const [open, setOpen] = useState(1)
+ const [order, setOrder] = useState(query?.orderBy)
+
+ const handleOpen = (value) => {
+ setOpen(open === value ? 0 : value)
+ }
+ const orderOptions = [
+ { value: 'price-asc', label: 'Harga Terendah' },
+ { value: 'price-desc', label: 'Harga Tertinggi' },
+ { value: 'popular', label: 'Populer' },
+ { value: 'stock', label: 'Ready Stock' }
+ ]
+
+ const handleOrderBy = (e) => {
+ let params = {
+ ...router.query,
+ orderBy: e.target.value
+ }
+ params = _.pickBy(params, _.identity)
+ params = toQuery(params)
+ router.push(`${prefixUrl}?${params}`)
+ }
+
useEffect(() => {
if (!products) {
setProducts(productSearch.data?.response?.products)
@@ -46,70 +76,180 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => {
}
return (
- <div className='p-4'>
- <h1 className='mb-2 font-semibold text-h-sm'>Produk</h1>
-
- <div className='mb-2 leading-6 text-gray_r-11'>
- {productFound > 0 ? (
- <>
- Menampilkan&nbsp;
- {pageCount > 1 ? (
+ <>
+ <MobileView>
+ <div className='p-4'>
+ <h1 className='mb-2 font-semibold text-h-sm'>Produk</h1>
+
+ <div className='mb-2 leading-6 text-gray_r-11'>
+ {productFound > 0 ? (
<>
- {productStart + 1}-
- {productStart + productRows > productFound
- ? productFound
- : productStart + productRows}
- &nbsp;dari&nbsp;
+ Menampilkan&nbsp;
+ {pageCount > 1 ? (
+ <>
+ {productStart + 1}-
+ {productStart + productRows > productFound
+ ? productFound
+ : productStart + productRows}
+ &nbsp;dari&nbsp;
+ </>
+ ) : (
+ ''
+ )}
+ {productFound}
+ &nbsp;produk{' '}
+ {query.q && (
+ <>
+ untuk pencarian <span className='font-semibold'>{query.q}</span>
+ </>
+ )}
</>
) : (
- ''
+ 'Mungkin yang anda cari'
)}
- {productFound}
- &nbsp;produk{' '}
- {query.q && (
- <>
- untuk pencarian <span className='font-semibold'>{query.q}</span>
- </>
- )}
- </>
- ) : (
- 'Mungkin yang anda cari'
- )}
- </div>
-
- <button
- className='btn-light mb-6 py-2 px-5'
- onClick={popup.activate}
- >
- Filter
- </button>
-
- <div className='grid grid-cols-2 gap-3'>
- {products &&
- products.map((product) => (
- <ProductCard
- product={product}
- key={product.id}
+ </div>
+
+ <button
+ className='btn-light mb-6 py-2 px-5'
+ onClick={popup.activate}
+ >
+ Filter
+ </button>
+
+ <div className='grid grid-cols-2 gap-3'>
+ {products &&
+ products.map((product) => (
+ <ProductCard
+ product={product}
+ key={product.id}
+ />
+ ))}
+ </div>
+
+ <Pagination
+ pageCount={pageCount}
+ currentPage={parseInt(page)}
+ url={`${prefixUrl}?${toQuery(_.omit(query, ['page']))}`}
+ className='mt-6 mb-2'
+ />
+
+ <ProductFilter
+ active={popup.active}
+ close={popup.deactivate}
+ brands={brands || []}
+ categories={categories || []}
+ prefixUrl={prefixUrl}
+ defaultBrand={defaultBrand}
+ />
+ </div>
+ </MobileView>
+ <DesktopView>
+ <div className='container mx-auto mt-10 flex mb-3'>
+ <div className='w-3/12'>
+ <ProductFilterDesktop
+ brands={brands || []}
+ categories={categories || []}
+ prefixUrl={prefixUrl}
+ defaultBrand={defaultBrand}
/>
- ))}
- </div>
-
- <Pagination
- pageCount={pageCount}
- currentPage={parseInt(page)}
- url={`${prefixUrl}?${toQuery(_.omit(query, ['page']))}`}
- className='mt-6 mb-2'
- />
-
- <ProductFilter
- active={popup.active}
- close={popup.deactivate}
- brands={brands || []}
- categories={categories || []}
- prefixUrl={prefixUrl}
- defaultBrand={defaultBrand}
- />
- </div>
+ </div>
+ <div className='w-9/12 p-3'>
+ <h1 className='text-2xl mb-2 font-semibold'>Hasil Pencarian</h1>
+ <div className='flex justify-between items-center mb-2'>
+ <div className='mb-2 leading-6 text-gray_r-11'>
+ {productFound > 0 ? (
+ <>
+ Menampilkan&nbsp;
+ {pageCount > 1 ? (
+ <>
+ {productStart + 1}-
+ {productStart + productRows > productFound
+ ? productFound
+ : productStart + productRows}
+ &nbsp;dari&nbsp;
+ </>
+ ) : (
+ ''
+ )}
+ {productFound}
+ &nbsp;produk{' '}
+ {query.q && (
+ <>
+ untuk pencarian <span className='font-semibold'>{query.q}</span>
+ </>
+ )}
+ </>
+ ) : (
+ 'Mungkin yang anda cari'
+ )}
+ </div>
+ <div className='justify-end flex '>
+ {/* <div>
+ <select
+ name='jumlah-baris'
+ className='form-input mt-2'
+ >
+ <option value=''>Jumlah Baris</option>
+ {orderOptions.map((option, index) => (
+ <option value={option.value}> {option.label} </option>
+ ))}
+ </select>
+ </div> */}
+ <div className='ml-3'>
+ <select
+ name='urutan'
+ className='form-input mt-2'
+ onChange={(e) => handleOrderBy(e)}
+ >
+ <option value=''>Urutkan</option>
+ {orderOptions.map((option, index) => (
+ <option value={option.value}> {option.label} </option>
+ ))}
+ </select>
+ </div>
+ </div>
+ </div>
+ <div className='grid grid-cols-5 gap-x-3 gap-y-6'>
+ {products &&
+ products.map((product) => (
+ <ProductCard
+ product={product}
+ key={product.id}
+ />
+ ))}
+ </div>
+ <div className='flex justify-between items-center mt-6 mb-2'>
+ <div className='pt-2 pb-6 flex items-center gap-x-3'>
+ <NextImage
+ src='/images/logo-question.png'
+ alt='Logo Question Indoteknik'
+ width={60}
+ height={60}
+ />
+ <div className='text-gray_r-12/90'>
+ <span>
+ Barang yang anda cari tidak ada?{' '}
+ <a
+ href='#'
+ className='text-red_r-9'
+ >
+ Hubungi Kami
+ </a>
+ </span>
+ </div>
+ </div>
+
+ <Pagination
+ pageCount={pageCount}
+ currentPage={parseInt(page)}
+ url={`${prefixUrl}?${toQuery(_.omit(query, ['page']))}`}
+ className='!justify-end'
+ />
+ </div>
+ </div>
+ </div>
+ </DesktopView>
+ </>
)
}
diff --git a/tailwind.config.js b/tailwind.config.js
index 95570311..f4335eb8 100644
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -1,6 +1,12 @@
/** @type {import('tailwindcss').Config} */
-module.exports = {
- content: ['./src/**/*.{js,ts,jsx,tsx}'],
+const withMT = require("@material-tailwind/react/utils/withMT");
+module.exports = withMT({
+ content: [
+ "./node_modules/flowbite-react/**/*.js",
+ './src/**/*.{js,ts,jsx,tsx}'],
+ plugins: [
+ require("flowbite/plugin")
+ ],
theme: {
extend: {
container: {
@@ -103,4 +109,4 @@ module.exports = {
}
},
plugins: [require('@tailwindcss/line-clamp'), require('@tailwindcss/typography')]
-}
+})