diff options
| author | it-fixcomart <it@fixcomart.co.id> | 2024-11-15 14:33:42 +0700 |
|---|---|---|
| committer | it-fixcomart <it@fixcomart.co.id> | 2024-11-15 14:33:42 +0700 |
| commit | e5544ace96dd9a200ca5876b8e9ba837ad0a26ac (patch) | |
| tree | d0f1bb7711e2d93859e1d534551328b14a3ae78e /src-migrate | |
| parent | 0d3c0cf6a00ef81bfdb944490e48f16af41fc029 (diff) | |
| parent | 49f2e6a5612d000c3a740513c1a54b73bb656d77 (diff) | |
Merge branch 'new-release' into CR/redis
Diffstat (limited to 'src-migrate')
11 files changed, 680 insertions, 160 deletions
diff --git a/src-migrate/modules/cart/components/Item.tsx b/src-migrate/modules/cart/components/Item.tsx index 6ffbb524..ab2e7ce1 100644 --- a/src-migrate/modules/cart/components/Item.tsx +++ b/src-migrate/modules/cart/components/Item.tsx @@ -36,26 +36,29 @@ const CartItem = ({ item, editable = true, selfPicking}: Props) => { )} <div className='w-2' /> <div> - Selamat! Pembelian anda lebih hemat {' '} + Selamat! Pembelian anda lebih hemat{' '} <span className={style.savingAmt}> - Rp{formatCurrency((item.package_price || 0) * item.quantity - item.subtotal)} + Rp + {formatCurrency( + (item.package_price || 0) * item.quantity - item.subtotal + )} </span> </div> </div> )} <div className={style.mainProdWrapper}> - {editable && ( - <CartItemSelect item={item} /> - )} + {editable && <CartItemSelect item={item} />} <div className='w-4' /> <CartItem.Image item={item} /> <div className={style.details}> - {(item.is_in_bu) && (item.on_hand_qty >= item.quantity) && ( + {item?.available_quantity > 0 && ( <div className='text-[10px] text-red-500 italic'> - *Barang ini bisa di pickup maksimal pukul 16.00 + {item.quantity <= item?.available_quantity + ? '*Barang ini bisa di pickup maksimal pukul 16.00' + : `*${item?.available_quantity} Barang ini bisa di pickup maksimal pukul 16.00`} </div> )} <CartItem.Name item={item} /> diff --git a/src-migrate/modules/product-detail/components/AddToQuotation.tsx b/src-migrate/modules/product-detail/components/AddToQuotation.tsx new file mode 100644 index 00000000..f9b6c2b3 --- /dev/null +++ b/src-migrate/modules/product-detail/components/AddToQuotation.tsx @@ -0,0 +1,227 @@ +import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; +import style from '../styles/price-action.module.css'; +import { Button, Link, useToast } from '@chakra-ui/react'; +import { ArrowDownTrayIcon } from '@heroicons/react/24/outline'; +import product from 'next-seo/lib/jsonld/product'; +import { useRouter } from 'next/router'; +import { useEffect, useState } from 'react'; +import Image from '~/components/ui/image'; +import { getAuth } from '~/libs/auth'; +import { upsertUserCart } from '~/services/cart'; +import LazyLoad from 'react-lazy-load'; +import ProductSimilar from '../../../../src/lib/product/components/ProductSimilar'; +import { IProductDetail } from '~/types/product'; +import ImageNext from 'next/image'; +import { useProductCartContext } from '@/contexts/ProductCartContext'; +import { createSlug } from '~/libs/slug'; +import formatCurrency from '~/libs/formatCurrency'; +import { useProductDetail } from '../stores/useProductDetail'; + +type Props = { + variantId: number | null; + quantity?: number; + source?: 'buy' | 'add_to_cart'; + products: IProductDetail; +}; + +type Status = 'idle' | 'loading' | 'success'; + +const AddToQuotation = ({ + variantId, + quantity = 1, + source = 'add_to_cart', + products, +}: Props) => { + const auth = getAuth(); + const router = useRouter(); + const toast = useToast({ + position: 'top', + isClosable: true, + }); + + const { askAdminUrl } = useProductDetail(); + + const [product, setProducts] = useState(products); + const [status, setStatus] = useState<Status>('idle'); + const { + productCart, + setRefreshCart, + setProductCart, + refreshCart, + isLoading, + setIsloading, + } = useProductCartContext(); + + const productSimilarQuery = [ + product?.name, + `fq=-product_id_i:${product.id}`, + `fq=-manufacture_id_i:${product.manufacture?.id || 0}`, + ].join('&'); + const [addCartAlert, setAddCartAlert] = useState(false); + + const handleButton = async () => { + if (typeof auth !== 'object') { + const currentUrl = encodeURIComponent(router.asPath); + router.push(`/login?next=${currentUrl}`); + return; + } + + if (!variantId || isNaN(quantity) || typeof auth !== 'object') return; + if (status === 'success') return; + setStatus('loading'); + await upsertUserCart({ + userId: auth.id, + type: 'product', + id: variantId, + qty: quantity, + selected: true, + source: source, + qtyAppend: true, + }); + setStatus('idle'); + setRefreshCart(true); + setAddCartAlert(true); + + toast({ + title: 'Tambah ke keranjang', + description: 'Berhasil menambahkan barang ke keranjang belanja', + status: 'success', + duration: 3000, + isClosable: true, + position: 'top', + }); + + if (source === 'buy') { + router.push('/shop/quotation?source=buy'); + } + }; + useEffect(() => { + if (status === 'success') + setTimeout(() => { + setStatus('idle'); + }, 3000); + }, [status]); + + const btnConfig = { + add_to_cart: { + colorScheme: 'yellow', + + text: 'Keranjang', + }, + buy: { + colorScheme: 'red', + text: 'Beli', + }, + }; + + return ( + <div className='w-full'> + <Button + onClick={handleButton} + color={'red'} + colorScheme='white' + className='w-full border-2 p-2 gap-1 hover:bg-slate-100 flex items-center' + > + <ImageNext + src='/images/writing.png' + alt='penawaran instan' + className='' + width={25} + height={25} + /> + Penawaran Harga Instan + </Button> + <BottomPopup + className='!container' + title='Berhasil Ditambahkan' + active={addCartAlert} + close={() => { + setAddCartAlert(false); + }} + > + <div className='flex mt-4'> + <div className='w-[10%]'> + <ImageNext + src={product.image} + alt={product.name} + className='h-32 object-contain object-center w-full border border-gray_r-4' + width={80} + height={80} + /> + </div> + <div className='ml-3 flex flex-1 items-start font-medium justify-center flex-col gap-y-1'> + {!!product.manufacture.name ? ( + <Link + href={createSlug( + '/shop/brands/', + product.manufacture.name, + product.manufacture.id.toString() + )} + className=' hover:underline' + color={'red'} + > + {product.manufacture.name} + </Link> + ) : ( + '-' + )} + <p className='text-ellipsis overflow-hidden'>{product.name}</p> + <p>{product.code}</p> + {!!product.lowest_price && product.lowest_price.price > 0 && ( + <> + <div className='flex items-end gap-x-2'> + {product.lowest_price.discount_percentage > 0 && ( + <> + <div className='badge-solid-red'> + {Math.floor(product.lowest_price.discount_percentage)}% + </div> + <div className='text-gray_r-11 line-through text-[11px] sm:text-caption-2'> + Rp {formatCurrency(product.lowest_price.price || 0)} + </div> + </> + )} + <div className='text-danger-500 font-semibold'> + Rp{' '} + {formatCurrency(product.lowest_price.price_discount || 0)} + </div> + </div> + </> + )} + + {!!product.lowest_price && product.lowest_price.price === 0 && ( + <span> + Hubungi kami untuk dapatkan harga terbaik,{' '} + <Link + href={askAdminUrl} + target='_blank' + className='font-medium underline' + color={'red'} + > + klik disini + </Link> + </span> + )} + </div> + <div className='ml-3 flex items-center font-normal'> + <Link + href='/shop/cart' + className='flex-1 py-2 text-gray_r-12 btn-yellow' + > + Lihat Keranjang + </Link> + </div> + </div> + <div className='mt-8 mb-4'> + <div className='text-h-sm font-semibold mb-6'> + Kamu Mungkin Juga Suka + </div> + <LazyLoad> + <ProductSimilar query={productSimilarQuery} /> + </LazyLoad> + </div> + </BottomPopup> + </div> + ); +}; + +export default AddToQuotation; diff --git a/src-migrate/modules/product-detail/components/Information.tsx b/src-migrate/modules/product-detail/components/Information.tsx index 75ae3c41..b9f4be91 100644 --- a/src-migrate/modules/product-detail/components/Information.tsx +++ b/src-migrate/modules/product-detail/components/Information.tsx @@ -1,56 +1,226 @@ -import style from '../styles/information.module.css' +import { + AutoComplete, + AutoCompleteInput, + AutoCompleteItem, + AutoCompleteList, +} from '@choc-ui/chakra-autocomplete'; +import style from '../styles/information.module.css'; -import React from 'react' -import dynamic from 'next/dynamic' -import Link from 'next/link' -import { useQuery } from 'react-query' +import dynamic from 'next/dynamic'; +import Link from 'next/link'; +import { useEffect, useRef, useState } from 'react'; -import { IProductDetail } from '~/types/product' -import { IProductVariantSLA } from '~/types/productVariant' -import { createSlug } from '~/libs/slug' -import { getVariantSLA } from '~/services/productVariant' -import { formatToShortText } from '~/libs/formatNumber' +import currencyFormat from '@/core/utils/currencyFormat'; +import { InputGroup, InputRightElement } from '@chakra-ui/react'; +import { ChevronDownIcon } from '@heroicons/react/24/outline'; +import Image from 'next/image'; +import { formatToShortText } from '~/libs/formatNumber'; +import { createSlug } from '~/libs/slug'; +import { getVariantSLA } from '~/services/productVariant'; +import { IProductDetail } from '~/types/product'; +import { useProductDetail } from '../stores/useProductDetail'; -const Skeleton = dynamic(() => import('@chakra-ui/react').then((mod) => mod.Skeleton)) +const Skeleton = dynamic(() => + import('@chakra-ui/react').then((mod) => mod.Skeleton) +); type Props = { - product: IProductDetail -} + product: IProductDetail; +}; const Information = ({ product }: Props) => { - const querySLA = useQuery<IProductVariantSLA>({ - queryKey: ['variant-sla', product.variants[0]?.id], - queryFn: () => getVariantSLA(product.variants[0].id), - enabled: product.variant_total === 1 - }) + const { selectedVariant, setSelectedVariant, setSla, setActive, sla } = + useProductDetail(); - const sla = querySLA?.data + const [inputValue, setInputValue] = useState<string | null>( + selectedVariant?.code + ' - ' + selectedVariant?.attributes[0] + ); + const [disableFilter, setDisableFilter] = useState<boolean>(false); + const inputRef = useRef<HTMLInputElement>(null); + + const [variantOptions, setVariantOptions] = useState<any[]>( + product?.variants + ); + // let variantOptions = product?.variants; + + // const querySLA = useQuery<IProductVariantSLA>({ + // queryKey: ['variant-sla', selectedVariant?.id], + // queryFn: () => getVariantSLA(selectedVariant?.id), + // enabled: !!selectedVariant?.id, + // }); + // const sla = querySLA?.data; + + const getsla = async () => { + const querySLA = await getVariantSLA(selectedVariant?.id); + setSla(querySLA); + }; + + useEffect(() => { + if (selectedVariant) { + getsla(); + setInputValue( + selectedVariant?.code + + (selectedVariant?.attributes[0] + ? ' - ' + selectedVariant?.attributes[0] + : '') + ); + } + }, [selectedVariant]); + + const handleOnChange = (vals: any) => { + setDisableFilter(true); + let code = vals.replace(/\s-\s.*$/, '').trim(); + let variant = variantOptions.find((item) => item.code === code); + setSelectedVariant(variant); + setInputValue( + variant?.code + + (variant?.attributes[0] ? ' - ' + variant?.attributes[0] : '') + ); + if (variant) { + const filteredOptions = product?.variants.filter( + (item) => item !== variant + ); + const newOptions = [variant, ...filteredOptions]; + setVariantOptions(newOptions); + } + }; + + const handleOnKeyUp = (e: any) => { + setDisableFilter(false); + setInputValue(e.target.value); + }; return ( <div className={style['wrapper']}> + <div className='realtive mb-5'> + <label className='form-label mb-2 text-lg text-red-600'> + Pilih Variant * :{' '} + <span className='text-gray_r-9 text-sm'> + {product?.variant_total} Variants + </span>{' '} + </label> + <AutoComplete + disableFilter={disableFilter} + openOnFocus + className='form-input' + onChange={(vals) => handleOnChange(vals)} + > + <InputGroup> + <AutoCompleteInput + ref={inputRef} + value={inputValue as string} + onChange={(e) => handleOnKeyUp(e)} + onFocus={() => setDisableFilter(true)} + /> + <InputRightElement className='mr-4'> + <ChevronDownIcon + className='h-6 w-6 text-gray-500' + onClick={() => inputRef?.current?.focus()} + /> + </InputRightElement> + </InputGroup> + + <AutoCompleteList> + {variantOptions.map((option, cid) => ( + <AutoCompleteItem + key={`option-${cid}`} + value={ + option.code + + (option?.attributes[0] ? ' - ' + option?.attributes[0] : '') + } + _selected={ + option.id === selectedVariant?.id + ? { + bg: 'gray.300', + } + : undefined + } + textTransform='capitalize' + > + <div + key={cid} + className='flex gap-x-2 w-full justify-between px-3 items-center p-2' + > + <div className='text-small'> + {option.code + + (option?.attributes[0] + ? ' - ' + option?.attributes[0] + : '')} + </div> + <div + className={ + option?.price?.discount_percentage + ? 'flex gap-x-4 items-center justify-between' + : '' + } + > + {option?.price?.discount_percentage > 0 && ( + <> + <div className='badge-solid-red text-xs'> + {Math.floor(option?.price?.discount_percentage)}% + </div> + <div className='min-w-16 sm:min-w-24 text-gray_r-11 line-through text-[11px] sm:text-caption-2'> + {currencyFormat(option?.price?.price)} + </div> + </> + )} + <div className='min-w-20 sm:min-w-28 text-danger-500 font-semibold'> + {currencyFormat(option?.price?.price_discount)} + </div> + </div> + </div> + </AutoCompleteItem> + ))} + </AutoCompleteList> + </AutoComplete> + </div> + <div className={style['row']}> - <div className={style['label']}>SKU Number</div> - <div className={style['value']}>SKU-{product.id}</div> + <div className={style['label']}>Item Code</div> + <div className={style['value']}>{selectedVariant?.code}</div> </div> <div className={style['row']}> <div className={style['label']}>Manufacture</div> <div className={style['value']}> {!!product.manufacture.name ? ( <Link - href={createSlug('/shop/brands/', product.manufacture.name, product.manufacture.id.toString())} - className='text-danger-500 hover:underline' + href={createSlug( + '/shop/brands/', + product.manufacture.name, + product.manufacture.id.toString() + )} > - {product.manufacture.name} + <Image + height={50} + width={100} + src={product.manufacture.logo} + alt={product.manufacture.name} + className='h-8 object-fit' + /> </Link> - ) : '-'} + ) : ( + '-' + )} + </div> + </div> + <div className={style['row']}> + <div className={style['label']}>Berat Barang</div> + <div className={style['value']}> + {selectedVariant?.weight > 0 ? `${selectedVariant?.weight} Kg` : '-'} </div> </div> <div className={style['row']}> <div className={style['label']}>Terjual</div> - <div className={style['value']}>{product.qty_sold > 0 ? formatToShortText(product.qty_sold) : '-'}</div> + <div className={style['value']}> + {product.qty_sold > 0 ? formatToShortText(product.qty_sold) : '-'} + </div> + </div> + <div className={style['row']}> + <div className={style['label']}>Persiapan Barang</div> + <div className={style['value']}>{sla?.sla_date}</div> </div> </div> - ) -} + ); +}; -export default Information
\ No newline at end of file +export default Information; diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx index 9021264e..413c643a 100644 --- a/src-migrate/modules/product-detail/components/PriceAction.tsx +++ b/src-migrate/modules/product-detail/components/PriceAction.tsx @@ -1,12 +1,17 @@ import style from '../styles/price-action.module.css'; -import React, { useEffect } from 'react'; +import Image from 'next/image'; +import Link from 'next/link'; +import { useEffect, useState } from 'react'; import formatCurrency from '~/libs/formatCurrency'; import { IProductDetail } from '~/types/product'; import { useProductDetail } from '../stores/useProductDetail'; import AddToCart from './AddToCart'; -import Link from 'next/link'; +import AddToQuotation from './AddToQuotation'; import { getAuth } from '~/libs/auth'; +import useDevice from '@/core/hooks/useDevice'; +import odooApi from '~/libs/odooApi'; +import { Button, Skeleton } from '@chakra-ui/react'; type Props = { product: IProductDetail; @@ -22,27 +27,58 @@ const PriceAction = ({ product }: Props) => { askAdminUrl, isApproval, setIsApproval, + selectedVariant, + sla, } = useProductDetail(); - + const [qtyPickUp, setQtyPickUp] = useState(0); + const { isDesktop, isMobile } = useDevice(); useEffect(() => { - setActive(product.variants[0]) - if(product.variants.length > 2 && product.variants[0].price.price === 0){ - const variants = product.variants + setActive(selectedVariant); + if (product.variants.length > 2 && product.variants[0].price.price === 0) { + const variants = product.variants; for (let i = 0; i < variants.length; i++) { - if(variants[i].price.price > 0){ - setActive(variants[i]) + if (variants[i].price.price > 0) { + setActive(variants[i]); break; } } } - - }, [product, setActive]); + }, [product, setActive, selectedVariant]); + useEffect(() => { + const fetchData = async () => { + const qty_available = await odooApi( + 'GET', + `/api/v1/product_variant/${selectedVariant.id}/qty_available` + ); + + setQtyPickUp(qty_available?.qty); + }; + fetchData(); + }, [selectedVariant]); + useEffect(() => { + setQuantityInput('1'); + }, [selectedVariant]); + + let voucherPastiHemat = 0; + + if ( + product?.voucher_pasti_hemat + ? product?.voucher_pasti_hemat.length + : voucherPastiHemat > 0 + ) { + const stringVoucher = product?.voucher_pasti_hemat[0]; + const validJsonString = stringVoucher.replace(/'/g, '"'); + voucherPastiHemat = JSON.parse(validJsonString); + } return ( <div - className='block md:sticky top-[150px] bg-white py-0 md:py-6 z-10' + className={`block md:sticky md:top-[150px] md:py-6 fixed bottom-0 left-0 right-0 bg-white p-2 z-10 ${ + isMobile && + 'pb-6 pt-6 rounded-lg shadow-[rgba(0,0,4,0.1)_0px_-4px_4px_0px] ' + }`} id='price-section' > {!!activePrice && activePrice.price > 0 && ( @@ -84,18 +120,69 @@ const PriceAction = ({ product }: Props) => { )} <div className='h-4' /> + <div className='flex gap-x-5 items-center'> + <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={style['quantity-input']} + /> + <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={sla} + h='21px' + // w={16} + className={sla?.qty < 10 ? 'text-red-600 font-medium' : ''} + > + Stock : {sla?.qty}{' '} + </Skeleton> + {/* <span className={sla?.qty < 10 ? 'text-red-600 font-medium' : ''}> + {' '} + </span> */} + </div> + <div> + {product?.is_in_bu && ( + <Link href='/panduan-pick-up-service' className='group'> + <Image + src='/images/PICKUP-NOW.png' + className='group-hover:scale-105 transition-transform duration-200' + alt='pickup now' + width={100} + height={12} + /> + </Link> + )} + </div> + </div> + {qtyPickUp > 0 && ( + <div className='text-[12px] mt-1 text-red-500 italic'> + * {qtyPickUp} barang bisa di pickup + </div> + )} + <div className='h-4' /> - <div className={style['action-wrapper']}> - <label htmlFor='quantity' className='hidden'> - Quantity - </label> - <input - type='number' - id='quantity' - value={quantityInput} - onChange={(e) => setQuantityInput(e.target.value)} - className={style['quantity-input']} - /> + <div className={`${style['action-wrapper']}`}> <AddToCart products={product} variantId={activeVariantId} @@ -110,6 +197,14 @@ const PriceAction = ({ product }: Props) => { /> )} </div> + <div className='mt-4'> + <AddToQuotation + source='buy' + products={product} + variantId={activeVariantId} + quantity={Number(quantityInput)} + /> + </div> </div> ); }; diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx index e4555913..b036cc2d 100644 --- a/src-migrate/modules/product-detail/components/ProductDetail.tsx +++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx @@ -1,46 +1,52 @@ -import style from '../styles/product-detail.module.css' - -import Link from 'next/link' -import { useRouter } from 'next/router' -import { useEffect } from 'react' - -import { Button } from '@chakra-ui/react' -import { MessageCircleIcon, Share2Icon } from 'lucide-react' -import { LazyLoadComponent } from 'react-lazy-load-image-component' -import { RWebShare } from 'react-web-share' - -import useDevice from '@/core/hooks/useDevice' -import { whatsappUrl } from '~/libs/whatsappUrl' -import ProductPromoSection from '~/modules/product-promo/components/Section' -import { IProductDetail } from '~/types/product' -import { useProductDetail } from '../stores/useProductDetail' -import AddToWishlist from './AddToWishlist' -import Breadcrumb from './Breadcrumb' -import ProductImage from './Image' -import Information from './Information' -import PriceAction from './PriceAction' -import SimilarBottom from './SimilarBottom' -import SimilarSide from './SimilarSide' -import VariantList from './VariantList' -import { getAuth } from '~/libs/auth' - -import { gtagProductDetail } from '@/core/utils/googleTag' +import style from '../styles/product-detail.module.css'; + +import Link from 'next/link'; +import { useRouter } from 'next/router'; +import { useEffect } from 'react'; + +import { Button } from '@chakra-ui/react'; +import { MessageCircleIcon, Share2Icon } from 'lucide-react'; +import { LazyLoadComponent } from 'react-lazy-load-image-component'; +import { RWebShare } from 'react-web-share'; + +import useDevice from '@/core/hooks/useDevice'; +import { getAuth } from '~/libs/auth'; +import { whatsappUrl } from '~/libs/whatsappUrl'; +import ProductPromoSection from '~/modules/product-promo/components/Section'; +import { IProductDetail } from '~/types/product'; +import { useProductDetail } from '../stores/useProductDetail'; +import AddToWishlist from './AddToWishlist'; +import Breadcrumb from './Breadcrumb'; +import ProductImage from './Image'; +import Information from './Information'; +import PriceAction from './PriceAction'; +import SimilarBottom from './SimilarBottom'; +import SimilarSide from './SimilarSide'; + +import { gtagProductDetail } from '@/core/utils/googleTag'; type Props = { - product: IProductDetail -} + product: IProductDetail; +}; -const SELF_HOST = process.env.NEXT_PUBLIC_SELF_HOST +const SELF_HOST = process.env.NEXT_PUBLIC_SELF_HOST; const ProductDetail = ({ product }: Props) => { - const { isDesktop, isMobile } = useDevice() - const router = useRouter() - const auth = getAuth() - const { setAskAdminUrl, askAdminUrl, activeVariantId, setIsApproval, isApproval } = useProductDetail() + const { isDesktop, isMobile } = useDevice(); + const router = useRouter(); + const auth = getAuth(); + const { + setAskAdminUrl, + askAdminUrl, + activeVariantId, + setIsApproval, + isApproval, + setSelectedVariant, + } = useProductDetail(); useEffect(() => { gtagProductDetail(product); - },[product]) + }, [product]); useEffect(() => { const createdAskUrl = whatsappUrl({ @@ -48,76 +54,43 @@ const ProductDetail = ({ product }: Props) => { payload: { manufacture: product.manufacture.name, productName: product.name, - url: process.env.NEXT_PUBLIC_SELF_HOST + router.asPath + url: process.env.NEXT_PUBLIC_SELF_HOST + router.asPath, }, - fallbackUrl: router.asPath - }) + fallbackUrl: router.asPath, + }); - setAskAdminUrl(createdAskUrl) - }, [router.asPath, product.manufacture.name, product.name, setAskAdminUrl]) + setAskAdminUrl(createdAskUrl); + }, [router.asPath, product.manufacture.name, product.name, setAskAdminUrl]); useEffect(() => { if (typeof auth === 'object') { setIsApproval(auth?.feature?.soApproval); } + setSelectedVariant(product?.variants[0]) }, []); return ( <> <div className='md:flex md:flex-wrap'> - <div className="w-full mb-4 md:mb-0 px-4 md:px-0"> + <div className='w-full mb-4 md:mb-0 px-4 md:px-0'> <Breadcrumb id={product.id} name={product.name} /> </div> <div className='md:w-9/12 md:flex md:flex-col md:pr-4 md:pt-6'> <div className='md:flex md:flex-wrap'> - <div className="md:w-4/12"> + <div className='md:w-4/12'> <ProductImage product={product} /> </div> <div className='md:w-8/12 px-4 md:pl-6'> <div className='h-6 md:h-0' /> - <h1 className={style['title']}> - {product.name} - </h1> + <h1 className={style['title']}>{product.name}</h1> - <div className='h-6 md:h-8' /> + <div className='h-3 md:h-0' /> <Information product={product} /> <div className='h-6' /> - - <div className="flex gap-x-5"> - <Button - as={Link} - href={askAdminUrl} - variant='link' - target='_blank' - colorScheme='gray' - leftIcon={<MessageCircleIcon size={18} />} - > - Ask Admin - </Button> - - <AddToWishlist productId={product.id} /> - - <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> @@ -131,38 +104,72 @@ const ProductDetail = ({ product }: Props) => { <div className='h-4 md:h-10' /> {!!activeVariantId && !isApproval && <ProductPromoSection product={product} productId={activeVariantId} />} - <div className={style['section-card']}> + {/* <div className={style['section-card']}> <h2 className={style['heading']}> Variant ({product.variant_total}) </h2> <div className='h-4' /> <VariantList variants={product.variants} /> - </div> + </div> */} <div className='h-0 md:h-6' /> <div className={style['section-card']}> - <h2 className={style['heading']}> - Informasi Produk - </h2> + <h2 className={style['heading']}>Informasi Produk</h2> <div className='h-4' /> <div className={style['description']} - dangerouslySetInnerHTML={{ __html: !product.description || product.description == '<p><br></p>' ? 'Belum ada deskripsi' : product.description }} + dangerouslySetInnerHTML={{ + __html: + !product.description || product.description == '<p><br></p>' + ? 'Belum ada deskripsi' + : product.description, + }} /> </div> </div> </div> {isDesktop && ( - <div className="md:w-3/12"> + <div className='md:w-3/12'> <PriceAction product={product} /> + <div className='flex gap-x-5 items-center justify-center'> + <Button + as={Link} + href={askAdminUrl} + variant='link' + target='_blank' + colorScheme='gray' + leftIcon={<MessageCircleIcon size={18} />} + > + Ask Admin + </Button> + + <span>|</span> + + <AddToWishlist productId={product.id} /> + + <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 className='h-6' /> - - <div className={style['heading']}> - Produk Serupa - </div> + <div className={style['heading']}>Produk Serupa</div> <div className='h-4' /> @@ -171,9 +178,7 @@ const ProductDetail = ({ product }: Props) => { )} <div className='md:w-full pt-4 md:py-10 px-4 md:px-0'> - <div className={style['heading']}> - Kamu Mungkin Juga Suka - </div> + <div className={style['heading']}>Kamu Mungkin Juga Suka</div> <div className='h-6' /> @@ -185,7 +190,7 @@ const ProductDetail = ({ product }: Props) => { <div className='h-6 md:h-0' /> </div> </> - ) -} + ); +}; -export default ProductDetail
\ No newline at end of file +export default ProductDetail; diff --git a/src-migrate/modules/product-detail/stores/useProductDetail.ts b/src-migrate/modules/product-detail/stores/useProductDetail.ts index eb409930..dee6b342 100644 --- a/src-migrate/modules/product-detail/stores/useProductDetail.ts +++ b/src-migrate/modules/product-detail/stores/useProductDetail.ts @@ -7,6 +7,8 @@ type State = { quantityInput: string; askAdminUrl: string; isApproval : boolean; + selectedVariant : any; + sla : any; }; type Action = { @@ -14,6 +16,8 @@ type Action = { setQuantityInput: (value: string) => void; setAskAdminUrl: (url: string) => void; setIsApproval : (value : boolean) => void; + setSelectedVariant : (value : any) => void; + setSla : (value : any) => void; }; export const useProductDetail = create<State & Action>((set, get) => ({ @@ -22,6 +26,8 @@ export const useProductDetail = create<State & Action>((set, get) => ({ quantityInput: '1', askAdminUrl: '', isApproval : false, + selectedVariant: null, + sla : null, setActive: (variant) => { set({ activeVariantId: variant?.id, activePrice: variant?.price }); }, @@ -33,5 +39,11 @@ export const useProductDetail = create<State & Action>((set, get) => ({ }, setIsApproval : (value : boolean) => { set({ isApproval : value }) + }, + setSelectedVariant : (value : any) => { + set({ selectedVariant : value }) + }, + setSla : (value : any ) => { + set({ sla : value }) } })); diff --git a/src-migrate/modules/product-detail/styles/information.module.css b/src-migrate/modules/product-detail/styles/information.module.css index c9b29020..5aa64fe5 100644 --- a/src-migrate/modules/product-detail/styles/information.module.css +++ b/src-migrate/modules/product-detail/styles/information.module.css @@ -3,11 +3,11 @@ } .row { - @apply flex p-3 rounded; + @apply flex p-4 rounded-sm bg-gray-100; } .row:nth-child(odd) { - @apply bg-gray-100; + @apply bg-white; } .label { diff --git a/src-migrate/modules/product-detail/styles/price-action.module.css b/src-migrate/modules/product-detail/styles/price-action.module.css index 651de958..cea50bff 100644 --- a/src-migrate/modules/product-detail/styles/price-action.module.css +++ b/src-migrate/modules/product-detail/styles/price-action.module.css @@ -8,7 +8,10 @@ @apply flex gap-x-2.5; } .quantity-input { - @apply px-2 rounded text-center border border-gray-300 w-14 h-10 focus:outline-none; + @apply w-24 h-10 text-center border border-gray-300 rounded focus:outline-none; + /* Padding di kiri dan kanan untuk memberi ruang bagi tombol */ + padding-left: 2rem; + padding-right: 2rem; } .contact-us { diff --git a/src-migrate/pages/shop/cart/index.tsx b/src-migrate/pages/shop/cart/index.tsx index c5386c91..70a28073 100644 --- a/src-migrate/pages/shop/cart/index.tsx +++ b/src-migrate/pages/shop/cart/index.tsx @@ -35,6 +35,8 @@ const CartPage = () => { const [hasChanged, setHasChanged] = useState(false); const prevCartRef = useRef<CartItem[] | null>(null); + console.log('ini cart', cart); + useEffect(() => { const handleScroll = () => { setIsTop(window.scrollY < 200); @@ -84,19 +86,19 @@ const CartPage = () => { const hasSelectedPromo = useMemo(() => { if (!cart) return false; - return cart.products.some( + return cart?.products?.some( (item) => item.cart_type === 'promotion' && item.selected ); }, [cart]); const hasSelected = useMemo(() => { if (!cart) return false; - return cart.products.some((item) => item.selected); + return cart?.products?.some((item) => item.selected); }, [cart]); const hasSelectNoPrice = useMemo(() => { if (!cart) return false; - return cart.products.some( + return cart?.products?.some( (item) => item.selected && item.price.price_discount === 0 ); }, [cart]); @@ -230,7 +232,7 @@ const CartPage = () => { </div> <div className={style['items']}> - {cart?.products.map((item) => ( + {cart?.products?.map((item) => ( <CartItemModule key={item.id} item={item} /> ))} diff --git a/src-migrate/types/cart.ts b/src-migrate/types/cart.ts index a3115103..05fdcadb 100644 --- a/src-migrate/types/cart.ts +++ b/src-migrate/types/cart.ts @@ -34,6 +34,7 @@ export type CartItem = { stock: number; is_in_bu: boolean; on_hand_qty: number; + available_quantity: number; weight: number; attributes: string[]; parent: { diff --git a/src-migrate/types/product.ts b/src-migrate/types/product.ts index e43a5ebb..85ea702a 100644 --- a/src-migrate/types/product.ts +++ b/src-migrate/types/product.ts @@ -32,7 +32,9 @@ export interface IProduct { manufacture: { id: number; name: string; + logo: string; }; + voucher_pasti_hemat : any; } export interface IProductDetail extends IProduct { |
