diff options
Diffstat (limited to 'src/lib/product')
| -rw-r--r-- | src/lib/product/api/productApi.js | 4 | ||||
| -rw-r--r-- | src/lib/product/api/productSearchApi.js | 4 | ||||
| -rw-r--r-- | src/lib/product/components/Product/ProductDesktop.jsx | 209 | ||||
| -rw-r--r-- | src/lib/product/components/Product/ProductMobile.jsx | 25 | ||||
| -rw-r--r-- | src/lib/product/components/ProductCard.jsx | 10 | ||||
| -rw-r--r-- | src/lib/product/components/ProductSearch.jsx | 64 |
6 files changed, 221 insertions, 95 deletions
diff --git a/src/lib/product/api/productApi.js b/src/lib/product/api/productApi.js index 8156d1ec..33f1265c 100644 --- a/src/lib/product/api/productApi.js +++ b/src/lib/product/api/productApi.js @@ -1,8 +1,8 @@ import odooApi from '@/core/api/odooApi' -const productApi = async ({ id }) => { +const productApi = async ({ id, headers = {} }) => { if (!id) return - const dataProduct = await odooApi('GET', `/api/v2/product/${id}`) + const dataProduct = await odooApi('GET', `/api/v2/product/${id}`, {}, headers) return dataProduct } diff --git a/src/lib/product/api/productSearchApi.js b/src/lib/product/api/productSearchApi.js index 71fb72e6..1626b7b7 100644 --- a/src/lib/product/api/productSearchApi.js +++ b/src/lib/product/api/productSearchApi.js @@ -1,9 +1,9 @@ import _ from 'lodash-contrib' import axios from 'axios' -const productSearchApi = async ({ query }) => { +const productSearchApi = async ({ query, operation = 'AND' }) => { const dataProductSearch = await axios( - `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/search?${query}&operation=OR` + `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/search?${query}&operation=${operation}` ) return dataProductSearch.data } diff --git a/src/lib/product/components/Product/ProductDesktop.jsx b/src/lib/product/components/Product/ProductDesktop.jsx index bb2b2db9..8ce6da00 100644 --- a/src/lib/product/components/Product/ProductDesktop.jsx +++ b/src/lib/product/components/Product/ProductDesktop.jsx @@ -11,7 +11,9 @@ import { updateItemCart } from '@/core/utils/cart' import { useRouter } from 'next/router' import { createSlug } from '@/core/utils/slug' import BottomPopup from '@/core/components/elements/Popup/BottomPopup' -import { Toast } from 'flowbite-react' +import ProductCard from '../ProductCard' +import productSimilarApi from '../../api/productSimilarApi' +import whatsappUrl from '@/core/utils/whatsappUrl' const ProductDesktop = ({ product, wishlist, toggleWishlist }) => { const router = useRouter() @@ -59,7 +61,6 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => { selected: true }) setAddCartAlert(true) - // toast.success('Berhasil menambahkan ke keranjang') } const handleBuy = (variantId) => { @@ -85,61 +86,117 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => { `fq=-manufacture_id_i:${product.manufacture?.id || 0}` ].join('&') + const [productSimilarInBrand, setProductSimilarInBrand] = useState(null) + + useEffect(() => { + const loadProductSimilarInBrand = async () => { + const productSimilarQuery = [product?.name, `fq=-product_id_i:${product.id}`].join('&') + const dataProductSimilar = await productSimilarApi({ query: productSimilarQuery }) + setProductSimilarInBrand(dataProductSimilar.products) + } + if (!productSimilarInBrand) loadProductSimilarInBrand() + }, [product, productSimilarInBrand]) + return ( <DesktopView> <div className='container mx-auto pt-10'> <div className='flex'> - <div className='w-[30%]'> - <Image - src={product.image} - alt={product.name} - className='h-96 object-contain object-center w-full border border-gray_r-4' - /> - </div> - <div className='w-[50%] px-4'> - <h1 className='text-title-md leading-10 font-medium'>{product?.name}</h1> - <div className='mt-10'> - <div className='flex p-3'> - <div className='w-1/4 text-gray_r-12/70'>Nomor SKU</div> - <div className='w-3/4'>SKU-{product.id}</div> - </div> - <div className='flex p-3 bg-gray_r-4'> - <div className='w-1/4 text-gray_r-12/70'>Part Number</div> - <div className='w-3/4'>{product.code || '-'}</div> + <div className='w-full flex flex-wrap'> + <div className='w-5/12'> + <Image + src={product.image} + alt={product.name} + className='h-96 object-contain object-center w-full border border-gray_r-4' + /> + </div> + + <div className='w-7/12 px-4'> + <h1 className='text-title-md leading-10 font-medium'>{product?.name}</h1> + <div className='mt-10'> + <div className='flex p-3'> + <div className='w-1/4 text-gray_r-12/70'>Nomor SKU</div> + <div className='w-3/4'>SKU-{product.id}</div> + </div> + <div className='flex p-3 bg-gray_r-4'> + <div className='w-1/4 text-gray_r-12/70'>Part Number</div> + <div className='w-3/4'>{product.code || '-'}</div> + </div> + <div className='flex p-3'> + <div className='w-1/4 text-gray_r-12/70'>Manufacture</div> + <div className='w-3/4'> + {product.manufacture?.name ? ( + <Link + href={createSlug( + '/shop/brands/', + product.manufacture?.name, + product.manufacture?.id + )} + > + {product.manufacture?.name} + </Link> + ) : ( + <div>-</div> + )} + </div> + </div> + <div className='flex p-3 bg-gray_r-4'> + <div className='w-1/4 text-gray_r-12/70'>Berat Barang</div> + <div className='w-3/4'> + {product?.weight > 0 && <span>{product?.weight} KG</span>} + {product?.weight == 0 && ( + <a + href={whatsappUrl('productWeight', { + name: product.name, + url: createSlug('/shop/product/', product.name, product.id, true) + })} + className='text-danger-500 font-medium' + > + Tanya Berat + </a> + )} + </div> + </div> </div> - <div className='flex p-3'> - <div className='w-1/4 text-gray_r-12/70'>Manufacture</div> - <div className='w-3/4'> - {product.manufacture?.name ? ( - <Link - href={createSlug( - '/shop/brands/', - product.manufacture?.name, - product.manufacture?.id - )} + </div> + + <div className='w-full'> + <div className='mt-12'> + <div className='text-h-lg font-semibold'>Informasi Produk</div> + <div className='flex gap-x-4 mt-6 mb-4'> + {informationTabOptions.map((option) => ( + <TabButton + value={option.value} + key={option.value} + active={informationTab == option.value} + onClick={() => setInformationTab(option.value)} > - {product.manufacture?.name} - </Link> - ) : ( - <div>-</div> - )} + {option.label} + </TabButton> + ))} </div> - </div> - <div className='flex p-3 bg-gray_r-4'> - <div className='w-1/4 text-gray_r-12/70'>Berat Barang</div> - <div className='w-3/4'> - {product?.weight > 0 && <span>{product?.weight} KG</span>} - {product?.weight == 0 && ( - <a href='https://wa.me' className='text-danger-500 font-medium'> - Tanya Berat - </a> - )} + <div className='flex'> + <div className='w-3/4 leading-7 product__description'> + <TabContent active={informationTab == 'description'}> + <span + dangerouslySetInnerHTML={{ + __html: + product.description != '' + ? product.description + : 'Belum ada deskripsi produk.' + }} + /> + </TabContent> + + <TabContent active={informationTab == 'information'}> + Belum ada informasi. + </TabContent> + </div> </div> </div> </div> </div> - <div className='w-[20%]'> + <div className='w-[25%]'> {product.variants.length > 1 && product.lowestPrice.priceDiscount > 0 && ( <div className='text-gray_r-12/80'>Harga mulai dari: </div> )} @@ -160,7 +217,13 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => { ) : ( <span className='text-gray_r-12/90 font-normal text-h-sm'> Hubungi kami untuk dapatkan harga terbaik, - <a href='https://wa.me/' className='text-danger-500 underline'> + <a + href={whatsappUrl('product', { + name: product.name, + url: createSlug('/shop/product/', product.name, product.id, true) + })} + className='text-danger-500 underline' + > klik disini </a> </span> @@ -209,6 +272,20 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => { Wishlist </button> </div> + + <div className='border border-gray_r-6 overflow-auto mt-4'> + <div className='font-medium text-center p-4 bg-gray_r-1 border-b border-gray_r-6 sticky top-0 z-10'> + Produk Serupa + </div> + <div className='h-full divide-y divide-gray_r-6 max-h-96'> + {productSimilarInBrand && + productSimilarInBrand?.map((product) => ( + <div className='py-2' key={product.id}> + <ProductCard product={product} variant='horizontal' /> + </div> + ))} + </div> + </div> </div> </div> @@ -244,7 +321,13 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => { {variant.price.priceDiscount > 0 ? ( currencyFormat(variant.price.priceDiscount) ) : ( - <a href='https://wa.me/' className='text-red_r-11'> + <a + href={whatsappUrl('product', { + name: variant.name, + url: createSlug('/shop/product/', product.name, product.id, true) + })} + className='text-red_r-11' + > Call for price </a> )} @@ -283,38 +366,6 @@ const ProductDesktop = ({ product, wishlist, toggleWishlist }) => { </div> )} - <div className='mt-12'> - <div className='text-h-lg font-semibold'>Informasi Produk</div> - <div className='flex gap-x-4 mt-6 mb-4'> - {informationTabOptions.map((option) => ( - <TabButton - value={option.value} - key={option.value} - active={informationTab == option.value} - onClick={() => setInformationTab(option.value)} - > - {option.label} - </TabButton> - ))} - </div> - <div className='flex'> - <div className='w-3/4 leading-7 product__description'> - <TabContent active={informationTab == 'description'}> - <span - dangerouslySetInnerHTML={{ - __html: - product.description != '' - ? product.description - : 'Belum ada deskripsi produk.' - }} - /> - </TabContent> - - <TabContent active={informationTab == 'information'}>Belum ada informasi.</TabContent> - </div> - </div> - </div> - <div className='my-12'> <div className='text-h-lg font-semibold mb-6'>Kamu Mungkin Juga Suka</div> <LazyLoad> diff --git a/src/lib/product/components/Product/ProductMobile.jsx b/src/lib/product/components/Product/ProductMobile.jsx index b75a191b..426fe1b8 100644 --- a/src/lib/product/components/Product/ProductMobile.jsx +++ b/src/lib/product/components/Product/ProductMobile.jsx @@ -13,6 +13,7 @@ import MobileView from '@/core/components/views/MobileView' import { toast } from 'react-hot-toast' import { createSlug } from '@/core/utils/slug' import BottomPopup from '@/core/components/elements/Popup/BottomPopup' +import whatsappUrl from '@/core/utils/whatsappUrl' const ProductMobile = ({ product, wishlist, toggleWishlist }) => { const router = useRouter() @@ -152,7 +153,13 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => { ) : ( <span className='text-gray_r-11 leading-6 font-normal'> Hubungi kami untuk dapatkan harga terbaik, - <a href='https://wa.me/' className='text-danger-500 underline'> + <a + href={whatsappUrl('product', { + name: product.name, + url: createSlug('/shop/product/', product.name, product.id, true) + })} + className='text-danger-500 underline' + > klik disini </a> </span> @@ -236,7 +243,13 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => { </span> )} {activeVariant?.stock == 0 && ( - <a href='https://wa.me' className='text-danger-500 font-medium'> + <a + href={whatsappUrl('product', { + name: product.name, + url: createSlug('/shop/product/', product.name, product.id, true) + })} + className='text-danger-500 font-medium' + > Tanya Stok </a> )} @@ -244,7 +257,13 @@ const ProductMobile = ({ product, wishlist, toggleWishlist }) => { <SpecificationContent label='Berat Barang'> {activeVariant?.weight > 0 && <span>{activeVariant?.weight} KG</span>} {activeVariant?.weight == 0 && ( - <a href='https://wa.me' className='text-danger-500 font-medium'> + <a + href={whatsappUrl('productWeight', { + name: product.name, + url: createSlug('/shop/product/', product.name, product.id, true) + })} + className='text-danger-500 font-medium' + > Tanya Berat </a> )} diff --git a/src/lib/product/components/ProductCard.jsx b/src/lib/product/components/ProductCard.jsx index a1c30d00..9300643e 100644 --- a/src/lib/product/components/ProductCard.jsx +++ b/src/lib/product/components/ProductCard.jsx @@ -2,8 +2,14 @@ import Image from '@/core/components/elements/Image/Image' import Link from '@/core/components/elements/Link/Link' import currencyFormat from '@/core/utils/currencyFormat' import { createSlug } from '@/core/utils/slug' +import whatsappUrl from '@/core/utils/whatsappUrl' const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { + const callForPriceWhatsapp = whatsappUrl('product', { + name: product.name, + url: createSlug('/shop/product/', product.name, product.id, true) + }) + if (variant == 'vertical') { return ( <div className='rounded shadow-sm border border-gray_r-4 h-full bg-white'> @@ -58,7 +64,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { {product?.lowestPrice.priceDiscount > 0 ? ( currencyFormat(product?.lowestPrice.priceDiscount) ) : ( - <a href='https://wa.me/'>Call for price</a> + <a href={callForPriceWhatsapp}>Call for price</a> )} </div> @@ -130,7 +136,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { {product?.lowestPrice?.priceDiscount > 0 ? ( currencyFormat(product?.lowestPrice?.priceDiscount) ) : ( - <a href='https://wa.me/'>Call for price</a> + <a href={callForPriceWhatsapp}>Call for price</a> )} </div> diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx index 81e7948b..cc85589d 100644 --- a/src/lib/product/components/ProductSearch.jsx +++ b/src/lib/product/components/ProductSearch.jsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react' +import { useEffect, useMemo, useState } from 'react' import useProductSearch from '../hooks/useProductSearch' import ProductCard from './ProductCard' import Pagination from '@/core/components/elements/Pagination/Pagination' @@ -12,6 +12,9 @@ import DesktopView from '@/core/components/views/DesktopView' import NextImage from 'next/image' import ProductFilterDesktop from './ProductFilterDesktop' import { useRouter } from 'next/router' +import searchSpellApi from '@/core/api/searchSpellApi' +import Link from '@/core/components/elements/Link/Link' +import whatsappUrl from '@/core/utils/whatsappUrl' const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { const router = useRouter() @@ -19,6 +22,7 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { if (defaultBrand) query.brand = defaultBrand.toLowerCase() const { productSearch } = useProductSearch({ query }) const [products, setProducts] = useState(null) + const [spellings, setSpellings] = useState(null) const popup = useActive() const pageCount = Math.ceil( @@ -28,6 +32,30 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { const productRows = productSearch.data?.responseHeader.params.rows const productFound = productSearch.data?.response.numFound + useEffect(() => { + if (productFound == 0 && query.q) { + searchSpellApi({ query: query.q }).then((response) => { + const oddIndexSuggestions = response.data.spellcheck.suggestions.filter( + (_, index) => index % 2 === 1 + ) + const oddIndexCollations = response.data.spellcheck.collations.filter( + (_, index) => index % 2 === 1 + ) + const dataSpellings = oddIndexSuggestions.reduce((acc, curr) => { + oddIndexCollations.forEach((collation) => { + acc.push(collation.collationQuery) + }) + curr.suggestion.forEach((s) => { + if (!acc.includes(s.word)) acc.push(s.word) + }) + return acc + }, []) + + setSpellings(dataSpellings) + }) + } + }, [productFound, query]) + const brands = productSearch.data?.facetCounts?.facetFields?.manufactureName?.filter( (value, index) => { if (index % 2 === 0) { @@ -66,6 +94,20 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { } }, [query, products, productSearch]) + const SpellingComponent = useMemo(() => { + return ( + <> + Mungkin yang anda cari{' '} + {spellings?.map((spelling, i) => ( + <Link href={`/shop/search?q=${spelling}`} key={i} className='inline'> + {spelling} + {i + 1 < spellings.length ? ', ' : ''} + </Link> + ))} + </> + ) + }, [spellings]) + return ( <> <MobileView> @@ -97,13 +139,15 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { )} </> ) : ( - 'Mungkin yang anda cari' + SpellingComponent )} </div> - <button className='btn-light mb-6 py-2 px-5' onClick={popup.activate}> - Filter - </button> + {productFound > 0 && ( + <button className='btn-light mb-6 py-2 px-5' onClick={popup.activate}> + Filter + </button> + )} <div className='grid grid-cols-2 gap-3'> {products && @@ -127,6 +171,7 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { /> </div> </MobileView> + <DesktopView> <div className='container mx-auto mt-10 flex mb-3'> <div className='w-3/12'> @@ -164,7 +209,7 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { )} </> ) : ( - 'Mungkin yang anda cari' + SpellingComponent )} </div> <div className='justify-end flex '> @@ -202,7 +247,12 @@ const ProductSearch = ({ query, prefixUrl, defaultBrand = null }) => { <div className='text-gray_r-12/90'> <span> Barang yang anda cari tidak ada?{' '} - <a href='#' className='text-danger-500'> + <a + href={whatsappUrl('productSearch', { + name: query.q + })} + className='text-danger-500' + > Hubungi Kami </a> </span> |
