summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIT Fixcomart <it@fixcomart.co.id>2024-08-02 03:25:54 +0000
committertrisusilo <tri.susilo@altama.co.id>2024-08-02 03:25:54 +0000
commit99a5965e1e5320e9acbc9718c3642ffae85bec57 (patch)
tree6a9aeca9b6833d7b87d0ea0a3d79d6bd15862751
parent12c7841770052aefceda899db52b3e6b6d0b5e92 (diff)
parent35204954ac02efd1497715dec3d2695fdd7976f8 (diff)
Merged in Feature/all-promotion (pull request #202)
Feature/all promotion Approved-by: trisusilo
-rw-r--r--public/images/ICON PROMO DISKON.svg13
-rw-r--r--public/images/icon_promo/angklung.svg9
-rw-r--r--public/images/icon_promo/barong.svg9
-rw-r--r--public/images/icon_promo/diskon.svg9
-rw-r--r--public/images/icon_promo/silat.svg9
-rw-r--r--public/images/reject.pngbin0 -> 1275897 bytes
-rw-r--r--src-migrate/constants/menu.ts3
-rw-r--r--src-migrate/hooks/useUtmSource.ts2
-rw-r--r--src-migrate/modules/cart/components/Item.tsx20
-rw-r--r--src-migrate/modules/cart/components/ItemSelect.tsx33
-rw-r--r--src-migrate/modules/footer-banner/index.tsx2
-rw-r--r--src-migrate/modules/product-promo/components/Card.tsx6
-rw-r--r--src-migrate/modules/product-promo/styles/card.module.css2
-rw-r--r--src-migrate/modules/promo/components/FlashSale.tsx20
-rw-r--r--src-migrate/modules/promo/components/Hero.tsx105
-rw-r--r--src-migrate/modules/promo/components/HeroDiskon.tsx137
-rw-r--r--src-migrate/modules/promo/components/PromoList.tsx135
-rw-r--r--src-migrate/modules/promo/components/PromotinProgram.jsx134
-rw-r--r--src-migrate/modules/promo/components/Voucher.tsx160
-rw-r--r--src-migrate/modules/promo/components/promoStore.js16
-rw-r--r--src-migrate/modules/promo/styles/hero.module.css27
-rw-r--r--src-migrate/modules/promo/styles/voucher.module.css43
-rw-r--r--src-migrate/modules/side-banner/index.tsx10
-rw-r--r--src-migrate/pages/shop/cart/index.tsx35
-rw-r--r--src-migrate/pages/shop/promo/index.tsx38
-rw-r--r--src-migrate/services/promotionProgram.ts8
-rw-r--r--src-migrate/services/voucher.ts8
-rw-r--r--src-migrate/types/banner.ts6
-rw-r--r--src-migrate/types/cart.ts1
-rw-r--r--src-migrate/types/promotionProgram.ts2
-rw-r--r--src-migrate/types/voucher.ts8
-rw-r--r--src/api/promoApi.js8
-rw-r--r--src/core/components/elements/Footer/PromoOffer.tsx112
-rw-r--r--src/core/components/elements/Footer/style/promoOffer.module.css39
-rw-r--r--src/core/components/elements/Navbar/NavbarDesktop.jsx45
-rw-r--r--src/core/components/elements/Navbar/style/NavbarDesktop.module.css14
-rw-r--r--src/core/components/elements/Sidebar/Sidebar.jsx7
-rw-r--r--src/lib/checkout/api/checkoutApi.js34
-rw-r--r--src/lib/checkout/api/getVoucher.js37
-rw-r--r--src/lib/checkout/components/Checkout.jsx219
-rw-r--r--src/lib/home/components/PreferredBrand.jsx5
-rw-r--r--src/lib/home/components/PromotionProgram.jsx12
-rw-r--r--src/lib/quotation/components/Quotation.jsx62
-rw-r--r--src/lib/transaction/api/rejectProductApi.js9
-rw-r--r--src/lib/transaction/components/Transaction.jsx499
-rw-r--r--src/lib/variant/components/VariantCard.jsx99
-rw-r--r--src/pages/my/recomendation/components/products-recomendatison.jsx10
-rw-r--r--src/pages/shop/promo/[slug].tsx8
-rw-r--r--src/pages/shop/promo/index.jsx40
-rw-r--r--src/pages/shop/promo/index.tsx186
50 files changed, 1962 insertions, 493 deletions
diff --git a/public/images/ICON PROMO DISKON.svg b/public/images/ICON PROMO DISKON.svg
new file mode 100644
index 00000000..f7c16ca8
--- /dev/null
+++ b/public/images/ICON PROMO DISKON.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 24.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ viewBox="0 0 163 51" style="enable-background:new 0 0 163 51;" xml:space="preserve">
+<style type="text/css">
+ .st0{fill:#ED1E27;}
+ .st1{fill:#FFFFFF;}
+ .st2{font-family:'ProximaNova-Extrabld';}
+ .st3{font-size:30.2617px;}
+</style>
+<polygon class="st0" points="163,48.3 114.7,0 163,0 "/>
+<text transform="matrix(1 0 0 1 137.5257 24.7005)" class="st1 st2 st3">%</text>
+</svg>
diff --git a/public/images/icon_promo/angklung.svg b/public/images/icon_promo/angklung.svg
new file mode 100644
index 00000000..a650d816
--- /dev/null
+++ b/public/images/icon_promo/angklung.svg
@@ -0,0 +1,9 @@
+<svg width="39" height="38" viewBox="0 0 39 38" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<rect x="0.666992" y="-0.00439453" width="38" height="38" fill="url(#pattern0_3825_7139)"/>
+<defs>
+<pattern id="pattern0_3825_7139" patternContentUnits="objectBoundingBox" width="1" height="1">
+<use xlink:href="#image0_3825_7139" transform="scale(0.00478469)"/>
+</pattern>
+<image id="image0_3825_7139" width="209" height="209" xlink:href="data:image/png;base64,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"/>
+</defs>
+</svg>
diff --git a/public/images/icon_promo/barong.svg b/public/images/icon_promo/barong.svg
new file mode 100644
index 00000000..22e51ff9
--- /dev/null
+++ b/public/images/icon_promo/barong.svg
@@ -0,0 +1,9 @@
+<svg width="39" height="38" viewBox="0 0 39 38" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<rect x="0.333008" y="-0.00439453" width="38" height="38" fill="url(#pattern0_3850_5455)"/>
+<defs>
+<pattern id="pattern0_3850_5455" patternContentUnits="objectBoundingBox" width="1" height="1">
+<use xlink:href="#image0_3850_5455" transform="scale(0.00478469)"/>
+</pattern>
+<image id="image0_3850_5455" width="209" height="209" xlink:href="data:image/png;base64,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"/>
+</defs>
+</svg>
diff --git a/public/images/icon_promo/diskon.svg b/public/images/icon_promo/diskon.svg
new file mode 100644
index 00000000..23d24875
--- /dev/null
+++ b/public/images/icon_promo/diskon.svg
@@ -0,0 +1,9 @@
+<svg width="38" height="38" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<rect y="-0.00439453" width="38" height="38" fill="url(#pattern0_3853_5915)"/>
+<defs>
+<pattern id="pattern0_3853_5915" patternContentUnits="objectBoundingBox" width="1" height="1">
+<use xlink:href="#image0_3853_5915" transform="scale(0.00478469)"/>
+</pattern>
+<image id="image0_3853_5915" width="209" height="209" xlink:href="data:image/png;base64,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"/>
+</defs>
+</svg>
diff --git a/public/images/icon_promo/silat.svg b/public/images/icon_promo/silat.svg
new file mode 100644
index 00000000..112ecaff
--- /dev/null
+++ b/public/images/icon_promo/silat.svg
@@ -0,0 +1,9 @@
+<svg width="38" height="38" viewBox="0 0 38 38" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+<rect y="-0.00439453" width="38" height="38" fill="url(#pattern0_3821_6700)"/>
+<defs>
+<pattern id="pattern0_3821_6700" patternContentUnits="objectBoundingBox" width="1" height="1">
+<use xlink:href="#image0_3821_6700" transform="scale(0.005)"/>
+</pattern>
+<image id="image0_3821_6700" width="200" height="200" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADICAMAAACahl6sAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAADNQTFRF+MHE8IKJ6URO5iUx9KGm/e/w5BYi7WRs++Dh5zU/9rG1+tDT73N78pKX61Rd4gYT////3hCh9wAAABF0Uk5T/////////////////////wAlrZliAAADrklEQVR42uzd65KjIBAFYGgF4iXR93/arYw3FNTZjRqOe/pnYtX4VY0a+wKqvUkoQgghhBBCCDkIUpX6lHiqx5UQUzTnhbsQUjdnRnkdRE6F6OsgxakQdx3kVEcjhPwrJHL77K8e2bjD9oe48Bv3LUjkGL1/vfaHRB6vihBCCCGEEEIISR6SO3EKH5Jn3ckpbEjPmFEAIR7Do8BBFoyRAgaJMHoKFESy1fe/QpAgs8jERj6Fg2T5+2OLDvlhvHOTAQUKMjBiFCCIzwgpMJAlY0lJHFJuMOYUkzakSwuvMTxK3SYOMVrqnbqAyd0rKk0K8tEfuBmkUGfEFyDnhM1vAmnsXSDTk4sQQsAhWaRe179xykbs1gNVCg9E2f+Lu79kCSGEEEIIIYSQTciFbU4RSP6Sp8GHdGULqw02ZKq+TBRAyLyINFDgIGEtrKOAQeIlvTcFCpKvlvSs/gASSYP1/ZVuI1PWH/IMv3nuQn4RBz0Qz+pFkUhJz7SlwEOGfL8SaIhftlhSgCDL6sucAgOJFZF8SuIQt1PSGylF4pCq2Svp9ZQ8cUirXuJ2SnpVLZK3qUM+CUJmkNKJiK7QIeXwK08qZMjj5T2paoMKebjY+w8cZMkYKVgQ5daGDB9QELvx5pMhQWb/T3XpvtuceQiku8IjFwwWZEqfBRQkiJ/QDCg4kDkjoKBAQsaCUqUNqbYYPiVr04b8rKKwzhgptkwd0lZqd9kRo5Rpk4ecU3ojhBBCCCGEEEIIIYQQQggh5H+EHD715jgIQwghhBDyC4iNjLRZRMhGv9ZWNhZqWoEQQgghhBBCEoTIwVH8FeShtToIcv0gzHQKlfRr82FDpg6KiQIImbfnDd3XcJBw9qVrmQeDqOi01ZsCBXmuDo1l+bFTb8MddH9LgsSn3urLpt6sfpjYWOJBkMvG9/oG9ZCCBXHjlkTBkpxIEDfbWWlBwYG4YIOoGSVxyGudMaekPvXWfWXd6nZdA0UnDmlz22SbPZ+teRbxBTk5LHZTiNFZU+TwkOEa9IZTESH+DX6k4EGWvx56ChrERJZY75Zeh4LEGD0FClKsl8oyQYIsTj27wdRbl3iKvMqBQcb8WUCBgvgZzSUFCCLLc51RYCASKwF4lMQh1RbDp1iTNqSVTcZE0W3ikDbXz73to6tFiSZNyDmlN0IIIYQQQgghhBBCCCGEEEIIuQICFoQAQOQukBckpAohJaJjarfxsoPlxnbfaYbUJgbBDkIIIYQQQr4SfwQYAF0sNCgAdJuiAAAAAElFTkSuQmCC"/>
+</defs>
+</svg>
diff --git a/public/images/reject.png b/public/images/reject.png
new file mode 100644
index 00000000..623d02e8
--- /dev/null
+++ b/public/images/reject.png
Binary files differ
diff --git a/src-migrate/constants/menu.ts b/src-migrate/constants/menu.ts
index d1adebca..e3e7b0c6 100644
--- a/src-migrate/constants/menu.ts
+++ b/src-migrate/constants/menu.ts
@@ -2,6 +2,9 @@ import { SecondaryNavItemProps } from '~/types/nav';
export const SECONDARY_MENU_ITEMS: SecondaryNavItemProps[] = [
{
+ label: 'Semua Promo',
+ href: '/shop/promo',
+ },{
label: 'Semua Brand',
href: '/shop/brands',
},
diff --git a/src-migrate/hooks/useUtmSource.ts b/src-migrate/hooks/useUtmSource.ts
index a72fae36..43fbdcae 100644
--- a/src-migrate/hooks/useUtmSource.ts
+++ b/src-migrate/hooks/useUtmSource.ts
@@ -7,7 +7,7 @@ const useUtmSource = () => {
const [source, setSource] = useState<string>();
useEffect(() => {
- console.log(router.pathname);
+ // console.log(router.pathname);
if (router.pathname) {
setSource(UTM_SOURCE[router.pathname as keyof typeof UTM_SOURCE]);
diff --git a/src-migrate/modules/cart/components/Item.tsx b/src-migrate/modules/cart/components/Item.tsx
index 6ded6373..74180fc9 100644
--- a/src-migrate/modules/cart/components/Item.tsx
+++ b/src-migrate/modules/cart/components/Item.tsx
@@ -17,12 +17,14 @@ import CartItemSelect from './ItemSelect'
type Props = {
item: CartItemProps
editable?: boolean
+ pilihSemuaCart?: boolean
}
-const CartItem = ({ item, editable = true }: Props) => {
+const CartItem = ({ item, editable = true, pilihSemuaCart }: Props) => {
+
return (
<div className={style.wrapper}>
- {item.cart_type === 'promotion' && (
+ {item.cart_type === 'promotion' && (
<div className={style.header}>
{item.promotion_type?.value && (
<Tooltip label={PROMO_CATEGORY[item.promotion_type?.value].description} placement="top" bgColor='red.600' p={2} rounded={6}>
@@ -43,7 +45,9 @@ const CartItem = ({ item, editable = true }: Props) => {
)}
<div className={style.mainProdWrapper}>
- {editable && <CartItemSelect item={item} />}
+ {editable && (
+ <CartItemSelect item={item} itemSelected={pilihSemuaCart} />
+ )}
<div className='w-4' />
<CartItem.Image item={item} />
@@ -87,7 +91,6 @@ const CartItem = ({ item, editable = true }: Props) => {
{!editable && <div className={style.quantity}>{item.quantity}</div>}
</div>
</div>
-
</div>
<div className="flex flex-col">
@@ -100,13 +103,14 @@ const CartItem = ({ item, editable = true }: Props) => {
CartItem.Image = function CartItemImage({ item }: { item: CartItemProps }) {
const image = item?.image || item?.parent?.image
+ const imageProgram = item?.image_program ? item.image_program[0] : item?.parent?.image;
- return (
+ return (
<>
{item.cart_type === 'promotion' && (
<div className={style.image}>
- {image && <Image src={image} alt={item.name} width={128} height={128} />}
- {!image && <div className={style.noImage}>No Image</div>}
+ {imageProgram && <Image src={imageProgram} alt={item.name} width={128} height={128} />}
+ {!imageProgram && <div className={style.noImage}>No Image</div>}
</div>
)}
@@ -153,4 +157,4 @@ CartItem.Skeleton = function CartItemSkeleton(props: SkeletonProps & { count: nu
))
}
-export default CartItem \ No newline at end of file
+export default CartItem
diff --git a/src-migrate/modules/cart/components/ItemSelect.tsx b/src-migrate/modules/cart/components/ItemSelect.tsx
index b904a1de..6b6b8f2b 100644
--- a/src-migrate/modules/cart/components/ItemSelect.tsx
+++ b/src-migrate/modules/cart/components/ItemSelect.tsx
@@ -1,5 +1,5 @@
import { Checkbox, Spinner } from '@chakra-ui/react'
-import React, { useState } from 'react'
+import React, { useState, useEffect } from 'react'
import { getAuth } from '~/libs/auth'
import { CartItem } from '~/types/cart'
@@ -9,15 +9,17 @@ import { useCartStore } from '../stores/useCartStore'
type Props = {
item: CartItem
+ itemSelected?: boolean
}
-const CartItemSelect = ({ item }: Props) => {
+const CartItemSelect = ({ item, itemSelected }: Props) => {
const auth = getAuth()
const { loadCart } = useCartStore()
const [isLoad, setIsLoad] = useState<boolean>(false)
+ const [isChecked, setIsChecked] = useState<boolean>(itemSelected ?? true)
- const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
+ const handleChange = async (isChecked: boolean) => {
if (typeof auth !== 'object') return
setIsLoad(true)
@@ -26,29 +28,40 @@ const CartItemSelect = ({ item }: Props) => {
type: item.cart_type,
id: item.id,
qty: item.quantity,
- selected: e.target.checked
+ selected: isChecked,
})
await loadCart(auth.id)
setIsLoad(false)
}
+ useEffect(() => {
+ if (typeof itemSelected === 'boolean') {
+ setIsChecked(itemSelected)
+ handleChange(itemSelected)
+ }
+ }, [itemSelected])
+
+ const handleCheckboxChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ const { checked } = e.target
+ setIsChecked(checked)
+ handleChange(checked)
+ }
+
return (
<div className='w-6 my-auto'>
- {isLoad && (
- <Spinner className='my-auto' size='sm' />
- )}
+ {isLoad && <Spinner className='my-auto' size='sm' />}
{!isLoad && (
<Checkbox
borderColor='gray.600'
colorScheme='red'
size='lg'
- isChecked={item.selected}
- onChange={handleChange}
+ isChecked={isChecked}
+ onChange={handleCheckboxChange}
/>
)}
</div>
)
}
-export default CartItemSelect \ No newline at end of file
+export default CartItemSelect
diff --git a/src-migrate/modules/footer-banner/index.tsx b/src-migrate/modules/footer-banner/index.tsx
index b214493d..86321815 100644
--- a/src-migrate/modules/footer-banner/index.tsx
+++ b/src-migrate/modules/footer-banner/index.tsx
@@ -10,7 +10,7 @@ const FooterBanner = () => {
queryKey: 'footerBanner',
queryFn: () => getBanner({ type: 'bottom-search-promotion' })
})
- // ubah dari static menjadid dynamic dengan menggunakan random index
+
const length = useMemo(() => fetchFooterBanner.data?.length, [fetchFooterBanner.data]);
const randomIndex = useMemo(() => getRandomInt(length), [length]);
const banner = fetchFooterBanner?.data?.[randomIndex] || false;
diff --git a/src-migrate/modules/product-promo/components/Card.tsx b/src-migrate/modules/product-promo/components/Card.tsx
index 56e29e38..728d23ca 100644
--- a/src-migrate/modules/product-promo/components/Card.tsx
+++ b/src-migrate/modules/product-promo/components/Card.tsx
@@ -90,10 +90,10 @@ const ProductPromoCard = ({ promotion}: Props) => {
<div className={style.title}>{promotion.name}</div>
<Tooltip label={PROMO_CATEGORY[promotion.type.value].description} placement="top" bgColor='red.600' p={1} rounded={6}>
- {/* <div className={style.badgeType} > */}
- {/* Paket {PROMO_CATEGORY[promotion.type.value].alias} */}
+ <div className={style.badgeType} >
+ Paket {PROMO_CATEGORY[promotion.type.value].alias}
<InfoIcon className={style.badgeType} size={25} />
- {/* </div> */}
+ </div>
</Tooltip>
</div>
diff --git a/src-migrate/modules/product-promo/styles/card.module.css b/src-migrate/modules/product-promo/styles/card.module.css
index faa3b370..4e294f1c 100644
--- a/src-migrate/modules/product-promo/styles/card.module.css
+++ b/src-migrate/modules/product-promo/styles/card.module.css
@@ -10,7 +10,7 @@
}
.badgeType {
- @apply p-2 flex gap-x-1.5 rounded-md border border-danger-500 text-danger-500;
+ @apply p-2 flex gap-x-1.5 rounded-md border border-danger-500 text-danger-500 items-center;
}
.productSection {
diff --git a/src-migrate/modules/promo/components/FlashSale.tsx b/src-migrate/modules/promo/components/FlashSale.tsx
new file mode 100644
index 00000000..c0259396
--- /dev/null
+++ b/src-migrate/modules/promo/components/FlashSale.tsx
@@ -0,0 +1,20 @@
+import dynamic from "next/dynamic";
+import React from "react";
+import { FlashSaleSkeleton } from "@/lib/flashSale/skeleton/FlashSaleSkeleton";
+
+const FlashSale = dynamic(
+ () => import('@/lib/flashSale/components/FlashSale'),
+ {
+ loading: () => <FlashSaleSkeleton />,
+ }
+ );
+
+ const FlashSalePromo = ()=> {
+ return(
+ <>
+ <FlashSale/>
+ </>
+ )
+ }
+
+ export default FlashSalePromo \ No newline at end of file
diff --git a/src-migrate/modules/promo/components/Hero.tsx b/src-migrate/modules/promo/components/Hero.tsx
new file mode 100644
index 00000000..c5f0afad
--- /dev/null
+++ b/src-migrate/modules/promo/components/Hero.tsx
@@ -0,0 +1,105 @@
+import 'swiper/css';
+
+import Image from 'next/image';
+import { useEffect, useMemo } from 'react';
+import { useQuery } from 'react-query';
+import { Swiper, SwiperProps, SwiperSlide } from 'swiper/react';
+import style from '../styles/hero.module.css';
+import 'swiper/css/navigation';
+import 'swiper/css/pagination';
+import { Navigation, Pagination, Autoplay } from 'swiper';
+import MobileView from '../../../../src/core/components/views/MobileView';
+import DesktopView from '@/core/components/views/DesktopView';
+import {bannerApi} from '../../../../src/api/bannerApi'
+
+interface IPromotionProgram {
+ headlineBanner: string;
+ descriptionBanner: string;
+ image: string ;
+ name: string;
+}
+
+const swiperBanner: SwiperProps = {
+ modules:[Navigation, Pagination, Autoplay],
+ autoplay: {
+ delay: 6000,
+ disableOnInteraction: false
+ },
+ loop: true,
+ className: 'h-[400px] w-full',
+ slidesPerView: 1,
+ spaceBetween: 10,
+ pagination:true,
+}
+const swiperBannerMob = {
+ autoplay: {
+ delay: 6000,
+ disableOnInteraction: false,
+ },
+ modules: [Pagination, Autoplay],
+ loop: true,
+ className: 'border border-gray_r-6 min-h-full',
+ slidesPerView: 1,
+};
+
+const Hero = () => {
+ const heroBanner = useQuery('allPromo', bannerApi({ type: 'banner-semua-promo' }));
+
+ const banners: IPromotionProgram[] = useMemo(
+ () => heroBanner?.data || [],
+ [heroBanner.data]
+ );
+
+ const swiperBannerMobile = {
+ ...swiperBannerMob,
+ pagination: { dynamicBullets: false, clickable: true },
+ };
+
+ return (
+ <>
+ <DesktopView>
+ <div className={style['wrapper']}>
+ <Swiper {...swiperBanner}>
+ {banners.map((banner, index) => (
+ <SwiperSlide key={index} className='flex flex-row'>
+ <div className={style['desc-section']}>
+ <div className={style['title']}>{banner.headlineBanner? banner.headlineBanner : "Pasti Hemat & Untung Selama Belanja di Indoteknik.com!"}</div>
+ <div className='h-4' />
+ <div className={style['subtitle']}>{banner.descriptionBanner? banner.descriptionBanner : "Cari paket yang kami sediakan dengan penawaran harga & Nikmati kemudahan dalam setiap transaksi dengan fitur lengkap Pembayaran hingga barang sampai!"}</div>
+ </div>
+ <div className={style['banner-section']}>
+ <Image
+ src={banner.image}
+ alt={banner.name}
+ width={666}
+ height={450}
+ quality={90}
+ className='w-full h-full object-fit object-center rounded-2xl' />
+ </div>
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ </div>
+ </DesktopView>
+ <MobileView>
+ <Swiper {...swiperBannerMobile}>
+ {banners?.map((banner, index) => (
+ <SwiperSlide key={index}>
+ <Image
+ width={439}
+ height={150}
+ quality={100}
+ src={banner.image}
+ alt={banner.name}
+ className='w-full h-full object-cover object-center rounded-2xl'
+ />
+ </SwiperSlide>
+ ))}
+ </Swiper>
+
+ </MobileView>
+ </>
+ )
+}
+
+export default Hero \ No newline at end of file
diff --git a/src-migrate/modules/promo/components/HeroDiskon.tsx b/src-migrate/modules/promo/components/HeroDiskon.tsx
new file mode 100644
index 00000000..6d38c763
--- /dev/null
+++ b/src-migrate/modules/promo/components/HeroDiskon.tsx
@@ -0,0 +1,137 @@
+import 'swiper/css';
+
+import Image from 'next/image';
+import { useEffect, useMemo } from 'react';
+import { useQuery } from 'react-query';
+import { Swiper, SwiperProps, SwiperSlide } from 'swiper/react';
+
+import { getBanner } from '~/services/banner';
+import style from '../styles/hero.module.css';
+import 'swiper/css/navigation';
+import { Autoplay, Navigation, Pagination } from 'swiper';
+
+const swiperBanner: SwiperProps = {
+ modules:[Navigation, Pagination, Autoplay],
+ autoplay: {
+ delay: 6000,
+ disableOnInteraction: false
+ },
+ loop: true,
+ className: 'h-[400px] w-full',
+ slidesPerView: 1,
+ spaceBetween: 10,
+ navigation:true,
+}
+const swiperBanner2: SwiperProps = {
+ modules: [Pagination, Autoplay],
+ autoplay: {
+ delay: 5000,
+ },
+ loop: true,
+ className: 'h-[400px] w-full',
+ slidesPerView: 1,
+ spaceBetween: 10,
+}
+
+const Hero = () => {
+ const bannerQuery = useQuery({
+ queryKey: ['banner.all-promo'],
+ queryFn: () => getBanner({ type: 'banner-promotion' })
+ })
+
+ const banners = useMemo(() => bannerQuery.data || [], [bannerQuery.data]);
+
+ useEffect(() => {
+ if (banners.length > 1) {
+ swiperBanner.slidesPerView = 1;
+ swiperBanner.loop = true;
+ }
+ }, [banners]);
+
+ return (
+
+ <div className="grid grid-cols-3 gap-4">
+ <div className="row-span-2 h-[446px] flex items-center ">
+ <Swiper {...swiperBanner}>
+ {banners.map((banner, index) => (
+ <SwiperSlide key={index}>
+ <Image
+ src={banner.image}
+ alt={banner.name}
+ width={666}
+ height={480}
+ className='w-[446px] h-[446px] object-fill object-center rounded-2xl'
+ />
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ </div>
+ <div className="w-[400px] h-[217px] ">
+ <Swiper {...swiperBanner2}>
+ {banners.map((banner, index) => (
+ <SwiperSlide key={index}>
+ <Image
+ src={banner.image}
+ alt={banner.name}
+ width={666}
+ height={450}
+ className='w-[400px] h-[217px] object-cover object-center rounded-2xl '
+ />
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ </div>
+ <div className="w-[400px] h-[217px]">
+ <Swiper {...swiperBanner2}>
+ {banners.map((banner, index) => (
+ <SwiperSlide key={index}>
+ <Image
+ src={banner.image}
+ alt={banner.name}
+ width={666}
+ height={450}
+ className='w-[400px] h-[217px] object-cover object-center rounded-2xl'
+ />
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ </div>
+ <div className="w-[400px] h-[217px]">
+ <Swiper {...swiperBanner2}>
+ {banners.map((banner, index) => (
+ <SwiperSlide key={index}>
+ <Image
+ src={banner.image}
+ alt={banner.name}
+ width={666}
+ height={450}
+ className='w-[400px] h-[217px] object-cover object-center rounded-2xl'
+ />
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ </div>
+ <div className="w-[400px] h-[217px]">
+ <Swiper {...swiperBanner2}>
+ {banners.map((banner, index) => (
+ <SwiperSlide key={index}>
+ <Image
+ src={banner.image}
+ alt={banner.name}
+ width={666}
+ height={450}
+ className='w-[400px] h-[217px] object-cover object-center rounded-2xl'
+ />
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ </div>
+
+ </div>
+
+
+
+ )
+}
+
+export default Hero \ No newline at end of file
diff --git a/src-migrate/modules/promo/components/PromoList.tsx b/src-migrate/modules/promo/components/PromoList.tsx
new file mode 100644
index 00000000..42725034
--- /dev/null
+++ b/src-migrate/modules/promo/components/PromoList.tsx
@@ -0,0 +1,135 @@
+import React, { useEffect, useState } from 'react';
+import { Button, Skeleton } from '@chakra-ui/react'
+import clsxm from "~/libs/clsxm"
+import ProductPromoCard from '../../product-promo/components/Card';
+import { fetchPromoItemsSolr } from '../../../../src/api/promoApi';
+import { Swiper, SwiperSlide } from 'swiper/react';
+import SwiperCore, { Navigation, Pagination } from 'swiper';
+import useDevice from '@/core/hooks/useDevice';
+import LogoSpinner from '../../../../src/core/components/elements/Spinner/LogoSpinner';
+import usePromoStore from './promoStore';
+import Link from "next/link"
+import { IPromotion } from '~/types/promotion';
+interface PromoListProps {
+ selectedPromo: string; // Tipe selectedPromo ditetapkan sebagai string
+}
+
+const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => {
+ const {
+ title,
+ slug,
+ promoItems,
+ promoData,
+ isLoading,
+ setTitle,
+ setSlug,
+ setPromoItems,
+ setPromoData,
+ setIsLoading,
+ } = usePromoStore();
+
+ const { isMobile, isDesktop } = useDevice();
+
+ const swiperBanner = {
+ modules: [Navigation],
+ className: 'h-[400px] w-full',
+ slidesPerView: isMobile ? 1.1 : 3.25,
+ spaceBetween: 10,
+ navigation:isMobile? true : false,
+ allowTouchMove:isMobile? false : true,
+ };
+
+ useEffect(() => {
+ if (selectedPromo === 'Bundling') {
+ setTitle('Kombinasi Kilat Pilihan Kami!');
+ setSlug('bundling');
+ } else if (selectedPromo === 'Loading') {
+ setTitle('Belanja Borong Pilihan Kami!');
+ setSlug('discount_loading');
+ } else if (selectedPromo === 'Merchandise') {
+ setTitle('Gratis Merchandise Spesial Indoteknik');
+ setSlug('merchandise');
+ }
+ }, [selectedPromo, setTitle, setSlug]);
+
+ useEffect(() => {
+ const fetchPromotions = async () => {
+ setIsLoading(true);
+ try {
+ const items = await fetchPromoItemsSolr(`type_value_s:${slug}`, 0, 10);
+ setPromoItems(items);
+
+ const promoDataPromises = items.map(async (item) => {
+ try {
+ const response = await fetchPromoItemsSolr(`id:${item.id}`, 0, 10);
+ return response;
+ } catch (fetchError) {
+ return [];
+ }
+ });
+
+ const promoDataArray = await Promise.all(promoDataPromises);
+ const mergedPromoData = promoDataArray.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
+ setPromoData(mergedPromoData);
+
+ } catch (error) {
+ console.error('Error fetching promo items:', error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ if (slug) {
+ setIsLoading(true);
+ setPromoItems([]);
+ setPromoData([]);
+ fetchPromotions();
+ }
+ }, [slug, setPromoItems, setPromoData, setIsLoading]);
+
+ return (
+ <div className='min-h-[360px]'>
+ <div className='flex justify-between items-center'>
+ <h1 className='text-h-sm md:text-h-lg font-semibold py-4'>{title}</h1>
+ <div>
+ <Link href={`/shop/promo/${slug}`} className='!text-red-500 font-semibold'>
+ Lihat Semua
+ </Link>
+ </div>
+ </div>
+ {isLoading ? (
+ <div className="loading-spinner flex justify-center">
+ <LogoSpinner width={48} height={48} />
+ </div>
+ ) : (
+ <Skeleton
+ isLoaded={!isLoading}
+ className={clsxm(
+ "flex gap-x-4 overflow-x-auto px-4 md:px-0", {
+ "min-h-[340px]": promoData[0] && promoData?.length > 0
+ })}
+ >
+ {isDesktop && (
+ <Swiper {...swiperBanner}>
+ {promoData?.map((promotion: IPromotion) => (
+ <SwiperSlide key={promotion.id}>
+ <div className="min-w-36 max-w-[400px] mb-[20px] sm:w-full md:w-full lg:w-full xl:w-full">
+ <ProductPromoCard promotion={promotion} />
+ </div>
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ )}
+ {isMobile && (promoData?.map((promotion: IPromotion) => (
+ <div key={promotion.id} className="min-w-[400px] max-w-[400px]">
+ <ProductPromoCard promotion={promotion} />
+ </div>
+ )))}
+
+ </Skeleton>
+ )}
+ </div>
+ );
+};
+
+export default PromoList; \ No newline at end of file
diff --git a/src-migrate/modules/promo/components/PromotinProgram.jsx b/src-migrate/modules/promo/components/PromotinProgram.jsx
new file mode 100644
index 00000000..33839944
--- /dev/null
+++ b/src-migrate/modules/promo/components/PromotinProgram.jsx
@@ -0,0 +1,134 @@
+import React from 'react';
+import Image from 'next/image';
+import { InfoIcon } from "lucide-react";
+import MobileView from '../../../../src/core/components/views/MobileView';
+import DesktopView from '@/core/components/views/DesktopView';
+import { Swiper, SwiperProps, SwiperSlide } from 'swiper/react';
+import 'swiper/css';
+import useDevice from '@/core/hooks/useDevice';
+
+const PromotionProgram = ({ selectedPromo, onSelectPromo }) => {
+ const { isMobile } = useDevice();
+ return (
+ <>
+ <div className="text-h-sm md:text-h-lg font-semibold py-4">Serba Serbi Promo</div>
+ <div className='px-4 sm:px-0'>
+ {/* <div className='w-full h-full '>
+ <div
+ onClick={() => onSelectPromo('Diskon')}
+ className={`border p-2 flex items-center gap-x-2 rounded-lg cursor-pointer ${selectedPromo === 'Diskon' ? 'bg-red-50 border-red-500 text-red-500' : 'border-gray-200 text-gray-900'}`}
+ >
+ <div>
+ <Image
+ width={24}
+ height={24}
+ quality={100}
+ src='/images/icon_promo/diskon.svg'
+ alt=''
+ className='h-12 w-12 rounded'
+ />
+ </div>
+ <div>
+ <div className='flex w-full flex-row items-center justify-start'>
+ <h1 className={`mr-1 font-semibold text-base ${selectedPromo === 'Diskon' ? 'text-red-500' : 'text-gray-900'}`}>Spesial Diskon</h1>
+ <InfoIcon className='mt-[1px] text-red-500' size={14} />
+ </div>
+ <p className={`text-xs md:text-sm ${selectedPromo === 'Diskon' ? 'text-red-500' : 'text-gray-500'}`}>
+ Harga lebih murah dan pasti makin hemat belinya..
+ </p>
+ </div>
+ </div>
+ </div> */}
+
+ <Swiper slidesPerView={isMobile ? 1.3 : 3} spaceBetween={10}>
+ <SwiperSlide>
+ <div className='w-full h-full '>
+ <div
+ onClick={() => onSelectPromo('Bundling')}
+ className={`border h-full p-1 flex items-center gap-x-2 rounded-lg cursor-pointer ${selectedPromo === 'Bundling' ? 'bg-red-50 border-red-500 text-red-500' : 'border-gray-200 text-gray-900'}`}
+ >
+ <div>
+ <Image
+ width={24}
+ height={24}
+ quality={100}
+ src='/images/icon_promo/silat.svg'
+ alt=''
+ className='h-12 w-12 rounded'
+ />
+ </div>
+ <div >
+ <div className='flex w-full flex-row items-center justify-start'>
+ <h1 className={`mr-1 font-semibold text-base ${selectedPromo === 'Bundling' ? 'text-red-500' : 'text-gray-900'}`}>Paket Silat</h1>
+ <InfoIcon className='mt-[1px] text-red-500' size={14} />
+ </div>
+ <p className={`text-xs md:text-sm ${selectedPromo === 'Bundling' ? 'text-red-500' : 'text-gray-500'}`}>
+ Pilihan bundling barang kombinasi Silat.
+ </p>
+ </div>
+ </div>
+ </div>
+ </SwiperSlide>
+ <SwiperSlide>
+ <div className='w-full h-full '>
+ <div
+ onClick={() => onSelectPromo('Loading')}
+ className={`border p-2 h-full flex items-center gap-x-2 rounded-lg cursor-pointer ${selectedPromo === 'Loading' ? 'bg-red-50 border-red-500 text-red-500' : 'border-gray-200 text-gray-900'}`}
+ >
+ <div>
+ <Image
+ width={24}
+ height={24}
+ quality={100}
+ src='/images/icon_promo/barong.svg'
+ alt=''
+ className='h-12 w-12 rounded'
+ />
+ </div>
+ <div>
+ <div className='flex w-full flex-row items-center justify-start'>
+ <h1 className={`mr-1 font-semibold text-base ${selectedPromo === 'Loading' ? 'text-red-500' : 'text-gray-900'}`}>Paket Barong</h1>
+ <InfoIcon className='mt-[1px] text-red-500' size={14} />
+ </div>
+ <p className={`text-xs md:text-sm ${selectedPromo === 'Loading' ? 'text-red-500' : 'text-gray-500'}`}>
+ Beli banyak barang/partai barang borong.
+ </p>
+ </div>
+ </div>
+ </div>
+ </SwiperSlide>
+ <SwiperSlide>
+ <div className='w-full h-full '>
+ <div
+ onClick={() => onSelectPromo('Merchandise')}
+ className={`border p-2 h-full flex items-center gap-x-2 rounded-lg cursor-pointer ${selectedPromo === 'Merchandise' ? 'bg-red-50 border-red-500 text-red-500' : 'border-gray-200 text-gray-900'}`}
+ >
+ <div>
+ <Image
+ width={24}
+ height={24}
+ quality={100}
+ src='/images/icon_promo/angklung.svg'
+ alt=''
+ className='h-12 w-12 rounded'
+ />
+ </div>
+ <div >
+ <div className='flex w-full flex-row items-center justify-start '>
+ <h1 className={`mr-1 font-semibold text-base ${selectedPromo === 'Merchandise' ? 'text-red-500' : 'text-gray-900'}`}>Paket Angklung</h1>
+ <InfoIcon className='mt-[1px] text-red-500' size={14} />
+ </div>
+ <p className={` m1 text-xs md:text-sm ${selectedPromo === 'Merchandise' ? 'text-red-500' : 'text-gray-500'}`}>
+ Gratis barang promosi/merchandise menang langsung.
+ </p>
+ </div>
+ </div>
+ </div>
+ </SwiperSlide>
+ </Swiper>
+ </div>
+ </>
+ );
+};
+
+export default PromotionProgram;
diff --git a/src-migrate/modules/promo/components/Voucher.tsx b/src-migrate/modules/promo/components/Voucher.tsx
new file mode 100644
index 00000000..e5877e51
--- /dev/null
+++ b/src-migrate/modules/promo/components/Voucher.tsx
@@ -0,0 +1,160 @@
+import { useMemo, useState, useEffect } from 'react';
+import { useQuery } from 'react-query';
+import { Swiper, SwiperProps, SwiperSlide } from 'swiper/react';
+import { getVoucherAll } from '~/services/voucher';
+import style from '../styles/voucher.module.css';
+import Image from 'next/image';
+import { useToast } from '@chakra-ui/react';
+import useDevice from '@/core/hooks/useDevice';
+import useAuth from '@/core/hooks/useAuth';
+import { getVoucher } from '../../../../src/lib/checkout/api/getVoucher';
+
+interface Auth {
+ id: string;
+}
+interface Voucher {
+ id: string;
+ image: string;
+ name: string;
+ description: string;
+ code: string;
+}
+
+const VoucherComponent = () => {
+ const [listVouchers, setListVouchers] = useState<Voucher[] | null>(null);
+ const [loadingVoucher, setLoadingVoucher] = useState(true);
+ const { isMobile } = useDevice();
+ const auth = useAuth() as unknown as Auth;
+ const toast = useToast();
+
+ useEffect(() => {
+ if (!listVouchers && auth?.id) {
+ (async () => {
+ try {
+ const dataVoucher = await getVoucher(auth.id);
+ setListVouchers(dataVoucher);
+ } finally {
+ setLoadingVoucher(false);
+ }
+ })();
+ }
+ }, [auth?.id, listVouchers]);
+
+ const voucherQuery = useQuery({
+ queryKey: ['voucher.all-voucher'],
+ queryFn: getVoucherAll,
+ });
+
+ const swiperVoucher: SwiperProps = {
+ autoplay: {
+ delay: 6000,
+ disableOnInteraction: false,
+ },
+ loop: false,
+ className: 'h-[160px] w-full',
+ slidesPerView: isMobile ? 1.2 : 3.2,
+ spaceBetween: 2,
+ };
+
+ const dataVouchers = useMemo(() => voucherQuery.data || [], [voucherQuery.data]);
+
+ const vouchers = auth?.id? listVouchers : dataVouchers;
+
+
+ const copyText = (text: string) => {
+ if (navigator.clipboard && navigator.clipboard.writeText) {
+ navigator.clipboard.writeText(text)
+ .then(() => {
+ toast({
+ title: 'Salin ke papan klip',
+ description: 'Kode voucher berhasil disalin',
+ status: 'success',
+ duration: 3000,
+ isClosable: true,
+ position: 'top',
+ })
+ })
+ .catch(() => {
+ fallbackCopyTextToClipboard(text);
+ });
+ } else {
+ fallbackCopyTextToClipboard(text);
+ }
+ }
+
+ const fallbackCopyTextToClipboard = (text: string) => {
+ const textArea = document.createElement("textarea");
+ textArea.value = text;
+ // Tambahkan style untuk menyembunyikan textArea secara visual
+ textArea.style.position = 'fixed';
+ textArea.style.top = '0';
+ textArea.style.left = '0';
+ textArea.style.width = '2em';
+ textArea.style.height = '2em';
+ textArea.style.padding = '0';
+ textArea.style.border = 'none';
+ textArea.style.outline = 'none';
+ textArea.style.boxShadow = 'none';
+ textArea.style.background = 'transparent';
+ document.body.appendChild(textArea);
+ textArea.focus();
+ textArea.select();
+ try {
+ document.execCommand('copy');
+ toast({
+ title: 'Salin ke papan klip',
+ description: 'Kode voucher berhasil disalin',
+ status: 'success',
+ duration: 3000,
+ isClosable: true,
+ position: 'top',
+ })
+ } catch (err) {
+ console.error('Fallback: Oops, unable to copy', err);
+ }
+ document.body.removeChild(textArea);
+ }
+
+ return (
+ <>
+ <div className={style['title']}>Pakai Voucher Belanja</div>
+
+ <div className='h-6' />
+
+ {voucherQuery.isLoading && (
+ <div className='grid grid-cols-3 gap-x-4 animate-pulse'>
+ {Array.from({ length: 3 }).map((_, index) => (
+ <div key={index} className='w-full h-[160px] bg-gray-200 rounded-xl' />
+ ))}
+ </div>
+ )}
+ {!voucherQuery.isLoading && (
+ <div className={style['voucher-section']}>
+ <Swiper {...swiperVoucher}>
+ {vouchers?.map((voucher) => (
+ <SwiperSlide key={voucher.id} className='pb-2'>
+ <div className={style['voucher-card']}>
+ <Image src={voucher.image} alt={voucher.name} width={128} height={128} className={style['voucher-image']} />
+
+ <div className={style['voucher-content']}>
+ <div className={style['voucher-title']}>{voucher.name}</div>
+ <div className={style['voucher-desc']}>{voucher.description}</div>
+ <div className={style['voucher-bottom']}>
+ <div>
+ <div className={style['voucher-code-desc']}>Kode Promo</div>
+ <div className={style['voucher-code']}>{voucher.code}</div>
+ </div>
+ <button className={style['voucher-copy']} onClick={() => copyText(voucher.code)}>Salin</button>
+ </div>
+ </div>
+ </div>
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ </div>
+ )}
+ </>
+ )
+}
+
+export default VoucherComponent
diff --git a/src-migrate/modules/promo/components/promoStore.js b/src-migrate/modules/promo/components/promoStore.js
new file mode 100644
index 00000000..c232de00
--- /dev/null
+++ b/src-migrate/modules/promo/components/promoStore.js
@@ -0,0 +1,16 @@
+import create from 'zustand';
+
+const usePromoStore = create((set) => ({
+ title: '',
+ slug: '',
+ promoItems: [],
+ promoData: [],
+ isLoading: true,
+ setTitle: (title) => set({ title }),
+ setSlug: (slug) => set({ slug }),
+ setPromoItems: (promoItems) => set({ promoItems }),
+ setPromoData: (promoData) => set({ promoData }),
+ setIsLoading: (isLoading) => set({ isLoading }),
+}));
+
+export default usePromoStore;
diff --git a/src-migrate/modules/promo/styles/hero.module.css b/src-migrate/modules/promo/styles/hero.module.css
new file mode 100644
index 00000000..a5ba6ecc
--- /dev/null
+++ b/src-migrate/modules/promo/styles/hero.module.css
@@ -0,0 +1,27 @@
+.wrapper {
+ @apply rounded-xl w-full h-[460px] flex;
+}
+
+.desc-section {
+ @apply w-full md:w-5/12
+ flex flex-col
+ md:justify-center
+ p-6 md:pl-10;
+}
+
+.title {
+ @apply text-title-sm md:text-title-lg
+ leading-[30px] md:leading-[42px]
+ font-semibold;
+}
+
+.subtitle {
+ @apply text-body-2 leading-7 text-gray-700;
+}
+
+.banner-section {
+ @apply md:w-7/12
+ flex flex-col
+ md:justify-center
+ md:pr-10;
+}
diff --git a/src-migrate/modules/promo/styles/voucher.module.css b/src-migrate/modules/promo/styles/voucher.module.css
new file mode 100644
index 00000000..22d07f91
--- /dev/null
+++ b/src-migrate/modules/promo/styles/voucher.module.css
@@ -0,0 +1,43 @@
+.title {
+ @apply text-h-sm md:text-h-lg font-semibold;
+}
+
+.voucher-section {
+ @apply w-full;
+}
+
+.voucher-card {
+ @apply w-full md:w-11/12 h-3/4 rounded-xl border items-center border-gray-200 shadow-md p-4 flex gap-x-4 ;
+}
+
+.voucher-image {
+ @apply bg-gray-100 rounded-lg w-4/12 h-fit object-contain object-center;
+}
+
+.voucher-content {
+ @apply flex-1 flex flex-col;
+}
+
+.voucher-title {
+ @apply font-medium text-body-1 leading-6 mb-1;
+}
+
+.voucher-desc {
+ @apply text-gray-800 line-clamp-2 text-caption-1;
+}
+
+.voucher-bottom {
+ @apply flex justify-between mt-2;
+}
+
+.voucher-code-desc {
+ @apply text-gray-500 text-caption-1;
+}
+
+.voucher-code {
+ @apply text-red-700 font-medium;
+}
+
+.voucher-copy {
+ @apply bg-gray-200 hover:bg-danger-500 text-danger-500 hover:text-white transition-colors rounded-lg flex items-center justify-center px-6;
+}
diff --git a/src-migrate/modules/side-banner/index.tsx b/src-migrate/modules/side-banner/index.tsx
index 6214edfb..878b8e70 100644
--- a/src-migrate/modules/side-banner/index.tsx
+++ b/src-migrate/modules/side-banner/index.tsx
@@ -1,7 +1,7 @@
import React, { useMemo } from "react";
-import Link from "next/link";
-import { useQuery } from "react-query";
-import Image from "~/components/ui/image";
+import Link from "next/link";;
+import { useQuery } from "react-query";;
+import Image from "~/components/ui/image";;
import { getBanner } from "~/services/banner";
import { getRandomInt } from '@/utils/getRandomInt';
@@ -10,7 +10,7 @@ const SideBanner = () => {
queryKey: 'sideBanner',
queryFn: () => getBanner({ type: 'side-banner-search' })
});
- // ubah dari static menjadid dynamic dengan menggunakan random index
+
const length = useMemo(() => fetchSideBanner.data?.length, [fetchSideBanner.data]);
const randomIndex = useMemo(() => getRandomInt(length), [length]);
const banner = fetchSideBanner?.data?.[randomIndex] || false;
@@ -25,6 +25,6 @@ const SideBanner = () => {
<Image src={banner.image} alt={banner.name} width={315} height={450} className='object-cover object-center rounded-lg' />
)}
</>
- );
+ )
}
export default SideBanner;
diff --git a/src-migrate/pages/shop/cart/index.tsx b/src-migrate/pages/shop/cart/index.tsx
index 7de96425..2ecf1c03 100644
--- a/src-migrate/pages/shop/cart/index.tsx
+++ b/src-migrate/pages/shop/cart/index.tsx
@@ -1,8 +1,8 @@
import style from './cart.module.css';
-import React, { useEffect, useMemo } from 'react';
+import React, { useEffect, useMemo, useState } from 'react';
import Link from 'next/link';
-import { Button, Toast, Tooltip } from '@chakra-ui/react';
+import { Button, Checkbox, Toast, Tooltip } from '@chakra-ui/react';
import { toast } from 'react-hot-toast';
import { useRouter } from 'next/router';
import { getAuth } from '~/libs/auth';
@@ -19,6 +19,9 @@ const CartPage = () => {
const router = useRouter();
const auth = getAuth();
const [isStepApproval, setIsStepApproval] = React.useState(false);
+ const [isSelectedAll, setIsSelectedAll] = useState<boolean>(false);
+ const [isButtonChek, setIsButtonChek] = useState<boolean>(false);
+ const [buttonSelectNow, setButtonSelectNow] = useState<boolean>(true);
const { loadCart, cart, summary } = useCartStore();
@@ -29,8 +32,9 @@ const CartPage = () => {
loadCart(auth.id);
setIsStepApproval(auth?.feature?.soApproval);
}
- }, [auth, loadCart, cart]);
-
+ }, [auth, loadCart, cart, isButtonChek, ]);
+
+
const hasSelectedPromo = useMemo(() => {
if (!cart) return false;
for (const item of cart.products) {
@@ -67,11 +71,27 @@ const CartPage = () => {
}
})
+ const handleChange = (()=>{
+ setButtonSelectNow(!buttonSelectNow)
+ setIsSelectedAll(!isSelectedAll)
+ setIsButtonChek(!isButtonChek)
+ })
+
return (
<>
<div className={style['title']}>Keranjang Belanja</div>
-
- <div className='h-6' />
+ <div className='h-2' />
+ <div className='flex items-center object-center mt-2'>
+ <Checkbox
+ borderColor='gray.600'
+ colorScheme='red'
+ size='lg'
+ isChecked={isButtonChek}
+ onChange={handleChange}
+ /> <p className='p-2 text-caption-2'>
+ {buttonSelectNow? "Select all" : "Unchek all" }
+ </p>
+ </div>
<div className={style['content']}>
<div className={style['item-wrapper']}>
@@ -81,8 +101,9 @@ const CartPage = () => {
<div className={style['items']}>
{cart?.products.map((item) => (
- <CartItem key={item.id} item={item} />
+ <CartItem key={item.id} item={item} pilihSemuaCart={isSelectedAll} />
))}
+
{cart?.products?.length === 0 && (
<div className='flex flex-col items-center'>
diff --git a/src-migrate/pages/shop/promo/index.tsx b/src-migrate/pages/shop/promo/index.tsx
new file mode 100644
index 00000000..febe31a4
--- /dev/null
+++ b/src-migrate/pages/shop/promo/index.tsx
@@ -0,0 +1,38 @@
+import dynamic from 'next/dynamic'
+import React, { useState } from 'react'
+import { LazyLoadComponent } from 'react-lazy-load-image-component'
+import Hero from '~/modules/promo/components/Hero'
+import PromotionProgram from '~/modules/promo/components/PromotinProgram'
+import Voucher from '~/modules/promo/components/Voucher'
+import FlashSale from '../../../modules/promo/components/FlashSale'
+const PromoList = dynamic(() => import('../../../modules/promo/components/PromoList'));
+
+
+
+const PromoPage = () => {
+ const [selectedPromo, setSelectedPromo] = useState('Bundling');
+ return (
+ <>
+ <LazyLoadComponent>
+ <Hero />
+ </LazyLoadComponent>
+ <LazyLoadComponent>
+ <PromotionProgram
+ selectedPromo={selectedPromo}
+ onSelectPromo={setSelectedPromo}
+ />
+ <PromoList selectedPromo={selectedPromo} />
+ </LazyLoadComponent>
+
+ <LazyLoadComponent>
+ <FlashSale />
+ </LazyLoadComponent>
+ <h1 className='h-1'></h1>
+ <LazyLoadComponent>
+ <Voucher />
+ </LazyLoadComponent>
+ </>
+ )
+}
+
+export default PromoPage \ No newline at end of file
diff --git a/src-migrate/services/promotionProgram.ts b/src-migrate/services/promotionProgram.ts
index c8c46c65..8bf2a0bd 100644
--- a/src-migrate/services/promotionProgram.ts
+++ b/src-migrate/services/promotionProgram.ts
@@ -6,3 +6,11 @@ export const getPromotionProgram = async (
const url = `/api/promotion-program/${programId}`;
return await fetch(url).then((res) => res.json());
};
+
+export const getPromotionProgramSolr = async () => {
+ const response = await fetch(`/solr/promotion_programs/select?indent=true&q.op=OR&q=*:*&fq=banner_s:[* TO *]`);
+ if (!response.ok) {
+ throw new Error('Network response was not ok');
+ }
+ return response.json();
+};
diff --git a/src-migrate/services/voucher.ts b/src-migrate/services/voucher.ts
new file mode 100644
index 00000000..13d9e2c0
--- /dev/null
+++ b/src-migrate/services/voucher.ts
@@ -0,0 +1,8 @@
+import odooApi from '~/libs/odooApi';
+import { IVoucher } from '~/types/voucher';
+
+export const getVoucherAll = async (): Promise<IVoucher[]> => {
+ const url = `/api/v1/voucher`;
+
+ return await odooApi('GET', url);
+};
diff --git a/src-migrate/types/banner.ts b/src-migrate/types/banner.ts
index dbccc378..e1604ad4 100644
--- a/src-migrate/types/banner.ts
+++ b/src-migrate/types/banner.ts
@@ -1,8 +1,8 @@
export interface IBanner {
- background_color: string | false;
- group_by_week: number | false;
image: string;
name: string;
sequence: number;
- url: string;
+ url: string | false;
+ group_by_week: number | false;
+ background_color: string | false;
}
diff --git a/src-migrate/types/cart.ts b/src-migrate/types/cart.ts
index 5a2cf4a9..4e3c8b99 100644
--- a/src-migrate/types/cart.ts
+++ b/src-migrate/types/cart.ts
@@ -23,6 +23,7 @@ export type CartProduct = {
};
export type CartItem = {
+ image_program: string;
cart_id: number;
quantity: number;
selected: boolean;
diff --git a/src-migrate/types/promotionProgram.ts b/src-migrate/types/promotionProgram.ts
index 205884b6..c02cbfd0 100644
--- a/src-migrate/types/promotionProgram.ts
+++ b/src-migrate/types/promotionProgram.ts
@@ -5,4 +5,6 @@ export type IPromotionProgram = {
end_time: string;
applies_to: string;
time_left: number;
+ image:string;
+ banner:string;
};
diff --git a/src-migrate/types/voucher.ts b/src-migrate/types/voucher.ts
new file mode 100644
index 00000000..3e90f449
--- /dev/null
+++ b/src-migrate/types/voucher.ts
@@ -0,0 +1,8 @@
+export interface IVoucher {
+ id: number;
+ image: string;
+ name: string;
+ code: string;
+ description: string | false;
+ remaining_time: string;
+}
diff --git a/src/api/promoApi.js b/src/api/promoApi.js
index 0e82c8b9..3f85db8e 100644
--- a/src/api/promoApi.js
+++ b/src/api/promoApi.js
@@ -13,11 +13,13 @@ export const fetchPromoItems = async (type) => {
}
};
-export const fetchPromoItemsSolr = async (type) => {
+export const fetchPromoItemsSolr = async (type, start, rows) => {
// let query = type ? `type_value_s:${type}` : '*:*';
let sort ='sort=if(exists(sequence_i),0,1) asc, sequence_i asc, if(exists(total_qty_sold_f), total_qty_sold_f, -1) desc';
- let start = 0
- let rows = 100
+ // let start = 0
+ // let rows = 100
+ // let start = 0
+ // let rows = 10
try {
const queryParams = new URLSearchParams({ q: type });
const response = await fetch(`/solr/promotion_program_lines/select?${queryParams.toString()}&rows=${rows}&start=${start}&${sort}`);
diff --git a/src/core/components/elements/Footer/PromoOffer.tsx b/src/core/components/elements/Footer/PromoOffer.tsx
new file mode 100644
index 00000000..a5432b6a
--- /dev/null
+++ b/src/core/components/elements/Footer/PromoOffer.tsx
@@ -0,0 +1,112 @@
+
+import React from "react";
+// import { useGeneralSetting } from "@/common/state-management/general-setting";
+import { FormEvent, useEffect, useState } from "react";
+import toast from "react-hot-toast";
+import style from "../Footer/style/promoOffer.module.css"
+const PromoOffer = ()=>{
+ // const { data, isLoading, fetchData } = useGeneralSetting();
+ const [formData, setFormData] = useState<FormData>({
+ email: "",
+ name: "",
+ telephone: "",
+ message: "",
+ });
+
+ useEffect(() => {
+ // fetchData();
+ }, []);
+
+ type FormData = {
+ email: string;
+ name: string;
+ telephone: string;
+ message: string;
+ };
+
+ const [errors, setErrors] = useState({
+ email: false,
+ name: false,
+ message: false,
+ });
+
+
+const handleGetOffer = async (e: FormEvent) => {
+ e.preventDefault();
+ let loadingToast;
+ try {
+ loadingToast = toast.loading("Mengirimkan formulir...");
+ const response = await fetch("/api/contactus", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ ...formData, from: "newsletter" }),
+ });
+
+ if (response.ok) {
+ toast.dismiss(loadingToast);
+ toast.success("Terima kasih telah menghubungi kami");
+ setFormData({
+ email: "",
+ name: "",
+ telephone: "",
+ message: "",
+ });
+ } else {
+ toast.dismiss(loadingToast);
+ toast.error("Gagal mengirimkan formulir. Silakan coba lagi nanti.");
+ }
+ } catch (error) {
+ toast.dismiss(loadingToast);
+ console.error("Gagal mengirimkan formulir", error);
+ toast.error("Terjadi kesalahan. Silakan coba lagi nanti.");
+ }
+ };
+
+
+
+
+ return(
+ <div className=" py-3 mb-3">
+ <div className="md:flex container mx-auto md:justify-between md:items-center md:gap-x-20">
+ <div className="">
+ <span className="text-black font-semibold text-sm md:text-lg">
+ Dapatkan Promo Menarik Setiap Bulan{" "}
+ </span>
+ <p>
+ Promo produk dengan penawaran terbatas setiap bulannya!
+ </p>
+ </div>
+ <div className=" flex-1 flex items-center h-full justify-end text-sm text-slate-950">
+ <form onSubmit={handleGetOffer} className={style['form-input']}>
+ <div className="flex justify-start w-full">
+ <div className="flex justify-end">
+ <input
+ type="email"
+ value={formData.email}
+ onChange={(e) =>
+ setFormData({ ...formData, email: e.target.value })
+ }
+ className={style['input']}
+ placeholder="Masukkan email anda disini"
+ />
+ <button
+ type="submit"
+ className={style['button']}
+ >
+ Dapatkan
+ </button>
+ </div>
+
+
+ </div>
+ </form>
+
+ </div>
+ </div>
+ </div>
+ )
+ };
+
+ export default PromoOffer;
diff --git a/src/core/components/elements/Footer/style/promoOffer.module.css b/src/core/components/elements/Footer/style/promoOffer.module.css
new file mode 100644
index 00000000..3184182d
--- /dev/null
+++ b/src/core/components/elements/Footer/style/promoOffer.module.css
@@ -0,0 +1,39 @@
+.form-input {
+ @apply
+ h-full
+ w-[514px]
+ text-slate-950
+ flex
+ justify-center;
+}
+
+.input{
+ @apply w-[320px]
+ sm:w-[320px]
+ md:w-[500px]
+ xl:w-[514px]
+ lg:w-[514px]
+ 2xl:w-[514px]
+ text-black
+ py-2
+ h-11
+ md:py-3
+ px-4
+ bg-[#FDF1C7]
+ rounded-3xl
+ focus:outline-none
+ ;
+}
+
+.button{
+ @apply bg-[#FAD147]
+ absolute
+ py-1.5
+ rounded-3xl
+ text-black
+ md:py-2.5
+ px-4
+ h-11
+ z-0
+ ;
+} \ No newline at end of file
diff --git a/src/core/components/elements/Navbar/NavbarDesktop.jsx b/src/core/components/elements/Navbar/NavbarDesktop.jsx
index 9de761a2..7d9e4264 100644
--- a/src/core/components/elements/Navbar/NavbarDesktop.jsx
+++ b/src/core/components/elements/Navbar/NavbarDesktop.jsx
@@ -27,6 +27,7 @@ import {
MenuList,
useDisclosure,
} from '@chakra-ui/react';
+import style from "./style/NavbarDesktop.module.css";
const Search = dynamic(() => import('./Search'), { ssr: false });
const TopBanner = dynamic(() => import('./TopBanner'), { ssr: false });
@@ -203,45 +204,65 @@ const NavbarDesktop = () => {
</div>
</button>
<div className='w-6/12 flex px-1 divide-x divide-gray_r-6'>
+ <Link
+ href="/shop/promo"
+ className={`${
+ router.asPath === '/shop/promo' && 'bg-gray_r-3'
+ } flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition group relative`} // Added relative position
+ target="_blank"
+ rel="noreferrer"
+ >
+ <p className="absolute inset-0 flex justify-center items-center group-hover:scale-105 group-hover:text-red-500 transition-transform duration-200 z-10">Semua Promo</p>
+ {/* <div className='w-full h-full flex justify-end items-start'>
+ <Image
+ src='/images/ICON PROMO DISKON.svg'
+ alt='promo'
+ width={100}
+ height={100}
+ quality={100}
+ className={`inline-block z-20`}
+ />
+ </div> */}
+ </Link>
+
+
<Link
href='/shop/brands'
- className={`${
+ className={`${
router.asPath === '/shop/brands' && 'bg-gray_r-3'
- } p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition`}
+ } p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition group`}
target='_blank'
rel='noreferrer'
>
- Semua Brand
+ <p className="group-hover:scale-105 group-hover:text-red-500 transition-transform duration-200">Semua Brand</p>
</Link>
<Link
href='/shop/search?orderBy=stock'
className={`${
router.asPath === '/shop/search?orderBy=stock' &&
'bg-gray_r-3'
- } p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition`}
+ } p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition group`}
target='_blank'
rel='noreferrer'
>
- Ready Stock
+ <p className="group-hover:scale-105 group-hover:text-red-500 transition-transform duration-200">Ready Stock</p>
</Link>
<Link
href='https://blog.indoteknik.com/'
- className='p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition'
+ className='p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition group'
target='_blank'
rel='noreferrer noopener'
>
- Blog Indoteknik
+ <p className="group-hover:scale-105 group-hover:text-red-500 transition-transform duration-200">Blog Indoteknik</p>
</Link>
- <Link
+ {/* <Link
href='/video'
- className={`${
- router.asPath === '/video' && 'bg-gray_r-3'
- } p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition`}
+ className='p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition'
target='_blank'
rel='noreferrer'
>
Indoteknik TV
- </Link>
+ </Link> */}
</div>
<div className='w-3/12 flex gap-x-1 relative'>
diff --git a/src/core/components/elements/Navbar/style/NavbarDesktop.module.css b/src/core/components/elements/Navbar/style/NavbarDesktop.module.css
new file mode 100644
index 00000000..9cddb127
--- /dev/null
+++ b/src/core/components/elements/Navbar/style/NavbarDesktop.module.css
@@ -0,0 +1,14 @@
+/* navbarDesktop.module.css */
+@keyframes blink {
+ 0%, 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0;
+ }
+}
+
+.blink {
+ animation: blink 0.8s infinite;
+}
+
diff --git a/src/core/components/elements/Sidebar/Sidebar.jsx b/src/core/components/elements/Sidebar/Sidebar.jsx
index 38fcdef8..55838890 100644
--- a/src/core/components/elements/Sidebar/Sidebar.jsx
+++ b/src/core/components/elements/Sidebar/Sidebar.jsx
@@ -117,6 +117,9 @@ const Sidebar = ({ active, close }) => {
</>
)}
</div>
+ <SidebarLink className={itemClassName} href='/shop/promo'>
+ Semua Promo
+ </SidebarLink>
<SidebarLink className={itemClassName} href='/shop/brands'>
Semua Brand
</SidebarLink>
@@ -128,9 +131,9 @@ const Sidebar = ({ active, close }) => {
>
Blog Indoteknik
</SidebarLink>
- <SidebarLink className={itemClassName} href='/video'>
+ {/* <SidebarLink className={itemClassName} href='/video'>
Indoteknik TV
- </SidebarLink>
+ </SidebarLink> */}
<SidebarLink className={itemClassName} href='/tentang-kami'>
Tentang Indoteknik
</SidebarLink>
diff --git a/src/lib/checkout/api/checkoutApi.js b/src/lib/checkout/api/checkoutApi.js
index 24f1868a..fd982fff 100644
--- a/src/lib/checkout/api/checkoutApi.js
+++ b/src/lib/checkout/api/checkoutApi.js
@@ -1,28 +1,20 @@
-import odooApi from '@/core/api/odooApi'
-import { getAuth } from '@/core/utils/auth'
+import odooApi from '@/core/api/odooApi';
+import { getAuth } from '@/core/utils/auth';
export const checkoutApi = async ({ data }) => {
- const auth = getAuth()
+ const auth = getAuth();
const dataCheckout = await odooApi(
'POST',
`/api/v1/partner/${auth.partnerId}/sale_order/checkout`,
data
- )
- return dataCheckout
-}
+ );
+ return dataCheckout;
+};
-export const getProductsCheckout = async (voucher, query) => {
- const id = getAuth()?.id
- let products
- if(voucher && query){
- products = await odooApi('GET',`/api/v1/user/${id}/sale_order/checkout?voucher=${voucher}&source=buy`)
- }else if (voucher){
- products = await odooApi('GET',`/api/v1/user/${id}/sale_order/checkout?voucher=${voucher}`)
- }else if (query) {
- products = await odooApi('GET',`/api/v1/user/${id}/sale_order/checkout?source=buy`)
- }else{
- products = await odooApi('GET',`/api/v1/user/${id}/sale_order/checkout`)
- }
-
- return products
-}
+export const getProductsCheckout = async (query) => {
+ const queryParam = new URLSearchParams(query);
+ const userId = getAuth()?.id;
+ const url = `/api/v1/user/${userId}/sale_order/checkout?${queryParam.toString()}`;
+ const result = await odooApi('GET', url);
+ return result;
+};
diff --git a/src/lib/checkout/api/getVoucher.js b/src/lib/checkout/api/getVoucher.js
index 15c6abbb..779cef43 100644
--- a/src/lib/checkout/api/getVoucher.js
+++ b/src/lib/checkout/api/getVoucher.js
@@ -1,25 +1,28 @@
-import odooApi from '@/core/api/odooApi'
+import odooApi from '@/core/api/odooApi';
import { getAuth } from '@/core/utils/auth'
-export const getVoucher = async (id, source) => {
- let dataVoucher = null
- if(source){
- dataVoucher = await odooApi('GET', `/api/v1/user/${id}/voucher?source=${source}`)
- }else {
- dataVoucher = await odooApi('GET', `/api/v1/user/${id}/voucher`)
- }
- return dataVoucher
-}
+export const getVoucher = async (id, query) => {
+ const queryParam = new URLSearchParams(query);
+ const url = `/api/v1/user/${id}/voucher?${queryParam.toString()}`;
+ const dataVoucher = await odooApi('GET', url);
+ return dataVoucher;
+};
export const findVoucher = async (code, id, source) => {
- let dataVoucher = null
- if(source){
- dataVoucher = await odooApi('GET', `/api/v1/user/${id}/voucher?code=${code}&source=${source}`)
- }else{
- dataVoucher = await odooApi('GET', `/api/v1/user/${id}/voucher?code=${code}`)
+ let dataVoucher = null;
+ if (source) {
+ dataVoucher = await odooApi(
+ 'GET',
+ `/api/v1/user/${id}/voucher?code=${code}&source=${source}`
+ );
+ } else {
+ dataVoucher = await odooApi(
+ 'GET',
+ `/api/v1/user/${id}/voucher?code=${code}`
+ );
}
- return dataVoucher
-}
+ return dataVoucher;
+};
export const getVoucherNew = async (source) => {
diff --git a/src/lib/checkout/components/Checkout.jsx b/src/lib/checkout/components/Checkout.jsx
index 9bc0257e..09a791ee 100644
--- a/src/lib/checkout/components/Checkout.jsx
+++ b/src/lib/checkout/components/Checkout.jsx
@@ -44,9 +44,16 @@ const Checkout = () => {
const auth = useAuth();
const [activeVoucher, SetActiveVoucher] = useState(null);
-
- const { data: cartCheckout } = useQuery('cartCheckout-' + activeVoucher, () =>
- getProductsCheckout(activeVoucher, query)
+ const [activeVoucherShipping, setActiveVoucherShipping] = useState(null);
+
+ const { data: cartCheckout } = useQuery(
+ ['cartCheckout', activeVoucher, activeVoucherShipping],
+ () =>
+ getProductsCheckout({
+ source: query,
+ voucher: activeVoucher,
+ voucher_shipping: activeVoucherShipping,
+ })
);
const [selectedAddress, setSelectedAddress] = useState({
@@ -104,6 +111,7 @@ const Checkout = () => {
const [bottomPopupTnC, SetBottomPopupTnC] = useState(null);
const [itemTnC, setItemTnC] = useState(null);
const [listVouchers, SetListVoucher] = useState(null);
+ const [listVoucherShippings, SetListVoucherShipping] = useState(null);
const [discountVoucher, SetDiscountVoucher] = useState(0);
const [codeVoucher, SetCodeVoucher] = useState(null);
const [findCodeVoucher, SetFindVoucher] = useState(null);
@@ -120,14 +128,23 @@ const Checkout = () => {
const voucher = async () => {
if (!listVouchers) {
try {
- let source = 'source=' + query;
- let dataVoucher = await getVoucherNew(source);
+ setLoadingVoucher(true);
+ let dataVoucher = await getVoucher(auth?.id, {
+ source: query,
+ });
SetListVoucher(dataVoucher);
+
+ let dataVoucherShipping = await getVoucher(auth?.id, {
+ source: query,
+ type: 'shipping',
+ });
+ SetListVoucherShipping(dataVoucherShipping);
} finally {
setLoadingVoucher(false);
}
}
};
+
const VoucherCode = async (code) => {
const source = 'code=' + code + '&source=' + query;
// let dataVoucher = await findVoucher(code, auth.id, query);
@@ -315,7 +332,7 @@ const Checkout = () => {
useEffect(() => {
const GT =
cartCheckout?.grandTotal +
- Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000;
+ Math.round(parseInt(finalShippingAmt * 1.1) / 1000) * 1000;
const finalGT = GT < 0 ? 0 : GT;
setGrandTotal(finalGT);
}, [biayaKirim, cartCheckout?.grandTotal, activeVoucher]);
@@ -361,6 +378,7 @@ const Checkout = () => {
delivery_service_type: selectedExpedisiService,
flash_sale: hasFlashSale, // dibuat negasi untuk ngetest kebalikan nilai false
voucher: activeVoucher,
+ voucher_shipping: activeVoucherShipping,
type: 'sale_order',
};
@@ -460,6 +478,11 @@ const Checkout = () => {
return false;
}, [products]);
+ const voucherShippingAmt = cartCheckout?.discountVoucherShipping || 0;
+ const discShippingAmt = Math.min(biayaKirim, voucherShippingAmt);
+
+ const finalShippingAmt = biayaKirim - discShippingAmt;
+
return (
<>
<BottomPopup
@@ -569,8 +592,145 @@ const Checkout = () => {
</div>
)}
- <hr className='mt-10 my-4 border-gray_r-10' />
- <div className=''>
+ <hr className='mt-8 mb-4 border-gray_r-8' />
+
+ {listVoucherShippings && listVoucherShippings?.length > 0 && (
+ <div>
+ <h3 className='font-semibold mb-4'>Promo Gratis Ongkir</h3>
+ {listVoucherShippings?.map((item) => (
+ <div key={item.id} className='relative'>
+ <div
+ className={`border border-solid mb-5 w-full hover:cursor-pointer p-2 pl-4 pr-4 `}
+ >
+ {item.canApply && (
+ <div
+ class='p-2 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:text-green-400'
+ role='alert'
+ >
+ <p>
+ Potensi potongan sebesar{' '}
+ <span className='text-green font-bold'>
+ {currencyFormat(item.discountVoucher)}
+ </span>
+ </p>
+ </div>
+ )}
+ {!item.canApply && (
+ <div
+ class='p-2 mb-4 text-sm text-red-800 rounded-lg bg-red-50'
+ role='alert'
+ onClick={() => handlingTnC(item)}
+ >
+ <p>
+ Voucher tidak bisa di gunakan,{' '}
+ <span className='text-red font-bold'>
+ Baca Selengkapnya !
+ </span>
+ </p>
+ </div>
+ )}
+
+ <div className={`flex gap-x-3 relative`}>
+ {item.canApply === false && (
+ <div className='absolute w-full h-full bg-gray_r-3/40 top-0 left-0 z-50' />
+ )}
+ <div className='hidden md:w-[250px] md:block'>
+ <Image
+ src={item.image}
+ alt={item.name}
+ className={`object-cover`}
+ />
+ </div>
+ <div className='w-full'>
+ <div className='flex justify-between gap-x-2 mb-1 items-center'>
+ <div className=''>
+ <h3 className='font-semibold'>{item.name}</h3>
+ <div className='mt-1'>
+ <span className='text-sm line-clamp-3'>
+ {item.description}{' '}
+ </span>
+ </div>
+ </div>
+ <div className='flex justify-end'>
+ <label class='relative inline-flex items-center cursor-pointer'>
+ <input
+ type='checkbox'
+ value=''
+ class='sr-only peer'
+ checked={activeVoucherShipping === item.code}
+ onChange={() =>
+ setActiveVoucherShipping(
+ activeVoucherShipping === item.code
+ ? null
+ : item.code
+ )
+ }
+ />
+ <div class="w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-focus:ring-4 peer-focus:ring-green-300 dark:peer-focus:ring-green-800 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-green-600"></div>
+ </label>
+ </div>
+ </div>
+ <hr className='mt-2 my-2 border-gray_r-8' />
+ <div className='flex justify-between items-center'>
+ <p className='text-justify text-sm md:text-xs'>
+ Kode Voucher :{' '}
+ <span className='text-red-500 font-semibold'>
+ {item.code}
+ </span>
+ </p>
+ <p className='text-sm md:text-xs'>
+ {activeVoucher === item.code && (
+ <span className=' text-green-600'>
+ Voucher digunakan{' '}
+ </span>
+ )}
+ </p>
+ </div>
+ <div className='flex items-center mt-3'>
+ <svg
+ aria-hidden='true'
+ fill='none'
+ stroke='currentColor'
+ stroke-width='1.5'
+ viewBox='0 0 24 24'
+ className='w-5 text-black'
+ >
+ <path
+ d='M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z'
+ stroke-linecap='round'
+ stroke-linejoin='round'
+ ></path>
+ </svg>
+ <div className='flex justify-between items-center'>
+ <div className='text-left ml-3 text-sm '>
+ Berakhir dalam{' '}
+ <span className='text-red-600'>
+ {item.remainingTime}
+ </span>{' '}
+ lagi,{' '}
+ </div>
+ <div
+ className='text-sm ml-2 text-red-600'
+ onClick={() => handlingTnC(item)}
+ >
+ Baca S&K
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div className='mt-3'>
+ <p className='text-justify text-sm '></p>
+ </div>
+ </div>
+ </div>
+ ))}
+ </div>
+ )}
+
+ <hr className='mt-8 mb-4 border-gray_r-8' />
+
+ <div>
{!loadingVoucher && listVouchers?.length === 0 ? (
<div className='flex items-center justify-center mt-4 mb-4'>
<div className='text-center'>
@@ -755,14 +915,7 @@ const Checkout = () => {
</div>
</div>
<div className='mt-3'>
- <p className='text-justify text-sm '>
- {/* {item.canApply === false
- ? 'Tambah ' +
- currencyFormat(item.differenceToApply) +
- ' untuk pakai promo ini'
- : 'Potensi potongan sebesar ' +
- currencyFormat(hitungDiscountVoucher(item.code))} */}
- </p>
+ <p className='text-justify text-sm '></p>
</div>
</div>
</div>
@@ -928,14 +1081,18 @@ const Checkout = () => {
</div>
<div className='flex gap-x-2 justify-between'>
<div className='text-gray_r-11'>
- Biaya Kirim <p className='text-xs mt-3'>{etdFix}</p>
- </div>
- <div>
- {currencyFormat(
- Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
- )}
+ Biaya Kirim <p className='text-xs mt-1'>{etdFix}</p>
</div>
+ <div>{currencyFormat(biayaKirim)}</div>
</div>
+ {activeVoucherShipping && voucherShippingAmt && (
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>Diskon Kirim</div>
+ <div className='text-danger-500'>
+ - {currencyFormat(discShippingAmt)}
+ </div>
+ </div>
+ )}
</div>
)}
@@ -964,7 +1121,7 @@ const Checkout = () => {
<div className='mt-4 mb-4'>
<button
type='button'
- onClick={() => {
+ onClick={async () => {
SetBottomPopup(true);
voucher();
}}
@@ -1220,14 +1377,18 @@ const Checkout = () => {
<div className='flex gap-x-2 justify-between'>
<div className='text-gray_r-11'>
Biaya Kirim
- <p className='text-xs mt-3'>{etdFix}</p>
- </div>
- <div>
- {currencyFormat(
- Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
- )}
+ <p className='text-xs mt-1'>{etdFix}</p>
</div>
+ <div>{currencyFormat(biayaKirim)}</div>
</div>
+ {activeVoucherShipping && voucherShippingAmt && (
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>Diskon Kirim</div>
+ <div className='text-danger-500'>
+ - {currencyFormat(discShippingAmt)}
+ </div>
+ </div>
+ )}
</div>
)}
diff --git a/src/lib/home/components/PreferredBrand.jsx b/src/lib/home/components/PreferredBrand.jsx
index ec09aa4e..6b64a444 100644
--- a/src/lib/home/components/PreferredBrand.jsx
+++ b/src/lib/home/components/PreferredBrand.jsx
@@ -48,6 +48,11 @@ const PreferredBrand = () => {
Lihat Semua
</Link>
)}
+ {isMobile && (
+ <Link href='/shop/brands' className='!text-red-500 font-semibold sm:text-h-sm'>
+ Lihat Semua
+ </Link>
+ )}
</div>
{manufactures.isLoading && <PreferredBrandSkeleton />}
{!manufactures.isLoading && (
diff --git a/src/lib/home/components/PromotionProgram.jsx b/src/lib/home/components/PromotionProgram.jsx
index b204df8e..99258d94 100644
--- a/src/lib/home/components/PromotionProgram.jsx
+++ b/src/lib/home/components/PromotionProgram.jsx
@@ -13,10 +13,14 @@ const BannerSection = () => {
<div className='flex justify-between items-center mb-4 '>
<div className='font-semibold sm:text-h-lg'>Promo Tersedia</div>
{isDesktop && (
- <div></div>
- // <Link href='/shop/promo' className='!text-red-500 font-semibold'>
- // Lihat Semua
- // </Link>
+ <Link href='/shop/promo' className='!text-red-500 font-semibold'>
+ Lihat Semua
+ </Link>
+ )}
+ {isMobile && (
+ <Link href='/shop/promo' className='!text-red-500 font-semibold sm:text-h-sm'>
+ Lihat Semua
+ </Link>
)}
</div>
{isDesktop && (promotionProgram.data &&
diff --git a/src/lib/quotation/components/Quotation.jsx b/src/lib/quotation/components/Quotation.jsx
index 8855c6c4..df234dc2 100644
--- a/src/lib/quotation/components/Quotation.jsx
+++ b/src/lib/quotation/components/Quotation.jsx
@@ -67,17 +67,19 @@ const Quotation = () => {
const [selectedExpedisiService, setselectedExpedisiService] = useState(null);
const [etd, setEtd] = useState(null);
const [etdFix, setEtdFix] = useState(null);
-
+
const [isApproval, setIsApproval] = useState(false);
-
+
const expedisiValidation = useRef(null);
-
+
const [selectedAddress, setSelectedAddress] = useState({
shipping: null,
invoicing: null,
});
-
+
const [addresses, setAddresses] = useState(null);
+
+ const [note_websiteText, setselectedNote_websiteText] = useState('');
useEffect(() => {
if (!auth) return;
@@ -262,6 +264,12 @@ const Quotation = () => {
}
if (!products || products.length == 0) return;
+
+ if (isApproval && note_websiteText == '') {
+ toast.error('Maaf, Note wajib dimasukkan.');
+ return;
+ }
+
setIsLoading(true);
const productOrder = products.map((product) => ({
product_id: product.id,
@@ -276,16 +284,18 @@ const Quotation = () => {
carrier_id: selectedCarrierId,
estimated_arrival_days: splitDuration(etd),
delivery_service_type: selectedExpedisiService,
+ note_website : note_websiteText,
};
- console.log('data checkout', data);
+
const isSuccess = await checkoutApi({ data });
- console.log('isSuccess', isSuccess);
+ ;
setIsLoading(false);
if (isSuccess?.id) {
for (const product of products) deleteItemCart({ productId: product.id });
router.push(`/shop/quotation/finish?id=${isSuccess.id}`);
return;
}
+
toast.error('Gagal melakukan transaksi, terjadi kesalahan internal');
};
@@ -442,8 +452,25 @@ const Quotation = () => {
</Link>{' '}
yang berlaku
</p>
+ <hr className='my-4 border-gray_r-6' />
+
+ <div className='flex gap-x-2 justify-start mb-4'>
+ <div className=''>Note</div>
+ {isApproval && (
+ <div className='text-caption-1 text-red-500 items-center flex'>*harus diisi</div>
+ )}
+ </div>
+ <div className='text-caption-2 text-gray_r-11'>
+ <textarea
+ rows="4"
+ cols="50"
+ className={`w-full p-1 rounded border border-gray_r-6`}
+ onChange={(e) => setselectedNote_websiteText(e.target.value)}
+ />
+ </div>
</div>
-
+
+
<Divider />
<div className='flex gap-x-3 p-4'>
@@ -576,6 +603,27 @@ const Quotation = () => {
yang berlaku
</p>
+ <div>
+ <hr className='my-4 border-gray_r-6' />
+
+ <div className='flex gap-x-1 flex-col mb-4'>
+ <div className='flex flex-row gap-x-1'>
+ <div className=''>Note</div>
+ {isApproval && (
+ <div className='text-caption-1 text-red-500 items-center flex'>*harus diisi</div>
+ )}
+ </div>
+ <div className='text-caption-2 text-gray_r-11'>
+ <textarea
+ rows="4"
+ cols="50"
+ className={`w-full p-1 rounded border border-gray_r-6`}
+ onChange={(e) => setselectedNote_websiteText(e.target.value)}
+ />
+ </div>
+ </div>
+ </div>
+
<hr className='my-4 border-gray_r-6' />
<button
diff --git a/src/lib/transaction/api/rejectProductApi.js b/src/lib/transaction/api/rejectProductApi.js
new file mode 100644
index 00000000..e03c7975
--- /dev/null
+++ b/src/lib/transaction/api/rejectProductApi.js
@@ -0,0 +1,9 @@
+import odooApi from '@/core/api/odooApi'
+
+const rejectProductApi = async ({ idSo, idProduct, reason }) => {
+ const dataCheckout = await odooApi('POST', `/api/v1/sale_order/${idSo}/reject/${idProduct}`, {
+ reason_reject: reason
+ });
+ return dataCheckout
+}
+export default rejectProductApi \ No newline at end of file
diff --git a/src/lib/transaction/components/Transaction.jsx b/src/lib/transaction/components/Transaction.jsx
index c6152ca9..9bef895a 100644
--- a/src/lib/transaction/components/Transaction.jsx
+++ b/src/lib/transaction/components/Transaction.jsx
@@ -1,4 +1,6 @@
import Spinner from '@/core/components/elements/Spinner/Spinner';
+import NextImage from 'next/image';
+import rejectImage from "../../../../public/images/reject.png"
import useTransaction from '../hooks/useTransaction';
import TransactionStatusBadge from './TransactionStatusBadge';
import Divider from '@/core/components/elements/Divider/Divider';
@@ -34,8 +36,14 @@ import useAuth from '@/core/hooks/useAuth';
import StepApproval from './stepper';
import aprpoveApi from '../api/approveApi';
import rejectApi from '../api/rejectApi';
+import rejectProductApi from '../api/rejectProductApi';
+import { useRouter } from 'next/router';
const Transaction = ({ id }) => {
+ const router = useRouter()
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [selectedProduct, setSelectedProduct] = useState(null);
+ const [reason, setReason] = useState('');
const auth = useAuth();
const { transaction } = useTransaction({ id });
@@ -141,6 +149,15 @@ const Transaction = ({ id }) => {
[transaction.data]
);
+ const memoizeVariantGroupCardReject = useMemo(
+ () => (
+ <div className='p-4 pt-0 flex flex-col gap-y-3'>
+ <VariantGroupCard variants={transaction.data?.productsRejectLine} buyMore />
+ </div>
+ ),
+ [transaction.data]
+ );
+
if (transaction.isLoading) {
return (
<div className='flex justify-center my-6'>
@@ -153,6 +170,38 @@ const Transaction = ({ id }) => {
setIdAWB(null);
};
+ const openModal = (product) => {
+ setSelectedProduct(product);
+ setIsModalOpen(true);
+ };
+
+ const closeModal = () => {
+ setIsModalOpen(false);
+ setSelectedProduct(null);
+ setReason('');
+ };
+
+ const handleRejectProduct = async () => {
+ try{
+ if (!reason.trim()) {
+ toast.error('Masukkan alasan terlebih dahulu');
+ return;
+ }else{
+ let idSo = transaction?.data.id
+ let idProduct = selectedProduct?.id
+ await rejectProductApi({ idSo, idProduct, reason});
+ closeModal();
+ toast.success("Produk berhasil di reject")
+ setTimeout(() => {
+ window.location.reload();
+ }, 1500);
+ }
+ }catch(error){
+ toast.error('Gagal reject produk. Silakan coba lagi.');
+ }
+ };
+
+
return (
transaction.data?.name && (
<>
@@ -301,6 +350,37 @@ const Transaction = ({ id }) => {
<Divider />
+ <div className='p-4'>
+ <p className='font-medium'>Invoice</p>
+ <div className='flex flex-col gap-y-3 mt-4'>
+ {transaction.data?.invoices?.map((invoice, index) => (
+ <Link href={`/my/invoices/${invoice.id}`} key={index}>
+ <div className='shadow rounded-md p-4 text-gray_r-12 font-normal flex justify-between'>
+ <div>
+ <p className='mb-2'>{invoice?.name}</p>
+ <div className='flex items-center gap-x-1'>
+ {invoice.amountResidual > 0 ? (
+ <div className='badge-red'>Belum Lunas</div>
+ ) : (
+ <div className='badge-green'>Lunas</div>
+ )}
+ <p className='text-caption-2 text-gray_r-11'>
+ {currencyFormat(invoice.amountTotal)}
+ </p>
+ </div>
+ </div>
+ <ChevronRightIcon className='w-5 stroke-2' />
+ </div>
+ </Link>
+ ))}
+ {transaction.data?.invoices?.length === 0 && (
+ <div className='badge-red text-sm px-2'>Belum ada invoice</div>
+ )}
+ </div>
+ </div>
+
+ <Divider />
+
{!auth?.feature.soApproval && (
<div className='p-4 flex flex-col gap-y-4'>
<DescriptionRow label='Purchase Order'>
@@ -326,8 +406,20 @@ const Transaction = ({ id }) => {
<Divider />
<div className='font-medium p-4'>Detail Produk</div>
+ {transaction?.data?.products.length > 0? (
+ <div>
+ {memoizeVariantGroupCard}
+ </div>
+ ) : (
+ <div className='badge-red text-sm px-2 ml-4'>Semua produk telah di reject</div>
+ )}
- {memoizeVariantGroupCard}
+ {transaction?.data?.productsRejectLine.length > 0 && (
+ <div>
+ <div className='font-medium p-4'>Detail Produk Reject</div>
+ {memoizeVariantGroupCardReject}
+ </div>
+ )}
<Divider />
@@ -335,37 +427,6 @@ const Transaction = ({ id }) => {
<Divider />
- <div className='p-4'>
- <p className='font-medium'>Invoice</p>
- <div className='flex flex-col gap-y-3 mt-4'>
- {transaction.data?.invoices?.map((invoice, index) => (
- <Link href={`/my/invoices/${invoice.id}`} key={index}>
- <div className='shadow rounded-md p-4 text-gray_r-12 font-normal flex justify-between'>
- <div>
- <p className='mb-2'>{invoice?.name}</p>
- <div className='flex items-center gap-x-1'>
- {invoice.amountResidual > 0 ? (
- <div className='badge-red'>Belum Lunas</div>
- ) : (
- <div className='badge-green'>Lunas</div>
- )}
- <p className='text-caption-2 text-gray_r-11'>
- {currencyFormat(invoice.amountTotal)}
- </p>
- </div>
- </div>
- <ChevronRightIcon className='w-5 stroke-2' />
- </div>
- </Link>
- ))}
- {transaction.data?.invoices?.length === 0 && (
- <div className='badge-red text-sm px-2'>Belum ada invoice</div>
- )}
- </div>
- </div>
-
- <Divider />
-
<div className='p-4 pt-0'>
{transaction.data?.status == 'draft' &&
auth?.feature.soApproval && (
@@ -562,67 +623,100 @@ const Transaction = ({ id }) => {
/>
</div>
</div>
-
- <div className='text-h-sm font-semibold mt-10 mb-4'>
- Pengiriman
- </div>
- <div className='grid grid-cols-3 gap-1'>
- {transaction?.data?.pickings?.map((airway) => (
- <button
- className='shadow rounded-md p-3 text-gray_r-12 font-normal flex justify-between items-center text-left'
- key={airway?.id}
- onClick={() => setIdAWB(airway?.id)}
- >
- <div>
- <span className='text-sm text-gray_r-11'>
- No Resi : {airway?.trackingNumber || '-'}{' '}
- </span>
- <p className='mt-1 font-medium'>{airway?.name}</p>
- </div>
- <div className='flex gap-x-2'>
- <div className='text-sm text-gray-600 badge-green leading-[1.5] mt-1'>
- {airway?.delivered ? 'Pesanan Tiba' : 'Sedang Dikirim'}
- </div>
- <ChevronRightIcon className='w-5 stroke-2' />
+ <div className='flex '>
+ <div className='w-1/2'>
+ <div className='text-h-sm font-semibold mt-10 mb-4'>
+ Pengiriman
+ </div>
+ {transaction?.data?.pickings.length == 0 && (
+ <div className='badge-red text-sm'>Belum ada pengiriman</div>
+ )}
+ <div className='grid grid-cols-1 gap-1 w-2/3'>
+ {transaction?.data?.pickings?.map((airway) => (
+ <button
+ className='shadow rounded-md p-3 text-gray_r-12 font-normal flex justify-between items-center text-left h-20'
+ key={airway?.id}
+ onClick={() => setIdAWB(airway?.id)}
+ >
+ <div>
+ <span className='text-sm text-gray_r-11'>
+ No Resi : {airway?.trackingNumber || '-'}{' '}
+ </span>
+ <p className='mt-1 font-medium'>{airway?.name}</p>
+ </div>
+ <div className='flex gap-x-2'>
+ <div className='text-sm text-gray-600 badge-green leading-[1.5] mt-1'>
+ {airway?.delivered ? 'Pesanan Tiba' : 'Sedang Dikirim'}
+ </div>
+ <ChevronRightIcon className='w-5 stroke-2' />
+ </div>
+ </button>
+ ))}
+ </div>
+ </div>
+ <div className='invoice w-1/2 '>
+ <div className='text-h-sm font-semibold mt-10 mb-4 '>Invoice</div>
+ {transaction.data?.invoices?.length === 0 && (
+ <div className='badge-red text-sm'>Belum ada invoice</div>
+ )}
+ <div className='grid grid-cols-1 gap-1 w-2/3 '>
+ {transaction.data?.invoices?.map((invoice, index) => (
+ <Link href={`/my/invoices/${invoice.id}`} key={index}>
+ <div className='shadow rounded-md p-4 text-gray_r-12 font-normal flex justify-between'>
+ <div>
+ <p className='mb-1'>{invoice?.name}</p>
+ <div className='flex items-center gap-x-1'>
+ {invoice.amountResidual > 0 ? (
+ <div className='badge-red'>Belum Lunas</div>
+ ) : (
+ <div className='badge-green'>Lunas</div>
+ )}
+ <p className='text-caption-2 text-gray_r-11'>
+ {currencyFormat(invoice.amountTotal)}
+ </p>
+ </div>
+ </div>
+ <ChevronRightIcon className='w-5 stroke-2' />
+ </div>
+ </Link>
+ ))}
</div>
- </button>
- ))}
+ </div>
</div>
- {transaction?.data?.pickings.length == 0 && (
- <div className='badge-red text-sm'>Belum ada pengiriman</div>
- )}
- <div className='text-h-sm font-semibold mt-10 mb-4'>
+ <div className='text-h-sm font-semibold mt-4 mb-4'>
Rincian Pembelian
</div>
- <table className='table-data'>
- <thead>
- <tr>
- <th>Nama Produk</th>
- {/* <th>Diskon</th> */}
- <th>Jumlah</th>
- <th>Harga</th>
- <th>Subtotal</th>
- </tr>
- </thead>
- <tbody>
- {transaction?.data?.products?.map((product) => (
- <tr key={product.id}>
- <td className='flex'>
- <Link
- href={createSlug(
- '/shop/product/',
- product?.parent.name,
- product?.parent.id
- )}
- className='w-[20%] flex-shrink-0'
- >
+ {transaction?.data?.products?.length > 0? (
+ <table className='table-data'>
+ <thead>
+ <tr>
+ <th>Nama Produk</th>
+ {/* <th>Diskon</th> */}
+ <th>Jumlah</th>
+ <th>Harga</th>
+ <th>Subtotal</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ {transaction?.data?.products?.map((product) => (
+ <tr key={product.id}>
+ <td className='flex'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='w-[20%] flex-shrink-0'
+ >
<div className='relative'>
- <Image
- src={product?.parent?.image}
- alt={product?.name}
- className='object-contain object-center border border-gray_r-6 h-32 w-full rounded-md'
- />
+ <Image
+ src={product?.parent?.image}
+ alt={product?.name}
+ className='object-contain object-center border border-gray_r-6 h-32 w-full rounded-md'
+ />
<div className='absolute top-0 right-4 flex mt-3'>
<div className='gambarB '>
{product.isSni && (
@@ -648,47 +742,92 @@ const Transaction = ({ id }) => {
</div>
</div>
</div>
- </Link>
- <div className='px-2 text-left'>
- <Link
- href={createSlug(
- '/shop/product/',
- product?.parent.name,
- product?.parent.id
- )}
- className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
- >
- {product?.parent?.name}
</Link>
- <div className='text-gray_r-11 mt-2'>
- {product?.code}{' '}
- {product?.attributes.length > 0
- ? `| ${product?.attributes.join(', ')}`
- : ''}
- </div>
- </div>
- </td>
- {/* <td>
- {product.price.discountPercentage > 0
- ? `${product.price.discountPercentage}%`
- : ''}
- </td> */}
- <td>{product.quantity}</td>
- <td>
- {/* {product.price.discountPercentage > 0 && (
- <div className='line-through mb-1 text-caption-1 text-gray_r-12/70'>
- {currencyFormat(product.price.price)}
+ <div className='px-2 text-left'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
+ >
+ {product?.parent?.name}
+ </Link>
+ <div className='text-gray_r-11 mt-2'>
+ {product?.code}{' '}
+ {product?.attributes.length > 0
+ ? `| ${product?.attributes.join(', ')}`
+ : ''}
+ </div>
</div>
- )} */}
- <div>{currencyFormat(product.price.priceDiscount)}</div>
- </td>
- <td>{currencyFormat(product.price.subtotal)}</td>
- </tr>
- ))}
- </tbody>
- </table>
-
- <div className='flex justify-end mt-4'>
+ </td>
+ {/* <td>
+ {product.price.discountPercentage > 0
+ ? `${product.price.discountPercentage}%`
+ : ''}
+ </td> */}
+ <td>{product.quantity}</td>
+ <td>
+ {/* {product.price.discountPercentage > 0 && (
+ <div className='line-through mb-1 text-caption-1 text-gray_r-12/70'>
+ {currencyFormat(product.price.price)}
+ </div>
+ )} */}
+ <div>{currencyFormat(product.price.priceDiscount)}</div>
+ </td>
+ <td>{currencyFormat(product.price.subtotal)}</td>
+ {/* {auth?.feature.soApproval && (auth.webRole == 2 || auth.webRole == 3) && (transaction.data.isReaject == false) && ( */}
+ {auth?.feature.soApproval && (auth.webRole == 2 || auth.webRole == 3) && (router.asPath.includes("/my/quotations/")) && transaction.data?.status == 'draft' && (
+ <td>
+ <button
+ className="bg-red-500 text-white py-1 px-3 rounded"
+ onClick={() => openModal(product)}
+ >
+ Reject
+ </button>
+ </td>
+ )}
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ ) : (
+ <div className='badge-red text-sm'>Semua produk telah di reject</div>
+ )}
+
+ {isModalOpen && (
+ <div className='fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center'>
+ <div className='bg-white p-4 rounded w-96
+ ease-in-out opacity-100
+ transform transition-transform duration-300 scale-100'>
+ <h2 className='text-lg mb-2'>Berikan Alasan</h2>
+ <textarea
+ value={reason}
+ onChange={(e) => setReason(e.target.value)}
+ className='w-full p-2 border rounded'
+ rows='4'
+ ></textarea>
+ <div className='mt-4 flex justify-end'>
+ <button
+ className='bg-gray-300 text-black py-1 px-3 rounded mr-2'
+ onClick={closeModal}
+ >
+ Batal
+ </button>
+ <button
+ className='bg-red-500 text-white py-1 px-3 rounded'
+ onClick={handleRejectProduct}
+ >
+ Reject
+ </button>
+ </div>
+ </div>
+ </div>
+ )}
+
+ {transaction?.data?.products?.map((product) => (
+ <div className='flex justify-end mt-4' key={product.id}>
<div className='w-1/4 grid grid-cols-2 gap-y-3 text-gray_r-12/80'>
<div className='text-right'>Subtotal</div>
<div className='text-right font-medium'>
@@ -713,33 +852,91 @@ const Transaction = ({ id }) => {
</div>
</div>
</div>
+ ))}
- <div className='text-h-sm font-semibold mt-10 mb-4'>Invoice</div>
- <div className='grid grid-cols-3 gap-4'>
- {transaction.data?.invoices?.map((invoice, index) => (
- <Link href={`/my/invoices/${invoice.id}`} key={index}>
- <div className='shadow rounded-md p-4 text-gray_r-12 font-normal flex justify-between'>
- <div>
- <p className='mb-2'>{invoice?.name}</p>
- <div className='flex items-center gap-x-1'>
- {invoice.amountResidual > 0 ? (
- <div className='badge-red'>Belum Lunas</div>
- ) : (
- <div className='badge-green'>Lunas</div>
- )}
- <p className='text-caption-2 text-gray_r-11'>
- {currencyFormat(invoice.amountTotal)}
- </p>
- </div>
- </div>
- <ChevronRightIcon className='w-5 stroke-2' />
- </div>
- </Link>
- ))}
- </div>
- {transaction.data?.invoices?.length === 0 && (
- <div className='badge-red text-sm'>Belum ada invoice</div>
+
+
+ {transaction?.data?.productsRejectLine.length > 0 && (
+ <div className='text-h-sm font-semibold mt-10 mb-4'>
+ Rincian Produk Reject
+ </div>
+ )}
+ {transaction?.data?.productsRejectLine.length > 0 && (
+ <table className='table-data'>
+ <thead>
+ <tr>
+ <th>Nama Produk</th>
+ {/* <th>Diskon</th> */}
+ <th>Jumlah</th>
+ <th>Harga</th>
+ <th>Subtotal</th>
+ </tr>
+ </thead>
+ <tbody>
+ {transaction?.data?.productsRejectLine?.map((product) => (
+ <tr key={product.id}>
+ <td className='flex'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='w-[20%] flex-shrink-0'
+ >
+ <Image
+ src={product?.parent?.image}
+ alt={product?.name}
+ className='object-contain object-center border border-gray_r-6 h-32 w-full rounded-md'
+ />
+ </Link>
+ <div className='px-2 text-left'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
+ >
+ {product?.parent?.name}
+ </Link>
+ <div className='text-gray_r-11 mt-2'>
+ {product?.code}{' '}
+ {product?.attributes.length > 0
+ ? `| ${product?.attributes.join(', ')}`
+ : ''}
+ </div>
+ </div>
+ </td>
+ {/* <td>
+ {product.price.discountPercentage > 0
+ ? `${product.price.discountPercentage}%`
+ : ''}
+ </td> */}
+ <td>{product.quantity}</td>
+ <td>
+ {/* {product.price.discountPercentage > 0 && (
+ <div className='line-through mb-1 text-caption-1 text-gray_r-12/70'>
+ {currencyFormat(product.price.price)}
+ </div>
+ )} */}
+ <div>{currencyFormat(product.price.priceDiscount)}</div>
+ </td>
+ <td className='flex justify-center'>
+ <NextImage
+ src={rejectImage}
+ alt='Reject'
+ width={90}
+ height={30}
+ />
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
)}
+
</div>
</div>
</DesktopView>
diff --git a/src/lib/variant/components/VariantCard.jsx b/src/lib/variant/components/VariantCard.jsx
index 9f65fc3c..68cdf54f 100644
--- a/src/lib/variant/components/VariantCard.jsx
+++ b/src/lib/variant/components/VariantCard.jsx
@@ -1,18 +1,26 @@
import { useRouter } from 'next/router'
import { toast } from 'react-hot-toast'
-
+import useAuth from '@/core/hooks/useAuth';
import Image from '@/core/components/elements/Image/Image'
import Link from '@/core/components/elements/Link/Link'
import { createSlug } from '@/core/utils/slug'
import currencyFormat from '@/core/utils/currencyFormat'
import { updateItemCart } from '@/core/utils/cart'
import whatsappUrl from '@/core/utils/whatsappUrl'
+import {useState } from 'react';
+import rejectProductApi from '../../../lib/transaction/api/rejectProductApi'
+// import {useTransaction} from 'C:\Users\Indoteknik\next-indoteknik\src\lib\transaction\hooks\useTransaction.js'
+import useTransaction from '../../../lib/transaction/hooks/useTransaction';
import ImageNext from 'next/image';
-import { useMemo, useEffect, useState } from 'react';
const VariantCard = ({ product, openOnClick = true, buyMore = false }) => {
const router = useRouter()
-
+ const id = router.query.id
+ const auth = useAuth();
+ const { transaction } = useTransaction({id});
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [selectedProduct, setSelectedProduct] = useState(null);
+ const [reason, setReason] = useState('');
@@ -24,11 +32,42 @@ const VariantCard = ({ product, openOnClick = true, buyMore = false }) => {
})
return
}
-
+
const checkoutItem = () => {
router.push(`/shop/checkout?product_id=${product.id}&qty=${product.quantity}`)
}
-
+
+ const openModal = (product) => {
+ setSelectedProduct(product);
+ setIsModalOpen(true);
+ };
+
+ const closeModal = () => {
+ setIsModalOpen(false);
+ setSelectedProduct(null);
+ setReason('');
+ };
+
+ const handleRejectProduct = async () => {
+ try {
+ if (!reason.trim()) {
+ toast.error('Masukkan alasan terlebih dahulu');
+ return;
+ }else{
+ let idSo = transaction?.data.id
+ let idProduct = selectedProduct.id
+ await rejectProductApi({ idSo, idProduct, reason});
+ closeModal();
+ toast.success("Produk berhasil di reject")
+ setTimeout(() => {
+ window.location.reload();
+ }, 1500);
+ }
+ } catch (error) {
+ toast.error('Gagal reject produk. Silakan coba lagi.');
+ }
+ };
+
const Card = () => (
<div className='flex gap-x-3'>
<div className='w-4/12 flex items-center gap-x-2'>
@@ -115,7 +154,7 @@ const VariantCard = ({ product, openOnClick = true, buyMore = false }) => {
<Link href={createSlug('/shop/product/', product.parent.name, product.parent.id)}>
<Card />
</Link>
- {buyMore && (
+ {buyMore && (!transaction?.data?.productsRejectLine.some(pr => pr.id === product.id)) && (
<div className='flex justify-end gap-x-2 mb-2'>
<button
type='button'
@@ -124,14 +163,48 @@ const VariantCard = ({ product, openOnClick = true, buyMore = false }) => {
>
Tambah Keranjang
</button>
- <button
- type='button'
- onClick={checkoutItem}
- className='btn-solid-red py-2 px-3 text-caption-1'
- >
- Beli Lagi
- </button>
+ {/* {auth?.feature.soApproval && (auth.webRole == 2 || auth.webRole == 3) && (transaction.data.isReaject == false) && ( */}
+ {auth?.feature.soApproval && (auth.webRole == 2 || auth.webRole == 3) && (router.asPath.includes("/my/quotations/")) && (
+ !transaction?.data?.productsRejectLine.some(pr => pr.id === product.id) && (
+ <button
+ className="bg-red-500 text-white py-1 px-3 rounded"
+ onClick={() => openModal(product)}
+ >
+ Reject
+ </button>
+ )
+ )}
+ {isModalOpen && (
+ <div className='fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center'>
+ <div className='bg-white p-4 rounded w-96
+ ease-in-out opacity-100
+ transform transition-transform duration-300 scale-100'>
+ <h2 className='text-lg mb-2'>Berikan Alasan</h2>
+ <textarea
+ value={reason}
+ onChange={(e) => setReason(e.target.value)}
+ className='w-full p-2 border rounded'
+ rows='4'
+ ></textarea>
+ <div className='mt-4 flex justify-end'>
+ <button
+ className='bg-gray-300 text-black py-1 px-3 rounded mr-2'
+ onClick={closeModal}
+ >
+ Batal
+ </button>
+ <button
+ className='bg-red-500 text-white py-1 px-3 rounded'
+ onClick={handleRejectProduct}
+ >
+ Reject
+ </button>
+ </div>
+ </div>
+ </div>
+ )}
</div>
+
)}
</>
)
diff --git a/src/pages/my/recomendation/components/products-recomendatison.jsx b/src/pages/my/recomendation/components/products-recomendatison.jsx
index d39d2a99..7da2fab1 100644
--- a/src/pages/my/recomendation/components/products-recomendatison.jsx
+++ b/src/pages/my/recomendation/components/products-recomendatison.jsx
@@ -80,7 +80,7 @@ const ProductsRecomendation = ({ id }) => {
}
});
- console.log('ini result', searchProduct.data.response);
+ // console.log('ini result', searchProduct.data.response);
}
return resultMapping;
@@ -112,7 +112,7 @@ const ProductsRecomendation = ({ id }) => {
setIsLoading(false);
} else {
setIsLoading(false);
- console.log('No excel data available');
+ // console.log('No excel data available');
}
};
@@ -129,7 +129,7 @@ const ProductsRecomendation = ({ id }) => {
const jsonData = XLSX.utils.sheet_to_json(worksheet);
setExcelData(jsonData);
- console.log('ini json data', jsonData);
+ // console.log('ini json data', jsonData);
setIsLoading(false);
};
@@ -146,13 +146,13 @@ const ProductsRecomendation = ({ id }) => {
products[foundIndex].result.code = variant?.code;
products[foundIndex].result.name = variant?.name;
} else {
- console.log('Data not found.');
+ // console.log('Data not found.');
}
setIsOpen(false);
};
const handlingOtherRec = ({ product }) => {
- console.log('ini product', product);
+ // console.log('ini product', product);
const result = async () =>
await searchRecomendation({ product, index: 0, operator: 'OR' });
diff --git a/src/pages/shop/promo/[slug].tsx b/src/pages/shop/promo/[slug].tsx
index bd69c071..aaee1249 100644
--- a/src/pages/shop/promo/[slug].tsx
+++ b/src/pages/shop/promo/[slug].tsx
@@ -91,7 +91,7 @@ export default function PromoDetail() {
setCurrentPage(pageNumber)
try {
- const items = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug}`);
+ const items = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug}`,0,100);
setPromoItems(items);
if (items.length === 0) {
@@ -147,16 +147,16 @@ export default function PromoDetail() {
const response = await fetchVariantSolr(`id:${item.product_id} AND ${combinedQuery}`);
const product = response.response.docs[0];
const product_id = product.id;
- const response2 = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug} AND product_ids:${product_id} AND ${combinedQueryPrice}`);
+ const response2 = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug} AND product_ids:${product_id} AND ${combinedQueryPrice}`,0,100);
return response2;
}else if(combinedQuery){
const response = await fetchVariantSolr(`id:${item.product_id} AND ${combinedQuery}`);
const product = response.response.docs[0];
const product_id = product.id;
- const response2 = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug} AND product_ids:${product_id} `);
+ const response2 = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug} AND product_ids:${product_id} `,0,100);
return response2;
} else {
- const response = await fetchPromoItemsSolr(`id:${item.id}`);
+ const response = await fetchPromoItemsSolr(`id:${item.id}`,0,100);
return response;
}
} catch (fetchError) {
diff --git a/src/pages/shop/promo/index.jsx b/src/pages/shop/promo/index.jsx
new file mode 100644
index 00000000..01a11aad
--- /dev/null
+++ b/src/pages/shop/promo/index.jsx
@@ -0,0 +1,40 @@
+import Seo from '@/core/components/Seo'
+import BasicLayout from '@/core/components/layouts/BasicLayout';
+import { Breadcrumb, BreadcrumbItem, BreadcrumbLink } from '@chakra-ui/react';
+import Link from 'next/link';
+import Promo from '~/pages/shop/promo';
+
+import React from 'react';
+
+const PromoPage = () => {
+ return (
+ <BasicLayout>
+ <Seo title='Promo Indoteknik.com' />
+ <div className='container mx-auto py-4 md:py-6 pb-0'>
+ <Breadcrumb>
+ <BreadcrumbItem>
+ <BreadcrumbLink
+ as={Link}
+ href='/'
+ className='!text-danger-500 whitespace-nowrap'
+ >
+ Home
+ </BreadcrumbLink>
+ </BreadcrumbItem>
+
+ <BreadcrumbItem isCurrentPage>
+ <BreadcrumbLink className='whitespace-nowrap'>
+ Promo
+ </BreadcrumbLink>
+ </BreadcrumbItem>
+ </Breadcrumb>
+
+ <div className='h-10' />
+
+ <Promo />
+ </div>
+ </BasicLayout>
+ );
+};
+
+export default PromoPage;
diff --git a/src/pages/shop/promo/index.tsx b/src/pages/shop/promo/index.tsx
deleted file mode 100644
index 7ec4f6b0..00000000
--- a/src/pages/shop/promo/index.tsx
+++ /dev/null
@@ -1,186 +0,0 @@
-import dynamic from 'next/dynamic'
-import { useEffect, useState } from 'react'
-import { useRouter } from 'next/router'
-import Seo from '../../../core/components/Seo.jsx'
-import Promocrumb from '../../../lib/promo/components/Promocrumb.jsx'
-import { fetchPromoItemsSolr } from '../../../api/promoApi.js'
-import LogoSpinner from '../../../core/components/elements/Spinner/LogoSpinner.jsx'
-import ProductPromoCard from '../../../../src-migrate/modules/product-promo/components/Card.tsx'
-import { IPromotion } from '../../../../src-migrate/types/promotion.ts'
-import React from 'react'
-import { SolrResponse } from "../../../../src-migrate/types/solr.ts";
-
-const BasicLayout = dynamic(() => import('../../../core/components/layouts/BasicLayout.jsx'))
-
-export default function Promo() {
- const router = useRouter()
- const { slug = '' } = router.query
- const [promoItems, setPromoItems] = useState<any[]>([])
- const [promoData, setPromoData] = useState<IPromotion[] | null>(null)
- const [loading, setLoading] = useState(true)
- const [currentPage, setCurrentPage] = useState(1)
- const [fetchingData, setFetchingData] = useState(false)
-
- useEffect(() => {
- const loadPromo = async () => {
- try {
- const items = await fetchPromoItemsSolr(`*:*`)
-
-
- setPromoItems(items)
-
-
- if (items.length === 0) {
- setPromoData([])
- setLoading(false);
- return;
- }
-
- const promoDataPromises = items.map(async (item) => {
- const queryParams = new URLSearchParams({ q: `id:${item.id}` })
-
-
- try {
- const response = await fetch(`/solr/promotion_program_lines/select?${queryParams.toString()}`)
-
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`)
- }
-
- const data: SolrResponse<any[]> = await response.json()
-
-
- const promotions = await map(data.response.docs)
- return promotions;
- } catch (fetchError) {
- console.error("Error fetching promotion data:", fetchError)
- return [];
- }
- });
-
- const promoDataArray = await Promise.all(promoDataPromises);
- const mergedPromoData = promoDataArray.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
- setPromoData(mergedPromoData);
- setTimeout(() => setLoading(false), 120); // Menambahkan delay 200ms sebelum mengubah status loading
- } catch (loadError) {
- console.error("Error loading promo items:", loadError)
- setLoading(false);
- }
- }
-
- if (slug) {
- loadPromo()
- }
- }, [slug])
-
- const map = async (promotions: any[]): Promise<IPromotion[]> => {
- const result: IPromotion[] = []
-
- for (const promotion of promotions) {
- const data: IPromotion = {
- id: promotion.id,
- program_id: promotion.program_id_i,
- name: promotion.name_s,
- type: {
- value: promotion.type_value_s,
- label: promotion.type_label_s,
- },
- limit: promotion.package_limit_i,
- limit_user: promotion.package_limit_user_i,
- limit_trx: promotion.package_limit_trx_i,
- price: promotion.price_f,
- total_qty: promotion.total_qty_i,
- products: JSON.parse(promotion.products_s),
- free_products: JSON.parse(promotion.free_products_s),
- }
-
- result.push(data)
- }
-
- return result
- }
-
-
-
-
- useEffect(() => {
- const handleScroll = () => {
- if (
- !fetchingData &&
- window.innerHeight + document.documentElement.scrollTop >= 0.95 * document.documentElement.offsetHeight
- ) {
- // User has scrolled to 95% of page height
-
- setTimeout(() => setFetchingData(true), 120);
- setCurrentPage((prevPage) => prevPage + 1)
- }
- }
-
- window.addEventListener('scroll', handleScroll)
- return () => window.removeEventListener('scroll', handleScroll)
- }, [fetchingData])
-
- useEffect(() => {
- if (fetchingData) {
- // Fetch more data
- // You may need to adjust this logic according to your API
- fetchMoreData()
- }
- }, [fetchingData])
-
- const fetchMoreData = async () => {
- try {
- // Add a delay of approximately 150ms
- setTimeout(async () => {
- // Fetch more data
- // Update promoData state with the new data
- }, 150)
- } catch (error) {
- console.error('Error fetching more data:', error)
- } finally {
- setTimeout(() => setFetchingData(false), 120);
-
- }
- }
-
- const visiblePromotions = promoData?.slice(0, currentPage * 12)
-
- return (
- <BasicLayout>
- <Seo
- title={`Promo ${Array.isArray(slug) ? slug[0] : slug} Terkini`}
- description='B2B Marketplace MRO & Industri dengan Layanan Pembayaran Tempo, Faktur Pajak, Online Quotation, Garansi Resmi & Harga Kompetitif'
- />
- {/* <Promocrumb brandName={capitalizeFirstLetter(Array.isArray(slug) ? slug[0] : slug)} /> */}
- <div className='container mx-auto mt-1 flex mb-1'>
- <div className=''>
- <h1 className='font-semibold'>Semua Promo di Indoteknik</h1>
- </div>
- </div>
- {loading ? (
- <div className='container flex justify-center my-4'>
- <LogoSpinner width={48} height={48} />
- </div>
- ) : promoData && promoItems.length >= 1 ? (
- <>
- <div className='flex flex-wrap justify-center'>
- {visiblePromotions?.map((promotion) => (
- <div key={promotion.id} className="min-w-[40px] max-w-[400px] mr-[20px] mb-[20px] sm:w-full md:w-1/2 lg:w-1/3 xl:w-1/4">
- <ProductPromoCard promotion={promotion} />
- </div>
- ))}
- </div>
- {fetchingData && (
- <div className='container flex justify-center my-4'>
- <LogoSpinner width={48} height={48} />
- </div>
- )}
- </>
- ) : (
- <div className="text-center my-8">
- <p>Belum ada promo pada kategori ini</p>
- </div>
- )}
- </BasicLayout>
- )
-}