diff options
| author | it-fixcomart <it@fixcomart.co.id> | 2024-11-28 10:47:43 +0700 |
|---|---|---|
| committer | it-fixcomart <it@fixcomart.co.id> | 2024-11-28 10:47:43 +0700 |
| commit | 5bc7a6807847610b190ea9d5046021d2db15afc5 (patch) | |
| tree | e895b02c65bf97e3c6c970bb8d777922120f4570 /src/lib/product | |
| parent | 7ed3fd96322d08bd91434b8ec4dcbc542a610998 (diff) | |
| parent | 952421c810b53ec4d25ad5ef605bae1bd1d5d616 (diff) | |
Merge branch 'new-release' into Feature/switch-account
Diffstat (limited to 'src/lib/product')
| -rw-r--r-- | src/lib/product/components/Product/ProductDesktopVariant.jsx | 419 | ||||
| -rw-r--r-- | src/lib/product/components/Product/ProductMobileVariant.jsx | 209 | ||||
| -rw-r--r-- | src/lib/product/components/ProductCard.jsx | 15 | ||||
| -rw-r--r-- | src/lib/product/components/ProductFilter.jsx | 127 | ||||
| -rw-r--r-- | src/lib/product/components/ProductFilterDesktop.jsx | 165 | ||||
| -rw-r--r-- | src/lib/product/components/ProductSearch.jsx | 20 |
6 files changed, 633 insertions, 322 deletions
diff --git a/src/lib/product/components/Product/ProductDesktopVariant.jsx b/src/lib/product/components/Product/ProductDesktopVariant.jsx index 09b30a44..5dfd452b 100644 --- a/src/lib/product/components/Product/ProductDesktopVariant.jsx +++ b/src/lib/product/components/Product/ProductDesktopVariant.jsx @@ -1,12 +1,12 @@ - -import { Box, Skeleton, Tooltip } from '@chakra-ui/react'; +import { Box, Button, Skeleton, Tooltip } from '@chakra-ui/react'; import { HeartIcon } from '@heroicons/react/24/outline'; -import { Info } from 'lucide-react'; +import { Info, MessageCircleIcon, Share2Icon } from 'lucide-react'; import { useRouter } from 'next/router'; import { useCallback, useEffect, useRef, useState } from 'react'; import { toast } from 'react-hot-toast'; +import AddToWishlist from '../../../../../src-migrate/modules/product-detail/components/AddToWishlist'; +import { RWebShare } from 'react-web-share'; import LazyLoad from 'react-lazy-load'; - import { useProductCartContext } from '@/contexts/ProductCartContext'; import odooApi from '@/core/api/odooApi'; import Image from '@/core/components/elements/Image/Image'; @@ -18,10 +18,16 @@ import { updateItemCart } from '@/core/utils/cart'; import currencyFormat from '@/core/utils/currencyFormat'; import { createSlug } from '@/core/utils/slug'; import whatsappUrl from '@/core/utils/whatsappUrl'; +import { getAuth } from '~/libs/auth'; +import ImageNext from 'next/image'; import productSimilarApi from '../../api/productSimilarApi'; import ProductCard from '../ProductCard'; import ProductSimilar from '../ProductSimilar'; +import ProductPromoSection from '~/modules/product-promo/components/Section'; +import SimilarBottom from '~/modules/product-detail/components/SimilarBottom'; + +const SELF_HOST = process.env.NEXT_PUBLIC_SELF_HOST; const ProductDesktopVariant = ({ product, @@ -30,9 +36,10 @@ const ProductDesktopVariant = ({ isVariant, }) => { const router = useRouter(); - const auth = useAuth(); + let auth = useAuth(); const { slug } = router.query; - + const { srsltid } = router.query; + const [askAdminUrl, setAskAdminUrl, isApproval] = useState(); const [lowestPrice, setLowestPrice] = useState(null); const [addCartAlert, setAddCartAlert] = useState(false); @@ -40,11 +47,20 @@ const ProductDesktopVariant = ({ const { setRefreshCart } = useProductCartContext(); + const [quantityInput, setQuantityInput] = useState(1); + + const createdAskUrl = whatsappUrl({ + template: 'product', + payload: { + manufacture: product.manufacture.name, + productName: product.name, + url: process.env.NEXT_PUBLIC_SELF_HOST + router.asPath, + }, + fallbackUrl: router.asPath, + }); + const getLowestPrice = useCallback(() => { const lowest = product.price; - /* const lowest = prices.reduce((lowest, price) => { - return price.priceDiscount < lowest.priceDiscount ? price : lowest - }, prices[0])*/ return lowest; }, [product]); @@ -74,10 +90,10 @@ const ProductDesktopVariant = ({ const handleAddToCart = (variant) => { if (!auth) { - router.push(`/login?next=/shop/product/${slug}`); + router.push(`/login?next=/shop/product/${slug}?srsltid=${srsltid}`); return; } - const quantity = variantQuantityRefs.current[product.id].value; + const quantity = quantityInput; if (!validQuantity(quantity)) return; updateItemCart({ productId: product.id, @@ -91,8 +107,34 @@ const ProductDesktopVariant = ({ setAddCartAlert(true); }; - const handleBuy = (variant) => { - const quantity = variantQuantityRefs.current[product.id].value; + const handleBuy = async (variant) => { + const quantity = variantQuantityRefs?.current[product.id]?.value; + let isLoggedIn = typeof auth === 'object'; + + if (!isLoggedIn) { + const currentUrl = encodeURIComponent(router.asPath); + await router.push(`/login?next=${currentUrl}`); + + // Tunggu login berhasil, misalnya dengan memantau perubahan status auth. + const authCheckInterval = setInterval(() => { + const newAuth = getAuth(); + if (typeof newAuth === 'object') { + isLoggedIn = true; + auth = newAuth; // Update nilai auth setelah login + clearInterval(authCheckInterval); + } + }, 500); // Periksa status login setiap 500ms + + await new Promise((resolve) => { + const checkLogin = setInterval(() => { + if (isLoggedIn) { + clearInterval(checkLogin); + resolve(null); + } + }, 500); + }); + } + if (!validQuantity(quantity)) return; updateItemCart({ @@ -105,6 +147,45 @@ const ProductDesktopVariant = ({ router.push(`/shop/checkout?source=buy`); }; + const handleButton = async (variant) => { + const quantity = quantityInput; + let isLoggedIn = typeof auth === 'object'; + + if (!isLoggedIn) { + const currentUrl = encodeURIComponent(router.asPath); + await router.push(`/login?next=${currentUrl}`); + + // Tunggu login berhasil, misalnya dengan memantau perubahan status auth. + const authCheckInterval = setInterval(() => { + const newAuth = getAuth(); + if (typeof newAuth === 'object') { + isLoggedIn = true; + auth = newAuth; // Update nilai auth setelah login + clearInterval(authCheckInterval); + } + }, 500); // Periksa status login setiap 500ms + + await new Promise((resolve) => { + const checkLogin = setInterval(() => { + if (isLoggedIn) { + clearInterval(checkLogin); + resolve(null); + } + }, 500); + }); + } + if (!validQuantity(quantity)) return; + + updateItemCart({ + productId: variant, + quantity, + programLineId: null, + selected: true, + source: 'buy', + }); + router.push('/shop/quotation?source=buy'); + }; + const variantSectionRef = useRef(null); const goToVariantSection = () => { if (variantSectionRef.current) { @@ -160,87 +241,45 @@ const ProductDesktopVariant = ({ <Image src={product.image + '?variant=True'} alt={product.name} - className='h-[430px] object-contain object-center w-full border border-gray_r-4' + className='w-full h-[350px]' /> </div> - <div className='w-7/12 px-4'> + <div className='w-7/12 px-6'> <h1 className='text-title-md leading-10 font-medium'> {product?.name} </h1> <div className='mt-10'> - <div className='flex p-3'> - <div className='w-4/12 text-gray_r-12/70'>Nomor SKU</div> - <div className='w-8/12'>SKU-{product.id}</div> - </div> <div className='flex p-3 bg-gray_r-4'> - <div className='w-4/12 text-gray_r-12/70'>Part Number</div> - <div className='w-8/12'>{product.code || '-'}</div> + <div className='w-4/12 text-gray_r-12/70'>Item Code</div> + <div className='w-8/12'>{product.code}</div> </div> - <div className='flex p-3'> + <div className='flex p-3 items-center '> <div className='w-4/12 text-gray_r-12/70'>Manufacture</div> <div className='w-8/12'> - {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 items-center bg-gray_r-4'> - <div className='w-4/12 text-gray_r-12/70'> - Persiapan Barang - </div> - <div className='w-8/12'> - {!product?.sla && <Skeleton width='20%' height='16px' />} - {product?.sla && ( - <Tooltip - placement='top' - label={`Masa Persiapan Barang ${product?.sla?.slaDate}`} - > - <Box className='w-fit flex items-center gap-x-2'> - {product?.sla?.slaDate} - <Info size={16} /> - </Box> - </Tooltip> - )} + <Link + href={createSlug( + '/shop/brands/', + product.manufacture.name, + product.manufacture.id.toString() + )} + > + {product?.manufacture.logo ? ( + <Image + width={100} + src={product.manufacture.logo} + alt={product.manufacture.name} + /> + ) : ( + <p className='font-bold text-red-500'> + {product.manufacture.name} + </p> + )} + </Link> </div> </div> - <div className='flex p-3'> - <div className='w-4/12 text-gray_r-12/70'>Stock</div> - <div className='w-8/12'> - {!product?.sla && <Skeleton width='10%' height='16px' />} - {product?.sla?.qty > 0 && <span>{product?.sla?.qty}</span>} - {product?.sla?.qty == 0 && ( - <a - href={whatsappUrl('product', { - name: product.name, - manufacture: product?.manufacture?.name, - url: createSlug( - '/shop/product/', - product.name, - product.id, - true - ), - })} - className='text-danger-500 font-medium' - > - Tanya Admin - </a> - )} - </div> - </div> - <div className='flex p-3 bg-gray_r-4'> + <div className='flex p-3 bg-gray_r-4 '> <div className='w-4/12 text-gray_r-12/70'>Berat Barang</div> <div className='w-8/12'> {product?.weight > 0 && <span>{product?.weight} KG</span>} @@ -262,24 +301,55 @@ const ProductDesktopVariant = ({ )} </div> </div> + <div className='flex p-3 items-center '> + <div className='w-4/12 text-gray_r-12/70'>Terjual</div> + <div className='w-8/12'>-</div> + </div> + + <div className='flex p-3 items-center bg-gray_r-4 '> + <div className='w-4/12 text-gray_r-12/70'> + Persiapan Barang + </div> + <div className='w-8/12'> + {!product?.sla && <Skeleton width='20%' height='16px' />} + {product?.sla && ( + <Tooltip + placement='top' + label={`Masa Persiapan Barang ${product?.sla?.slaDate}`} + > + <Box className='w-fit flex items-center gap-x-2'> + {product?.sla?.slaDate} + <Info size={16} /> + </Box> + </Tooltip> + )} + </div> + </div> </div> </div> - <div className='p-4 md:p-6 md:bg-gray-50 rounded-xl'> - <h2 className='text-h-md md:text-h-lg font-medium'>Informasi Produk</h2> - <div className='h-4' /> - <div - className='leading-relaxed text-gray-700' - dangerouslySetInnerHTML={{ - __html: - !product.parent.description || product.parent.description == '<p><br></p>' - ? 'Belum ada deskripsi' - : product.parent.description, - }} - /> + <div className='p-4 md:p-6 w-full'> + <ProductPromoSection product={product} productId={product.id} /> + + <div className='p-4 md:p-6 md:bg-gray-50 rounded-xl'> + <h2 className='text-h-md md:text-h-lg font-medium'> + Informasi Produk + </h2> + <div className='h-4' /> + <div + className='leading-relaxed text-gray-700' + dangerouslySetInnerHTML={{ + __html: + !product.parent.description || + product.parent.description == '<p><br></p>' + ? 'Belum ada deskripsi' + : product.parent.description, + }} + /> + </div> </div> </div> - <div className='w-[25%]'> + <div className='w-[33%]'> {product?.isFlashsale > 0 && product?.price?.discountPercentage > 0 ? ( <> @@ -337,46 +407,143 @@ const ProductDesktopVariant = ({ )} </h3> )} - <div className='flex gap-x-3 mt-4'> - <input - type='number' - className='form-input w-16 py-2 text-center bg-gray_r-1' - ref={setVariantQuantityRef(product.id)} - defaultValue={1} - /> - <button - type='button' + <div className='flex justify-between items-center py-5 px-3'> + <div className='relative flex items-center'> + <button + type='button' + className='absolute left-0 px-2 py-1 h-full text-gray-500' + onClick={() => + setQuantityInput( + String(Math.max(1, Number(quantityInput) - 1)) + ) + } + > + - + </button> + <input + type='number' + id='quantity' + min={1} + value={quantityInput} + onChange={(e) => setQuantityInput(e.target.value)} + className=' w-24 h-10 text-center border border-gray-300 rounded focus:outline-none' + /> + <button + type='button' + className='absolute right-0 px-2 py-1 h-full text-gray-500' + onClick={() => + setQuantityInput(String(Number(quantityInput) + 1)) + } + > + + + </button> + </div> + <div> + <Skeleton + isLoaded={!isLoadingSLA} + h='21px' + // w={16} + className={ + product?.sla?.qty < 10 ? 'text-red-600 font-medium' : '' + } + > + Stock : {product?.sla?.qty}{' '} + </Skeleton> + </div> + <div> + {product?.sla?.qty > 0 && ( + <Link href='/panduan-pick-up-service' className='group'> + <Image + src='/images/PICKUP-NOW.png' + className='group-hover:scale-105 transition-transform duration-200 w-28' + alt='pickup now' + /> + </Link> + )} + </div> + </div> + <div className='flex gap-x-3'> + <Button onClick={() => handleAddToCart(product.id)} - className='flex-1 py-2 btn-yellow' + className='w-full' + colorScheme='yellow' > Keranjang - </button> - <button - type='button' + </Button> + <Button onClick={() => handleBuy(product.id)} - className='flex-1 py-2 btn-solid-red' + className='w-full' + colorScheme='red' > Beli - </button> + </Button> </div> - <div className='flex mt-4'> - <button - className='flex items-center gap-x-1' - onClick={toggleWishlist} - > - {wishlist.data?.productTotal > 0 ? ( - <HeartIcon className='w-6 fill-danger-500 text-danger-500' /> - ) : ( - <HeartIcon className='w-6' /> - )} - Wishlist - </button> + <Button + onClick={() => handleButton(product.id)} + color={'red'} + colorScheme='white' + className='w-full border-2 p-2 gap-1 mt-2 hover:bg-slate-100 flex items-center' + > + <ImageNext + src='/images/writing.png' + alt='penawaran instan' + className='' + width={25} + height={25} + /> + Penawaran Harga Instan + </Button> + <div className='flex py-5'> + <div className='flex gap-x-5 items-center justify-center'> + <Button + as={Link} + href={createdAskUrl} + variant='link' + target='_blank' + colorScheme='gray' + leftIcon={<MessageCircleIcon size={18} />} + > + Ask Admin + </Button> + + <span>|</span> + + <button + className='flex items-center gap-x-1' + onClick={toggleWishlist} + > + {wishlist.data?.productTotal > 0 ? ( + <HeartIcon className='w-6 fill-danger-500 text-danger-500' /> + ) : ( + <HeartIcon className='w-6' /> + )} + Wishlist + </button> + + <span>|</span> + + <RWebShare + data={{ + text: 'Check out this product', + title: `${product.name} - Indoteknik.com`, + url: SELF_HOST + router.asPath, + }} + > + <Button + variant='link' + colorScheme='gray' + leftIcon={<Share2Icon size={18} />} + > + Share + </Button> + </RWebShare> + </div> </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'> + <div className='h-full divide-y divide-gray_r-6 max-h-[500px]'> {productSimilarInBrand && productSimilarInBrand?.map((product) => ( <div className='py-2' key={product.id}> @@ -393,8 +560,11 @@ const ProductDesktopVariant = ({ Kamu Mungkin Juga Suka </div> <LazyLoad> - <ProductSimilar query={productSimilarQuery} /> + <SimilarBottom product={product} /> </LazyLoad> + {/* <LazyLoad> + <ProductSimilar query={productSimilarQuery} /> + </LazyLoad> */} </div> <BottomPopup @@ -429,8 +599,11 @@ const ProductDesktopVariant = ({ Kamu Mungkin Juga Suka </div> <LazyLoad> - <ProductSimilar query={productSimilarQuery} /> + <SimilarBottom product={product} /> </LazyLoad> + {/* <LazyLoad> + <ProductSimilar query={productSimilarQuery} /> + </LazyLoad> */} </div> </BottomPopup> </div> diff --git a/src/lib/product/components/Product/ProductMobileVariant.jsx b/src/lib/product/components/Product/ProductMobileVariant.jsx index af9e52bb..de5c3f10 100644 --- a/src/lib/product/components/Product/ProductMobileVariant.jsx +++ b/src/lib/product/components/Product/ProductMobileVariant.jsx @@ -1,10 +1,10 @@ -import { Skeleton } from '@chakra-ui/react'; +import { Button, Skeleton } from '@chakra-ui/react'; import { HeartIcon } from '@heroicons/react/24/outline'; import { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; import { toast } from 'react-hot-toast'; import LazyLoad from 'react-lazy-load'; - +import ImageNext from 'next/image'; import odooApi from '@/core/api/odooApi'; import Divider from '@/core/components/elements/Divider/Divider'; import Image from '@/core/components/elements/Image/Image'; @@ -16,12 +16,15 @@ import currencyFormat from '@/core/utils/currencyFormat'; import { gtagAddToCart } from '@/core/utils/googleTag'; import { createSlug } from '@/core/utils/slug'; import whatsappUrl from '@/core/utils/whatsappUrl'; - +import { getAuth } from '~/libs/auth'; +import SimilarBottom from '~/modules/product-detail/components/SimilarBottom'; import ProductSimilar from '../ProductSimilar'; const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { const router = useRouter(); - + const { slug } = router.query; + const { srsltid } = router.query; + let auth = getAuth(); const [quantity, setQuantity] = useState('1'); const [selectedVariant, setSelectedVariant] = useState(product.id); const [informationTab, setInformationTab] = useState( @@ -73,11 +76,16 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { return isValid; }; - const handleClickCart = () => { + const handleClickCart = async () => { + if (!auth) { + router.push(`/login?next=/shop/product/${slug}?srsltid=${srsltid}`); + return; + } + if (!validAction()) return; gtagAddToCart(activeVariant, quantity); updateItemCart({ - productId: variant, + productId: product.id, quantity, programLineId: null, selected: true, @@ -86,7 +94,33 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { setAddCartAlert(true); }; - const handleClickBuy = () => { + const handleClickBuy = async () => { + let isLoggedIn = typeof auth === 'object'; + + if (!isLoggedIn) { + const currentUrl = encodeURIComponent(router.asPath); + await router.push(`/login?next=${currentUrl}`); + + // Tunggu login berhasil, misalnya dengan memantau perubahan status auth. + const authCheckInterval = setInterval(() => { + const newAuth = getAuth(); + if (typeof newAuth === 'object') { + isLoggedIn = true; + auth = newAuth; // Update nilai auth setelah login + clearInterval(authCheckInterval); + } + }, 500); // Periksa status login setiap 500ms + + await new Promise((resolve) => { + const checkLogin = setInterval(() => { + if (isLoggedIn) { + clearInterval(checkLogin); + resolve(null); + } + }, 500); + }); + } + if (!validAction()) return; updateItemCart({ @@ -99,6 +133,20 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { router.push(`/shop/checkout?source=buy`); }; + const handleButton = (variant) => { + const quantity = quantityInput; + if (!validQuantity(quantity)) return; + + updateItemCart({ + productId: variant, + quantity, + programLineId: null, + selected: true, + source: 'buy', + }); + router.push('/shop/quotation?source=buy'); + }; + const productSimilarQuery = [ product?.name, `fq=-product_id_i:${product.id}`, @@ -120,42 +168,14 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { return ( <MobileView> - <Image - src={product.image + '?variant=True'} - alt={product.name} - className='h-72 object-contain object-center w-full border-b border-gray_r-4' - /> - - <div className='p-4'> - <div className='flex items-end mb-2'> - {product.manufacture?.name ? ( - <Link - href={createSlug( - '/shop/brands/', - product.manufacture?.name, - product.manufacture?.id - )} - > - {product.manufacture?.name} - </Link> - ) : ( - <div>-</div> - )} - <button type='button' className='ml-auto' onClick={toggleWishlist}> - {wishlist.data?.productTotal > 0 ? ( - <HeartIcon className='w-6 fill-danger-500 text-danger-500' /> - ) : ( - <HeartIcon className='w-6' /> - )} - </button> - </div> - <h1 className='leading-6 font-medium mb-3'>{activeVariant?.name}</h1> - + <div + className={`px-4 block md:sticky md:top-[150px] md:py-6 fixed bottom-0 left-0 right-0 bg-white p-2 z-10 pb-6 pt-6 rounded-lg shadow-[rgba(0,0,4,0.1)_0px_-4px_4px_0px] `} + > {activeVariant.isFlashSale && activeVariant?.price?.discountPercentage > 0 ? ( <> <div className='flex gap-x-1 items-center'> - <div className='badge-solid-red'> + <div className='bg-danger-500 px-2 py-1.5 rounded text-white text-caption-2'> {activeVariant?.price?.discountPercentage}% </div> <div className='text-gray_r-11 line-through text-caption-1'> @@ -173,7 +193,7 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { </div> </> ) : ( - <h3 className='text-danger-500 font-semibold mt-1'> + <div className='text-danger-500 font-semibold mt-1 text-3xl'> {activeVariant?.price?.price > 0 ? ( <> {currencyFormat(activeVariant?.price?.price)} @@ -203,39 +223,84 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { </a> </span> )} - </h3> + </div> )} + <div className=''> + <div className='mt-4 mb-2'>Jumlah</div> + <div className='flex gap-x-3'> + <div className='w-2/12'> + <input + name='quantity' + type='number' + className='form-input' + value={quantity} + onChange={(e) => setQuantity(e.target.value)} + /> + </div> + <button + type='button' + className='btn-yellow flex-1' + onClick={handleClickCart} + > + Keranjang + </button> + <button + type='button' + className='btn-solid-red flex-1' + onClick={handleClickBuy} + > + Beli + </button> + </div> + <Button + onClick={() => handleButton(product.id)} + color={'red'} + colorScheme='white' + className='w-full border-2 p-2 gap-1 mt-2 hover:bg-slate-100 flex items-center' + > + <ImageNext + src='/images/writing.png' + alt='penawaran instan' + className='' + width={25} + height={25} + /> + Penawaran Harga Instan + </Button> + </div> </div> - - <Divider /> + <Image + src={product.image + '?variant=True'} + alt={product.name} + className='h-72 object-contain object-center w-full border-b border-gray_r-4' + /> <div className='p-4'> - <div className='mt-4 mb-2'>Jumlah</div> - <div className='flex gap-x-3'> - <div className='w-2/12'> - <input - name='quantity' - type='number' - className='form-input' - value={quantity} - onChange={(e) => setQuantity(e.target.value)} - /> - </div> - <button - type='button' - className='btn-yellow flex-1' - onClick={handleClickCart} - > - Keranjang - </button> - <button - type='button' - className='btn-solid-red flex-1' - onClick={handleClickBuy} - > - Beli + <div className='flex items-end mb-2'> + {product.manufacture?.name ? ( + <Link + href={createSlug( + '/shop/brands/', + product.manufacture?.name, + product.manufacture?.id + )} + > + {product.manufacture?.name} + </Link> + ) : ( + <div>-</div> + )} + <button type='button' className='ml-auto' onClick={toggleWishlist}> + {wishlist.data?.productTotal > 0 ? ( + <HeartIcon className='w-6 fill-danger-500 text-danger-500' /> + ) : ( + <HeartIcon className='w-6' /> + )} </button> </div> + <h1 className='font-medium text-h-lg leading-8 md:text-title-md md:leading-10 mb-3'> + {activeVariant?.name} + </h1> </div> <Divider /> @@ -375,8 +440,11 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { <div className='p-4'> <h2 className='font-semibold mb-4'>Kamu Mungkin Juga Suka</h2> <LazyLoad> - <ProductSimilar query={productSimilarQuery} /> + <SimilarBottom product={product} /> </LazyLoad> + {/* <LazyLoad> + <ProductSimilar query={productSimilarQuery} /> + </LazyLoad> */} </div> <BottomPopup @@ -409,8 +477,11 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => { Kamu Mungkin Juga Suka </div> <LazyLoad> - <ProductSimilar query={productSimilarQuery} /> + <SimilarBottom product={product} /> </LazyLoad> + {/* <LazyLoad> + <ProductSimilar query={productSimilarQuery} /> + </LazyLoad> */} </div> </BottomPopup> </MobileView> diff --git a/src/lib/product/components/ProductCard.jsx b/src/lib/product/components/ProductCard.jsx index d3b50302..3e6a6913 100644 --- a/src/lib/product/components/ProductCard.jsx +++ b/src/lib/product/components/ProductCard.jsx @@ -10,12 +10,13 @@ import { sellingProductFormat } from '@/core/utils/formatValue'; import { createSlug } from '@/core/utils/slug'; import whatsappUrl from '@/core/utils/whatsappUrl'; import useUtmSource from '~/hooks/useUtmSource'; +import useDevice from '@/core/hooks/useDevice'; const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { const router = useRouter(); const utmSource = useUtmSource(); const [discount, setDiscount] = useState(0); - + const { isDesktop, isMobile } = useDevice(); let voucherPastiHemat = 0; voucherPastiHemat = product?.newVoucherPastiHemat[0]; @@ -26,9 +27,13 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { }); const image = useMemo(() => { - if (product.image) return product.image + '?ratio=square'; - return '/images/noimage.jpeg'; - }, [product.image]); + if (!isDesktop && product.image_mobile) { + return product.image_mobile + '?ratio=square'; + } else { + if (product.image) return product.image + '?ratio=square'; + return '/images/noimage.jpeg'; + } + }, [product.image, product.image_mobile]); const URL = { product: @@ -143,7 +148,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { <div className='p-2 sm:p-3 pb-3 text-caption-2 sm:text-body-2 leading-5'> <div className='flex justify-between '> {product?.manufacture?.name ? ( - <Link href={URL.manufacture} className='mb-1 mt-1'> + <Link href={URL.manufacture} className='mb-1 mt-1 truncate'> {product.manufacture.name} </Link> ) : ( diff --git a/src/lib/product/components/ProductFilter.jsx b/src/lib/product/components/ProductFilter.jsx index d52fcb90..947550b7 100644 --- a/src/lib/product/components/ProductFilter.jsx +++ b/src/lib/product/components/ProductFilter.jsx @@ -1,88 +1,96 @@ -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 { Checkbox } from '@chakra-ui/react' +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 { Checkbox } from '@chakra-ui/react'; const orderOptions = [ { value: 'price-asc', label: 'Harga Terendah' }, { value: 'price-desc', label: 'Harga Tertinggi' }, { value: 'popular', label: 'Populer' }, - { value: 'stock', label: 'Ready Stock' } -] + { value: 'stock', label: 'Ready Stock' }, +]; -const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBrand = null }) => { - const router = useRouter() - const { query } = router - const [order, setOrder] = useState(query?.orderBy || 'popular') - const [brand, setBrand] = useState(query?.brand) - const [category, setCategory] = useState(query?.category) - const [priceFrom, setPriceFrom] = useState(query?.priceFrom) - const [priceTo, setPriceTo] = useState(query?.priceTo) +const ProductFilter = ({ + active, + close, + brands, + categories, + prefixUrl, + defaultBrand = null, +}) => { + const router = useRouter(); + const { query } = router; + const [order, setOrder] = useState(query?.orderBy || 'popular'); + const [brand, setBrand] = useState(query?.brand); + const [category, setCategory] = useState(query?.category); + const [priceFrom, setPriceFrom] = useState(query?.priceFrom); + const [priceTo, setPriceTo] = useState(query?.priceTo); - const [stock, setStock] = useState(query?.stock) + const [stock, setStock] = useState(query?.stock); - const [activeRange, setActiveRange] = useState(null) + const [activeRange, setActiveRange] = useState(null); const priceRange = [ { priceFrom: 100000, - priceTo: 200000 + priceTo: 200000, }, { priceFrom: 200000, - priceTo: 300000 + priceTo: 300000, }, { priceFrom: 300000, - priceTo: 400000 + priceTo: 400000, }, { priceFrom: 400000, - priceTo: 500000 - } - ] + priceTo: 500000, + }, + ]; const handlePriceFromChange = async (priceFromr, priceTor, index) => { - await setPriceFrom(priceFromr) - await setPriceTo(priceTor) - setActiveRange(index) - } + await setPriceFrom(priceFromr); + await setPriceTo(priceTor); + setActiveRange(index); + }; const handleReadyStockChange = (event) => { - const value = event.target.value - const isChecked = event.target.checked + const value = event.target.value; + const isChecked = event.target.checked; if (isChecked) { - setStock(value) + setStock(value); } else { - setStock(null) + setStock(null); } - } + }; const handleSubmit = () => { let params = { + penawaran: router.query.penawaran, q: router.query.q, orderBy: order, brand, category, priceFrom, priceTo, - stock: stock - } - params = _.pickBy(params, _.identity) - params = toQuery(params) - router.push(`${prefixUrl}?${params}`) - } + stock: stock, + }; + params = _.pickBy(params, _.identity); + params = toQuery(params); + router.push(`${prefixUrl}?${params}`); + }; const formatCurrency = (value) => { if (value >= 1000) { - const thousands = Math.floor(value / 1000) // Menghitung ribuan - return `Rp${thousands}k` + const thousands = Math.floor(value / 1000); // Menghitung ribuan + return `Rp${thousands}k`; } else { - return `Rp${value}` + return `Rp${value}`; } - } + }; return ( <BottomPopup active={active} close={close} title='Filter Produk'> @@ -101,7 +109,10 @@ const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBr <option value=''>Pilih Brand...</option> {brands.map((brand, index) => ( <option value={brand.brand} key={index}> - {brand.brand} <span className='text-sm text-gray-200'>({brand.qty})</span> + {brand.brand}{' '} + <span className='text-sm text-gray-200'> + ({brand.qty}) + </span> </option> ))} </> @@ -125,7 +136,10 @@ const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBr <option value=''>Pilih Kategori...</option> {categories.map((category, index) => ( <option value={category.name} key={index}> - {category.name} <span className='text-sm text-gray-200'>({category.qty})</span> + {category.name}{' '} + <span className='text-sm text-gray-200'> + ({category.qty}) + </span> </option> ))} </> @@ -141,7 +155,9 @@ const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBr <button key={orderOption.value} className={`btn-light px-3 font-normal flex-shrink-0 ${ - order == orderOption.value ? 'bg-warning-500' : 'bg-transparent' + order == orderOption.value + ? 'bg-warning-500' + : 'bg-transparent' }`} onClick={() => setOrder(orderOption.value)} > @@ -173,13 +189,16 @@ const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBr {priceRange.map((price, i) => ( <button key={i} - onClick={() => handlePriceFromChange(price.priceFrom, price.priceTo, i)} + onClick={() => + handlePriceFromChange(price.priceFrom, price.priceTo, i) + } className={`w-full border ${ i === activeRange ? 'border-red-600' : 'border-gray-400' } py-2 p-3 rounded-full text-sm whitespace-nowrap`} > - {formatCurrency(price.priceFrom)} - {formatCurrency(price.priceTo)} + {formatCurrency(price.priceFrom)} -{' '} + {formatCurrency(price.priceTo)} </button> ))} </div> @@ -197,12 +216,16 @@ const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBr </Checkbox> </div> </div> */} - <button type='button' className='btn-solid-red w-full mt-2' onClick={handleSubmit}> + <button + type='button' + className='btn-solid-red w-full mt-2' + onClick={handleSubmit} + > Terapkan Filter </button> </div> </BottomPopup> - ) -} + ); +}; -export default ProductFilter +export default ProductFilter; diff --git a/src/lib/product/components/ProductFilterDesktop.jsx b/src/lib/product/components/ProductFilterDesktop.jsx index 73fecab5..d2ecb4d9 100644 --- a/src/lib/product/components/ProductFilterDesktop.jsx +++ b/src/lib/product/components/ProductFilterDesktop.jsx @@ -1,7 +1,7 @@ -import { useRouter } from 'next/router' -import { useEffect, useState } from 'react' -import _ from 'lodash' -import { toQuery } from 'lodash-contrib' +import { useRouter } from 'next/router'; +import { useEffect, useState } from 'react'; +import _ from 'lodash'; +import { toQuery } from 'lodash-contrib'; import { Accordion, AccordionButton, @@ -15,110 +15,119 @@ import { InputGroup, InputLeftAddon, Stack, - VStack -} from '@chakra-ui/react' -import Image from '@/core/components/elements/Image/Image' -import { formatCurrency } from '@/core/utils/formatValue' + VStack, +} from '@chakra-ui/react'; +import Image from '@/core/components/elements/Image/Image'; +import { formatCurrency } from '@/core/utils/formatValue'; -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 [stock, setStock] = useState(query?.stock) - const [activeRange, setActiveRange] = useState(null) - const [activeIndeces, setActiveIndeces] = useState([]) +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 [stock, setStock] = useState(query?.stock); + const [activeRange, setActiveRange] = useState(null); + const [activeIndeces, setActiveIndeces] = useState([]); const priceRange = [ { priceFrom: 100000, - priceTo: 200000 + priceTo: 200000, }, { priceFrom: 200000, - priceTo: 300000 + priceTo: 300000, }, { priceFrom: 300000, - priceTo: 400000 + priceTo: 400000, }, { priceFrom: 400000, - priceTo: 500000 - } - ] + priceTo: 500000, + }, + ]; const indexRange = priceRange.findIndex((range) => { - return range.priceFrom === parseInt(priceFrom) && range.priceTo == parseInt(priceTo) - }) + return ( + range.priceFrom === parseInt(priceFrom) && + range.priceTo == parseInt(priceTo) + ); + }); const handleCategoriesChange = (event) => { - const value = event.target.value - const isChecked = event.target.checked + const value = event.target.value; + const isChecked = event.target.checked; if (isChecked) { - setCategory([...categoryValues, value]) + setCategory([...categoryValues, value]); } else { - setCategory(categoryValues.filter((val) => val !== value)) + setCategory(categoryValues.filter((val) => val !== value)); } - } + }; const handleBrandsChange = (event) => { - const value = event.target.value - const isChecked = event.target.checked + const value = event.target.value; + const isChecked = event.target.checked; if (isChecked) { - setBrand([...brandValues, value]) + setBrand([...brandValues, value]); } else { - setBrand(brandValues.filter((val) => val !== value)) + setBrand(brandValues.filter((val) => val !== value)); } - } + }; const handleReadyStockChange = (event) => { - const value = event.target.value - const isChecked = event.target.checked + const value = event.target.value; + const isChecked = event.target.checked; if (isChecked) { - setStock(value) + setStock(value); } else { - setStock(null) + setStock(null); } - } + }; const handlePriceFromChange = async (priceFromr, priceTor, index) => { - await setPriceFrom(priceFromr) - await setPriceTo(priceTor) - setActiveRange(index) - } + await setPriceFrom(priceFromr); + await setPriceTo(priceTor); + setActiveRange(index); + }; const handleSubmit = () => { let params = { + penawaran: router.query.penawaran, q: router.query.q, orderBy: order, brand: brandValues.join(','), category: categoryValues.join(','), priceFrom, priceTo, - stock: stock - } - params = _.pickBy(params, _.identity) - params = toQuery(params) + stock: stock, + }; + params = _.pickBy(params, _.identity); + params = toQuery(params); - const slug = Array.isArray(router.query.slug) ? router.query.slug[0] : router.query.slug; + const slug = Array.isArray(router.query.slug) + ? router.query.slug[0] + : router.query.slug; if (slug) { - if(prefixUrl.includes('category') || prefixUrl.includes('lob')){ - router.push(`${prefixUrl}?${params}`) - }else{ - router.push(`${prefixUrl}/${slug}?${params}`) + if (prefixUrl.includes('category') || prefixUrl.includes('lob')) { + router.push(`${prefixUrl}?${params}`); + } else { + router.push(`${prefixUrl}/${slug}?${params}`); } } else { - router.push(`${prefixUrl}?${params}`) + router.push(`${prefixUrl}?${params}`); } - } - - + }; /*const handleIndexAccordion = async () => { if (brandValues) { @@ -136,9 +145,8 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu }*/ useEffect(() => { - setActiveRange(indexRange) - }, []) - + setActiveRange(indexRange); + }, []); return ( <> @@ -165,13 +173,17 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu > <div className='flex items-center gap-2'> <span>{brand.brand} </span> - <span className='text-sm text-gray-600'>({brand.qty})</span> + <span className='text-sm text-gray-600'> + ({brand.qty}) + </span> </div> </Checkbox> </div> )) ) : ( - <div className='flex items-center gap-2'>Brands tidak tersedia</div> + <div className='flex items-center gap-2'> + Brands tidak tersedia + </div> )} </Stack> </AccordionPanel> @@ -199,13 +211,17 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu > <div className='flex items-center gap-2'> <span>{category.name} </span> - <span className='text-sm text-gray-600'>({category.qty})</span> + <span className='text-sm text-gray-600'> + ({category.qty}) + </span> </div> </Checkbox> </div> )) ) : ( - <div className='flex items-center gap-2'>Kategori tidak tersedia</div> + <div className='flex items-center gap-2'> + Kategori tidak tersedia + </div> )} </Stack> </AccordionPanel> @@ -243,13 +259,16 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu {priceRange.map((price, i) => ( <button key={i} - onClick={() => handlePriceFromChange(price.priceFrom, price.priceTo, i)} + onClick={() => + handlePriceFromChange(price.priceFrom, price.priceTo, i) + } className={`w-full border ${ i === activeRange ? 'border-red-600' : 'border-gray-400' } py-2 p-3 rounded-full text-sm whitespace-nowrap`} > - {formatCurrency(price.priceFrom)} - {formatCurrency(price.priceTo)} + {formatCurrency(price.priceFrom)} -{' '} + {formatCurrency(price.priceTo)} </button> ))} </div> @@ -282,7 +301,7 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu Terapkan </Button> </> - ) -} + ); +}; -export default ProductFilterDesktop +export default ProductFilterDesktop; diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx index 26114acf..f7b044aa 100644 --- a/src/lib/product/components/ProductSearch.jsx +++ b/src/lib/product/components/ProductSearch.jsx @@ -79,6 +79,24 @@ const ProductSearch = ({ } }, [categoryId]); + useEffect(() => { + const checkIfPenawaran = async () => { + if (router.asPath.includes('penawaran')) { + query = { + ...query, + fq: [ + `-flashsale_id_i:${router.query.penawaran}`, + `flashsale_price_f:[1 TO *]`, + ], + orderBy: 'flashsale-discount-desc', + }; + setFinalQuery(query); + setOrderBy('flashsale-discount-desc'); + } + }; + checkIfPenawaran(); + }, [router.query]); + const collectIds = (category) => { const ids = []; function recurse(cat) { @@ -337,6 +355,7 @@ const ProductSearch = ({ const handleDeleteFilter = async (source, value) => { let params = { + penawaran: router.query.penawaran, q: router.query.q, orderBy: orderBy, brand: brandValues.join(','), @@ -364,6 +383,7 @@ const ProductSearch = ({ break; case 'delete': params = { + penawaran: router.query.penawaran, q: router.query.q, orderBy: orderBy, }; |
