diff options
| author | Rafi Zadanly <zadanlyr@gmail.com> | 2024-01-18 16:24:54 +0700 |
|---|---|---|
| committer | Rafi Zadanly <zadanlyr@gmail.com> | 2024-01-18 16:24:54 +0700 |
| commit | 5ac82c38ed3ec4db1fe4ae96e7493a55154716ef (patch) | |
| tree | f493df6c4c9d96b6efa86896fd6d27d2995726c4 | |
| parent | 7298d8e811a68cb92c02a7d810f412498d1609d8 (diff) | |
Update product detail page
| -rw-r--r-- | src-migrate/modules/product-detail/components/Image.tsx | 69 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/components/PriceAction.tsx | 18 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/components/VariantList.tsx | 10 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/styles/image.module.css | 35 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/styles/price-action.module.css | 8 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/styles/variant-list.module.css | 8 | ||||
| -rw-r--r-- | src-migrate/services/product.ts | 1 | ||||
| -rw-r--r-- | src-migrate/types/product.ts | 5 | ||||
| -rw-r--r-- | src/utils/solrMapping.js | 94 |
9 files changed, 189 insertions, 59 deletions
diff --git a/src-migrate/modules/product-detail/components/Image.tsx b/src-migrate/modules/product-detail/components/Image.tsx index 6ec715d8..2ab3ff59 100644 --- a/src-migrate/modules/product-detail/components/Image.tsx +++ b/src-migrate/modules/product-detail/components/Image.tsx @@ -1,27 +1,53 @@ -import React from 'react' +import style from '../styles/image.module.css'; + +import React, { useEffect, useState } from 'react' import { InfoIcon } from 'lucide-react' import { Tooltip } from '@chakra-ui/react' import { IProductDetail } from '~/types/product' import ImageUI from '~/components/ui/image' +import moment from 'moment'; type Props = { product: IProductDetail } const Image = ({ product }: Props) => { + const flashSale = product.flash_sale + + const [count, setCount] = useState(flashSale?.remaining_time || 0); + + useEffect(() => { + let interval: NodeJS.Timeout; + + if (flashSale?.remaining_time && flashSale.remaining_time > 0) { + setCount(flashSale.remaining_time); + + interval = setInterval(() => { + setCount((prevCount) => prevCount - 1); + }, 1000); + } + + return () => { + clearInterval(interval); + }; + }, [flashSale?.remaining_time]); + + const duration = moment.duration(count, 'seconds') + return ( - <div className='h-[250px] md:h-[340px] flex items-center justify-center border border-gray-200 rounded-lg p-4 relative'> + <div className={style['wrapper']}> <ImageUI src={product.image || '/images/noimage.jpeg'} alt={product.name} width={256} height={256} - className='object-contain object-center h-full w-full' + className={style['image']} loading='eager' priority /> - <div className='absolute hidden md:block top-4 right-4'> + + <div className={style['absolute-info']}> <Tooltip placement='bottom-end' label='Gambar atau foto berperan sebagai ilustrasi produk. Kadang tidak sesuai dengan kondisi terbaru dengan berbagai perubahan dan perbaikan. Hubungi admin kami untuk informasi yang lebih baik perihal gambar.' @@ -31,6 +57,41 @@ const Image = ({ product }: Props) => { </div> </Tooltip> </div> + + {flashSale.remaining_time > 0 && ( + <div className='absolute bottom-0 w-full h-14'> + <div className="relative w-full h-full"> + <ImageUI + src='/images/GAMBAR-BG-FLASH-SALE.jpg' + alt='Flash Sale Indoteknik' + width={200} + height={100} + className={style['flashsale-bg']} + /> + + <div className={style['flashsale']}> + <div className='flex items-center gap-x-3'> + <div className={style['disc-badge']}>{Math.floor(product.lowest_price.discount_percentage)}%</div> + <div className={style['flashsale-text']}> + <ImageUI + src='/images/ICON_FLASH_SALE_WEBSITE_INDOTEKNIK.svg' + alt='Icon Flash Sale' + width={20} + height={20} + /> + {product.flash_sale.tag} + </div> + </div> + <div className={style['countdown']}> + <span>{duration.hours().toString().padStart(2, '0')}</span> + <span>{duration.minutes().toString().padStart(2, '0')}</span> + <span>{duration.seconds().toString().padStart(2, '0')}</span> + </div> + </div> + + </div> + </div> + )} </div> ) } diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx index cade21b8..f25847a5 100644 --- a/src-migrate/modules/product-detail/components/PriceAction.tsx +++ b/src-migrate/modules/product-detail/components/PriceAction.tsx @@ -29,13 +29,25 @@ const PriceAction = ({ product }: Props) => { <div className='block md:sticky top-[150px] bg-white py-0 md:py-6 z-10' id='price-section'> {!!activePrice && activePrice.price > 0 && ( <> - <div className={style['main-price']}> - Rp {formatCurrency(activePrice.price || 0)} + <div className='flex items-end gap-x-2'> + {activePrice.discount_percentage > 0 && ( + <> + <div className={style['disc-badge']}> + {Math.floor(activePrice.discount_percentage)}% + </div> + <div className={style['disc-price']}> + Rp {formatCurrency(activePrice.price || 0)} + </div> + </> + )} + <div className={style['main-price']}> + Rp {formatCurrency(activePrice.price_discount || 0)} + </div> </div> <div className='h-1' /> <div className={style['secondary-text']}> Termasuk PPN: {' '} - Rp {formatCurrency(Math.round(activePrice.price * 1.11))} + Rp {formatCurrency(Math.round(activePrice.price_discount * 1.11))} </div> </> )} diff --git a/src-migrate/modules/product-detail/components/VariantList.tsx b/src-migrate/modules/product-detail/components/VariantList.tsx index 1da478e7..3d5b9b74 100644 --- a/src-migrate/modules/product-detail/components/VariantList.tsx +++ b/src-migrate/modules/product-detail/components/VariantList.tsx @@ -89,8 +89,14 @@ const Row = ({ variant }: { variant: IProductVariantDetail }) => { {variant.weight > 0 ? `${variant.weight} Kg` : '-'} </div> <div className='w-3/12'> - {variant.price.price > 0 && `Rp ${formatCurrency(variant.price.price)}`} - {variant.price.price === 0 && '-'} + {variant.price.discount_percentage > 0 && ( + <div className='flex items-center gap-x-1'> + <div className={style['disc-badge']}>{Math.floor(variant.price.discount_percentage)}%</div> + <div className={style['disc-price']}>Rp {formatCurrency(variant.price.price)}</div> + </div> + )} + {variant.price.price_discount > 0 && `Rp ${formatCurrency(variant.price.price_discount)}`} + {variant.price.price_discount === 0 && '-'} </div> <div className='w-1/12 sticky right-0 bg-white md:bg-transparent'> <Button diff --git a/src-migrate/modules/product-detail/styles/image.module.css b/src-migrate/modules/product-detail/styles/image.module.css new file mode 100644 index 00000000..e472fe8d --- /dev/null +++ b/src-migrate/modules/product-detail/styles/image.module.css @@ -0,0 +1,35 @@ +.wrapper { + @apply h-[250px] md:h-[340px] flex items-center justify-center border border-gray-200 rounded-lg p-4 relative; +} + +.image { + @apply object-contain object-center h-full w-full; +} + +.absolute-info { + @apply absolute hidden md:block top-4 right-4; +} + +.disc-badge { + @apply bg-warning-500 py-1 px-3 w-fit font-semibold rounded-full; +} + +.countdown { + @apply flex gap-x-1; +} + +.countdown span { + @apply py-0.5 w-8 bg-warning-500 rounded-md text-center; +} + +.flashsale-text { + @apply flex items-center gap-x-2 text-white font-medium text-caption-1; +} + +.flashsale-bg { + @apply absolute top-0 w-full h-full object-cover object-center z-10; +} + +.flashsale { + @apply absolute top-0 w-full h-full z-20 flex items-center justify-between px-3; +} 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 a8ec0ed3..651de958 100644 --- a/src-migrate/modules/product-detail/styles/price-action.module.css +++ b/src-migrate/modules/product-detail/styles/price-action.module.css @@ -14,3 +14,11 @@ .contact-us { @apply text-danger-500 font-medium underline; } + +.disc-badge { + @apply bg-danger-500 px-2 py-1.5 rounded text-white text-caption-2; +} + +.disc-price { + @apply line-through text-gray-600 text-caption-2; +} diff --git a/src-migrate/modules/product-detail/styles/variant-list.module.css b/src-migrate/modules/product-detail/styles/variant-list.module.css index a56822c1..6d46df84 100644 --- a/src-migrate/modules/product-detail/styles/variant-list.module.css +++ b/src-migrate/modules/product-detail/styles/variant-list.module.css @@ -25,3 +25,11 @@ .stock-ready { @apply bg-green-50 border border-green-500 text-green-800; } + +.disc-badge { + @apply bg-danger-500 p-1 rounded text-white text-caption-2; +} + +.disc-price { + @apply text-caption-2 line-through text-gray-600; +} diff --git a/src-migrate/services/product.ts b/src-migrate/services/product.ts index 4ef027e1..fe415d11 100644 --- a/src-migrate/services/product.ts +++ b/src-migrate/services/product.ts @@ -15,7 +15,6 @@ export const getProductById = async ( .then((res) => res.json()) .then((res) => { if (res.length > 0) return snakeCase(res[0]) as IProductDetail; - return null; }); }; diff --git a/src-migrate/types/product.ts b/src-migrate/types/product.ts index b411224b..08de98e0 100644 --- a/src-migrate/types/product.ts +++ b/src-migrate/types/product.ts @@ -17,10 +17,7 @@ export interface IProduct { }[]; flash_sale: { id: string; - remaining_time: { - remaining_time: number; - is_flashsale: boolean; - }; + remaining_time: number; tag: string; }; lowest_price: { diff --git a/src/utils/solrMapping.js b/src/utils/solrMapping.js index 41d24b53..7e887253 100644 --- a/src/utils/solrMapping.js +++ b/src/utils/solrMapping.js @@ -1,17 +1,18 @@ export const productMappingSolr = (products, pricelist) => { return products.map((product) => { - let price = product.price_tier1_v2_f || 0 - let priceDiscount = 0 - let discountPercentage = 0 + let price = product.price_tier1_v2_f || 0; + let priceDiscount = price; + let discountPercentage = 0; if (pricelist && product?.[`price_${pricelist}_f`] < price) { - price = product?.[`price_${pricelist}_f`] || 0 + price = product?.[`price_${pricelist}_f`] || 0; + priceDiscount = price; } - if (product?.flashsale_id_i > 0 ) { - price = product?.flashsale_base_price_f || 0 - priceDiscount = product?.flashsale_price_f || 0 - discountPercentage = product?.flashsale_discount_f || 0 + if (product?.flashsale_id_i > 0) { + price = product?.flashsale_base_price_f || 0; + priceDiscount = product?.flashsale_price_f || 0; + discountPercentage = product?.flashsale_discount_f || 0; } let productMapped = { @@ -29,48 +30,48 @@ export const productMappingSolr = (products, pricelist) => { categories: [], flashSale: { id: product?.flashsale_id_i, - remainingTime: flashsaleTime(product?.flashsale_end_date_s)?.remainingTime, + remainingTime: flashsaleTime(product?.flashsale_end_date_s) + ?.remainingTime, name: product?.product?.flashsale_name_s, - tag: product?.flashsale_tag_s || 'FLASH SALE' + tag: product?.flashsale_tag_s || 'FLASH SALE', }, - qtySold : product?.qty_sold_f || 0 - } + qtySold: product?.qty_sold_f || 0, + }; if (product.manufacture_id_i && product.manufacture_name_s) { productMapped.manufacture = { id: product.manufacture_id_i || '', name: product.manufacture_name_s || '', imagePromotion1: product.image_promotion_1_s || '', - imagePromotion2: product.image_promotion_2_s || '' - } + imagePromotion2: product.image_promotion_2_s || '', + }; } productMapped.categories = [ { id: product.category_id_i || '', - name: product.category_name_s || '' - } - ] - return productMapped - }) -} + name: product.category_name_s || '', + }, + ]; + return productMapped; + }); +}; export const variantsMappingSolr = (parent, products, pricelist) => { return products.map((product) => { - let price = product.price_tier1_v2_f || 0 - let priceDiscount = 0 - let discountPercentage = 0 + let price = product.price_tier1_v2_f || 0; + let priceDiscount = price; + let discountPercentage = 0; - if (pricelist) { - if (product?.[`price_${pricelist}_f`] < price) { - price = product?.[`price_${pricelist}_f`] || 0 - } + if (pricelist && product?.[`price_${pricelist}_f`] < price) { + price = product?.[`price_${pricelist}_f`] || 0; + priceDiscount = price; } if (product?.flashsale_id_i > 0 && product?.flashsale_price_f < price) { - price = product?.flashsale_base_price_f || 0 - priceDiscount = product?.flashsale_price_f || 0 - discountPercentage = product?.flashsale_discount_f || 0 + price = product?.flashsale_base_price_f || 0; + priceDiscount = product?.flashsale_price_f || 0; + discountPercentage = product?.flashsale_discount_f || 0; } let productMapped = { @@ -87,30 +88,33 @@ export const variantsMappingSolr = (parent, products, pricelist) => { weight: product.weight_f || 0, manufacture: {}, parent: {}, - qtySold : product?.qty_sold_f || 0 - } + qtySold: product?.qty_sold_f || 0, + }; if (product.manufacture_id_i && product.manufacture_name_s) { productMapped.manufacture = { id: product.manufacture_id_i || '', - name: product.manufacture_name_s || '' - } + name: product.manufacture_name_s || '', + }; } productMapped.parent = { id: parent.product_id_i || '', image: parent.image_s || '', - name: parent.name_s || '' - } - return productMapped - }) -} + name: parent.name_s || '', + }; + return productMapped; + }); +}; const flashsaleTime = (endDate) => { - const flashsaleEndDate = new Date(endDate) - const currentTime = new Date() + const flashsaleEndDate = new Date(endDate); + const currentTime = new Date(); - const timeDifferenceInMillis = flashsaleEndDate - currentTime - const timeDifferenceInSeconds = timeDifferenceInMillis / 1000 + const timeDifferenceInMillis = flashsaleEndDate - currentTime; + const timeDifferenceInSeconds = timeDifferenceInMillis / 1000; - return { remainingTime: timeDifferenceInSeconds, isFlashSale: flashsaleEndDate > currentTime } -} + return { + remainingTime: timeDifferenceInSeconds, + isFlashSale: flashsaleEndDate > currentTime, + }; +}; |
