summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortrisusilo48 <tri.susilo@altama.co.id>2024-09-09 10:01:12 +0700
committertrisusilo48 <tri.susilo@altama.co.id>2024-09-09 10:01:12 +0700
commitafd84f86d2f26a3e0347dab7552060717030df19 (patch)
treed63d095e16a6dd052fb7d6858e78c1aac1115794
parentf8133c76306d9f70e01ac510c74dcfabe7f79b37 (diff)
parent495b327ba0a45b17f4f0156f846ebe8bddbcd075 (diff)
Merge branch 'release' into CR/product_detail
# Conflicts: # src-migrate/modules/product-detail/components/ProductDetail.tsx
-rw-r--r--public/images/PICKUP-NOW.pngbin0 -> 1926 bytes
-rw-r--r--public/robots.txt2
-rw-r--r--src-migrate/libs/auth.ts5
-rw-r--r--src-migrate/modules/cart/components/Item.tsx9
-rw-r--r--src-migrate/modules/cart/components/ItemAction.tsx1
-rw-r--r--src-migrate/modules/cart/stores/useCartStore.ts4
-rw-r--r--src-migrate/modules/product-detail/components/AddToCart.tsx171
-rw-r--r--src-migrate/modules/product-detail/components/PriceAction.tsx2
-rw-r--r--src-migrate/modules/product-detail/components/ProductDetail.tsx4
-rw-r--r--src-migrate/modules/product-promo/components/AddToCart.tsx171
-rw-r--r--src-migrate/modules/product-promo/components/Card.tsx10
-rw-r--r--src-migrate/modules/product-promo/components/Modal.tsx8
-rw-r--r--src-migrate/modules/product-promo/components/ModalContent.tsx8
-rw-r--r--src-migrate/modules/product-promo/components/Section.tsx9
-rw-r--r--src-migrate/modules/promo/components/Hero.tsx10
-rw-r--r--src-migrate/modules/promo/components/HeroDiskon.tsx22
-rw-r--r--src-migrate/modules/promo/components/PromoList.tsx8
-rw-r--r--src-migrate/modules/promo/components/Voucher.tsx16
-rw-r--r--src-migrate/pages/api/product-variant/[id].tsx1
-rw-r--r--src-migrate/pages/shop/cart/index.tsx4
-rw-r--r--src-migrate/services/product.ts1
-rw-r--r--src-migrate/types/cart.ts2
-rw-r--r--src-migrate/types/product.ts1
-rw-r--r--src-migrate/types/promotion.ts7
-rw-r--r--src/api/productApi.js5
-rw-r--r--src/components/ui/PopularProduct.jsx23
-rw-r--r--src/contexts/ProductCartContext.js3
-rw-r--r--src/core/components/elements/Footer/BasicFooter.jsx7
-rw-r--r--src/core/components/elements/Navbar/NavbarDesktop.jsx72
-rw-r--r--src/core/components/elements/Navbar/NavbarMobile.jsx4
-rw-r--r--src/core/components/elements/Navbar/TopBanner.jsx35
-rw-r--r--src/core/components/elements/Sidebar/Sidebar.jsx193
-rw-r--r--src/lib/brand/components/BrandCard.jsx8
-rw-r--r--src/lib/cart/components/Cartheader.jsx264
-rw-r--r--src/lib/category/api/popularProduct.js32
-rw-r--r--src/lib/category/components/Category.jsx66
-rw-r--r--src/lib/category/components/PopularBrand.jsx96
-rw-r--r--src/lib/checkout/components/Checkout.jsx263
-rw-r--r--src/lib/home/api/CategoryPilihanApi.js8
-rw-r--r--src/lib/home/api/categoryManagementApi.js8
-rw-r--r--src/lib/home/components/CategoryDynamic.jsx132
-rw-r--r--src/lib/home/components/CategoryDynamicMobile.jsx101
-rw-r--r--src/lib/home/components/CategoryPilihan.jsx123
-rw-r--r--src/lib/home/components/PreferredBrand.jsx46
-rw-r--r--src/lib/home/hooks/useCategoryManagement.js13
-rw-r--r--src/lib/home/hooks/useCategoryPilihan.js13
-rw-r--r--src/lib/lob/components/Breadcrumb.jsx55
-rw-r--r--src/lib/product/api/productSearchApi.js2
-rw-r--r--src/lib/product/components/CategorySection.jsx105
-rw-r--r--src/lib/product/components/LobSectionCategory.jsx80
-rw-r--r--src/lib/product/components/ProductCard.jsx32
-rw-r--r--src/lib/product/components/ProductFilterDesktop.jsx7
-rw-r--r--src/lib/product/components/ProductSearch.jsx146
-rw-r--r--src/lib/promo/api/productSearchApi.js11
-rw-r--r--src/lib/promo/hooks/usePromotionSearch.js15
-rw-r--r--src/lib/quotation/components/Quotation.jsx7
-rw-r--r--src/lib/quotation/components/Quotationheader.jsx265
-rw-r--r--src/lib/tracking-order/api/trackingOrder.js8
-rw-r--r--src/lib/tracking-order/component/TrackingOrder.jsx139
-rw-r--r--src/lib/treckingAwb/component/Manifest.jsx16
-rw-r--r--src/pages/_document.jsx8
-rw-r--r--src/pages/api/activation-request.js2
-rw-r--r--src/pages/api/shop/product-homepage.js3
-rw-r--r--src/pages/api/shop/promo.js202
-rw-r--r--src/pages/google_merchant/products/[page].js4
-rw-r--r--src/pages/index.jsx57
-rw-r--r--src/pages/shop/brands/[slug].jsx2
-rw-r--r--src/pages/shop/category/[slug].jsx9
-rw-r--r--src/pages/shop/lob/[slug].jsx48
-rw-r--r--src/pages/shop/product/variant/[slug].jsx2
-rw-r--r--src/pages/shop/promo/[slug].jsx394
-rw-r--r--src/pages/shop/promo/[slug].tsx523
-rw-r--r--src/pages/sitemap/brands.xml.js4
-rw-r--r--src/pages/sitemap/products/[page].js2
-rw-r--r--src/pages/tracking-order.jsx27
-rw-r--r--src/styles/globals.css8
-rw-r--r--src/utils/solrMapping.js28
-rw-r--r--tsconfig.json2
78 files changed, 3265 insertions, 939 deletions
diff --git a/public/images/PICKUP-NOW.png b/public/images/PICKUP-NOW.png
new file mode 100644
index 00000000..d7c01f44
--- /dev/null
+++ b/public/images/PICKUP-NOW.png
Binary files differ
diff --git a/public/robots.txt b/public/robots.txt
index 7ae1495b..3972ad17 100644
--- a/public/robots.txt
+++ b/public/robots.txt
@@ -16,6 +16,8 @@ Disallow: /shop/search/*
Disallow: /promo/*
Disallow: /shop/brands/*?*
Disallow: /shop/category/*?*
+Disallow: /login?*
+Disallow: /register?*
User-agent: Adsbot-Google
Allow: /my/*
diff --git a/src-migrate/libs/auth.ts b/src-migrate/libs/auth.ts
index 86ce26e1..e8516747 100644
--- a/src-migrate/libs/auth.ts
+++ b/src-migrate/libs/auth.ts
@@ -1,5 +1,8 @@
import { deleteCookie, getCookie, setCookie } from 'cookies-next';
import { AuthProps } from '~/types/auth';
+// @ts-ignore
+import camelcaseObjectDeep from 'camelcase-object-deep';
+
const COOKIE_KEY = 'auth';
@@ -14,7 +17,7 @@ export const getAuth = (): AuthProps | boolean => {
};
export const setAuth = (user: AuthProps): boolean => {
- setCookie(COOKIE_KEY, JSON.stringify(user));
+ setCookie(COOKIE_KEY, JSON.stringify(camelcaseObjectDeep(user)));
return true;
};
diff --git a/src-migrate/modules/cart/components/Item.tsx b/src-migrate/modules/cart/components/Item.tsx
index 47893498..6ffbb524 100644
--- a/src-migrate/modules/cart/components/Item.tsx
+++ b/src-migrate/modules/cart/components/Item.tsx
@@ -17,11 +17,11 @@ import CartItemSelect from './ItemSelect'
type Props = {
item: CartItemProps
editable?: boolean
+ selfPicking?: boolean
pilihSemuaCart?: boolean
}
-const CartItem = ({ item, editable = true,}: Props) => {
-
+const CartItem = ({ item, editable = true, selfPicking}: Props) => {
return (
<div className={style.wrapper}>
{item.cart_type === 'promotion' && (
@@ -53,6 +53,11 @@ const CartItem = ({ item, editable = true,}: Props) => {
<CartItem.Image item={item} />
<div className={style.details}>
+ {(item.is_in_bu) && (item.on_hand_qty >= item.quantity) && (
+ <div className='text-[10px] text-red-500 italic'>
+ *Barang ini bisa di pickup maksimal pukul 16.00
+ </div>
+ )}
<CartItem.Name item={item} />
<div className='mt-2 flex justify-between w-full'>
diff --git a/src-migrate/modules/cart/components/ItemAction.tsx b/src-migrate/modules/cart/components/ItemAction.tsx
index e5e7f314..7220e362 100644
--- a/src-migrate/modules/cart/components/ItemAction.tsx
+++ b/src-migrate/modules/cart/components/ItemAction.tsx
@@ -13,7 +13,6 @@ import { useDebounce } from 'usehooks-ts'
import { useCartStore } from '../stores/useCartStore'
import { useProductCartContext } from '@/contexts/ProductCartContext'
-
type Props = {
item: CartItem
}
diff --git a/src-migrate/modules/cart/stores/useCartStore.ts b/src-migrate/modules/cart/stores/useCartStore.ts
index 3b50ec32..c2ebf50f 100644
--- a/src-migrate/modules/cart/stores/useCartStore.ts
+++ b/src-migrate/modules/cart/stores/useCartStore.ts
@@ -54,7 +54,7 @@ export const useCartStore = create<State & Action>((set, get) => ({
const computeSummary = (cart: CartProps) => {
let subtotal = 0;
let discount = 0;
- for (const item of cart.products) {
+ for (const item of cart?.products) {
if (!item.selected) continue;
let price = 0;
@@ -71,4 +71,4 @@ const computeSummary = (cart: CartProps) => {
let grandTotal = total + tax;
return { subtotal, discount, total, tax, grandTotal };
-};
+}; \ No newline at end of file
diff --git a/src-migrate/modules/product-detail/components/AddToCart.tsx b/src-migrate/modules/product-detail/components/AddToCart.tsx
index 097db98a..a5284637 100644
--- a/src-migrate/modules/product-detail/components/AddToCart.tsx
+++ b/src-migrate/modules/product-detail/components/AddToCart.tsx
@@ -1,19 +1,35 @@
-import { Button, useToast } from '@chakra-ui/react'
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
+import style from '../styles/price-action.module.css';
+import { Button, Link, useToast } from '@chakra-ui/react'
+import product from 'next-seo/lib/jsonld/product'
import { useRouter } from 'next/router'
-
+import { useEffect, useState } from 'react'
+import Image from '~/components/ui/image'
import { getAuth } from '~/libs/auth'
import { upsertUserCart } from '~/services/cart'
+import LazyLoad from 'react-lazy-load';
+import ProductSimilar from '../../../../src/lib/product/components/ProductSimilar';
+import { IProductDetail } from '~/types/product';
+import ImageNext from 'next/image';
+import { useProductCartContext } from '@/contexts/ProductCartContext'
+import { createSlug } from '~/libs/slug'
+import formatCurrency from '~/libs/formatCurrency'
+import { useProductDetail } from '../stores/useProductDetail';
type Props = {
variantId: number | null,
quantity?: number;
source?: 'buy' | 'add_to_cart';
+ products : IProductDetail
}
+type Status = 'idle' | 'loading' | 'success'
+
const AddToCart = ({
variantId,
quantity = 1,
- source = 'add_to_cart'
+ source = 'add_to_cart',
+ products
}: Props) => {
const auth = getAuth()
const router = useRouter()
@@ -22,40 +38,65 @@ const AddToCart = ({
isClosable: true
})
- const handleClick = async () => {
+ const {
+ askAdminUrl,
+ } = useProductDetail();
+
+ const [product, setProducts] = useState(products);
+ const [status, setStatus] = useState<Status>('idle')
+ const { productCart, setRefreshCart, setProductCart, refreshCart, isLoading, setIsloading } =
+ useProductCartContext()
+
+ const productSimilarQuery = [
+ product?.name,
+ `fq=-product_id_i:${product.id}`,
+ `fq=-manufacture_id_i:${product.manufacture?.id || 0}`,
+ ].join('&');
+ const [addCartAlert, setAddCartAlert] = useState(false);
+
+ const handleButton = async () => {
if (typeof auth !== 'object') {
const currentUrl = encodeURIComponent(router.asPath)
router.push(`/login?next=${currentUrl}`)
return;
}
-
+
if (
!variantId ||
isNaN(quantity) ||
typeof auth !== 'object'
) return;
-
- toast.promise(
- upsertUserCart({
- userId: auth.id,
+ if (status === 'success') return
+ setStatus('loading')
+ await upsertUserCart({
+ userId: auth.id,
type: 'product',
id: variantId,
qty: quantity,
selected: true,
source: source,
qtyAppend: true
- }),
- {
- loading: { title: 'Menambahkan ke keranjang', description: 'Mohon tunggu...' },
- success: { title: 'Menambahkan ke keranjang', description: 'Berhasil menambahkan ke keranjang belanja' },
- error: { title: 'Menambahkan ke keranjang', description: 'Gagal menambahkan ke keranjang belanja' },
- }
- )
-
+ })
+ setStatus('idle')
+ setRefreshCart(true);
+ setAddCartAlert(true);
+
+ toast({
+ title: 'Tambah ke keranjang',
+ description: 'Berhasil menambahkan barang ke keranjang belanja',
+ status: 'success',
+ duration: 3000,
+ isClosable: true,
+ position: 'top',
+ })
+
if (source === 'buy') {
router.push('/shop/checkout?source=buy')
}
}
+ useEffect(() => {
+ if (status === 'success') setTimeout(() => { setStatus('idle') }, 3000)
+ }, [status])
const btnConfig = {
'add_to_cart': {
@@ -69,10 +110,98 @@ const AddToCart = ({
}
return (
- <Button onClick={handleClick} colorScheme={btnConfig[source].colorScheme} className='w-full'>
- {btnConfig[source].text}
- </Button>
+ <div className='w-full'>
+ <Button onClick={handleButton} colorScheme={btnConfig[source].colorScheme} className='w-full'>
+ {btnConfig[source].text}
+ </Button>
+ <BottomPopup
+ className='!container'
+ title='Berhasil Ditambahkan'
+ active={addCartAlert}
+ close={() => {
+ setAddCartAlert(false);
+ }}
+ >
+ <div className='flex mt-4'>
+ <div className='w-[10%]'>
+ <ImageNext
+ src={product.image}
+ alt={product.name}
+ className='h-32 object-contain object-center w-full border border-gray_r-4'
+ width={80}
+ height={80}
+ />
+ </div>
+ <div className='ml-3 flex flex-1 items-start font-medium justify-center flex-col gap-y-1'>
+ {!!product.manufacture.name ? (
+ <Link
+ href={createSlug('/shop/brands/', product.manufacture.name, product.manufacture.id.toString())}
+ className=' hover:underline'
+ color={"red"}
+ >
+ {product.manufacture.name}
+ </Link>
+ ) : '-'}
+ <p className='text-ellipsis overflow-hidden'>
+ {product.name}
+ </p>
+ <p>
+ {product.code}
+ </p>
+ {!!product.lowest_price && product.lowest_price.price > 0 && (
+ <>
+ <div className='flex items-end gap-x-2'>
+ {product.lowest_price.discount_percentage > 0 && (
+ <>
+ <div className='badge-solid-red'>
+ {Math.floor(product.lowest_price.discount_percentage)}%
+ </div>
+ <div className='text-gray_r-11 line-through text-[11px] sm:text-caption-2'>
+ Rp {formatCurrency(product.lowest_price.price || 0)}
+ </div>
+ </>
+ )}
+ <div className='text-danger-500 font-semibold'>
+ Rp {formatCurrency(product.lowest_price.price_discount || 0)}
+ </div>
+ </div>
+ </>
+ )}
+
+ {!!product.lowest_price && product.lowest_price.price === 0 && (
+ <span>
+ Hubungi kami untuk dapatkan harga terbaik,{' '}
+ <Link
+ href={askAdminUrl}
+ target='_blank'
+ className='font-medium underline'
+ color={'red'}
+ >
+ klik disini
+ </Link>
+ </span>
+ )}
+ </div>
+ <div className='ml-3 flex items-center font-normal'>
+ <Link
+ href='/shop/cart'
+ className='flex-1 py-2 text-gray_r-12 btn-yellow'
+ >
+ Lihat Keranjang
+ </Link>
+ </div>
+ </div>
+ <div className='mt-8 mb-4'>
+ <div className='text-h-sm font-semibold mb-6'>
+ Kamu Mungkin Juga Suka
+ </div>
+ <LazyLoad>
+ <ProductSimilar query={productSimilarQuery} />
+ </LazyLoad>
+ </div>
+ </BottomPopup>
+ </div>
)
}
-export default AddToCart \ No newline at end of file
+export default AddToCart \ No newline at end of file
diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx
index b46afac9..6042d578 100644
--- a/src-migrate/modules/product-detail/components/PriceAction.tsx
+++ b/src-migrate/modules/product-detail/components/PriceAction.tsx
@@ -121,12 +121,14 @@ const PriceAction = ({ product }: Props) => {
<div className={style['action-wrapper']}>
<AddToCart
+ products={product}
variantId={activeVariantId}
quantity={Number(quantityInput)}
/>
{!isApproval && (
<AddToCart
source='buy'
+ products={product}
variantId={activeVariantId}
quantity={Number(quantityInput)}
/>
diff --git a/src-migrate/modules/product-detail/components/ProductDetail.tsx b/src-migrate/modules/product-detail/components/ProductDetail.tsx
index c19c288a..554a3868 100644
--- a/src-migrate/modules/product-detail/components/ProductDetail.tsx
+++ b/src-migrate/modules/product-detail/components/ProductDetail.tsx
@@ -103,9 +103,7 @@ const ProductDetail = ({ product }: Props) => {
)}
<div className='h-4 md:h-10' />
- {!!activeVariantId && !isApproval && (
- <ProductPromoSection productId={activeVariantId} />
- )}
+ {!!activeVariantId && !isApproval && <ProductPromoSection product={product} productId={activeVariantId} />}
{/* <div className={style['section-card']}>
<h2 className={style['heading']}>
diff --git a/src-migrate/modules/product-promo/components/AddToCart.tsx b/src-migrate/modules/product-promo/components/AddToCart.tsx
index 87017c14..7b3863f9 100644
--- a/src-migrate/modules/product-promo/components/AddToCart.tsx
+++ b/src-migrate/modules/product-promo/components/AddToCart.tsx
@@ -5,53 +5,71 @@ import { useEffect, useState } from 'react'
import { getAuth } from '~/libs/auth'
import { upsertUserCart } from '~/services/cart'
-import { IPromotion } from '~/types/promotion'
+import { IPromotion, IProductVariantPromo } from '~/types/promotion'
import DesktopView from '../../../../src/core/components/views/DesktopView';
import MobileView from '../../../../src/core/components/views/MobileView';
-
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
+import ImageNext from 'next/image';
+import Link from 'next/link'
+import LazyLoad from 'react-lazy-load'
+import ProductSimilar from '../../../../src/lib/product/components/ProductSimilar';
+import { IProductDetail } from '~/types/product';
+import { useProductCartContext } from '@/contexts/ProductCartContext'
+import { createSlug } from '~/libs/slug'
+import formatCurrency from '~/libs/formatCurrency'
+import { useProductDetail } from '../../product-detail/stores/useProductDetail';
type Props = {
promotion: IPromotion
+ product: IProductDetail
+ variant: IProductVariantPromo,
}
type Status = 'idle' | 'loading' | 'success'
-const ProductPromoAddToCart = ({ promotion }: Props) => {
+const ProductPromoAddToCart = ({product, promotion, variant }: Props) => {
const auth = getAuth()
const toast = useToast()
const router = useRouter()
-
+ const {askAdminUrl} = useProductDetail();
const [status, setStatus] = useState<Status>('idle')
-
+ const { productCart, setRefreshCart, setProductCart, refreshCart, isLoading, setIsloading } =
+ useProductCartContext()
+ const productSimilarQuery = [
+ promotion?.name,
+ `fq=-product_id_i:${promotion.products[0].product_id}`,
+ ].join('&');
+ const [addCartAlert, setAddCartAlert] = useState(false);
+
const handleButton = async () => {
if (typeof auth !== 'object') {
- const currentUrl = encodeURIComponent(router.asPath)
- router.push(`/login?next=${currentUrl}`)
- return
- }
- if (status === 'success') return
-
- setStatus('loading')
- await upsertUserCart({
- userId: auth.id,
- type: 'promotion',
- id: promotion.id,
- qty: 1,
- selected: true,
- source: 'add_to_cart',
- qtyAppend: true
- })
- setStatus('idle')
-
- toast({
- title: 'Tambah ke keranjang',
- description: 'Berhasil menambahkan barang ke keranjang belanja',
- status: 'success',
- duration: 3000,
- isClosable: true,
- position: 'top',
- })
+ const currentUrl = encodeURIComponent(router.asPath)
+ router.push(`/login?next=${currentUrl}`)
+ return
}
+ if (status === 'success') return
+ setStatus('loading')
+ await upsertUserCart({
+ userId: auth.id,
+ type: 'promotion',
+ id: promotion.id,
+ qty: 1,
+ selected: true,
+ source: 'add_to_cart',
+ qtyAppend: true
+ })
+ setStatus('idle')
+ setRefreshCart(true);
+ setAddCartAlert(true);
+ toast({
+ title: 'Tambah ke keranjang',
+ description: 'Berhasil menambahkan barang ke keranjang belanja',
+ status: 'success',
+ duration: 3000,
+ isClosable: true,
+ position: 'top',
+ })
+}
useEffect(() => {
if (status === 'success') setTimeout(() => { setStatus('idle') }, 3000)
@@ -92,6 +110,97 @@ const ProductPromoAddToCart = ({ promotion }: Props) => {
{status === 'success' && <span>Berhasil</span>}
{status !== 'success' && <span>Keranjang</span>}
</Button>
+ <BottomPopup
+ className='!container'
+ title='Berhasil Ditambahkan'
+ active={addCartAlert}
+ close={() => {
+ setAddCartAlert(false);
+ }}
+ >
+ <div className='flex mt-4'>
+ <div className='w-[10%]'>
+ <ImageNext
+ src={
+ product?.image
+ ? product?.image
+ : variant?.image || ''
+ }
+ alt={product?.name ? product?.name : variant?.display_name || ''}
+ className='h-32 object-contain object-center w-full border border-gray_r-4'
+ width={80}
+ height={80}
+ />
+
+ </div>
+ <div className='ml-3 flex flex-1 items-start font-medium justify-center flex-col gap-y-1'>
+ {!!product?.manufacture?.name || variant?.manufacture && (
+ <Link
+ href={createSlug('/shop/brands/', product?.manufacture?.name? product?.manufacture?.name : variant?.manufacture?.manufacture_name, product?.manufacture?.id? product?.manufacture?.id.toString() : variant?.manufacture?.manufacture_id.toString())}
+ className=' hover:underline text-red-500'
+ color={"red"}
+ >
+ {product?.manufacture?.name ? product?.manufacture?.name : variant?.manufacture?.manufacture_name}
+ </Link>
+ )}
+ <p className='text-ellipsis overflow-hidden'>
+ {product?.name ? product?.name : variant?.name}
+ </p>
+ <p>
+ {product?.code}
+ </p>
+ {(!!product?.lowest_price && product?.lowest_price?.price > 0) || variant?.price?.price > 0 && (
+ <>
+ <div className='flex items-end gap-x-2'>
+ {(product?.lowest_price?.discount_percentage > 0) || variant?.price?.discount_percentage > 0 && (
+ <>
+ <div className='badge-solid-red'>
+ {Math.floor(product?.lowest_price?.discount_percentage ? product?.lowest_price?.discount_percentage : variant?.price?.discount_percentage)}%
+ </div>
+ <div className='text-gray_r-11 line-through text-[11px] sm:text-caption-2'>
+ Rp {formatCurrency(product?.lowest_price?.price ? product?.lowest_price?.price || 0 : variant?.price?.price || 0)}
+ </div>
+ </>
+ )}
+ <div className='text-danger-500 font-semibold'>
+ Rp {formatCurrency(product?.lowest_price?.price_discount? product?.lowest_price?.price_discount || 0 : variant?.price?.price_discount)}
+ </div>
+ </div>
+ </>
+ )}
+
+ {(!!product?.lowest_price && product?.lowest_price?.price === 0) || variant?.price?.price === 0 && (
+ <span>
+ Hubungi kami untuk dapatkan harga terbaik,{' '}
+ <Link
+ href={askAdminUrl}
+ target='_blank'
+ className='font-medium underline'
+ color={'red'}
+ >
+ klik disini
+ </Link>
+ </span>
+ )}
+ </div>
+ <div className='ml-3 flex items-center font-normal'>
+ <Link
+ href='/shop/cart'
+ className='flex-1 py-2 text-gray_r-12 btn-yellow'
+ >
+ Lihat Keranjang
+ </Link>
+ </div>
+ </div>
+ <div className='mt-8 mb-4'>
+ <div className='text-h-sm font-semibold mb-6'>
+ Kamu Mungkin Juga Suka
+ </div>
+ <LazyLoad>
+ <ProductSimilar query={productSimilarQuery} />
+ </LazyLoad>
+ </div>
+ </BottomPopup>
</DesktopView>
</div>
)
diff --git a/src-migrate/modules/product-promo/components/Card.tsx b/src-migrate/modules/product-promo/components/Card.tsx
index 728d23ca..b8abe5ec 100644
--- a/src-migrate/modules/product-promo/components/Card.tsx
+++ b/src-migrate/modules/product-promo/components/Card.tsx
@@ -15,16 +15,16 @@ import clsxm from '~/libs/clsxm'
import ProductPromoItem from './Item'
import ProductPromoAddToCart from "./AddToCart"
import ProductPromoCardCountdown from "./CardCountdown"
-
+import { IProductDetail } from '~/types/product';
import MobileView from '../../../../src/core/components/views/MobileView';
import DesktopView from '../../../../src/core/components/views/DesktopView';
type Props = {
promotion: IPromotion
-
+ product: IProductDetail
}
-const ProductPromoCard = ({ promotion}: Props) => {
+const ProductPromoCard = ({product, promotion}: Props) => {
const [products, setProducts] = useState<IProductVariantPromo[]>([])
const [freeProducts, setFreeProducts] = useState<IProductVariantPromo[]>([])
const [error, setError] = useState<string | null>(null)
@@ -132,7 +132,7 @@ const ProductPromoCard = ({ promotion}: Props) => {
</div>
<div>
- <ProductPromoAddToCart promotion={promotion} />
+ <ProductPromoAddToCart product={product} promotion={promotion} variant={products[0]} />
</div>
</div>
@@ -189,7 +189,7 @@ const ProductPromoCard = ({ promotion}: Props) => {
</div>
</div>
<div>
- <ProductPromoAddToCart promotion={promotion} />
+ <ProductPromoAddToCart product={product} promotion={promotion} variant={products[0]}/>
</div>
</div>
diff --git a/src-migrate/modules/product-promo/components/Modal.tsx b/src-migrate/modules/product-promo/components/Modal.tsx
index 0de672c2..1722b9df 100644
--- a/src-migrate/modules/product-promo/components/Modal.tsx
+++ b/src-migrate/modules/product-promo/components/Modal.tsx
@@ -3,8 +3,12 @@ import { Modal } from "~/components/ui/modal"
import { useModalStore } from '../stores/useModalStore'
import ProductPromoCategoryTab from './CategoryTab'
import ProductPromoModalContent from './ModalContent'
+import { IProductDetail } from '~/types/product';
-const ProductPromoModal = () => {
+type Props = {
+ product: IProductDetail
+}
+const ProductPromoModal = ({product}:Props) => {
const { active, closeModal } = useModalStore()
return (
@@ -17,7 +21,7 @@ const ProductPromoModal = () => {
<div className='h-4' />
- <ProductPromoModalContent />
+ <ProductPromoModalContent product={product} />
</Modal>
)
}
diff --git a/src-migrate/modules/product-promo/components/ModalContent.tsx b/src-migrate/modules/product-promo/components/ModalContent.tsx
index ab5129f8..256ef61a 100644
--- a/src-migrate/modules/product-promo/components/ModalContent.tsx
+++ b/src-migrate/modules/product-promo/components/ModalContent.tsx
@@ -6,7 +6,11 @@ import { getVariantPromoByCategory } from "~/services/productVariant"
import { useModalStore } from "../stores/useModalStore"
import ProductPromoCard from "./Card"
-const ProductPromoModalContent = () => {
+import { IProductDetail } from '~/types/product';
+type Props = {
+ product: IProductDetail
+}
+const ProductPromoModalContent = ({product}:Props) => {
const { activeTab, variantId } = useModalStore()
const promotionsQuery = useQuery(
@@ -24,7 +28,7 @@ const ProductPromoModalContent = () => {
<Skeleton isLoaded={!promotionsQuery.isLoading} className='min-h-[70vh] max-h-[70vh]'>
<div className="grid grid-cols-1 gap-y-6 pb-6">
{promotions?.data.map((promo) => (
- <ProductPromoCard key={promo.id} promotion={promo} />
+ <ProductPromoCard key={promo.id} promotion={promo} product={product} />
))}
{promotions?.data.length === 0 && (
<div className="py-10 rounded-lg h-fit text-center text-body-1 font-semibold text-gray-800 bg-gray-200">Belum ada promo pada kategori ini</div>
diff --git a/src-migrate/modules/product-promo/components/Section.tsx b/src-migrate/modules/product-promo/components/Section.tsx
index 4e8a7dd5..e1719998 100644
--- a/src-migrate/modules/product-promo/components/Section.tsx
+++ b/src-migrate/modules/product-promo/components/Section.tsx
@@ -9,12 +9,14 @@ import { IPromotion } from '~/types/promotion'
import { useModalStore } from "../stores/useModalStore"
import ProductPromoCard from './Card'
import ProductPromoModal from "./Modal"
+import { IProductDetail } from '~/types/product';
type Props = {
productId: number;
+ product: IProductDetail;
}
-const ProductPromoSection = ({ productId }: Props) => {
+const ProductPromoSection = ({ product, productId }: Props) => {
const promotionsQuery = useQuery({
queryKey: [`promotions.highlight`, productId],
queryFn: async () => await fetch(`/api/product-variant/${productId}/promotion/highlight`).then((res) => res.json()) as { data: IPromotion[] }
@@ -23,14 +25,13 @@ const ProductPromoSection = ({ productId }: Props) => {
const promotions = promotionsQuery.data
const { openModal } = useModalStore()
-
return (
<SmoothRender
isLoaded={(promotions?.data && promotions?.data.length > 0) || false}
height='450px'
duration='700ms'
>
- <ProductPromoModal />
+ <ProductPromoModal product={product}/>
{promotions?.data && promotions?.data.length > 0 && (
<div className={style.titleWrapper}>
@@ -50,7 +51,7 @@ const ProductPromoSection = ({ productId }: Props) => {
>
{promotions?.data.map((promotion) => (
<div key={promotion.id} className="min-w-[400px] max-w-[400px]">
- <ProductPromoCard promotion={promotion} />
+ <ProductPromoCard product={product} promotion={promotion} />
</div>
))}
</Skeleton>
diff --git a/src-migrate/modules/promo/components/Hero.tsx b/src-migrate/modules/promo/components/Hero.tsx
index c5f0afad..97cbe0b7 100644
--- a/src-migrate/modules/promo/components/Hero.tsx
+++ b/src-migrate/modules/promo/components/Hero.tsx
@@ -60,12 +60,12 @@ const Hero = () => {
<DesktopView>
<div className={style['wrapper']}>
<Swiper {...swiperBanner}>
- {banners.map((banner, index) => (
+ {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={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 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
@@ -89,8 +89,8 @@ const Hero = () => {
width={439}
height={150}
quality={100}
- src={banner.image}
- alt={banner.name}
+ src={banner?.image}
+ alt={banner?.name}
className='w-full h-full object-cover object-center rounded-2xl'
/>
</SwiperSlide>
diff --git a/src-migrate/modules/promo/components/HeroDiskon.tsx b/src-migrate/modules/promo/components/HeroDiskon.tsx
index 6d38c763..8b8edcc0 100644
--- a/src-migrate/modules/promo/components/HeroDiskon.tsx
+++ b/src-migrate/modules/promo/components/HeroDiskon.tsx
@@ -39,7 +39,7 @@ const Hero = () => {
queryFn: () => getBanner({ type: 'banner-promotion' })
})
- const banners = useMemo(() => bannerQuery.data || [], [bannerQuery.data]);
+ const banners = useMemo(() => bannerQuery?.data || [], [bannerQuery?.data]);
useEffect(() => {
if (banners.length > 1) {
@@ -56,8 +56,8 @@ const Hero = () => {
{banners.map((banner, index) => (
<SwiperSlide key={index}>
<Image
- src={banner.image}
- alt={banner.name}
+ src={banner?.image}
+ alt={banner?.name}
width={666}
height={480}
className='w-[446px] h-[446px] object-fill object-center rounded-2xl'
@@ -71,8 +71,8 @@ const Hero = () => {
{banners.map((banner, index) => (
<SwiperSlide key={index}>
<Image
- src={banner.image}
- alt={banner.name}
+ src={banner?.image}
+ alt={banner?.name}
width={666}
height={450}
className='w-[400px] h-[217px] object-cover object-center rounded-2xl '
@@ -86,8 +86,8 @@ const Hero = () => {
{banners.map((banner, index) => (
<SwiperSlide key={index}>
<Image
- src={banner.image}
- alt={banner.name}
+ src={banner?.image}
+ alt={banner?.name}
width={666}
height={450}
className='w-[400px] h-[217px] object-cover object-center rounded-2xl'
@@ -101,8 +101,8 @@ const Hero = () => {
{banners.map((banner, index) => (
<SwiperSlide key={index}>
<Image
- src={banner.image}
- alt={banner.name}
+ src={banner?.image}
+ alt={banner?.name}
width={666}
height={450}
className='w-[400px] h-[217px] object-cover object-center rounded-2xl'
@@ -116,8 +116,8 @@ const Hero = () => {
{banners.map((banner, index) => (
<SwiperSlide key={index}>
<Image
- src={banner.image}
- alt={banner.name}
+ src={banner?.image}
+ alt={banner?.name}
width={666}
height={450}
className='w-[400px] h-[217px] object-cover object-center rounded-2xl'
diff --git a/src-migrate/modules/promo/components/PromoList.tsx b/src-migrate/modules/promo/components/PromoList.tsx
index 42725034..d59d1867 100644
--- a/src-migrate/modules/promo/components/PromoList.tsx
+++ b/src-migrate/modules/promo/components/PromoList.tsx
@@ -59,7 +59,7 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => {
const items = await fetchPromoItemsSolr(`type_value_s:${slug}`, 0, 10);
setPromoItems(items);
- const promoDataPromises = items.map(async (item) => {
+ const promoDataPromises = items?.map(async (item) => {
try {
const response = await fetchPromoItemsSolr(`id:${item.id}`, 0, 10);
return response;
@@ -69,7 +69,7 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => {
});
const promoDataArray = await Promise.all(promoDataPromises);
- const mergedPromoData = promoDataArray.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
+ const mergedPromoData = promoDataArray?.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
setPromoData(mergedPromoData);
} catch (error) {
@@ -114,7 +114,7 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => {
{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} />
+ <ProductPromoCard product={promoItems} promotion={promotion} />
</div>
</SwiperSlide>
))}
@@ -122,7 +122,7 @@ const PromoList: React.FC<PromoListProps> = ({ selectedPromo }) => {
)}
{isMobile && (promoData?.map((promotion: IPromotion) => (
<div key={promotion.id} className="min-w-[400px] max-w-[400px]">
- <ProductPromoCard promotion={promotion} />
+ <ProductPromoCard product={promoItems} promotion={promotion} />
</div>
)))}
diff --git a/src-migrate/modules/promo/components/Voucher.tsx b/src-migrate/modules/promo/components/Voucher.tsx
index e5877e51..510fe746 100644
--- a/src-migrate/modules/promo/components/Voucher.tsx
+++ b/src-migrate/modules/promo/components/Voucher.tsx
@@ -56,7 +56,7 @@ const VoucherComponent = () => {
spaceBetween: 2,
};
- const dataVouchers = useMemo(() => voucherQuery.data || [], [voucherQuery.data]);
+ const dataVouchers = useMemo(() => voucherQuery?.data || [], [voucherQuery?.data]);
const vouchers = auth?.id? listVouchers : dataVouchers;
@@ -121,30 +121,30 @@ const VoucherComponent = () => {
<div className='h-6' />
- {voucherQuery.isLoading && (
+ {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 && (
+ {!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']} />
+ <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-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 className={style['voucher-code']}>{voucher?.code}</div>
</div>
- <button className={style['voucher-copy']} onClick={() => copyText(voucher.code)}>Salin</button>
+ <button className={style['voucher-copy']} onClick={() => copyText(voucher?.code)}>Salin</button>
</div>
</div>
</div>
diff --git a/src-migrate/pages/api/product-variant/[id].tsx b/src-migrate/pages/api/product-variant/[id].tsx
index 955fde6a..2c46ac89 100644
--- a/src-migrate/pages/api/product-variant/[id].tsx
+++ b/src-migrate/pages/api/product-variant/[id].tsx
@@ -38,6 +38,7 @@ const map = async (variant: any, price_tier: string) => {
data.name = variant.name_s
data.default_code = variant.default_code_s
data.price = { discount_percentage: 0, price, price_discount: price }
+ data.manufacture = {manufacture_name: variant.manufacture_name_s, manufacture_id:variant.manufacture_id_i}
return data
}
diff --git a/src-migrate/pages/shop/cart/index.tsx b/src-migrate/pages/shop/cart/index.tsx
index 5e3e042a..4768f62d 100644
--- a/src-migrate/pages/shop/cart/index.tsx
+++ b/src-migrate/pages/shop/cart/index.tsx
@@ -176,7 +176,7 @@ const CartPage = () => {
return (
<>
- <div className={`${isTop ? 'border-b-[0px]' : 'border-b-[1px]'} sticky top-[157px] bg-white py-4 border-gray-300 z-50 w-3/4`}>
+ <div className={`${isTop ? 'border-b-[0px]' : 'border-b-[1px]'} sticky md:top-[157px] flex-col bg-white py-4 border-gray-300 z-50 sm:w-full md:w-3/4`}>
<div className={`${style['title']}`}>Keranjang Belanja</div>
<div className='h-2' />
<div className={`flex items-center object-center justify-between `}>
@@ -315,4 +315,4 @@ const CartPage = () => {
);
};
-export default CartPage;
+export default CartPage; \ No newline at end of file
diff --git a/src-migrate/services/product.ts b/src-migrate/services/product.ts
index fe415d11..a6abba47 100644
--- a/src-migrate/services/product.ts
+++ b/src-migrate/services/product.ts
@@ -43,7 +43,6 @@ export const getProductSimilar = async ({
const query = [
`q=${name}`,
'page=1',
- 'orderBy=popular-weekly',
'operation=OR',
'priceFrom=1',
];
diff --git a/src-migrate/types/cart.ts b/src-migrate/types/cart.ts
index 4e3c8b99..a3115103 100644
--- a/src-migrate/types/cart.ts
+++ b/src-migrate/types/cart.ts
@@ -32,6 +32,8 @@ export type CartItem = {
id: number;
name: string;
stock: number;
+ is_in_bu: boolean;
+ on_hand_qty: number;
weight: number;
attributes: string[];
parent: {
diff --git a/src-migrate/types/product.ts b/src-migrate/types/product.ts
index 927a0ca3..3854f304 100644
--- a/src-migrate/types/product.ts
+++ b/src-migrate/types/product.ts
@@ -12,6 +12,7 @@ export interface IProduct {
variant_total: number;
description: string;
isSni: boolean;
+ is_in_bu: boolean;
isTkdn: boolean;
categories: {
id: string;
diff --git a/src-migrate/types/promotion.ts b/src-migrate/types/promotion.ts
index 85190aad..dce442ad 100644
--- a/src-migrate/types/promotion.ts
+++ b/src-migrate/types/promotion.ts
@@ -10,15 +10,18 @@ export interface IPromotion {
limit_user: number;
limit_trx: number;
price: number;
+ image: string;
total_qty: number;
products: {
product_id: number;
qty: number;
+ name: string;
}[];
free_products: {
product_id: number;
qty: number;
}[];
+
}
export interface IProductVariantPromo {
@@ -34,6 +37,10 @@ export interface IProductVariantPromo {
price_discount: number;
};
qty: number;
+ manufacture: {
+ manufacture_name: string;
+ manufacture_id:number;
+ }
}
export type CategoryPromo = 'bundling' | 'discount_loading' | 'merchandise';
diff --git a/src/api/productApi.js b/src/api/productApi.js
index 009d95ef..4a29b59d 100644
--- a/src/api/productApi.js
+++ b/src/api/productApi.js
@@ -2,8 +2,11 @@ import axios from 'axios'
export const popularProductApi = () => {
return async () => {
+ const today = new Date();
+ const dayOfYear = Math.floor((today - new Date(today.getFullYear(), 0, 0)) / 86400000);
+ const page = (dayOfYear % 24) + 1;
const dataPopularProducts = await axios(
- `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/search?q=*&page=1&orderBy=popular-weekly&priceFrom=1`
+ `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/search?q=*&page=${page}&orderBy=stock&priceFrom=1`
)
return dataPopularProducts.data.response
}
diff --git a/src/components/ui/PopularProduct.jsx b/src/components/ui/PopularProduct.jsx
index bbbd18bc..92b2a1b6 100644
--- a/src/components/ui/PopularProduct.jsx
+++ b/src/components/ui/PopularProduct.jsx
@@ -5,6 +5,7 @@ import { useQuery } from 'react-query'
import { PopularProductSkeleton } from '../skeleton/PopularProductSkeleton'
import DesktopView from '@/core/components/views/DesktopView'
import ProductCard from '@/lib/product/components/ProductCard'
+import Link from '@/core/components/elements/Link/Link'
const PopularProduct = () => {
const popularProduct = useQuery('popularProduct', popularProductApi())
@@ -16,15 +17,31 @@ const PopularProduct = () => {
<>
<MobileView>
<div className='px-4'>
- <div className='font-semibold mb-4'>Produk Banyak Dilihat</div>
+ <div className='font-semibold mb-4 flex justify-between items-center'><p>
+ Produk Ready Stock
+ </p>
+ <Link
+ href='/shop/search?orderBy=stock'
+ className=''
+ >
+ <p className='text-danger-500 font-semibold'>Lihat Semua</p>
+ </Link></div>
<ProductSlider products={popularProduct.data} simpleTitle />
</div>
</MobileView>
<DesktopView>
<div className='border border-gray_r-6 h-full overflow-auto'>
- <div className='font-semibold text-center p-4 bg-gray_r-1 border-b border-gray_r-6 sticky top-0 z-10'>
- Produk Banyak Dilihat
+ <div className='font-semibold text-center p-4 bg-gray_r-1 border-b border-gray_r-6 sticky top-0 z-10 flex justify-between items-center'>
+ <p>
+ Produk Ready Stock
+ </p>
+ <Link
+ href='/shop/search?orderBy=stock'
+ className=''
+ >
+ <p className='text-danger-500 font-semibold'>Lihat Semua</p>
+ </Link>
</div>
<div className='h-full divide-y divide-gray_r-6'>
{popularProduct.data &&
diff --git a/src/contexts/ProductCartContext.js b/src/contexts/ProductCartContext.js
index 06e97563..3a21d2e0 100644
--- a/src/contexts/ProductCartContext.js
+++ b/src/contexts/ProductCartContext.js
@@ -6,10 +6,11 @@ export const ProductCartProvider = ({ children }) => {
const [productCart, setProductCart] = useState(null)
const [refreshCart, setRefreshCart] = useState(false)
const [isLoading, setIsloading] = useState(false)
+ const [productQuotation, setProductQuotation] = useState(null)
return (
<ProductCartContext.Provider
- value={{ productCart, setProductCart, refreshCart, setRefreshCart, isLoading, setIsloading }}
+ value={{ productCart, setProductCart, refreshCart, setRefreshCart, isLoading, setIsloading, productQuotation, setProductQuotation}}
>
{children}
</ProductCartContext.Provider>
diff --git a/src/core/components/elements/Footer/BasicFooter.jsx b/src/core/components/elements/Footer/BasicFooter.jsx
index 4beea604..8f024d86 100644
--- a/src/core/components/elements/Footer/BasicFooter.jsx
+++ b/src/core/components/elements/Footer/BasicFooter.jsx
@@ -175,7 +175,7 @@ const CustomerGuide = () => (
<div>
<div className={headerClassName}>Bantuan & Panduan</div>
<ul className='flex flex-col gap-y-3'>
- <li>
+ <li >
<InternalItemLink href='/metode-pembayaran'>
Metode Pembayaran
</InternalItemLink>
@@ -210,6 +210,11 @@ const CustomerGuide = () => (
Panduan Pick Up Service
</InternalItemLink>
</li>
+ <li>
+ <InternalItemLink href='/tracking-order'>
+ Tracking Order
+ </InternalItemLink>
+ </li>
</ul>
</div>
);
diff --git a/src/core/components/elements/Navbar/NavbarDesktop.jsx b/src/core/components/elements/Navbar/NavbarDesktop.jsx
index 2ddf5efe..ebbcf857 100644
--- a/src/core/components/elements/Navbar/NavbarDesktop.jsx
+++ b/src/core/components/elements/Navbar/NavbarDesktop.jsx
@@ -5,7 +5,9 @@ import { createSlug } from '@/core/utils/slug';
import whatsappUrl from '@/core/utils/whatsappUrl';
import IndoteknikLogo from '@/images/logo.png';
import Cardheader from '@/lib/cart/components/Cartheader';
+import Quotationheader from "../../../../../src/lib/quotation/components/Quotationheader.jsx"
import Category from '@/lib/category/components/Category';
+import { useProductCartContext } from '@/contexts/ProductCartContext';
import {
ChevronDownIcon,
DocumentCheckIcon,
@@ -29,6 +31,8 @@ import {
useDisclosure,
} from '@chakra-ui/react';
import style from "./style/NavbarDesktop.module.css";
+import useTransactions from '@/lib/transaction/hooks/useTransactions';
+import { useCartStore } from '~/modules/cart/stores/useCartStore';
const Search = dynamic(() => import('./Search'), { ssr: false });
const TopBanner = dynamic(() => import('./TopBanner'), { ssr: false });
@@ -38,15 +42,27 @@ const NavbarDesktop = () => {
const auth = useAuth();
const [cartCount, setCartCount] = useState(0);
-
+ const [quotationCount, setQuotationCount] = useState(0);
+ const [pendingTransactions, setPendingTransactions] = useState([])
const [templateWA, setTemplateWA] = useState(null);
const [payloadWA, setPayloadWa] = useState(null);
const [urlPath, setUrlPath] = useState(null);
-
+ const { loadCart, cart, summary, updateCartItem } = useCartStore();
const router = useRouter();
const { product } = useProductContext();
const { isOpen, onOpen, onClose } = useDisclosure();
+
+ const query = {
+ context: 'quotation',
+ site:
+ (auth?.webRole === null && auth?.site ? auth.site : null),
+ };
+
+ const { transactions } = useTransactions({ query });
+ const data = transactions?.data?.saleOrders.filter(
+ (transaction) => transaction.status === 'draft'
+ );
const [showPopup, setShowPopup] = useState(false);
const [isTop, setIsTop] = useState(true);
@@ -89,6 +105,11 @@ const NavbarDesktop = () => {
}, []);
useEffect(() => {
+ setPendingTransactions(data);
+ }, [transactions.data]);
+
+
+ useEffect(() => {
if (router.pathname === '/shop/product/[slug]') {
setPayloadWa({
name: product?.name,
@@ -96,11 +117,11 @@ const NavbarDesktop = () => {
url: createSlug('/shop/product/', product?.name, product?.id, true),
});
setTemplateWA('product');
-
+
setUrlPath(router.asPath);
}
}, [product, router]);
-
+
useEffect(() => {
const handleCartChange = () => {
const cart = async () => {
@@ -109,15 +130,31 @@ const NavbarDesktop = () => {
};
cart();
};
- handleCartChange();
-
+ handleCartChange();
+
window.addEventListener('localStorageChange', handleCartChange);
-
+
return () => {
window.removeEventListener('localStorageChange', handleCartChange);
};
- }, []);
+ }, [transactions.data, cart]);
+ useEffect(() => {
+ const handleQuotationChange = () => {
+ const quotation = async () => {
+ setQuotationCount(pendingTransactions?.length);
+ };
+ quotation();
+ };
+ handleQuotationChange();
+
+ window.addEventListener('localStorageChange', handleQuotationChange);
+
+ return () => {
+ window.removeEventListener('localStorageChange', handleQuotationChange);
+ };
+ }, [pendingTransactions]);
+
return (
<DesktopView>
<TopBanner onLoad={handleTopBannerLoad} />
@@ -180,17 +217,7 @@ const NavbarDesktop = () => {
<Search />
</div>
<div className='flex gap-x-4 items-center'>
- <Link
- href='/my/transactions'
- target='_blank'
- rel='noreferrer'
- className='flex items-center gap-x-2 !text-gray_r-12/80'
- >
- <DocumentCheckIcon className='w-7' />
- Daftar
- <br />
- Quotation
- </Link>
+ <Quotationheader quotationCount={quotationCount} data={pendingTransactions} />
<Cardheader cartCount={cartCount} />
@@ -225,8 +252,7 @@ const NavbarDesktop = () => {
<div className='container mx-auto mt-6'>
<div className='flex'>
- <button
- type='button'
+ <div
onClick={() => setIsOpenCategory((isOpen) => !isOpen)}
onBlur={() => setIsOpenCategory(false)}
className='w-3/12 p-4 font-semibold border border-gray_r-6 rounded-t-xl flex items-center relative'
@@ -243,7 +269,7 @@ const NavbarDesktop = () => {
>
<Category />
</div>
- </button>
+ </div>
<div className='w-6/12 flex px-1 divide-x divide-gray_r-6'>
<Link
@@ -291,7 +317,7 @@ const NavbarDesktop = () => {
<Link
href='/shop/search?orderBy=stock'
className={`${
- router.asPath === '/shop/search?orderBy=stock' &&
+ router.asPath.includes('/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 group`}
target='_blank'
diff --git a/src/core/components/elements/Navbar/NavbarMobile.jsx b/src/core/components/elements/Navbar/NavbarMobile.jsx
index bcf45e0a..90314671 100644
--- a/src/core/components/elements/Navbar/NavbarMobile.jsx
+++ b/src/core/components/elements/Navbar/NavbarMobile.jsx
@@ -11,7 +11,7 @@ import Image from 'next/image';
import { useEffect, useState } from 'react';
import MobileView from '../../views/MobileView';
import Link from '../Link/Link';
-// import TopBanner from './TopBanner';
+import TopBanner from './TopBanner';
const Search = dynamic(() => import('./Search'));
@@ -39,7 +39,7 @@ const NavbarMobile = () => {
return (
<MobileView>
- {/* <TopBanner /> */}
+ <TopBanner />
<nav className='px-4 py-2 pb-3 sticky top-0 z-50 bg-white shadow'>
<div className='flex justify-between items-center mb-2'>
<Link href='/'>
diff --git a/src/core/components/elements/Navbar/TopBanner.jsx b/src/core/components/elements/Navbar/TopBanner.jsx
index 7bc8fb6a..f438ae67 100644
--- a/src/core/components/elements/Navbar/TopBanner.jsx
+++ b/src/core/components/elements/Navbar/TopBanner.jsx
@@ -1,19 +1,20 @@
import Image from 'next/image';
-import { useQuery } from 'react-query';
-
+import { useQuery } from 'react-query';import useDevice from '@/core/hooks/useDevice'
import odooApi from '@/core/api/odooApi';
import SmoothRender from '~/components/ui/smooth-render';
import Link from '../Link/Link';
+import { background } from '@chakra-ui/react';
import { useEffect } from 'react';
-const TopBanner = ({ onLoad }) => {
+const TopBanner = ({ onLoad = () => {} }) => {
+ const { isDesktop, isMobile } = useDevice()
const topBanner = useQuery({
queryKey: 'topBanner',
queryFn: async () => await odooApi('GET', '/api/v1/banner?type=top-banner'),
refetchOnWindowFocus: false,
});
- const backgroundColor = topBanner.data?.[0]?.backgroundColor || 'transparent';
+ // const backgroundColor = topBanner.data?.[0]?.backgroundColor || 'transparent';
const hasData = topBanner.data?.length > 0;
const data = topBanner.data?.[0] || null;
@@ -26,21 +27,21 @@ const TopBanner = ({ onLoad }) => {
return (
<SmoothRender
isLoaded={hasData}
- height='36px'
+ // height='36px'
duration='700ms'
delay='300ms'
- style={{ backgroundColor }}
- >
- <Link href={data?.url}>
- <Image
- src={data?.image}
- alt={data?.name}
- width={1440}
- height={40}
- className='object-cover object-center h-full mx-auto'
- />
- </Link>
- </SmoothRender>
+ className='h-auto'
+ >
+ <Link
+ href={data?.url}
+ className="block bg-cover bg-center h-3 md:h-6 lg:h-[36px]"
+ style={{
+ backgroundImage: `url('${data?.image}')`,
+ }}
+ >
+ </Link>
+
+ </SmoothRender>
);
};
diff --git a/src/core/components/elements/Sidebar/Sidebar.jsx b/src/core/components/elements/Sidebar/Sidebar.jsx
index 55838890..ddae3e20 100644
--- a/src/core/components/elements/Sidebar/Sidebar.jsx
+++ b/src/core/components/elements/Sidebar/Sidebar.jsx
@@ -5,6 +5,8 @@ import { AnimatePresence, motion } from 'framer-motion'
import { ChevronDownIcon, ChevronUpIcon, CogIcon, UserIcon } from '@heroicons/react/24/outline'
import { Fragment, useEffect, useState } from 'react'
import odooApi from '@/core/api/odooApi'
+import { createSlug } from '@/core/utils/slug'
+import Image from 'next/image'
const Sidebar = ({ active, close }) => {
const auth = useAuth()
@@ -79,7 +81,7 @@ const Sidebar = ({ active, close }) => {
exit={{ left: '-80%' }}
transition={transition}
>
- <div className='divide-y divide-gray_r-6'>
+ <div className='divide-y divide-gray_r-6 h-full flex flex-col'>
<div className='p-4 flex gap-x-3'>
{!auth && (
<>
@@ -112,104 +114,115 @@ const Sidebar = ({ active, close }) => {
href='/my/menu'
className='!text-gray_r-11 ml-auto my-auto'
>
- <UserIcon class='h-6 w-6 text-gray-500' />
+ <UserIcon className='h-6 w-6 text-gray-500' />
</Link>
</>
)}
</div>
- <SidebarLink className={itemClassName} href='/shop/promo'>
- Semua Promo
- </SidebarLink>
- <SidebarLink className={itemClassName} href='/shop/brands'>
- Semua Brand
- </SidebarLink>
- <SidebarLink
- className={itemClassName}
- href='https://blog.indoteknik.com/'
- target='_blank'
- rel='noreferrer noopener'
- >
- Blog Indoteknik
- </SidebarLink>
- {/* <SidebarLink className={itemClassName} href='/video'>
- Indoteknik TV
- </SidebarLink> */}
- <SidebarLink className={itemClassName} href='/tentang-kami'>
- Tentang Indoteknik
- </SidebarLink>
- <SidebarLink className={itemClassName} href='/contact-us'>
- Hubungi Kami
- </SidebarLink>
- <button
- className={`${itemClassName} w-full text-left flex`}
- onClick={() => setOpenCategory(!isOpenCategory)}
- >
- Kategori
- <div className='ml-auto'>
- {!isOpenCategory && <ChevronDownIcon className='text-gray_r-12 w-5' />}
- {isOpenCategory && <ChevronUpIcon className='text-gray_r-12 w-5' />}
- </div>
- </button>
- {isOpenCategory &&
- categories.map((category) => (
- <Fragment key={category.id}>
- <div className='flex w-full text-gray_r-11 border-b border-gray_r-6 px-4 pl-8 items-center'>
- <Link
- href={`/shop/search?category=${category.name}`}
- className='flex-1 font-normal !text-gray_r-11 py-4'
- >
- {category.name}
- </Link>
- <div
- className='ml-4 h-full py-4'
- onClick={() => toggleCategories(category.id)}
- >
- {!category.isOpen && <ChevronDownIcon className='text-gray_r-11 w-5' />}
- {category.isOpen && <ChevronUpIcon className='text-gray_r-11 w-5' />}
+ <div className='overflow-y-auto flex-1'>
+ <SidebarLink className={itemClassName} href='/shop/promo'>
+ Semua Promo
+ </SidebarLink>
+ <SidebarLink className={itemClassName} href='/shop/brands'>
+ Semua Brand
+ </SidebarLink>
+ <SidebarLink
+ className={itemClassName}
+ href='https://blog.indoteknik.com/'
+ target='_blank'
+ rel='noreferrer noopener'
+ >
+ Blog Indoteknik
+ </SidebarLink>
+ {/* <SidebarLink className={itemClassName} href='/video'>
+ Indoteknik TV
+ </SidebarLink> */}
+ <SidebarLink className={itemClassName} href='/tentang-kami'>
+ Tentang Indoteknik
+ </SidebarLink>
+ <SidebarLink className={itemClassName} href='/contact-us'>
+ Hubungi Kami
+ </SidebarLink>
+ <button
+ className={`${itemClassName} w-full text-left flex`}
+ onClick={() => setOpenCategory(!isOpenCategory)}
+ >
+ Kategori
+ <div className='ml-auto'>
+ {!isOpenCategory && <ChevronDownIcon className='text-gray_r-12 w-5' />}
+ {isOpenCategory && <ChevronUpIcon className='text-gray_r-12 w-5' />}
+ </div>
+ </button>
+ {isOpenCategory &&
+ categories.map((category) => (
+ <Fragment key={category.id}>
+ <div className='flex w-full text-gray_r-11 border-b border-gray_r-6 px-4 pl-8 items-center'>
+ <Link
+ href={createSlug('/shop/category/', category.name, category.id)}
+ className='flex-1 font-normal !text-gray_r-11 py-4 flex items-center flex-row'
+ >
+ <div className='mr-2 flex justify-center items-center'>
+ <Image src={category.image} alt='' width={25} height={25} />
+ </div>
+ {category.name}
+ </Link>
+ <div
+ className='ml-4 h-full py-4'
+ onClick={() => toggleCategories(category.id)}
+ >
+ {!category.isOpen && <ChevronDownIcon className='text-gray_r-11 w-5' />}
+ {category.isOpen && <ChevronUpIcon className='text-gray_r-11 w-5' />}
+ </div>
</div>
- </div>
- {category.isOpen &&
- category.childs.map((child1Category) => (
- <Fragment key={child1Category.id}>
- <div
- className={`flex w-full !text-gray_r-11 border-b border-gray_r-6 p-4 pl-12 ${
- category.isOpen ? 'bg-gray_r-2' : ''
- }`}
- >
- <Link
- href={`/shop/search?category=${child1Category.name}`}
- className='flex-1 font-normal !text-gray_r-11'
+ {category.isOpen &&
+ category.childs.map((child1Category) => (
+ <Fragment key={child1Category.id}>
+ <div
+ className={`flex w-full !text-gray_r-11 border-b border-gray_r-6 p-4 pl-12 ${
+ category.isOpen ? 'bg-gray_r-2' : ''
+ }`}
>
- {child1Category.name}
- </Link>
- {child1Category.childs.length > 0 && (
- <div
- className='ml-4 h-full'
- onClick={() => toggleCategories(child1Category.id)}
- >
- {!child1Category.isOpen && (
- <ChevronDownIcon className='text-gray_r-11 w-5' />
- )}
- {child1Category.isOpen && (
- <ChevronUpIcon className='text-gray_r-11 w-5' />
- )}
- </div>
- )}
- </div>
- {child1Category.isOpen &&
- child1Category.childs.map((child2Category) => (
<Link
- key={child2Category.id}
- href={`/shop/search?category=${child2Category.name}`}
- className='flex w-full font-normal !text-gray_r-11 border-b border-gray_r-6 p-4 pl-16'
+ href={createSlug('/shop/category/', child1Category.name, child1Category.id)}
+ className='flex-1 font-normal !text-gray_r-11 flex flex-row items-center'
>
- {child2Category.name}
+ <div className='mr-2 flex justify-center items-center'>
+ <Image src={`https://erp.indoteknik.com/api/image/product.public.category/image_1920/${child1Category.id}`} alt='' width={25} height={25} />
+ </div>
+ {child1Category.name}
</Link>
- ))}
- </Fragment>
- ))}
- </Fragment>
- ))}
+ {child1Category.childs.length > 0 && (
+ <div
+ className='ml-4 h-full'
+ onClick={() => toggleCategories(child1Category.id)}
+ >
+ {!child1Category.isOpen && (
+ <ChevronDownIcon className='text-gray_r-11 w-5' />
+ )}
+ {child1Category.isOpen && (
+ <ChevronUpIcon className='text-gray_r-11 w-5' />
+ )}
+ </div>
+ )}
+ </div>
+ {child1Category.isOpen &&
+ child1Category.childs.map((child2Category) => (
+ <Link
+ key={child2Category.id}
+ href={createSlug('/shop/category/', child2Category.name, child2Category.id)}
+ className='flex w-full font-normal !text-gray_r-11 border-b border-gray_r-6 p-4 pl-16 flex-row'
+ >
+ <div className='mr-2 flex justify-center items-center'>
+ <Image src={`https://erp.indoteknik.com/api/image/product.public.category/image_1920/${child2Category.id}`} alt='' width={25} height={25} />
+ </div>
+ {child2Category.name}
+ </Link>
+ ))}
+ </Fragment>
+ ))}
+ </Fragment>
+ ))}
+ </div>
</div>
</motion.div>
</>
diff --git a/src/lib/brand/components/BrandCard.jsx b/src/lib/brand/components/BrandCard.jsx
index 731214ff..2d78d956 100644
--- a/src/lib/brand/components/BrandCard.jsx
+++ b/src/lib/brand/components/BrandCard.jsx
@@ -8,7 +8,7 @@ const BrandCard = ({ brand }) => {
return (
<Link
href={createSlug('/shop/brands/', brand.name, brand.id)}
- className={`py-1 px-2 rounded border border-gray_r-6 flex justify-center items-center ${
+ className={`py-1 px-2 border-gray_r-6 flex justify-center items-center hover:scale-110 transition duration-500 ease-in-out ${
isMobile ? 'h-16' : 'h-24'
}`}
>
@@ -16,9 +16,9 @@ const BrandCard = ({ brand }) => {
<Image
src={brand.logo}
alt={brand.name}
- width={128}
- height={128}
- className='h-full w-full object-contain object-center'
+ width={50}
+ height={50}
+ className='h-full w-[122px] object-contain object-center'
/>
)}
{!brand.logo && (
diff --git a/src/lib/cart/components/Cartheader.jsx b/src/lib/cart/components/Cartheader.jsx
index 19f79bc9..ddb77c1f 100644
--- a/src/lib/cart/components/Cartheader.jsx
+++ b/src/lib/cart/components/Cartheader.jsx
@@ -1,14 +1,20 @@
import { useCallback, useEffect, useMemo, useState } from 'react'
import { getCartApi } from '../api/CartApi'
+import currencyFormat from '@/core/utils/currencyFormat'
+import { createSlug } from '@/core/utils/slug'
import useAuth from '@/core/hooks/useAuth'
import { useRouter } from 'next/router'
import odooApi from '@/core/api/odooApi'
import { useProductCartContext } from '@/contexts/ProductCartContext'
-
+import Image from '@/core/components/elements/Image/Image'
+import whatsappUrl from '@/core/utils/whatsappUrl'
+import { AnimatePresence, motion } from 'framer-motion'
+import style from '../../../../src-migrate/modules/cart/styles/item-promo.module.css'
const { ShoppingCartIcon, PhotoIcon } = require('@heroicons/react/24/outline')
const { default: Link } = require('next/link')
const Cardheader = (cartCount) => {
+
const router = useRouter()
const [subTotal, setSubTotal] = useState(null)
const [buttonLoading, SetButtonTerapkan] = useState(false)
@@ -19,7 +25,7 @@ const Cardheader = (cartCount) => {
useProductCartContext()
const [isHovered, setIsHovered] = useState(false)
-
+ const [isTop, setIsTop] = useState(true)
const products = useMemo(() => {
return productCart?.products || []
}, [productCart])
@@ -42,7 +48,7 @@ const Cardheader = (cartCount) => {
setIsloading(true)
let cart = await getCartApi()
setProductCart(cart)
- setCountCart(cart.productTotal)
+ setCountCart(cart?.productTotal)
setIsloading(false)
}, [setProductCart, setIsloading])
@@ -75,14 +81,26 @@ const Cardheader = (cartCount) => {
useEffect(() => {
setCountCart(cartCount.cartCount)
+ setRefreshCart(false)
}, [cartCount])
+ useEffect(() => {
+ const handleScroll = () => {
+ setIsTop(window.scrollY === 0)
+ }
+ window.addEventListener('scroll', handleScroll)
+ return () => {
+ window.removeEventListener('scroll', handleScroll)
+ }
+ }, [])
+
const handleCheckout = async () => {
SetButtonTerapkan(true)
let checkoutAll = await odooApi('POST', `/api/v1/user/${auth.id}/cart/select-all`)
router.push('/shop/checkout')
}
+
return (
<div className='relative group'>
<div>
@@ -109,6 +127,246 @@ const Cardheader = (cartCount) => {
</span>
</Link>
</div>
+ <AnimatePresence>
+ {isHovered && (
+ <>
+ <motion.div
+ initial={{ opacity: 0 }}
+ animate={{ opacity: 1, top: isTop ? 230 : 155 }}
+ exit={{ opacity: 0 }}
+ transition={{ duration: 0.15, top: { duration: 0.3 } }}
+ className={`fixed left-0 w-full h-full bg-black/50 z-10`}
+ />
+ <motion.div
+ initial={{ opacity: 0 }}
+ animate={{ opacity: 1, transition: { duration: 0.2 } }}
+ exit={{ opacity: 0, transition: { duration: 0.3 } }}
+ className='absolute z-10 left-0 w-96'
+ onMouseEnter={handleMouseEnter}
+ onMouseLeave={handleMouseLeave}
+ >
+ <motion.div
+ initial={{ height: 0 }}
+ animate={{ height: 'auto' }}
+ exit={{ height: 0 }}
+ className='w-full max-w-md p-2 bg-white border border-gray-200 rounded-lg shadow overflow-hidden'
+ >
+ <div className='p-2 flex justify-between items-center'>
+ <h5 className='text-base font-semibold leading-none'>Keranjang Belanja</h5>
+ <Link href='/shop/cart' class='text-sm font-medium text-red-600 underline'>
+ Lihat Semua
+ </Link>
+ </div>
+ <hr className='mt-3 mb-3 border border-gray-100' />
+ <div className='flow-root max-h-[250px] overflow-y-auto'>
+ {!auth && (
+ <div className='justify-center p-4'>
+ <p className='text-gray-500 text-center '>
+ Silahkan{' '}
+ <Link href='/login' className='text-red-600 underline leading-6'>
+ Login
+ </Link>{' '}
+ Untuk Melihat Daftar Keranjang Belanja Anda
+ </p>
+ </div>
+ )}
+ {isLoading &&
+ itemLoading.map((item) => (
+ <div key={item} role='status' className='max-w-sm animate-pulse'>
+ <div className='flex items-center space-x-4 mb- 2'>
+ <div className='flex-shrink-0'>
+ <PhotoIcon className='h-16 w-16 text-gray-500' />
+ </div>
+ <div className='flex-1 min-w-0'>
+ <div className='h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4'></div>
+ <div className='h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px] mb-2.5'></div>
+ <div className='h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5'></div>
+ </div>
+ </div>
+ </div>
+ ))}
+ {auth && products.length === 0 && !isLoading && (
+ <div className='justify-center p-4'>
+ <p className='text-gray-500 text-center '>
+ Tidak Ada Produk di Keranjang Belanja Anda
+ </p>
+ </div>
+ )}
+ {auth && products.length > 0 && !isLoading && (
+ <>
+ <ul role='list' className='divide-y divide-gray-200 dark:divide-gray-700'>
+ {products &&
+ products?.map((product, index) => (
+ <>
+ <li className='py-1 sm:py-2'>
+ <div className='flex items-center space-x-4'>
+ <div className='bagian gambar flex-shrink-0'>
+ {product.cartType === 'promotion' && (
+ <Image
+ src={product.imageProgram[0]}
+ alt={product.name}
+ className='object-contain object-center border border-gray_r-6 h-16 w-16 rounded-md'
+ />
+ )}
+ {product.cartType === 'product' && (
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
+ >
+ <Image
+ src={product?.parent?.image}
+ alt={product?.name}
+ className='object-contain object-center border border-gray_r-6 h-16 w-16 rounded-md'
+ />
+ </Link>
+ )}
+ </div>
+ <div className='bagian tulisan dan harga flex-1 min-w-0'>
+ {product.cartType === 'promotion' && (
+ <p className='text-caption-2 font-medium text-gray-900 truncate dark:text-white'>
+ {product.name}
+ </p>
+ )}
+ {product.cartType === 'product' && (
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
+ >
+ {' '}
+ <p className='text-caption-2 font-medium text-gray-900 truncate dark:text-white'>
+ {product.parent.name}
+ </p>
+ </Link>
+ )}
+ {product?.hasFlashsale && (
+ <div className='flex gap-x-1 items-center mb-2 mt-1'>
+ <div className='badge-solid-red'>
+ {product?.price?.discountPercentage}%
+ </div>
+ <div className='text-gray_r-11 line-through text-caption-2'>
+ {currencyFormat(product?.price?.price)}
+ </div>
+ </div>
+ )}
+
+ <div className='flex justify-between items-center'>
+ <div className='font-semibold text-sm text-red-600'>
+ {product?.price?.priceDiscount > 0 ? (
+ currencyFormat(product?.price?.priceDiscount)
+ ) : (
+ <span className='text-gray_r-12/90 font-normal text-caption-1'>
+ <a
+ href={whatsappUrl('product', {
+ name: product.name,
+ manufacture: product.manufacture?.name,
+ url: createSlug(
+ '/shop/product/',
+ product.name,
+ product.id,
+ true
+ )
+ })}
+ className='text-danger-500 underline'
+ rel='noopener noreferrer'
+ target='_blank'
+ >
+ Call For Price
+ </a>
+ </span>
+ )}
+ </div>
+ </div>
+ </div>
+ </div>
+ <div className="flex flex-col w-3/4">
+ {product.products?.map((product) =>
+ <div key={product.id} className='md:ml-8 ml-4 mt-2 flex'>
+ <Link href={createSlug('/shop/product/', product.parent.name, product.parent.id.toString())} className='md:h-12 md:w-12 md:min-w-[48px] h-10 w-10 min-w-[40px] border border-gray-300 rounded '>
+ {product?.image && <Image src={product.image} alt={product.name} width={40} height={40} className='w-full h-full object-fill' />}
+ </Link>
+
+ <div className="ml-4 w-full flex flex-col gap-y-1">
+ <Link href={createSlug('/shop/product/', product.parent.name, product.parent.id.toString())} className="text-caption-2 font-medium text-gray-900 truncate dark:text-white">
+ {product.displayName}
+ </Link>
+
+ <div className='flex w-full'>
+ <div className="flex flex-col">
+ {/* <div className="text-gray-500 text-caption-1">{product.code}</div> */}
+ <div>
+ <span className="text-gray-500 text-caption-1">Berat Barang: </span>
+ <span className="text-gray-500 text-caption-1">{product.packageWeight} Kg</span>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ </div>
+ )}
+ {product.freeProducts?.map((product) =>
+ <div key={product.id} className='md:ml-8 ml-4 mt-2 flex'>
+ <Link href={createSlug('/shop/product/', product.parent.name, product.parent.id.toString())} className='md:h-12 md:w-12 md:min-w-[48px] h-10 w-10 min-w-[40px] border border-gray-300 rounded '>
+ {product?.image && <Image src={product.image} alt={product.name} width={40} height={40} className='w-full h-full object-fill' />}
+ </Link>
+
+ <div className="ml-4 w-full flex flex-col gap-y-1">
+ <Link href={createSlug('/shop/product/', product.parent.name, product.parent.id.toString())} className="text-caption-2 font-medium text-gray-900 truncate dark:text-white">
+ {product.displayName}
+ </Link>
+
+ <div className='flex w-full'>
+ <div className="flex flex-col">
+ {/* <div className="text-gray-500 text-caption-1">{product.code}</div> */}
+ <div>
+ <span className="text-gray-500 text-caption-1">Berat Barang: </span>
+ <span className="text-gray-500 text-caption-1">{product.packageWeight} Kg</span>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ </div>
+ )}
+ </div>
+ </li>
+ </>
+ ))}
+ </ul>
+ <hr />
+ </>
+ )}
+ </div>
+ {auth && products.length > 0 && !isLoading && (
+ <>
+ <div className='mt-3'>
+ <span className='text-gray-400 text-caption-2'>Subtotal Sebelum PPN : </span>
+ <span className='font-semibold text-red-600'>{currencyFormat(subTotal)}</span>
+ </div>
+ <div className='mt-5 mb-2'>
+ <button
+ type='button'
+ className='btn-solid-red rounded-lg w-full'
+ onClick={handleCheckout}
+ disabled={buttonLoading}
+ >
+ {buttonLoading ? 'Loading...' : 'Lanjutkan Ke Pembayaran'}
+ </button>
+ </div>
+ </>
+ )}
+ </motion.div>
+ </motion.div>
+ </>
+ )}
+ </AnimatePresence>
</div>
)
}
diff --git a/src/lib/category/api/popularProduct.js b/src/lib/category/api/popularProduct.js
new file mode 100644
index 00000000..3fdfc41c
--- /dev/null
+++ b/src/lib/category/api/popularProduct.js
@@ -0,0 +1,32 @@
+
+export const fetchPopulerProductSolr = async (category_id_ids) => {
+ let sort ='sort=qty_sold_f desc';
+ try {
+ const queryParams = new URLSearchParams({ q: category_id_ids });
+ const response = await fetch(`/solr/product/select?${queryParams.toString()}&rows=2000&fl=manufacture_name_s,manufacture_id_i,id,display_name_s,qty_sold_f,qty_sold_f&${sort}`);
+ if (!response.ok) {
+ throw new Error(`HTTP error! status: ${response.status}`);
+ }
+ const data = await response.json();
+ const promotions = await map(data.response.docs);
+ return promotions;
+ } catch (error) {
+ console.error("Error fetching promotion data:", error);
+ return [];
+ }
+ };
+
+ const map = async (promotions) => {
+ const result = [];
+ for (const promotion of promotions) {
+ const data = {
+ id: promotion.id,
+ name: promotion.display_name_s,
+ manufacture_name: promotion.manufacture_name_s,
+ manufacture_id: promotion.manufacture_id_i,
+ qty_sold: promotion.qty_sold_f,
+ };
+ result.push(data);
+ }
+ return result;
+ }; \ No newline at end of file
diff --git a/src/lib/category/components/Category.jsx b/src/lib/category/components/Category.jsx
index e6ea5acf..91553295 100644
--- a/src/lib/category/components/Category.jsx
+++ b/src/lib/category/components/Category.jsx
@@ -2,11 +2,20 @@ import odooApi from '@/core/api/odooApi'
import Link from '@/core/components/elements/Link/Link'
import DesktopView from '@/core/components/views/DesktopView'
import { createSlug } from '@/core/utils/slug'
+import { ChevronRightIcon } from '@heroicons/react/24/outline'
+import Image from 'next/image'
import { useEffect, useState } from 'react'
+import PopularBrand from './PopularBrand'
+import { bannerApi } from '@/api/bannerApi';
+const { useQuery } = require('react-query')
const Category = () => {
const [categories, setCategories] = useState([])
+ const [openCategories, setOpenCategory] = useState([]);
+ const [banner, setBanner] = useState([]);
+ const promotionProgram = useQuery('banner-promo-category-card', bannerApi({ type: 'banner-promo-category-card' }));
+
useEffect(() => {
const loadCategories = async () => {
let dataCategories = await odooApi('GET', '/api/v1/category/tree')
@@ -26,46 +35,65 @@ const Category = () => {
}
loadCategories()
}, [])
-
return (
<DesktopView>
<div className='category-mega-box'>
{categories?.map((category) => (
- <div key={category.id}>
+ <div key={category.id} className='flex'>
<Link
href={createSlug('/shop/category/', category.name, category.id)}
- className='category-mega-box__parent'
+ className='category-mega-box__parent flex items-center'
>
+ <div className='mr-2 flex justify-center items-center'>
+ <Image src={category.image} alt='' width={25} height={25} />
+ </div>
{category.name}
</Link>
<div className='category-mega-box__child-wrapper'>
- <div className='grid grid-cols-3 gap-x-4 gap-y-6 max-h-full overflow-auto'>
+ <div className='grid grid-cols-3 gap-x-4 gap-y-6 max-h-full !w-[590px] overflow-auto'>
{category.childs.map((child1Category) => (
- <div key={child1Category.id}>
+ <div key={child1Category.id} className='w-full'>
<Link
href={createSlug('/shop/category/', child1Category.name, child1Category.id)}
- className='category-mega-box__child-one mb-4'
+ className='category-mega-box__child-one mb-4 w-full h-8 flex justify-center line-clamp-2'
>
{child1Category.name}
</Link>
- <div className='flex flex-col gap-y-3'>
- {child1Category.childs.map((child2Category) => (
- <Link
- href={createSlug(
- '/shop/category/',
- child2Category.name,
- child2Category.id
- )}
- className='category-mega-box__child-two'
- key={child2Category.id}
- >
- {child2Category.name}
- </Link>
+ <div className='flex flex-col gap-y-3 w-full'>
+ {child1Category.childs.map((child2Category, index) => (
+ (index < 4) && (
+ <Link
+ href={createSlug('/shop/category/', child2Category.name, child2Category.id)}
+ className='category-mega-box__child-two truncate'
+ key={child2Category.id}
+ >
+ {child2Category.name}
+ </Link>
+ )
))}
+ {child1Category.childs.length > 5 && (
+ <div className='flex hover:bg-gray_r-8/35 rounded-10'>
+ <Link
+ href={createSlug('/shop/category/', child1Category.name, child1Category.id)}
+ className='category-mega-box__child-one flex items-center gap-4 font-bold hover:ml-4'
+ >
+ <p className='mt-2 mb-0 text-danger-500 font-semibold'>Lihat Semua</p>
+ <ChevronRightIcon className='w-4 text-danger-500 font-bold' />
+ </Link>
+ </div>
+ )}
</div>
</div>
))}
</div>
+ {/* <div className='category-mega-box__child-wrapper !w-[260px] !flex !flex-col !gap-4'>
+ <PopularBrand category={category} />
+ {Array.isArray(promotionProgram?.data) && promotionProgram?.data.length > 0 && promotionProgram?.data[0]?.map((banner, index) => (
+ <div key={index} className='flex w-60 h-20 object-cover'>
+ <Image src={`${banner.image}`} alt={`${banner.name}`} width={275} height={4} />
+ </div>
+ ))}
+ </div> */}
</div>
</div>
))}
diff --git a/src/lib/category/components/PopularBrand.jsx b/src/lib/category/components/PopularBrand.jsx
new file mode 100644
index 00000000..8124b5b4
--- /dev/null
+++ b/src/lib/category/components/PopularBrand.jsx
@@ -0,0 +1,96 @@
+import odooApi from '@/core/api/odooApi'
+import React, { useEffect, useState } from 'react'
+import axios from 'axios';
+import { useQuery } from 'react-query'
+import Link from '@/core/components/elements/Link/Link'
+import { createSlug } from '@/core/utils/slug'
+import Image from 'next/image'
+import { ChevronRightIcon } from '@heroicons/react/24/outline'
+import useProductSearch from '../../../lib/product/hooks/useProductSearch';
+import { SolrResponse } from "~/types/solr";
+import { fetchPopulerProductSolr } from '../api/popularProduct'
+
+const SOLR_HOST = process.env.SOLR_HOST
+
+const PopularBrand = ({ category }) => {
+ // const [topBrands, setTopBrands] = useState([]);
+
+ // const fetchTopBrands = async () => {
+ // try {
+ // const items = await fetchPopulerProductSolr(`category_id_ids:(${category?.categoryDataIds?.join(' OR ')})`);
+ // const getTop12UniqueBrands = (prod) => {
+ // const brandMap = new Map();
+
+ // for (const product of prod) {
+ // const { manufacture_name, manufacture_id, qty_sold } = product;
+
+ // if (brandMap.has(manufacture_name)) {
+ // // Update the existing brand's qty_sold
+ // brandMap.set(manufacture_name, {
+ // name: manufacture_name,
+ // id: manufacture_id,
+ // qty_sold: brandMap.get(manufacture_name).qty_sold + qty_sold
+ // });
+ // } else {
+ // // Add a new brand to the map
+ // brandMap.set(manufacture_name, {
+ // name: manufacture_name,
+ // id: manufacture_id,
+ // qty_sold
+ // });
+ // }
+ // }
+
+ // // Convert the map to an array and sort by qty_sold in descending order
+ // const sortedBrands = Array.from(brandMap.values()).sort((a, b) => b.qty_sold - a.qty_sold);
+
+ // // Return the top 12 brands
+ // return sortedBrands.slice(0, 18);
+ // };
+
+ // // Using the fetched products
+ // const products = items;
+ // const top12UniqueBrands = getTop12UniqueBrands(products);
+
+ // // Set the top 12 brands to the state
+ // setTopBrands(top12UniqueBrands);
+ // } catch (error) {
+ // console.error("Error fetching data from Solr", error);
+ // throw error;
+ // }
+ // }
+
+ // useEffect(() => {
+ // fetchTopBrands();
+ // }, [category]);
+
+ return (
+ <div className='flex flex-col'>
+ {/* <div className='grid grid-cols-3 max-h-full w-full gap-2'>
+ {topBrands.map((brand, index) => (
+ <div key={index} className='w-full flex items-center justify-center pb-2'>
+ <Link
+ href={createSlug('/shop/brands/', brand.name, brand.id)}
+ className='category-mega-box__child-one w-8 h-full flex items-center justify-center '
+ >
+ <Image src={`https://erp.indoteknik.com/api/image/x_manufactures/x_logo_manufacture/${brand.id}` } alt={`${brand.name}`} width={104} height={44} objectFit='cover' />
+ </Link>
+ </div>
+ ))}
+ </div> */}
+ {/* {topBrands.length > 8 && (
+ <div className='flex hover:bg-gray_r-8/35 rounded-10'>
+ <Link
+ href={createSlug('/shop/category/', category.name, category.id)}
+ className='category-mega-box__child-one flex items-center gap-4 font-bold hover:ml-4'
+ >
+ <p className='mt-2 mb-0 text-danger-500 font-semibold'>Lihat Semua Brand</p>
+ <ChevronRightIcon className='w-4 text-danger-500 font-bold' />
+ </Link>
+ </div>
+ )} */}
+ </div>
+ )
+}
+
+export default PopularBrand;
diff --git a/src/lib/checkout/components/Checkout.jsx b/src/lib/checkout/components/Checkout.jsx
index 09a791ee..f63ef457 100644
--- a/src/lib/checkout/components/Checkout.jsx
+++ b/src/lib/checkout/components/Checkout.jsx
@@ -131,6 +131,7 @@ const Checkout = () => {
setLoadingVoucher(true);
let dataVoucher = await getVoucher(auth?.id, {
source: query,
+ type: 'all,brand',
});
SetListVoucher(dataVoucher);
@@ -146,40 +147,94 @@ const Checkout = () => {
};
const VoucherCode = async (code) => {
- const source = 'code=' + code + '&source=' + query;
// let dataVoucher = await findVoucher(code, auth.id, query);
- let dataVoucher = await getVoucherNew(source);
+ let dataVoucher = await getVoucher(auth?.id, {
+ source: query,
+ code: code,
+ });
if (dataVoucher.length <= 0) {
SetFindVoucher(1);
return;
}
- let addNewLine = dataVoucher[0];
- let checkList = listVouchers?.findIndex(
- (voucher) => voucher.code == addNewLine.code
- );
- if (checkList >= 0) {
- if (listVouchers[checkList].canApply) {
- ToggleSwitch(code);
- SetCodeVoucher(null);
+ dataVoucher.forEach((addNewLine) => {
+ if (addNewLine.applyType !== 'shipping') {
+ // Mencari voucher dalam listVouchers
+ let checkList = listVouchers?.findIndex(
+ (voucher) => voucher.code === addNewLine.code
+ );
+
+ if (checkList >= 0) {
+ if (listVouchers[checkList].canApply) {
+ ToggleSwitch(addNewLine.code); // Perbaikan: Gunakan code voucher yang benar
+ SetCodeVoucher(null);
+ } else {
+ SetSelisihHargaCode(listVouchers[checkList].differenceToApply);
+ SetFindVoucher(2);
+ }
+ return; // Hentikan eksekusi lebih lanjut pada iterasi ini
+ }
+ // Memeriksa apakah subtotal memenuhi syarat minimal pembelian
+ if (cartCheckout?.subtotal < addNewLine.minPurchaseAmount) {
+ SetSelisihHargaCode(
+ currencyFormat(
+ addNewLine.minPurchaseAmount - cartCheckout?.subtotal
+ )
+ );
+ SetFindVoucher(2);
+ return;
+ } else {
+ SetFindVoucher(3);
+ SetButtonTerapkan(true);
+ }
+
+ // Tambahkan voucher ke list dan set voucher aktif
+ SetListVoucher((prevList) => [addNewLine, ...prevList]);
+ if (addNewLine.canApply) {
+ SetActiveVoucher(addNewLine.code);
+ }
} else {
- SetSelisihHargaCode(listVouchers[checkList].differenceToApply);
- SetFindVoucher(2);
+ // Mencari voucher dalam listVoucherShippings
+ let checkList = listVoucherShippings?.findIndex(
+ (voucher) => voucher.code === addNewLine.code
+ );
+
+ if (checkList >= 0) {
+ if (listVoucherShippings[checkList].canApply) {
+ ToggleSwitch(addNewLine.code); // Perbaikan: Gunakan code voucher yang benar
+ SetCodeVoucher(null);
+ } else {
+ SetSelisihHargaCode(
+ listVoucherShippings[checkList].differenceToApply
+ );
+ SetFindVoucher(2);
+ }
+ return; // Hentikan eksekusi lebih lanjut pada iterasi ini
+ }
+
+ // Memeriksa apakah subtotal memenuhi syarat minimal pembelian
+ if (cartCheckout?.subtotal < addNewLine.minPurchaseAmount) {
+ SetSelisihHargaCode(
+ currencyFormat(
+ addNewLine.minPurchaseAmount - cartCheckout?.subtotal
+ )
+ );
+ SetFindVoucher(2);
+ return;
+ } else {
+ SetFindVoucher(3);
+ SetButtonTerapkan(true);
+ }
+
+ // Tambahkan voucher ke list pengiriman dan set voucher aktif pengiriman
+ SetListVoucherShipping((prevList) => [addNewLine, ...prevList]);
+ if (addNewLine.canApply) {
+ setActiveVoucherShipping(addNewLine.code);
+ }
}
- return;
- }
- if (cartCheckout?.subtotal < addNewLine.minPurchaseAmount) {
- SetSelisihHargaCode(
- currencyFormat(addNewLine.minPurchaseAmount - cartCheckout?.subtotal)
- );
- SetFindVoucher(2);
- return;
- } else {
- SetFindVoucher(3);
- SetButtonTerapkan(true);
- }
- SetListVoucher((prevList) => [addNewLine, ...prevList]);
- SetActiveVoucher(addNewLine.code);
+ });
+
+ // let addNewLine = dataVoucher[0];
};
useEffect(() => {
@@ -187,7 +242,7 @@ const Checkout = () => {
}, [bottomPopup]);
useEffect(() => {
- voucher();
+ // voucher();
const loadExpedisi = async () => {
let dataExpedisi = await ExpedisiList();
dataExpedisi = dataExpedisi.map((expedisi) => ({
@@ -210,13 +265,23 @@ const Checkout = () => {
};
}, []);
- const hitungDiscountVoucher = (code) => {
- let dataVoucherIndex = listVouchers.findIndex(
- (voucher) => voucher.code == code
- );
- let dataActiveVoucher = listVouchers[dataVoucherIndex];
+ const hitungDiscountVoucher = (code, source) => {
+ let countDiscount = 0;
+ if (source === 'voucher') {
+ let dataVoucherIndex = listVouchers.findIndex(
+ (voucher) => voucher.code == code
+ );
+ let dataActiveVoucher = listVouchers[dataVoucherIndex];
+
+ countDiscount = dataActiveVoucher.discountVoucher;
+ } else {
+ let dataVoucherIndex = listVoucherShippings.findIndex(
+ (voucher) => voucher.code == code
+ );
+ let dataActiveVoucher = listVoucherShippings[dataVoucherIndex];
- let countDiscount = dataActiveVoucher.discountVoucher;
+ countDiscount = dataActiveVoucher.discountVoucher;
+ }
/*if (dataActiveVoucher.discountType === 'percentage') {
countDiscount = cartCheckout?.subtotal * (dataActiveVoucher.discountAmount / 100)
@@ -233,14 +298,24 @@ const Checkout = () => {
return countDiscount;
};
- useEffect(() => {
- if (!listVouchers) return;
- if (!activeVoucher) return;
+ // useEffect(() => {
+ // if (!listVouchers) return;
+ // if (!activeVoucher) return;
+
+ // console.log('voucher')
+ // const countDiscount = hitungDiscountVoucher(activeVoucher, 'voucher');
+
+ // SetDiscountVoucher(countDiscount);
+ // }, [activeVoucher, listVouchers]);
- const countDiscount = hitungDiscountVoucher(activeVoucher);
+ // useEffect(() => {
+ // if (!listVoucherShippings) return;
+ // if (!activeVoucherShipping) return;
- SetDiscountVoucher(countDiscount);
- }, [activeVoucher, listVouchers]);
+ // const countDiscount = hitungDiscountVoucher(activeVoucherShipping, 'voucher_shipping');
+
+ // SetDiscountVoucherOngkir(countDiscount);
+ // }, [activeVoucherShipping, listVoucherShippings]);
useEffect(() => {
if (qVoucher === 'PASTIHEMAT' && listVouchers) {
@@ -335,7 +410,7 @@ const Checkout = () => {
Math.round(parseInt(finalShippingAmt * 1.1) / 1000) * 1000;
const finalGT = GT < 0 ? 0 : GT;
setGrandTotal(finalGT);
- }, [biayaKirim, cartCheckout?.grandTotal, activeVoucher]);
+ }, [biayaKirim, cartCheckout?.grandTotal, activeVoucher, activeVoucherShipping]);
const checkout = async () => {
const file = poFile.current.files[0];
@@ -389,14 +464,22 @@ const Checkout = () => {
if (typeof file !== 'undefined') data.po_file = await getFileBase64(file);
const isCheckouted = await checkoutApi({ data });
+
if (!isCheckouted?.id) {
toast.error('Gagal melakukan transaksi, terjadi kesalahan internal');
return;
- }
-
- gtagPurchase(products, biayaKirim, isCheckouted.name);
+ } else {
+ gtagPurchase(products, biayaKirim, isCheckouted.name);
+
+ gtag('event', 'conversion', {
+ send_to: 'AW-954540379/nDymCL3BhaQYENvClMcD',
+ value:
+ cartCheckout?.grandTotal +
+ Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000,
+ currency: 'IDR',
+ transaction_id: isCheckouted.id,
+ });
- const midtrans = async () => {
for (const product of products) deleteItemCart({ productId: product.id });
if (grandTotal > 0) {
const payment = await axios.post(
@@ -412,17 +495,25 @@ const Checkout = () => {
'-'
)}`;
}
- };
+ }
- gtag('event', 'conversion', {
- send_to: 'AW-954540379/nDymCL3BhaQYENvClMcD',
- value:
- cartCheckout?.grandTotal +
- Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000,
- currency: 'IDR',
- transaction_id: isCheckouted.id,
- event_callback: midtrans,
- });
+ /* const midtrans = async () => {
+ for (const product of products) deleteItemCart({ productId: product.id });
+ if (grandTotal > 0) {
+ const payment = await axios.post(
+ `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/midtrans-payment?transactionId=${isCheckouted.id}`
+ );
+ setIsLoading(false);
+ window.location.href = payment.data.redirectUrl;
+ } else {
+ window.location.href = `${
+ process.env.NEXT_PUBLIC_SELF_HOST
+ }/shop/checkout/success?order_id=${isCheckouted.name.replace(
+ /\//g,
+ '-'
+ )}`;
+ }
+ };*/
};
const handlingActivateCode = async () => {
@@ -483,6 +574,10 @@ const Checkout = () => {
const finalShippingAmt = biayaKirim - discShippingAmt;
+ const totalDiscountVoucher =
+ cartCheckout?.discountVoucher +
+ (cartCheckout?.discountVoucherShipping || 0);
+
return (
<>
<BottomPopup
@@ -593,10 +688,25 @@ const Checkout = () => {
)}
<hr className='mt-8 mb-4 border-gray_r-8' />
+ {/* {!loadingVoucher &&
+ listVouchers?.length === 1 &&
+ listVoucherShippings?.length === 1}
+ {
+ <div className='flex items-center justify-center mt-4 mb-4'>
+ <div className='text-center'>
+ <h1 className='font-bold mb-4'>Tidak ada voucher tersedia</h1>
+ <p className='text-gray-500'>
+ Maaf, saat ini tidak ada voucher yang tersedia.
+ </p>
+ </div>
+ </div>
+ } */}
{listVoucherShippings && listVoucherShippings?.length > 0 && (
<div>
- <h3 className='font-semibold mb-4'>Promo Gratis Ongkir</h3>
+ <h3 className='font-semibold mb-4'>
+ Promo Extra Potongan Ongkir
+ </h3>
{listVoucherShippings?.map((item) => (
<div key={item.id} className='relative'>
<div
@@ -731,16 +841,7 @@ const Checkout = () => {
<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'>
- <h1 className='font-bold mb-4'>Tidak ada voucher tersedia</h1>
- <p className='text-gray-500'>
- Maaf, saat ini tidak ada voucher yang tersedia.
- </p>
- </div>
- </div>
- ) : (
+ {!loadingVoucher && listVouchers?.length > 0 && (
<h3 className='font-semibold mb-4'>
Promo Khusus Untuk {auth?.name}
</h3>
@@ -932,7 +1033,7 @@ const Checkout = () => {
</div>
<span className='leading-5'>
Jika mengalami kesulitan dalam melakukan pembelian di website
- Indoteknik. Hubungi kami disini
+ Indoteknik. <a href={whatsappUrl()}>Hubungi kami disini</a>
</span>
</Alert>
</div>
@@ -1004,7 +1105,12 @@ const Checkout = () => {
<div className='p-4 flex flex-col gap-y-4'>
{!!products &&
snakecaseKeys(products).map((item, index) => (
- <CartItem key={index} item={item} editable={false} />
+ <CartItem
+ key={index}
+ item={item}
+ editable={false}
+ selfPicking={selectedExpedisi === '1,32' ? true : false}
+ />
))}
</div>
@@ -1067,7 +1173,7 @@ const Checkout = () => {
<div className='flex gap-x-2 justify-between'>
<div className='text-gray_r-11'>Diskon Voucher</div>
<div className='text-danger-500'>
- - {currencyFormat(discountVoucher)}
+ - {currencyFormat(cartCheckout?.discountVoucher)}
</div>
</div>
)}
@@ -1083,7 +1189,7 @@ const Checkout = () => {
<div className='text-gray_r-11'>
Biaya Kirim <p className='text-xs mt-1'>{etdFix}</p>
</div>
- <div>{currencyFormat(biayaKirim)}</div>
+ <div>{currencyFormat(Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000)}</div>
</div>
{activeVoucherShipping && voucherShippingAmt && (
<div className='flex gap-x-2 justify-between'>
@@ -1135,10 +1241,10 @@ const Checkout = () => {
className='object-contain object-center h-6 rounded-md'
/>
</span>
- {activeVoucher ? (
+ {activeVoucher || activeVoucherShipping ? (
<div className=''>
<div className='text-left text-sm text-black font-semibold'>
- Potongan Senilai {currencyFormat(discountVoucher)}
+ Potongan Senilai {currencyFormat(totalDiscountVoucher)}
</div>
<div className='text-left mt-1 text-green-600 text-xs'>
Voucher berhasil digunakan
@@ -1295,7 +1401,12 @@ const Checkout = () => {
<div className='flex flex-col gap-y-8 border-t border-gray-300 pt-8'>
{!!products &&
snakecaseKeys(products).map((item, index) => (
- <CartItem key={index} item={item} editable={false} />
+ <CartItem
+ key={index}
+ item={item}
+ editable={false}
+ selfPicking={selectedExpedisi === '1,32' ? true : false}
+ />
))}
</div>
</div>
@@ -1362,7 +1473,7 @@ const Checkout = () => {
<div className='flex gap-x-2 justify-between'>
<div className='text-gray_r-11'>Diskon Voucher</div>
<div className='text-danger-500'>
- - {currencyFormat(discountVoucher)}
+ - {currencyFormat(cartCheckout?.discountVoucher)}
</div>
</div>
)}
@@ -1379,7 +1490,7 @@ const Checkout = () => {
Biaya Kirim
<p className='text-xs mt-1'>{etdFix}</p>
</div>
- <div>{currencyFormat(biayaKirim)}</div>
+ <div>{currencyFormat(Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000) }</div>
</div>
{activeVoucherShipping && voucherShippingAmt && (
<div className='flex gap-x-2 justify-between'>
@@ -1431,10 +1542,10 @@ const Checkout = () => {
className='object-contain object-center h-6 w-full rounded-md'
/>
</span>
- {activeVoucher ? (
+ {activeVoucher || activeVoucherShipping ? (
<div className=''>
<div className='text-left text-sm text-black font-semibold'>
- Hemat {currencyFormat(discountVoucher)}
+ Hemat {currencyFormat(totalDiscountVoucher)}
</div>
<div className='text-left mt-1 text-green-600 text-xs'>
Voucher berhasil digunakan
diff --git a/src/lib/home/api/CategoryPilihanApi.js b/src/lib/home/api/CategoryPilihanApi.js
new file mode 100644
index 00000000..8a0b38d3
--- /dev/null
+++ b/src/lib/home/api/CategoryPilihanApi.js
@@ -0,0 +1,8 @@
+import odooApi from '@/core/api/odooApi'
+
+const categoryPilihanApi = async () => {
+ const dataCategoryPilihan = await odooApi('GET', '/api/v1/lob_homepage')
+ return dataCategoryPilihan
+}
+
+export default categoryPilihanApi
diff --git a/src/lib/home/api/categoryManagementApi.js b/src/lib/home/api/categoryManagementApi.js
new file mode 100644
index 00000000..b70d60ce
--- /dev/null
+++ b/src/lib/home/api/categoryManagementApi.js
@@ -0,0 +1,8 @@
+import odooApi from '@/core/api/odooApi'
+
+const categoryManagementApi = async () => {
+ const dataCategoryManagement = await odooApi('GET', '/api/v1/categories_management')
+ return dataCategoryManagement
+}
+
+export default categoryManagementApi
diff --git a/src/lib/home/components/CategoryDynamic.jsx b/src/lib/home/components/CategoryDynamic.jsx
new file mode 100644
index 00000000..14015e28
--- /dev/null
+++ b/src/lib/home/components/CategoryDynamic.jsx
@@ -0,0 +1,132 @@
+import React, { useEffect, useState, useCallback } from 'react';
+import useCategoryManagement from '../hooks/useCategoryManagement';
+import {fetchPopulerProductSolr} from '../api/categoryManagementApi'
+import NextImage from 'next/image';
+import Link from "next/link";
+import { createSlug } from '@/core/utils/slug';
+import odooApi from '@/core/api/odooApi';
+import { Skeleton } from '@chakra-ui/react';
+import { Swiper, SwiperSlide } from 'swiper/react';
+import 'swiper/css';
+import 'swiper/css/navigation';
+import 'swiper/css/pagination';
+import { Navigation, Pagination, Autoplay } from 'swiper';
+
+const CategoryDynamic = () => {
+
+ const { categoryManagement } = useCategoryManagement();
+ // const [categoryData, setCategoryData] = useState({});
+ // const [subCategoryData, setSubCategoryData] = useState({});
+
+ // useEffect(() => {
+ // const fetchCategoryData = async () => {
+ // if (categoryManagement && categoryManagement.data) {
+ // const updatedCategoryData = {};
+ // const updatedSubCategoryData = {};
+
+ // for (const category of categoryManagement.data) {
+ // const countLevel1 = await odooApi('GET', `/api/v1/category/numFound?parent_id=${category.categoryIdI}`);
+
+ // updatedCategoryData[category.categoryIdI] = countLevel1?.numFound;
+
+ // for (const subCategory of countLevel1?.children) {
+ // updatedSubCategoryData[subCategory.id] = subCategory?.numFound;
+ // }
+ // }
+
+ // setCategoryData(updatedCategoryData);
+ // setSubCategoryData(updatedSubCategoryData);
+ // }
+ // };
+
+ // fetchCategoryData();
+ // }, [categoryManagement.isLoading]);
+
+ const swiperBanner = {
+ modules: [Pagination, ],
+ classNames:'mySwiper',
+ slidesPerView: 3,
+ spaceBetween:10,
+ pagination: {
+ dynamicBullets: true,
+ clickable: true,
+ }
+ };
+
+ return (
+ <div>
+ {categoryManagement && categoryManagement.data?.map((category) => {
+ // const countLevel1 = categoryData[category.categoryIdI] || 0;
+
+ return (
+ <Skeleton key={category.id} isLoaded={categoryManagement}>
+ <div key={category.id}>
+ <div className='bagian-judul flex flex-row justify-start items-center gap-3 mb-4 mt-4'>
+ <div className='font-semibold sm:text-h-lg mr-2'>{category.name}</div>
+ {/* <Skeleton isLoaded={countLevel1 != 0}>
+ <p className={`text-gray_r-10 text-sm`}>{countLevel1} Produk tersedia</p>
+ </Skeleton> */}
+ <Link href={createSlug('/shop/category/', category?.name, category?.categoryIdI)} className="!text-red-500 font-semibold">Lihat Semua</Link>
+ </div>
+
+ {/* Swiper for SubCategories */}
+ <Swiper {...swiperBanner}
+ >
+ {category.categories.map((subCategory) => {
+ // const countLevel2 = subCategoryData[subCategory.idLevel2] || 0;
+
+ return (
+ <SwiperSlide key={subCategory.id}>
+ <div className='border rounded justify-start items-start '>
+ <div className='p-3'>
+ <div className='flex flex-row border rounded mb-2 justify-start items-center'>
+ <NextImage
+ src={subCategory.image ? subCategory.image : "/images/noimage.jpeg"}
+ alt={subCategory.name}
+ width={90}
+ height={30}
+ className='object-fit p-4'
+ />
+ <div className='bagian-judul flex flex-col justify-center items-start gap-2 ml-2'>
+ <div className='font-semibold text-lg mr-2'>{subCategory?.name}</div>
+ {/* <Skeleton isLoaded={countLevel2 != 0}>
+ <p className={`text-gray_r-10 text-sm`}>
+ {countLevel2} Produk tersedia
+ </p>
+ </Skeleton> */}
+ <Link href={createSlug('/shop/category/', subCategory?.name, subCategory?.idLevel2)} className="!text-red-500 font-semibold">Lihat Semua</Link>
+ </div>
+ </div>
+ <div className='grid grid-cols-2 gap-2 overflow-y-auto max-h-[240px] min-h-[240px] content-start'>
+ {subCategory.childFrontendIdI.map((childCategory) => (
+ <div key={childCategory.id} className=''>
+ <Link href={createSlug('/shop/category/', childCategory?.name, childCategory?.idLevel3)} className="flex flex-row gap-2 border rounded group hover:border-red-500">
+ <NextImage
+ src={childCategory.image ? childCategory.image : "/images/noimage.jpeg"}
+ alt={childCategory.name}
+ className='p-2 ml-1'
+ width={40}
+ height={40}
+ />
+ <div className='bagian-judul flex flex-col justify-center items-center gap-2 break-words line-clamp-2 group-hover:text-red-500'>
+ <div className='font-semibold line-clamp-2 group-hover:text-red-500 text-sm mr-2'>{childCategory.name}</div>
+ </div>
+ </Link>
+ </div>
+ ))}
+ </div>
+ </div>
+ </div>
+ </SwiperSlide>
+ );
+ })}
+ </Swiper>
+ </div>
+ </Skeleton>
+ );
+ })}
+ </div>
+ );
+};
+
+export default CategoryDynamic;
diff --git a/src/lib/home/components/CategoryDynamicMobile.jsx b/src/lib/home/components/CategoryDynamicMobile.jsx
new file mode 100644
index 00000000..2877a5a7
--- /dev/null
+++ b/src/lib/home/components/CategoryDynamicMobile.jsx
@@ -0,0 +1,101 @@
+import React, { useEffect, useState } from 'react';
+import useCategoryManagement from '../hooks/useCategoryManagement';
+import NextImage from 'next/image';
+import Link from "next/link";
+import { createSlug } from '@/core/utils/slug';
+import { Swiper, SwiperSlide } from 'swiper/react';
+import 'swiper/css';
+
+const CategoryDynamicMobile = () => {
+ const { categoryManagement } = useCategoryManagement()
+ const [selectedCategory, setSelectedCategory] = useState({});
+
+ useEffect(() => {
+ const loadPromo = async () => {
+ try {
+ if (categoryManagement.data?.length > 0) {
+ const initialSelections = categoryManagement.data.reduce((acc, category) => {
+ if (category.categories.length > 0) {
+ acc[category.id] = category.categories[0].idLevel2;
+ }
+ return acc;
+ }, {});
+ setSelectedCategory(initialSelections);
+ }
+ } catch (loadError) {
+ // console.error("Error loading promo items:", loadError);
+ }
+ };
+
+ loadPromo();
+ }, [categoryManagement.data]);
+
+ const handleCategoryLevel2Click = (categoryIdI, idLevel2) => {
+ setSelectedCategory(prev => ({
+ ...prev,
+ [categoryIdI]: idLevel2
+ }));
+ };
+
+ return (
+ <div className='p-4'>
+ {categoryManagement.data && categoryManagement.data.map((category) => (
+ <div key={category.id}>
+ <div className='bagian-judul flex flex-row justify-between items-center gap-3 mb-4 mt-4'>
+ <div className='font-semibold sm:text-h-sm mr-2'>{category.name}</div>
+ <Link href={createSlug('/shop/category/', category?.name, category?.categoryIdI)} className="!text-red-500 font-semibold text-sm">Lihat Semua</Link>
+ </div>
+ <Swiper slidesPerView={2.3} spaceBetween={10}>
+ {category.categories.map((index) => (
+ <SwiperSlide key={index.id}>
+ <div
+ onClick={() => handleCategoryLevel2Click(category.id, index?.idLevel2)}
+ className={`border flex justify-start items-center max-w-48 max-h-16 rounded ${selectedCategory[category.id] === index?.idLevel2 ? 'bg-red-50 border-red-500 text-red-500' : 'border-gray-200 text-gray-900'}`}
+ >
+ <div className='p-1 flex justify-start items-center'>
+ <div className='flex flex-row justify-center items-center'>
+ <NextImage
+ src={index.image ? index.image : "/images/noimage.jpeg"}
+ alt={index.name}
+ width={30}
+ height={30}
+ className=''
+ />
+ <div className='bagian-judul flex flex-col justify-center items-start gap-1 ml-2'>
+ <div className='font-semibold text-[10px] line-clamp-1'>{index.name}</div>
+ </div>
+ </div>
+ </div>
+ </div>
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ <div className='p-3 mt-2 border'>
+ <div className='grid grid-cols-2 gap-2 overflow-y-auto max-h-[240px]'>
+ {category.categories.map((index) => (
+ selectedCategory[category.id] === index?.idLevel2 && index.childFrontendIdI.map((x) => (
+ <div key={x.id}>
+ <Link href={createSlug('/shop/category/', x?.name, x?.idLevel3)} className="flex flex-row gap-1 border rounded group hover:border-red-500">
+ <NextImage
+ src={x.image ? x.image : "/images/noimage.jpeg"}
+ alt={x.name}
+ width={40}
+ height={40}
+ className='p-2'
+ />
+ <div className='bagian-judul flex flex-col justify-center items-start gap-1 break-words line-clamp-2 group-hover:text-red-500'>
+ <div className='font-semibold line-clamp-2 group-hover:text-red-500 text-[10px]'>{x.name}</div>
+ </div>
+ </Link>
+ </div>
+ ))
+ ))}
+ </div>
+ </div>
+ </div>
+ ))}
+ </div>
+ );
+};
+
+export default CategoryDynamicMobile;
diff --git a/src/lib/home/components/CategoryPilihan.jsx b/src/lib/home/components/CategoryPilihan.jsx
new file mode 100644
index 00000000..6dbf771e
--- /dev/null
+++ b/src/lib/home/components/CategoryPilihan.jsx
@@ -0,0 +1,123 @@
+import Image from 'next/image'
+import useCategoryHome from '../hooks/useCategoryHome'
+import Link from '@/core/components/elements/Link/Link'
+import { createSlug } from '@/core/utils/slug'
+import { useEffect, useState } from 'react';
+import { bannerApi } from '../../../api/bannerApi';
+const { useQuery } = require('react-query')
+import { HeroBannerSkeleton } from '../../../components/skeleton/BannerSkeleton';
+import useCategoryPilihan from '../hooks/useCategoryPilihan';
+import useDevice from '@/core/hooks/useDevice'
+import { Swiper, SwiperSlide } from 'swiper/react';
+import 'swiper/css';
+
+const CategoryPilihan = ({ id, categories }) => {
+ const { isDesktop, isMobile } = useDevice()
+ const { categoryPilihan } = useCategoryPilihan();
+ const heroBanner = useQuery('categoryPilihan', bannerApi({ type: 'banner-category-list' }));
+ return (
+ categoryPilihan.length > 0 && (
+ <section>
+ {isDesktop && (
+ <div>
+ <div className='flex flex-row items-center mb-4'>
+ <div className='font-semibold sm:text-h-lg mr-2'>LOB Kategori Pilihan</div>
+ <p className='text-gray_r-10 text-sm'>200 Rb+ Produk Unggulan & 800+ Brand Rekomendasi tersedia!</p>
+ </div>
+ {heroBanner.data &&
+ heroBanner.data?.length > 0 && (
+ <div className='flex w-full h-full justify-center mb-4 bg-cover bg-center'>
+ <Link key={heroBanner.data[0].id} href={heroBanner.data[0].url}>
+ <Image
+ width={1260}
+ height={170}
+ quality={100}
+ src={heroBanner.data[0].image}
+ alt={heroBanner.data[0].name}
+ className='h-full object-cover w-full'
+ />
+ </Link>
+ </div>
+ )}
+ <div className="group/item grid grid-cols-6 gap-y-2 w-full h-full col-span-2 ">
+ {categoryPilihan?.data?.map((category) => (
+ <div key={category.id} className="KartuInti h-48 w-60 max-w-sm lg:max-w-full flex flex-col border-[1px] border-gray-200 relative group">
+ <div className='KartuB absolute h-48 w-60 inset-0 flex items-center justify-center '>
+ <div className="group/edit flex items-center justify-end h-48 w-60 flex-col group-hover/item:visible">
+ <div className=' h-36 flex justify-end items-end'>
+ <Image className='group-hover:scale-105 transition-transform duration-300 ' src={category?.image? category?.image : '/images/noimage.jpeg'} width={120} height={120} alt={category?.name} />
+ </div>
+ <h2 className="text-gray-700 content-center h-12 border-t-[1px] px-1 w-60 border-gray-200 font-normal text-sm text-center">{category?.industries}</h2>
+ </div>
+ </div>
+ <div className='KartuA relative inset-0 flex h-36 w-60 items-center justify-center opacity-0 group-hover:opacity-75 group-hover:bg-[#E20613] transition-opacity '>
+ <Link
+ href={createSlug('/shop/lob/', category?.industries, category?.id)}
+ className='category-mega-box__parent text-white rounded-lg'
+ >
+ Lihat semua
+ </Link>
+ </div>
+ </div>
+ ))}
+ </div>
+ </div>
+ )}
+ {isMobile && (
+ <div className='p-4'>
+ <div className='flex flex-row items-center mb-4'>
+ <div className='font-semibold sm:text-h-md mr-2'>LOB Kategori Pilihan</div>
+ {/* <p className='text-gray_r-10 text-sm'>200 Rb+ Produk Unggulan & 800+ Brand Rekomendasi tersedia!</p> */}
+ </div>
+ <div className='flex'>
+ {heroBanner.data &&
+ heroBanner.data?.length > 0 && (
+ <div className=' object-fill '>
+ <Link key={heroBanner.data[0].id} href={heroBanner.data[0].url}>
+ <Image
+ width={439}
+ height={150}
+ quality={100}
+ src={heroBanner.data[0].image}
+ alt={heroBanner.data[0].name}
+ className='object-cover'
+ />
+ </Link>
+ </div>
+ )}
+ </div>
+ <Swiper slidesPerView={2.1} spaceBetween={10}>
+ {categoryPilihan?.data?.map((category) => (
+ <SwiperSlide key={category.id}>
+ <div key={category.id} className="KartuInti mt-2 h-48 w-48 max-w-sm lg:max-w-full flex flex-col border-[1px] border-gray-200 relative group">
+ <div className='KartuB absolute h-48 w-48 inset-0 flex items-center justify-center '>
+ <div className="group/edit flex items-center justify-end h-48 w-48 flex-col group-hover/item:visible">
+ <div className=' h-36 flex justify-end items-end'>
+ <Image className='group-hover:scale-105 transition-transform duration-300 ' src={category?.image? category?.image : '/images/noimage.jpeg'} width={120} height={120} alt={category?.name} />
+ </div>
+ <h2 className="text-gray-700 content-center h-12 border-t-[1px] px-1 w-48 border-gray-200 font-normal text-sm text-center">{category?.industries}</h2>
+ </div>
+ </div>
+ <div className='KartuA relative inset-0 flex h-36 w-48 items-center justify-center opacity-0 group-hover:opacity-75 group-hover:bg-[#E20613] transition-opacity '>
+ <Link
+ href={createSlug('/shop/lob/', category?.industries, category?.id)}
+ className='category-mega-box__parent text-white rounded-lg'
+ >
+ Lihat semua
+ </Link>
+ </div>
+ </div>
+ </SwiperSlide>
+ ))}
+
+ </Swiper>
+
+ </div>
+ )}
+ </section>
+
+ )
+ )
+}
+
+export default CategoryPilihan
diff --git a/src/lib/home/components/PreferredBrand.jsx b/src/lib/home/components/PreferredBrand.jsx
index 6b64a444..b30fa5c9 100644
--- a/src/lib/home/components/PreferredBrand.jsx
+++ b/src/lib/home/components/PreferredBrand.jsx
@@ -1,4 +1,5 @@
import { Swiper, SwiperSlide } from 'swiper/react'
+import { Navigation, Pagination, Autoplay } from 'swiper';
import { useCallback, useEffect, useState } from 'react'
import usePreferredBrand from '../hooks/usePreferredBrand'
import PreferredBrandSkeleton from './Skeleton/PreferredBrandSkeleton'
@@ -38,7 +39,23 @@ const PreferredBrand = () => {
const { preferredBrands } = usePreferredBrand(query)
const { isMobile, isDesktop } = useDevice()
-
+ const swiperBanner = {
+ modules:[Navigation, Pagination, Autoplay],
+ autoplay: {
+ delay: 4000,
+ disableOnInteraction: false
+ },
+ loop: true,
+ className: 'h-[70px] md:h-[100px] w-full',
+ slidesPerView: isMobile ? 4 : 8,
+ spaceBetween: isMobile ? 12 : 0,
+ pagination: {
+ dynamicBullets: true,
+ dynamicMainBullets: isMobile ? 6 : 8,
+ clickable: true,
+ }
+ }
+ const preferredBrandsData = manufactures ? manufactures.slice(0, 20) : []
return (
<div className='px-4 sm:px-0'>
<div className='flex justify-between items-center mb-4'>
@@ -48,24 +65,21 @@ const PreferredBrand = () => {
Lihat Semua
</Link>
)}
- {isMobile && (
- <Link href='/shop/brands' className='!text-red-500 font-semibold sm:text-h-sm'>
- Lihat Semua
- </Link>
+ </div>
+ <div className=''>
+ {manufactures.isLoading && <PreferredBrandSkeleton />}
+ {!manufactures.isLoading && (
+ <Swiper {...swiperBanner}>
+ {preferredBrandsData.map((manufacture) => (
+ <SwiperSlide key={manufacture.id}>
+ <BrandCard brand={manufacture} />
+ </SwiperSlide>
+ ))}
+ </Swiper>
)}
</div>
- {manufactures.isLoading && <PreferredBrandSkeleton />}
- {!manufactures.isLoading && (
- <Swiper slidesPerView={isMobile ? 3.5 : 7.5} spaceBetween={isMobile ? 12 : 24} freeMode>
- {manufactures.map((manufacture) => (
- <SwiperSlide key={manufacture.id}>
- <BrandCard brand={manufacture} />
- </SwiperSlide>
- ))}
- </Swiper>
- )}
</div>
)
}
-export default PreferredBrand
+export default PreferredBrand \ No newline at end of file
diff --git a/src/lib/home/hooks/useCategoryManagement.js b/src/lib/home/hooks/useCategoryManagement.js
new file mode 100644
index 00000000..c1dda585
--- /dev/null
+++ b/src/lib/home/hooks/useCategoryManagement.js
@@ -0,0 +1,13 @@
+import categoryManagementApi from '../api/categoryManagementApi'
+import { useQuery } from 'react-query'
+
+const useCategoryManagement = () => {
+ const fetchCategoryManagement = async () => await categoryManagementApi()
+ const { isLoading, data } = useQuery('categoryManagementApi', fetchCategoryManagement)
+
+ return {
+ categoryManagement: { data, isLoading }
+ }
+}
+
+export default useCategoryManagement \ No newline at end of file
diff --git a/src/lib/home/hooks/useCategoryPilihan.js b/src/lib/home/hooks/useCategoryPilihan.js
new file mode 100644
index 00000000..12a86f7e
--- /dev/null
+++ b/src/lib/home/hooks/useCategoryPilihan.js
@@ -0,0 +1,13 @@
+import categoryPilihanApi from '../api/CategoryPilihanApi'
+import { useQuery } from 'react-query'
+
+const useCategoryPilihan = () => {
+ const fetchCategoryPilihan = async () => await categoryPilihanApi()
+ const { isLoading, data } = useQuery('categoryPilihanApi', fetchCategoryPilihan)
+
+ return {
+ categoryPilihan: { data, isLoading }
+ }
+}
+
+export default useCategoryPilihan \ No newline at end of file
diff --git a/src/lib/lob/components/Breadcrumb.jsx b/src/lib/lob/components/Breadcrumb.jsx
new file mode 100644
index 00000000..5722fd39
--- /dev/null
+++ b/src/lib/lob/components/Breadcrumb.jsx
@@ -0,0 +1,55 @@
+import odooApi from '@/core/api/odooApi'
+import { createSlug } from '@/core/utils/slug'
+import {
+ Breadcrumb as ChakraBreadcrumb,
+ BreadcrumbItem,
+ BreadcrumbLink,
+ Skeleton
+} from '@chakra-ui/react'
+import Link from 'next/link'
+import React from 'react'
+import { useQuery } from 'react-query'
+
+/**
+ * Render a breadcrumb component.
+ *
+ * @param {object} categoryId - The ID of the category.
+ * @return {JSX.Element} The breadcrumb component.
+ */
+const Breadcrumb = ({ categoryId }) => {
+ const breadcrumbs = useQuery(
+ `lob-breadcrumbs/${categoryId}`,
+ async () => await odooApi('GET', `/api/v1/lob_homepage/${categoryId}/category_id`)
+ )
+ return (
+ <div className='container mx-auto py-4 md:py-6'>
+ <Skeleton isLoaded={!breadcrumbs.isLoading} className='w-2/3'>
+ <ChakraBreadcrumb>
+ <BreadcrumbItem>
+ <BreadcrumbLink as={Link} href='/' className='!text-danger-500 whitespace-nowrap'>
+ Home
+ </BreadcrumbLink>
+ </BreadcrumbItem>
+
+ {breadcrumbs?.data?.map((category, index) => (
+ <BreadcrumbItem key={index} isCurrentPage={index === breadcrumbs.data.length - 1}>
+ {index === breadcrumbs.data.length - 1 ? (
+ <BreadcrumbLink className='whitespace-nowrap'>{category.industries}</BreadcrumbLink>
+ ) : (
+ <BreadcrumbLink
+ as={Link}
+ href={createSlug('/shop/lob/', category.industries, category.id)}
+ className='!text-danger-500 whitespace-nowrap'
+ >
+ {category.industries}
+ </BreadcrumbLink>
+ )}
+ </BreadcrumbItem>
+ ))}
+ </ChakraBreadcrumb>
+ </Skeleton>
+ </div>
+ )
+}
+
+export default Breadcrumb
diff --git a/src/lib/product/api/productSearchApi.js b/src/lib/product/api/productSearchApi.js
index 1626b7b7..8ff8e57d 100644
--- a/src/lib/product/api/productSearchApi.js
+++ b/src/lib/product/api/productSearchApi.js
@@ -1,7 +1,7 @@
import _ from 'lodash-contrib'
import axios from 'axios'
-const productSearchApi = async ({ query, operation = 'AND' }) => {
+const productSearchApi = async ({ query, operation = 'OR' }) => {
const dataProductSearch = await axios(
`${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/search?${query}&operation=${operation}`
)
diff --git a/src/lib/product/components/CategorySection.jsx b/src/lib/product/components/CategorySection.jsx
new file mode 100644
index 00000000..a287fa78
--- /dev/null
+++ b/src/lib/product/components/CategorySection.jsx
@@ -0,0 +1,105 @@
+import Image from "next/image";
+import Link from 'next/link';
+import { createSlug } from '@/core/utils/slug';
+import useDevice from '@/core/hooks/useDevice';
+import { Swiper, SwiperSlide } from 'swiper/react';
+import 'swiper/css';
+import { useQuery } from 'react-query';
+import { useRouter } from 'next/router';
+import {
+ ChevronDownIcon,
+ ChevronUpIcon, // Import ChevronUpIcon for toggling
+ DocumentCheckIcon,
+ HeartIcon,
+} from '@heroicons/react/24/outline';
+import { useState } from 'react'; // Import useState
+import { getIdFromSlug } from '@/core/utils/slug'
+
+const CategorySection = ({ categories }) => {
+ const { isDesktop, isMobile } = useDevice();
+ const [isOpenCategory, setIsOpenCategory] = useState(false); // State to manage category visibility
+
+ const handleToggleCategories = () => {
+ setIsOpenCategory(!isOpenCategory);
+ };
+
+
+ const displayedCategories = isOpenCategory ? categories : categories.slice(0, 10);
+
+ return (
+ <section>
+ {isDesktop && (
+ <div className="group/item grid grid-cols-5 gap-y-2 gap-x-2 w-full h-full col-span-2 ">
+ {displayedCategories.map((category) => (
+ <Link href={createSlug('/shop/category/', category?.name, category?.id)} key={category?.id} passHref>
+ <div className="group transition-colors duration-300 ">
+ <div className="KartuInti h-12 w-26 max-w-sm lg:max-w-full flex flex-col border-[2px] border-gray-200 group-hover:border-red-400 rounded relative ">
+ <div className="flex items-center justify-start h-full px-1 flex-row ">
+ <Image className="h-full p-1" src={category?.image1920 ? category?.image1920 : '/images/noimage.jpeg'} width={56} height={48} alt={category?.name} />
+ <h2 className="text-gray-700 group-hover:text-[#E20613] line-clamp-2 content-center h-fit w-60 px-1 font-semibold text-sm text-start">{category?.name}</h2>
+ </div>
+ </div>
+ </div>
+ </Link>
+ ))}
+ </div>
+ )}
+ {isDesktop && categories.length > 10 && (
+ <div className="w-full flex justify-center mt-4">
+ <button
+ onClick={handleToggleCategories}
+ className="flex justify-end mt-4 text-red-500 font-bold px-4 py-2 rounded"
+ >
+ {isOpenCategory ? 'Sembunyikan' : 'Lihat semua'}
+ {isOpenCategory ? (
+ <ChevronUpIcon className="ml-auto w-5 font-bold" />
+ ) : (
+ <ChevronDownIcon className="ml-auto w-5 font-bold" />
+ )}
+ </button>
+ </div>
+ )}
+
+ {isMobile && (
+ <div className="py-4">
+ <Swiper slidesPerView={2.3} spaceBetween={10}>
+ {categories.map((category) => (
+ <SwiperSlide key={category?.id}>
+ <Link href={createSlug('/shop/category/', category?.name, category?.id)} passHref>
+ <div className="group transition-colors duration-300">
+ <div className="KartuInti min-h-16 max-h-16 w-26 max-w-sm lg:max-w-full flex flex-col border-[2px] border-gray-200 group-hover:bg-red-200 group-hover:border-red-400 rounded relative">
+ <div className="flex items-center justify-center h-full px-1 flex-row">
+ <Image
+ src={category?.image1920 ? category?.image1920 : '/images/noimage.jpeg'}
+ width={56}
+ height={48}
+ alt={category?.name}
+ className="p-3"
+ />
+ <h2 className="text-gray-700 group-hover:text-[#E20613] line-clamp-2 content-center h-fit w-60 px-1 font-semibold text-sm text-start">
+ {category?.name}
+ </h2>
+ </div>
+ </div>
+ </div>
+ </Link>
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ {/* {categories.length > 10 && (
+ <div className="w-full flex justify-end mt-4">
+ <button
+ onClick={handleToggleCategories}
+ className="flex justify-end mt-4 bg-red-500 text-white text-sm px-4 py-2 rounded"
+ >
+ {isOpenCategory ? 'Sembunyikan Semua' : 'Lihat Semua'}
+ </button>
+ </div>
+ )} */}
+ </div>
+ )}
+ </section>
+ )
+}
+
+export default CategorySection
diff --git a/src/lib/product/components/LobSectionCategory.jsx b/src/lib/product/components/LobSectionCategory.jsx
new file mode 100644
index 00000000..5cd467e9
--- /dev/null
+++ b/src/lib/product/components/LobSectionCategory.jsx
@@ -0,0 +1,80 @@
+import Image from "next/image";
+import Link from 'next/link';
+import { createSlug } from '@/core/utils/slug';
+import useDevice from '@/core/hooks/useDevice';
+import { Swiper, SwiperSlide } from 'swiper/react';
+import 'swiper/css';
+import { useQuery } from 'react-query';
+import { useRouter } from 'next/router';
+import {
+ ChevronDownIcon,
+ ChevronUpIcon, // Import ChevronUpIcon for toggling
+ DocumentCheckIcon,
+ HeartIcon,
+} from '@heroicons/react/24/outline';
+import { useState } from 'react'; // Import useState
+import { getIdFromSlug } from '@/core/utils/slug'
+
+const LobSectionCategory = ({ categories }) => {
+ const { isDesktop, isMobile } = useDevice();
+ const [isOpenCategory, setIsOpenCategory] = useState(false); // State to manage category visibility
+
+ const handleToggleCategories = () => {
+ setIsOpenCategory(!isOpenCategory);
+ };
+
+ const displayedCategories = categories[0]?.categoryIds;
+ return (
+ <section>
+ {isDesktop && (
+ <div className="group/item grid grid-flow-col gap-y-2 gap-x-4 w-full h-full">
+ {displayedCategories?.map((category) => (
+ <Link
+ href={createSlug('/shop/category/', category?.name, category?.id)}
+ key={category?.id}
+ passHref
+ className="block hover:scale-105 transition-transform duration-300 bg-cover bg-center h-[144px]"
+ style={{
+ backgroundImage: `url('${category?.image ? category?.image : 'https://erp.indoteknik.com/web/image?model=x_banner.banner&id=5&field=x_banner_image&unique=09202023100557'}')`,
+ }}
+ >
+ </Link>
+ ))}
+ </div>
+ )}
+
+ {isMobile && (
+ <div className="py-4">
+ <Swiper slidesPerView={1.2} spaceBetween={10}>
+ {displayedCategories?.map((category) => (
+ <SwiperSlide key={category?.id}>
+ <Link
+ href={createSlug('/shop/category/', category?.name, category?.id)}
+ key={category?.id}
+ passHref
+ className="block bg-cover bg-center h-[144px]"
+ style={{
+ backgroundImage: `url('${category?.image ? category?.image : 'https://erp.indoteknik.com/web/image?model=x_banner.banner&id=5&field=x_banner_image&unique=09202023100557'}')`,
+ }}
+ >
+ </Link>
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ {categories.length > 10 && (
+ <div className="w-full flex justify-end mt-4">
+ <button
+ onClick={handleToggleCategories}
+ className="flex justify-end mt-4 bg-red-500 text-white text-sm px-4 py-2 rounded"
+ >
+ {isOpenCategory ? 'Sembunyikan Semua' : 'Lihat Semua'}
+ </button>
+ </div>
+ )}
+ </div>
+ )}
+ </section>
+ )
+}
+
+export default LobSectionCategory
diff --git a/src/lib/product/components/ProductCard.jsx b/src/lib/product/components/ProductCard.jsx
index 35e2a665..22ce0533 100644
--- a/src/lib/product/components/ProductCard.jsx
+++ b/src/lib/product/components/ProductCard.jsx
@@ -146,13 +146,22 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
)}
</Link>
<div className='p-2 sm:p-3 pb-3 text-caption-2 sm:text-body-2 leading-5'>
- {product?.manufacture?.name ? (
- <Link href={URL.manufacture} className='mb-1'>
- {product.manufacture.name}
+ <div className='flex justify-between '>
+ {product?.manufacture?.name ? (
+ <Link href={URL.manufacture} className='mb-1 mt-1'>
+ {product.manufacture.name}
+ </Link>
+ ) : (
+ <div>-</div>
+ )}
+ {product?.isInBu && (
+ <Link href='/panduan-pick-up-service' className='group'>
+ <Image src='/images/PICKUP-NOW.png' className='group-hover:scale-105 transition-transform duration-200' alt='pickup now' width={90} height={12} />
</Link>
- ) : (
- <div>-</div>
- )}
+
+
+ )}
+ </div>
<Link
href={URL.product}
className={`mb-2 !text-gray_r-12 leading-6 block line-clamp-3 h-[64px]`}
@@ -292,9 +301,18 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
</div>
)}
{product?.manufacture?.name ? (
- <Link href={URL.manufacture} className='mb-1'>
+ <div className='flex justify-between'>
+ <Link href={URL.manufacture} className='mb-1'>
{product.manufacture.name}
</Link>
+ {/* {product?.is_in_bu && (
+ <div className='bg-red-500 rounded'>
+ <span className='p-[6px] text-xs text-white'>
+ Click & Pickup
+ </span>
+ </div>
+ )} */}
+ </div>
) : (
<div>-</div>
)}
diff --git a/src/lib/product/components/ProductFilterDesktop.jsx b/src/lib/product/components/ProductFilterDesktop.jsx
index a8073036..73fecab5 100644
--- a/src/lib/product/components/ProductFilterDesktop.jsx
+++ b/src/lib/product/components/ProductFilterDesktop.jsx
@@ -22,6 +22,7 @@ import { formatCurrency } from '@/core/utils/formatValue'
const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = null }) => {
+
const router = useRouter()
const { query } = router
const [order, setOrder] = useState(query?.orderBy)
@@ -107,7 +108,11 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu
const slug = Array.isArray(router.query.slug) ? router.query.slug[0] : router.query.slug;
if (slug) {
- router.push(`${prefixUrl}/${slug}?${params}`)
+ if(prefixUrl.includes('category') || prefixUrl.includes('lob')){
+ router.push(`${prefixUrl}?${params}`)
+ }else{
+ router.push(`${prefixUrl}/${slug}?${params}`)
+ }
} else {
router.push(`${prefixUrl}?${params}`)
}
diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx
index fb9017f4..ab55cae0 100644
--- a/src/lib/product/components/ProductSearch.jsx
+++ b/src/lib/product/components/ProductSearch.jsx
@@ -26,6 +26,10 @@ import ProductSearchSkeleton from './Skeleton/ProductSearchSkeleton';
import SideBanner from '~/modules/side-banner';
import FooterBanner from '~/modules/footer-banner';
+import CategorySection from './CategorySection';
+import LobSectionCategory from './LobSectionCategory';
+import { getIdFromSlug } from '@/core/utils/slug'
+import { data } from 'autoprefixer';
const ProductSearch = ({
query,
@@ -37,11 +41,108 @@ const ProductSearch = ({
const { page = 1 } = query;
const [q, setQ] = useState(query?.q || '*');
const [search, setSearch] = useState(query?.q || '*');
- const [limit, setLimit] = useState(query?.limit || 30);
+ const [limit, setLimit] = useState(router.query?.limit || 30);
const [orderBy, setOrderBy] = useState(router.query?.orderBy);
+ const [finalQuery, setFinalQuery] = useState({});
+ const [queryFinal, setQueryFinal] = useState({});
+ const [dataCategoriesProduct, setDataCategoriesProduct] = useState([])
+ const [dataCategoriesLob, setDataCategoriesLob] = useState([])
+ const categoryId = getIdFromSlug(prefixUrl)
+ const [data, setData] = useState([])
+ const [dataLob, setDataLob] = useState([]);
if (defaultBrand) query.brand = defaultBrand.toLowerCase();
+ const dataIdCategories = []
+ useEffect(() => {
+ if(prefixUrl.includes('category')){
+ const loadProduct = async () => {
+ const getCategoriesId = await odooApi('GET', `/api/v1/category/numFound?parent_id=${categoryId}`);
+ if (getCategoriesId) {
+ setDataCategoriesProduct(getCategoriesId);
+ }
+ };
+ loadProduct();
+ }else if(prefixUrl.includes('lob')){
+ const loadProduct = async () => {
+ const lobData = await odooApi('GET', `/api/v1/lob_homepage/${categoryId}/category_id`);
+
+ if (lobData) {
+ setDataLob(lobData);
+ }
+ };
+ loadProduct();
+
+ }
+ }, [categoryId]);
+
+ const collectIds = (category) => {
+ const ids = [];
+ function recurse(cat) {
+ if (cat && cat.id) {
+ ids.push(cat.id);
+ }
+ if (Array.isArray(cat.children)) {
+ cat.children.forEach(recurse);
+ }
+ }
+ recurse(category);
+ return ids;
+ };
+ useEffect(() => {
+ if(prefixUrl.includes('category')){
+ const ids = collectIds(dataCategoriesProduct);
+ const newQuery = {
+ fq: `category_id_ids:(${ids.join(' OR ')})`,
+ page : router.query.page? router.query.page : 1,
+ brand : router.query.brand? router.query.brand : '',
+ category : router.query.category? router.query.category : '',
+ priceFrom : router.query.priceFrom? router.query.priceFrom : '',
+ priceTo : router.query.priceTo? router.query.priceTo : '',
+ limit : router.query.limit? router.query.limit : '',
+ orderBy : router.query.orderBy? router.query.orderBy : ''
+ };
+ setFinalQuery(newQuery);
+ } else if (prefixUrl.includes('lob')){
+
+ const fetchCategoryData = async () => {
+ if (dataLob[0]?.categoryIds) {
+
+ for (const cate of dataLob[0].categoryIds) {
+
+ dataIdCategories.push(cate.childId)
+ }
+
+
+ const mergedArray = dataIdCategories.flat();
+
+ const newQuery = {
+ fq: `category_id_ids:(${mergedArray.join(' OR ')})`,
+ category : router.query.category? router.query.category : '',
+ page : router.query.page? router.query.page : 1,
+ brand : router.query.brand? router.query.brand : '',
+ priceFrom : router.query.priceFrom? router.query.priceFrom : '',
+ priceTo : router.query.priceTo? router.query.priceTo : '',
+ limit : router.query.limit? router.query.limit : '',
+ orderBy : router.query.orderBy? router.query.orderBy : ''
+ };
+
+ setFinalQuery(newQuery);
+
+ }
+ };
+ fetchCategoryData();
+ }
+ }, [dataCategoriesProduct, dataLob]);
+
+ useEffect(() => {
+ if (prefixUrl.includes('category') || prefixUrl.includes('lob')) {
+ setQueryFinal({ ...finalQuery, q, limit, orderBy });
+ } else {
+ setQueryFinal({ ...query, q, limit, orderBy });
+ }
+ }, [prefixUrl,dataCategoriesProduct, query, finalQuery]);
+
const { productSearch } = useProductSearch({
- query: { ...query, q, limit, orderBy },
+ query: queryFinal,
operation: 'AND',
});
const [products, setProducts] = useState(null);
@@ -53,22 +154,24 @@ const ProductSearch = ({
const numRows = [30, 50, 80, 100];
const [brandValues, setBrand] = useState(
!router.pathname.includes('brands')
- ? query.brand
- ? query.brand.split(',')
+ ? router.query.brand
+ ? router.query.brand.split(',')
: []
: []
);
const [categoryValues, setCategory] = useState(
- query?.category?.split(',') || []
+ router.query?.category?.split(',') || router.query?.category?.split(',')
);
- const [priceFrom, setPriceFrom] = useState(query?.priceFrom || null);
- const [priceTo, setPriceTo] = useState(query?.priceTo || null);
+
+ const [priceFrom, setPriceFrom] = useState(router.query?.priceFrom || null);
+ const [priceTo, setPriceTo] = useState(router.query?.priceTo || null);
const pageCount = Math.ceil(productSearch.data?.response.numFound / limit);
const productStart = productSearch.data?.responseHeader.params.start;
const productRows = limit;
const productFound = productSearch.data?.response.numFound;
-
+ const [dataCategories, setDataCategories] = useState([])
+
useEffect(() => {
if (productFound == 0 && query.q && !spellings) {
searchSpellApi({ query: query.q }).then((response) => {
@@ -98,7 +201,7 @@ const ProductSearch = ({
});
}
}, [productFound, query, spellings]);
-
+ let id = []
useEffect(() => {
const checkIfBrand = async () => {
const brand = await axios(
@@ -115,6 +218,21 @@ const ProductSearch = ({
checkIfBrand();
}
}, [q]);
+
+ useEffect(() => {
+ if(prefixUrl.includes('category')){
+ const loadCategories = async () => {
+ const getCategories = await odooApi('GET', `/api/v1/category/child?parent_id=${categoryId}`)
+ if(getCategories){
+ setDataCategories(getCategories)
+ }
+ }
+ loadCategories()
+ }
+ }, [])
+
+
+
const brands = [];
for (
@@ -224,7 +342,7 @@ const ProductSearch = ({
q: router.query.q,
orderBy: orderBy,
brand: brandValues.join(','),
- category: categoryValues.join(','),
+ category: categoryValues?.join(','),
priceFrom,
priceTo,
};
@@ -263,7 +381,6 @@ const ProductSearch = ({
};
const isNotReadyStockPage = router.asPath !== '/shop/search?orderBy=stock';
-
return (
<>
<MobileView>
@@ -324,6 +441,8 @@ const ProductSearch = ({
SpellingComponent
)}
</div>
+ <LobSectionCategory categories={dataLob}/>
+ <CategorySection categories={dataCategories}/>
{productFound > 0 && (
<div className='flex items-center gap-x-2 mb-5 justify-between'>
@@ -364,6 +483,7 @@ const ProductSearch = ({
pageCount={pageCount}
currentPage={parseInt(page)}
url={`${prefixUrl}?${toQuery(_.omit(query, ['page']))}`}
+ // url={prefixUrl.includes('category') || prefixUrl.includes('lob')? `${prefixUrl}?${toQuery(_.omit(finalQuery, ['page']))}` : `${prefixUrl}?${toQuery(_.omit(query, ['page']))}`}
className='mt-6 mb-2'
/>
@@ -412,7 +532,10 @@ const ProductSearch = ({
<SideBanner />
</div>
+
<div className='w-9/12 pl-6'>
+ <LobSectionCategory categories={dataLob}/>
+ <CategorySection categories={dataCategories}/>
{bannerPromotionHeader && bannerPromotionHeader?.image && (
<div className='mb-3'>
<Image
@@ -550,6 +673,7 @@ const ProductSearch = ({
pageCount={pageCount}
currentPage={parseInt(page)}
url={`${prefixUrl}?${toQuery(_.omit(query, ['page']))}`}
+ // url={prefixUrl.includes('category') || prefixUrl.includes('lob')? `${prefixUrl}?${toQuery(_.omit(finalQuery, ['page']))}` : `${prefixUrl}?${toQuery(_.omit(query, ['page']))}`}
className='!justify-end'
/>
</div>
diff --git a/src/lib/promo/api/productSearchApi.js b/src/lib/promo/api/productSearchApi.js
new file mode 100644
index 00000000..2f792fd4
--- /dev/null
+++ b/src/lib/promo/api/productSearchApi.js
@@ -0,0 +1,11 @@
+import _ from 'lodash-contrib'
+import axios from 'axios'
+
+const productSearchApi = async ({ query, operation = 'AND' }) => {
+ const dataProductSearch = await axios(
+ `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/promo?${query}&operation=${operation}`
+ )
+ return dataProductSearch.data
+}
+
+export default productSearchApi
diff --git a/src/lib/promo/hooks/usePromotionSearch.js b/src/lib/promo/hooks/usePromotionSearch.js
new file mode 100644
index 00000000..1a194646
--- /dev/null
+++ b/src/lib/promo/hooks/usePromotionSearch.js
@@ -0,0 +1,15 @@
+import { useQuery } from 'react-query'
+import productSearchApi from '../api/productSearchApi'
+import _ from 'lodash-contrib'
+
+const usePromotionSearch = ({ query, operation }) => {
+ const queryString = _.toQuery(query)
+ const fetchProductSearch = async () => await productSearchApi({ query: queryString , operation : operation})
+ const productSearch = useQuery(`promoSearch-${queryString}`, fetchProductSearch)
+
+ return {
+ productSearch
+ }
+}
+
+export default usePromotionSearch
diff --git a/src/lib/quotation/components/Quotation.jsx b/src/lib/quotation/components/Quotation.jsx
index df234dc2..0ad042de 100644
--- a/src/lib/quotation/components/Quotation.jsx
+++ b/src/lib/quotation/components/Quotation.jsx
@@ -9,6 +9,7 @@ import _ from 'lodash';
import { deleteItemCart, getCart, getItemCart } from '@/core/utils/cart';
import currencyFormat from '@/core/utils/currencyFormat';
import { toast } from 'react-hot-toast';
+import { useProductCartContext } from '@/contexts/ProductCartContext';
// import checkoutApi from '@/lib/checkout/api/checkoutApi'
import { useRouter } from 'next/router';
import VariantGroupCard from '@/lib/variant/components/VariantGroupCard';
@@ -38,11 +39,12 @@ const { getProductsCheckout } = require('@/lib/checkout/api/checkoutApi');
const Quotation = () => {
const router = useRouter();
const auth = useAuth();
-
+
const { data: cartCheckout } = useQuery('cartCheckout', () =>
getProductsCheckout()
- );
+);
+const { setRefreshCart } = useProductCartContext();
const SELF_PICKUP_ID = 32;
const [products, setProducts] = useState(null);
@@ -293,6 +295,7 @@ const Quotation = () => {
if (isSuccess?.id) {
for (const product of products) deleteItemCart({ productId: product.id });
router.push(`/shop/quotation/finish?id=${isSuccess.id}`);
+ setRefreshCart(true);
return;
}
diff --git a/src/lib/quotation/components/Quotationheader.jsx b/src/lib/quotation/components/Quotationheader.jsx
new file mode 100644
index 00000000..4529c977
--- /dev/null
+++ b/src/lib/quotation/components/Quotationheader.jsx
@@ -0,0 +1,265 @@
+import { useCallback, useEffect, useMemo, useState } from 'react';
+import { createSlug } from '@/core/utils/slug';
+import useAuth from '@/core/hooks/useAuth';
+import { useRouter } from 'next/router';
+import odooApi from '@/core/api/odooApi';
+import { useProductCartContext } from '@/contexts/ProductCartContext';
+import Image from '@/core/components/elements/Image/Image';
+import whatsappUrl from '@/core/utils/whatsappUrl';
+import { AnimatePresence, motion } from 'framer-motion';
+import style from '../../../../src-migrate/modules/cart/styles/item-promo.module.css';
+import useTransactions from '../../transaction/hooks/useTransactions';
+import currencyFormat from '@/core/utils/currencyFormat';
+const { DocumentCheckIcon, PhotoIcon } = require('@heroicons/react/24/outline');
+const { default: Link } = require('next/link');
+
+const Quotationheader = (quotationCount) => {
+ const auth = useAuth();
+ const query = {
+ context: 'quotation',
+ site: auth?.webRole === null && auth?.site ? auth.site : null,
+ };
+
+ const router = useRouter();
+ const [subTotal, setSubTotal] = useState(null);
+ const [buttonLoading, SetButtonTerapkan] = useState(false);
+ const itemLoading = [1, 2, 3];
+ const [countQuotation, setCountQuotation] = useState(null);
+ const { productCart, setProductCart, refreshCart, setRefreshCart, isLoading, setIsloading, productQuotation, setProductQuotation } =
+ useProductCartContext();
+
+ const [isHovered, setIsHovered] = useState(false);
+ const [isTop, setIsTop] = useState(true);
+
+ const qotation = useMemo(() => {
+ return productQuotation || [];
+ }, [productQuotation]);
+
+ const handleMouseEnter = () => {
+ setIsHovered(true);
+ getCart();
+ };
+
+ const handleMouseLeave = () => {
+ setIsHovered(false);
+ };
+
+ const getCart = () => {
+ if (!productQuotation && auth) {
+ refreshCartf();
+ }
+ };
+ let { transactions } = useTransactions({ query });
+
+ const refreshCartf = useCallback(async () => {
+ setIsloading(true);
+ let pendingTransactions = transactions?.data?.saleOrders.filter(transaction => transaction.status === 'draft');
+ setProductQuotation(pendingTransactions);
+ setCountQuotation(pendingTransactions?.length ? pendingTransactions?.length : pendingTransactions?.length);
+ setIsloading(false);
+ }, [setProductQuotation, setIsloading]);
+
+ useEffect(() => {
+ if (!qotation) return
+
+ let calculateTotalDiscountAmount = 0
+ for (const product of qotation) {
+ // if (qotation.quantity == '') continue
+ calculateTotalDiscountAmount += product.amountUntaxed
+ }
+ let subTotal = calculateTotalDiscountAmount
+ setSubTotal(subTotal)
+ }, [qotation])
+
+ useEffect(() => {
+ if (refreshCart) {
+ refreshCartf();
+ }
+ setRefreshCart(false);
+ }, [ refreshCartf, setRefreshCart]);
+
+ useEffect(() => {
+ setCountQuotation(quotationCount.quotationCount);
+ setProductQuotation(quotationCount.data);
+ }, [quotationCount]);
+
+ useEffect(() => {
+ const handleScroll = () => {
+ setIsTop(window.scrollY === 0);
+ };
+ window.addEventListener('scroll', handleScroll);
+ return () => {
+ window.removeEventListener('scroll', handleScroll);
+ };
+ }, []);
+
+ const handleCheckout = async () => {
+ SetButtonTerapkan(true);
+ let checkoutAll = await odooApi('POST', `/api/v1/user/${auth.id}/cart/select-all`);
+ router.push('/my/quotations');
+ };
+
+ return (
+ <div className='relative group'>
+ <div>
+ <Link
+ href='/my/quotations'
+ target='_blank'
+ rel='noreferrer'
+ className='flex items-center gap-x-2 !text-gray_r-12/80'
+ onMouseEnter={handleMouseEnter}
+ onMouseLeave={handleMouseLeave}
+ >
+ <div className={`relative ${countQuotation > 0 && 'mr-2'}`}>
+ <DocumentCheckIcon className='w-7' />
+ {countQuotation > 0 && (
+ <span className='absolute -top-2 -right-2 badge-solid-red rounded-full w-5 h-5 flex items-center justify-center'>
+ {countQuotation}
+ </span>
+ )}
+ </div>
+ <span>
+ List
+ <br />
+ Quotation
+ </span>
+ </Link>
+ </div>
+ <AnimatePresence>
+ {isHovered && (
+ <>
+ <motion.div
+ initial={{ opacity: 0 }}
+ animate={{ opacity: 1, top: isTop ? 230 : 155 }}
+ exit={{ opacity: 0 }}
+ transition={{ duration: 0.15, top: { duration: 0.3 } }}
+ className={`fixed left-0 w-full h-full bg-black/50 z-10`}
+ />
+ <motion.div
+ initial={{ opacity: 0 }}
+ animate={{ opacity: 1, transition: { duration: 0.2 } }}
+ exit={{ opacity: 0, transition: { duration: 0.3 } }}
+ className='absolute z-10 left-0 w-96'
+ onMouseEnter={handleMouseEnter}
+ onMouseLeave={handleMouseLeave}
+ >
+ <motion.div
+ initial={{ height: 0 }}
+ animate={{ height: 'auto' }}
+ exit={{ height: 0 }}
+ className='w-full max-w-md p-2 bg-white border border-gray-200 rounded-lg shadow overflow-hidden'
+ >
+ <div className='p-2 flex justify-between items-center'>
+ <h5 className='text-base font-semibold leading-none'>Daftar Quotation</h5>
+ </div>
+ <hr className='mt-3 mb-3 border border-gray-100' />
+ <div className='flow-root max-h-[250px] overflow-y-auto'>
+ {!auth && (
+ <div className='justify-center p-4'>
+ <p className='text-gray-500 text-center '>
+ Silahkan{' '}
+ <Link href='/login' className='text-red-600 underline leading-6'>
+ Login
+ </Link>{' '}
+ Untuk Melihat Daftar Quotation Anda
+ </p>
+ </div>
+ )}
+ {isLoading &&
+ itemLoading.map((item) => (
+ <div key={item} role='status' className='max-w-sm animate-pulse'>
+ <div className='flex items-center space-x-4 mb- 2'>
+ <div className='flex-shrink-0'>
+ <PhotoIcon className='h-16 w-16 text-gray-500' />
+ </div>
+ <div className='flex-1 min-w-0'>
+ <div className='h-2.5 bg-gray-200 rounded-full dark:bg-gray-700 w-48 mb-4'></div>
+ <div className='h-2 bg-gray-200 rounded-full dark:bg-gray-700 max-w-[360px] mb-2.5'></div>
+ <div className='h-2 bg-gray-200 rounded-full dark:bg-gray-700 mb-2.5'></div>
+ </div>
+ </div>
+ </div>
+ ))}
+ {auth && qotation.length === 0 && !isLoading && (
+ <div className='justify-center p-4'>
+ <p className='text-gray-500 text-center '>
+ Tidak Ada Quotation
+ </p>
+ </div>
+ )}
+ {auth && qotation.length > 0 && !isLoading && (
+ <>
+ <ul role='list' className='divide-y divide-gray-200 dark:divide-gray-700'>
+ {qotation &&
+ qotation?.map((product, index) => (
+ <>
+ <li className='py-1 sm:py-2'>
+ <div className='flex justify-between border p-2 flex-col gap-y-2 hover:border-red-500'>
+ <Link
+ href={`/my/quotations/${product?.id}`}
+ className='hover:border-red-500'
+ >
+ <div className='flex justify-between mb-2'>
+ <div className='flex flex-row items-center'>
+ <p className='tanggal text-xs opacity-80 mr-[2px]'>Sales : </p>
+ <p className='tanggal text-xs text-red-500 font-semibold'>{product.sales}</p>
+ </div>
+ <div className='flex flex-row items-center'>
+ <p className='text-xs opacity-80 mr-[2px]'>Status :</p>
+ <p className='badge-red h-fit text-xs whitespace-nowrap'>Pending Quotation</p>
+ </div>
+ </div>
+ <div className='flex justify-between mb-2'>
+ <div className='flex flex-col items-start'>
+ <p className=' text-xs opacity-80 mr-[2px]'>No. Transaksi</p>
+ <p className=' text-sm text-red-500 font-semibold'> {product.name}</p>
+ </div>
+ <div className='flex flex-col items-end'>
+ <p className='text-xs opacity-80 mr-[2px]'>No. Purchase Order</p>
+ <p className='font-semibold text-sm text-red-500'> {product.purchaseOrderName ? product.purchaseOrderName : '-'}</p>
+ </div>
+ </div>
+ {/* <div className='my-0.5 h-0.5 bg-gray-200'></div> */}
+ <hr className='mt-3 mb-3 border border-gray-100' />
+ <div className='bagian bawah flex justify-between mt-2'>
+ <p className='font-semibold text-sm'>Total</p>
+ <p className='font-semibold text-sm'>{currencyFormat(product.amountUntaxed)}</p>
+ </div>
+ </Link>
+ </div>
+ </li>
+ </>
+ ))}
+ </ul>
+ <hr />
+ </>
+ )}
+ </div>
+ {auth && qotation.length > 0 && !isLoading && (
+ <>
+ <div className='mt-3 ml-1'>
+ <span className='text-gray-400 text-caption-2'>Subtotal Sebelum PPN : </span>
+ <span className='font-semibold text-red-600'>{currencyFormat(subTotal)}</span>
+ </div>
+ <div className='mt-5 mb-2'>
+ <button
+ type='button'
+ className='btn-solid-red rounded-lg w-full'
+ onClick={handleCheckout}
+ disabled={buttonLoading}
+ >
+ {buttonLoading ? 'Loading...' : 'Lihat Semua'}
+ </button>
+ </div>
+ </>
+ )}
+ </motion.div>
+ </motion.div>
+ </>
+ )}
+ </AnimatePresence>
+ </div>
+ );
+};
+
+export default Quotationheader;
diff --git a/src/lib/tracking-order/api/trackingOrder.js b/src/lib/tracking-order/api/trackingOrder.js
new file mode 100644
index 00000000..cc48c40c
--- /dev/null
+++ b/src/lib/tracking-order/api/trackingOrder.js
@@ -0,0 +1,8 @@
+import odooApi from "@/core/api/odooApi";
+
+export const trackingOrder = async ({query}) => {
+ const params = new URLSearchParams(query).toString();
+ const list = await odooApi('GET', `/api/v1/tracking_order?${params}`)
+
+ return list;
+}
diff --git a/src/lib/tracking-order/component/TrackingOrder.jsx b/src/lib/tracking-order/component/TrackingOrder.jsx
new file mode 100644
index 00000000..394979c1
--- /dev/null
+++ b/src/lib/tracking-order/component/TrackingOrder.jsx
@@ -0,0 +1,139 @@
+import { yupResolver } from '@hookform/resolvers/yup'
+import React, { useEffect, useState } from 'react'
+import { useForm } from 'react-hook-form'
+import * as Yup from 'yup'
+import Manifest from '@/lib/treckingAwb/component/Manifest'
+import { trackingOrder } from '../api/trackingOrder'
+import { useQuery } from 'react-query'
+import { Spinner } from '@chakra-ui/react';
+import { Search } from 'lucide-react';
+import whatsappUrl from '@/core/utils/whatsappUrl';
+import Link from 'next/link'
+
+const TrackingOrder = () => {
+ const [idAWB, setIdAWB] = useState(null)
+ const [inputQuery, setInputQuery] = useState(null)
+ const [buttonClick, setButtonClick] = useState(false)
+ const [apiError, setApiError] = useState(null) // State to store API error message
+
+ const closePopup = () => {
+ setIdAWB(null)
+ setButtonClick(false)
+ setInputQuery(null)
+ setApiError(null) // Reset error message on close
+ }
+
+ const {
+ register,
+ handleSubmit,
+ formState: { errors },
+ control,
+ reset
+ } = useForm({
+ resolver: yupResolver(validationSchema),
+ defaultValues
+ })
+
+ const query = {
+ email: inputQuery?.email,
+ so: inputQuery?.id
+ }
+
+ const { data: tracking, isLoading, isError, error } = useQuery(
+ ['tracking', query],
+ () => trackingOrder({ query: query }),
+ {
+ enabled: !!query.email && !!query.so,
+ onSuccess: (data) => {
+ if (buttonClick) {
+ if (data?.code === 403 || data?.code === 400 || data?.code === 404) {
+ setApiError(data?.description);
+ } else if (data?.pickings?.length > 0) {
+ setIdAWB(data.pickings[0]?.id);
+ } else {
+ setApiError('No pickings data available');
+ }
+ setButtonClick(false);
+ setInputQuery(null);
+ }
+ },
+ }
+ );
+
+ const onSubmitHandler = async (values) => {
+ setInputQuery(values)
+ setButtonClick(true)
+ }
+
+ return (
+ <div className='container mx-auto flex py-10 flex-col'>
+ <h1 className='text-h-sm md:text-title-sm font-semibold mb-6'>Tracking Order</h1>
+ <div className='flex justify-start items-start'>
+ <span className='text-base w-full'>
+ {`Untuk melacak pesanan Anda, masukkan Nomor Transaksi di kotak bawah ini dan masukkan Email login anda lalu tekan tombol "Lacak". Nomor Transaksi ini dapat Anda lihat dalam menu `}
+ <Link href='/my/transactions' className='text-red-500'>
+ Daftar Transaksi
+ </Link>
+ {`. Jika mengalami kesulitan `}
+ <Link href='https://wa.me/6281717181922' target='_blank' rel='noreferrer' className='text-red-500'>
+ hubungi kami
+ </Link>
+ {`.`}
+ </span>
+ </div>
+ <div>
+ <form onSubmit={handleSubmit(onSubmitHandler)} className='flex mt-4 flex-row w-full '>
+ <div className='w-[90%] grid grid-cols-2 gap-4'>
+ <div className='flex flex-col '>
+ <label className='form-label mb-2'>ID Pesanan*</label>
+ <input
+ {...register('id')}
+ placeholder='dapat dilihat pada email konfirmasi anda'
+ type='text'
+ className='form-input mb-2'
+ aria-invalid={errors.id?.message}
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>{errors.id?.message}</div>
+ </div>
+ <div className='flex flex-col '>
+ <label className='form-label mb-2'>Email Penagihan*</label>
+ <input
+ {...register('email')}
+ placeholder='Email yang anda gunakan saat pembayaran'
+ type='text'
+ className='form-input'
+ aria-invalid={errors.email?.message}
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>{errors.email?.message}</div>
+ </div>
+ </div>
+ <div className={` ${errors.id?.message ? 'mt-2' : 'mt-5'} flex items-center ml-4`}>
+ <button
+ type='submit'
+ className='bg-red-600 border border-red-600 rounded-md text-sm text-white w-24 h-11 mb-1 content-center flex flex-row justify-center items-center'
+ >
+ {isLoading && <Spinner size='xs' className='mr-2'/>}
+ {!isLoading && <Search size={16} strokeWidth={1} className='mr-2'/>}
+ <p>Lacak</p>
+ </button>
+ </div>
+ </form>
+ {/* Display the API error message */}
+ {apiError && <div className='text-danger-500 mt-4'>{apiError}</div>}
+ <Manifest idAWB={idAWB} closePopup={closePopup} />
+ </div>
+ </div>
+ )
+}
+
+const validationSchema = Yup.object().shape({
+ email: Yup.string().email('Format harus seperti contoh@email.com').required('Harus di-isi'),
+ id: Yup.string().required('Harus di-isi'),
+})
+
+const defaultValues = {
+ email: '',
+ id: ''
+}
+
+export default TrackingOrder
diff --git a/src/lib/treckingAwb/component/Manifest.jsx b/src/lib/treckingAwb/component/Manifest.jsx
index cf2fa4ed..fbc95702 100644
--- a/src/lib/treckingAwb/component/Manifest.jsx
+++ b/src/lib/treckingAwb/component/Manifest.jsx
@@ -40,10 +40,18 @@ const Manifest = ({ idAWB, closePopup }) => {
const getManifest = async () => {
setIsLoading(true)
const auth = getAuth()
- const list = await odooApi(
- 'GET',
- `/api/v1/partner/${auth.partnerId}/stock-picking/${idAWB}/tracking`
- )
+ let list
+ if(auth){
+ list = await odooApi(
+ 'GET',
+ `/api/v1/partner/${auth.partnerId}/stock-picking/${idAWB}/tracking`
+ )
+ }else{
+ list = await odooApi(
+ 'GET',
+ `/api/v1/stock-picking/${idAWB}/tracking`
+ )
+ }
setManifests(list)
setIsLoading(false)
}
diff --git a/src/pages/_document.jsx b/src/pages/_document.jsx
index cd60bd89..6af6294f 100644
--- a/src/pages/_document.jsx
+++ b/src/pages/_document.jsx
@@ -41,13 +41,13 @@ export default function MyDocument() {
content='328wmjs7hcnz74rwsqzxvq50rmbtm2'
/>
- <Script
+ {/* <Script
async
strategy='beforeInteractive'
src='https://www.googletagmanager.com/gtag/js?id=UA-10501937-1'
- />
+ /> */}
- <Script
+ {/* <Script
async
id='google-analytics-ua'
strategy='beforeInteractive'
@@ -59,7 +59,7 @@ export default function MyDocument() {
gtag('config', 'UA-10501937-1');
`,
}}
- />
+ /> */}
<Script
async
diff --git a/src/pages/api/activation-request.js b/src/pages/api/activation-request.js
index 61dbb597..98d27f78 100644
--- a/src/pages/api/activation-request.js
+++ b/src/pages/api/activation-request.js
@@ -7,7 +7,7 @@ export default async function handler(req, res) {
let result = await odooApi('POST', '/api/v1/user/activation-request', { email })
if (result.activationRequest) {
mailer.sendMail({
- from: 'sales@indoteknik.com',
+ from: 'Indoteknik.com <noreply@indoteknik.com>',
to: result.user.email,
subject: 'Permintaan Aktivasi Akun Indoteknik',
html: `
diff --git a/src/pages/api/shop/product-homepage.js b/src/pages/api/shop/product-homepage.js
index 02c01ee0..61732c77 100644
--- a/src/pages/api/shop/product-homepage.js
+++ b/src/pages/api/shop/product-homepage.js
@@ -36,7 +36,8 @@ const respoonseMap = (productHomepage, products) => {
name: productHomepage.name_s,
image: productHomepage.image_s,
url: productHomepage.url_s,
- products: products
+ products: products,
+ categoryIds: productHomepage.category_id_ids,
}
return productMapped
diff --git a/src/pages/api/shop/promo.js b/src/pages/api/shop/promo.js
new file mode 100644
index 00000000..221a9adb
--- /dev/null
+++ b/src/pages/api/shop/promo.js
@@ -0,0 +1,202 @@
+import { productMappingSolr, promoMappingSolr } from '@/utils/solrMapping';
+import axios from 'axios';
+import camelcaseObjectDeep from 'camelcase-object-deep';
+
+export default async function handler(req, res) {
+ const {
+ q = '*',
+ page = 1,
+ brand = '',
+ category = '',
+ priceFrom = 0,
+ priceTo = 0,
+ orderBy = 'if(exists(sequence_i),0,1) asc, sequence_i asc,',
+ operation = 'AND',
+ fq = '',
+ limit = 30,
+ } = req.query;
+
+ let { stock = '' } = req.query;
+
+ let paramOrderBy = 'if(exists(sequence_i),0,1) asc, sequence_i asc,';
+ switch (orderBy) {
+ case 'price-asc':
+ paramOrderBy += 'price_tier1_v2_f ASC';
+ break;
+ case 'price-desc':
+ paramOrderBy += 'price_tier1_v2_f DESC';
+ break;
+ case 'popular':
+ paramOrderBy += 'product_rating_f DESC, search_rank_i DESC,';
+ break;
+ case 'popular-weekly':
+ paramOrderBy += 'search_rank_weekly_i DESC';
+ break;
+ case 'stock':
+ paramOrderBy += 'product_rating_f DESC, stock_total_f DESC';
+ break;
+ case 'flashsale-price-asc':
+ paramOrderBy += 'flashsale_price_f ASC';
+ break;
+ default:
+ paramOrderBy += '';
+ break;
+ }
+
+ let checkQ = q.trim().split(/[\s\+\-\!\(\)\{\}\[\]\^"~\*\?:\\\/]+/);
+ let newQ = checkQ.length > 1 ? escapeSolrQuery(q) + '*' : escapeSolrQuery(q);
+
+ let offset = (page - 1) * limit;
+ let parameter = [
+ 'facet.field=manufacture_name_s',
+ 'facet.field=category_name',
+ 'facet=true',
+ 'indent=true',
+ // `facet.query=${escapeSolrQuery(q)}`,
+ `q.op=${operation}`,
+ `q=${q}`,
+ // 'qf=name_s',
+ `start=${parseInt(offset)}`,
+ `rows=${limit}`,
+ `sort=${paramOrderBy}`,
+ `fq=product_ids:[* TO *]`,
+ ];
+
+ if (priceFrom > 0 || priceTo > 0) {
+ parameter.push(
+ `fq=price_tier1_v2_f:[${priceFrom == '' ? '*' : priceFrom} TO ${
+ priceTo == '' ? '*' : priceTo
+ }]`
+ );
+ }
+
+ let { auth } = req.cookies;
+ if (auth) {
+ auth = JSON.parse(auth);
+ if (auth.feature.onlyReadyStock) stock = true;
+ }
+
+ if (brand)
+ parameter.push(
+ `fq=${brand
+ .split(',')
+ .map(
+ (manufacturer) =>
+ `manufacture_name_s:"${encodeURIComponent(manufacturer)}"`
+ )
+ .join(' OR ')}`
+ );
+ if (category)
+ parameter.push(
+ `fq=${category
+ .split(',')
+ .map((cat) => `category_name:"${encodeURIComponent(cat)}"`)
+ .join(' OR ')}`
+ );
+ // if (category) parameter.push(`fq=category_name:${capitalizeFirstLetter(category.replace(/,/g, ' OR '))}`)
+ if (stock) parameter.push(`fq=stock_total_f:{1 TO *}`);
+
+ // Single fq in url params
+ if (typeof fq === 'string') parameter.push(`fq=${fq}`);
+ // Multi fq in url params
+ if (Array.isArray(fq))
+ parameter = parameter.concat(fq.map((val) => `fq=${val}`));
+
+ let result = await axios(
+ process.env.SOLR_HOST + '/solr/promotion_program_lines/select?' + parameter.join('&')
+ );
+ try {
+ result.data.response.products = promoMappingSolr(
+ result.data.response.docs
+ );
+
+ result.data.responseHeader.params.start = parseInt(
+ result.data.responseHeader.params.start
+ );
+ result.data.responseHeader.params.rows = parseInt(
+ result.data.responseHeader.params.rows
+ );
+ delete result.data.response.docs;
+ // result.data = camelcaseObjectDeep(result.data);
+ result.data = result.data;
+ res.status(200).json(result.data);
+ } catch (error) {
+ res.status(400).json({ error: error.message });
+ }
+}
+
+const escapeSolrQuery = (query) => {
+ if (query == '*') return query;
+
+ query = query.replace(/-/g, ' ');
+
+ const specialChars = /([\+\!\(\)\{\}\[\]\^"~\*\?:\\\/])/g;
+ const words = query.split(/\s+/);
+ const escapedWords = words.map((word) => {
+ if (specialChars.test(word)) {
+ return word.replace(specialChars, '\\$1');
+ }
+ return word;
+ });
+
+ return escapedWords.join(' ');
+};
+
+
+/*const productResponseMap = (products, pricelist) => {
+ return products.map((product) => {
+ let price = product.price_tier1_v2_f || 0
+ let priceDiscount = product.price_discount_f || 0
+ let discountPercentage = product.discount_f || 0
+
+ if (pricelist) {
+ // const pricelistDiscount = product?.[`price_${pricelist}_f`] || false
+ // const pricelistDiscountPerc = product?.[`discount_${pricelist}_f`] || false
+
+ // if (pricelistDiscount && pricelistDiscount > 0) priceDiscount = pricelistDiscount
+ // if (pricelistDiscountPerc && pricelistDiscountPerc > 0)
+ // discountPercentage = pricelistDiscountPerc
+
+ price = product?.[`price_${pricelist}_f`] || 0
+ }
+
+ if (product?.flashsale_id_i > 0) {
+ price = product?.flashsale_base_price_f || 0
+ priceDiscount = product?.flashsale_price_f || 0
+ discountPercentage = product?.flashsale_discount_f || 0
+ }
+
+ let productMapped = {
+ id: product.product_id_i || '',
+ image: product.image_s || '',
+ code: product.default_code_s || '',
+ name: product.name_s || '',
+ lowestPrice: { price, priceDiscount, discountPercentage },
+ variantTotal: product.variant_total_i || 0,
+ stockTotal: product.stock_total_f || 0,
+ weight: product.weight_f || 0,
+ manufacture: {},
+ categories: [],
+ flashSale: {
+ id: product?.flashsale_id_i,
+ name: product?.product?.flashsale_name_s,
+ tag : product?.flashsale_tag_s || 'FLASH SALE'
+ }
+ }
+
+ if (product.manufacture_id_i && product.manufacture_name_s) {
+ productMapped.manufacture = {
+ id: product.manufacture_id_i || '',
+ name: product.manufacture_name_s || ''
+ }
+ }
+
+ productMapped.categories = [
+ {
+ id: product.category_id_i || '',
+ name: product.category_name_s || ''
+ }
+ ]
+ return productMapped
+ })
+}*/
diff --git a/src/pages/google_merchant/products/[page].js b/src/pages/google_merchant/products/[page].js
index 6e0eb703..0c2cf3c5 100644
--- a/src/pages/google_merchant/products/[page].js
+++ b/src/pages/google_merchant/products/[page].js
@@ -50,7 +50,7 @@ export async function getServerSideProps({ res, query }) {
let categoryId = null;
if (brandId && brandId in brandsData) {
- categoryId = brandsData[brandId].category_ids?.[0] ?? null;
+ categoryId = brandsData[brandId]?.category_ids?.[0] ?? null;
} else {
const solrBrand = await getBrandById(brandId);
brandsData[brandId] = solrBrand;
@@ -58,7 +58,7 @@ export async function getServerSideProps({ res, query }) {
}
if (categoryId && categoryId in categoriesData) {
- categoryName = categoriesData[categoryId].name_s ?? null;
+ categoryName = categoriesData[categoryId]?.name_s ?? null;
} else {
const solrCategory = await getCategoryById(categoryId);
categoriesData[categoryId] = solrCategory;
diff --git a/src/pages/index.jsx b/src/pages/index.jsx
index 4493fe31..613950a6 100644
--- a/src/pages/index.jsx
+++ b/src/pages/index.jsx
@@ -1,6 +1,5 @@
import dynamic from 'next/dynamic';
-import { useRef } from 'react';
-
+import { useEffect, useRef, useState } from 'react';
import { HeroBannerSkeleton } from '@/components/skeleton/BannerSkeleton';
import { PopularProductSkeleton } from '@/components/skeleton/PopularProductSkeleton';
import Seo from '@/core/components/Seo';
@@ -12,8 +11,11 @@ import PreferredBrandSkeleton from '@/lib/home/components/Skeleton/PreferredBran
import BannerPromoSkeleton from '@/lib/home/components/Skeleton/BannerPromoSkeleton';
import PromotinProgram from '@/lib/promotinProgram/components/HomePage';
import PagePopupIformation from '~/modules/popup-information';
-import useProductDetail from '~/modules/product-detail/stores/useProductDetail';
+import CategoryPilihan from '../lib/home/components/CategoryPilihan';
+import odooApi from '@/core/api/odooApi';
import { getAuth } from '~/libs/auth';
+// import { getAuth } from '~/libs/auth';
+import useProductDetail from '~/modules/product-detail/stores/useProductDetail';
const BasicLayout = dynamic(() =>
import('@/core/components/layouts/BasicLayout')
@@ -58,12 +60,23 @@ const BannerSection = dynamic(() =>
const CategoryHomeId = dynamic(() =>
import('@/lib/home/components/CategoryHomeId')
);
+
+const CategoryDynamic = dynamic(() =>
+ import('@/lib/home/components/CategoryDynamic')
+);
+
+const CategoryDynamicMobile = dynamic(() =>
+import('@/lib/home/components/CategoryDynamicMobile')
+);
+
const CustomerReviews = dynamic(() =>
import('@/lib/review/components/CustomerReviews')
);
const ServiceList = dynamic(() => import('@/lib/home/components/ServiceList'));
-export default function Home() {
+
+
+export default function Home({categoryId}) {
const bannerRef = useRef(null);
const wrapperRef = useRef(null);
@@ -74,7 +87,19 @@ export default function Home() {
bannerRef.current?.querySelector(':first-child')?.clientHeight + 'px';
};
+ useEffect(() => {
+ const loadCategories = async () => {
+ const getCategories = await odooApi('GET', '/api/v1/category/child?partner_id='+{categoryId})
+ if(getCategories){
+ setDataCategories(getCategories)
+ }
+ }
+ loadCategories()
+ }, [])
+
+ const [dataCategories, setDataCategories] = useState([])
return (
+ <>
<BasicLayout>
<Seo
title='Indoteknik.com: B2B Industrial Supply & Solution'
@@ -82,11 +107,9 @@ export default function Home() {
additionalMetaTags={[
{
name: 'keywords',
- content:
- 'indoteknik, indoteknik.com, toko teknik, toko perkakas, jual genset, jual fogging, jual krisbow, harga krisbow, harga alat safety, harga pompa air',
+ content: 'indoteknik, indoteknik.com, toko teknik, toko perkakas, jual genset, jual fogging, jual krisbow, harga krisbow, harga alat safety, harga pompa air',
},
- ]}
- />
+ ]} />
<PagePopupIformation />
@@ -126,18 +149,21 @@ export default function Home() {
</>
)}
<PromotinProgram />
+ {dataCategories &&(
+ <CategoryPilihan categories={dataCategories} />
+ )}
+ <CategoryDynamic />
<CategoryHomeId />
<BannerSection />
<CustomerReviews />
</div>
</div>
- </DesktopView>
-
- <MobileView>
+ </DesktopView>
+ <MobileView>
<DelayRender renderAfter={200}>
<HeroBanner />
</DelayRender>
- <div className='flex flex-col gap-y-12 my-6'>
+ <div className='flex flex-col gap-y-4 my-6'>
<DelayRender renderAfter={400}>
<ServiceList />
</DelayRender>
@@ -159,6 +185,12 @@ export default function Home() {
<DelayRender renderAfter={600}>
<PromotinProgram />
</DelayRender>
+ <DelayRender renderAfter={600}>
+ {dataCategories &&(
+ <CategoryPilihan categories={dataCategories} />
+ )}
+ <CategoryDynamicMobile />
+ </DelayRender>
<DelayRender renderAfter={800}>
<PopularProduct />
</DelayRender>
@@ -172,5 +204,6 @@ export default function Home() {
</div>
</MobileView>
</BasicLayout>
+ </>
);
} \ No newline at end of file
diff --git a/src/pages/shop/brands/[slug].jsx b/src/pages/shop/brands/[slug].jsx
index e786ef78..88e9b302 100644
--- a/src/pages/shop/brands/[slug].jsx
+++ b/src/pages/shop/brands/[slug].jsx
@@ -18,7 +18,7 @@ export default function BrandDetail() {
const brandName = getNameFromSlug(slug)
const id = getIdFromSlug(slug)
const {brand} = useBrand({id})
- if (!brand || !brand.data || _.isEmpty(brand.data)) {
+ if ( !brand.isLoading && _.isEmpty(brand.data)) {
return <PageNotFound />;
}
return (
diff --git a/src/pages/shop/category/[slug].jsx b/src/pages/shop/category/[slug].jsx
index 1afe30bf..11840d47 100644
--- a/src/pages/shop/category/[slug].jsx
+++ b/src/pages/shop/category/[slug].jsx
@@ -5,6 +5,8 @@ import { useRouter } from 'next/router';
import Seo from '@/core/components/Seo';
import { getIdFromSlug, getNameFromSlug } from '@/core/utils/slug';
import Breadcrumb from '@/lib/category/components/Breadcrumb';
+import { useEffect, useState } from 'react';
+import odooApi from '@/core/api/odooApi';
const BasicLayout = dynamic(() =>
import('@/core/components/layouts/BasicLayout')
@@ -12,10 +14,14 @@ const BasicLayout = dynamic(() =>
const ProductSearch = dynamic(() =>
import('@/lib/product/components/ProductSearch')
);
+const CategorySection = dynamic(() =>
+ import('@/lib/product/components/CategorySection')
+)
export default function CategoryDetail() {
const router = useRouter();
const { slug = '', page = 1 } = router.query;
+ const [dataCategories, setDataCategories] = useState([])
const categoryName = getNameFromSlug(slug);
const categoryId = getIdFromSlug(slug);
@@ -43,8 +49,9 @@ export default function CategoryDetail() {
<Breadcrumb categoryId={categoryId} />
+
{!_.isEmpty(router.query) && (
- <ProductSearch query={query} prefixUrl={`/shop/category/${slug}`} />
+ <ProductSearch query={query} categories ={categoryId} prefixUrl={`/shop/category/${slug}`} />
)}
</BasicLayout>
);
diff --git a/src/pages/shop/lob/[slug].jsx b/src/pages/shop/lob/[slug].jsx
new file mode 100644
index 00000000..d939c25c
--- /dev/null
+++ b/src/pages/shop/lob/[slug].jsx
@@ -0,0 +1,48 @@
+import _ from 'lodash';
+import dynamic from 'next/dynamic';
+import { useRouter } from 'next/router';
+import Seo from '@/core/components/Seo';
+import { getIdFromSlug, getNameFromSlug } from '@/core/utils/slug';
+import Breadcrumb from '../../../lib/lob/components/Breadcrumb';
+import { useEffect, useState } from 'react';
+import odooApi from '@/core/api/odooApi';
+import { div } from 'lodash-contrib';
+
+const BasicLayout = dynamic(() => import('@/core/components/layouts/BasicLayout'));
+const ProductSearch = dynamic(() => import('@/lib/product/components/ProductSearch'));
+const CategorySection = dynamic(() => import('@/lib/product/components/CategorySection'));
+
+export default function CategoryDetail() {
+ const router = useRouter();
+ const { slug = '', page = 1 } = router.query;
+ const [dataLob, setDataLob] = useState([]);
+ const [finalQuery, setFinalQuery] = useState({});
+ const [dataCategoriesProduct, setDataCategoriesProduct] = useState([])
+ const [data, setData] = useState([])
+ const dataIdCategories = []
+
+ const categoryName = getNameFromSlug(slug);
+ const lobId = getIdFromSlug(slug);
+ const q = router?.query.q || null;
+
+ return (
+ <BasicLayout>
+ <Seo
+ title={`Beli ${categoryName} di Indoteknik`}
+ description={`Jual ${categoryName} Kirim Jakarta Surabaya Semarang Makassar Manado Denpasar Balikpapan Medan Palembang Lampung Bali Bandung Makassar Manado.`}
+ additionalMetaTags={[
+ {
+ property: 'keywords',
+ content: `Jual ${categoryName}, harga ${categoryName}, ${categoryName} murah, toko ${categoryName}, ${categoryName} jakarta, ${categoryName} surabaya`,
+ },
+ ]}
+ />
+
+ <Breadcrumb categoryId={getIdFromSlug(slug)} />
+
+ {!_.isEmpty(router.query) && (
+ <ProductSearch query={router.query} categories={getIdFromSlug(slug)} prefixUrl={`/shop/lob/${slug}`} />
+ )}
+ </BasicLayout>
+ );
+}
diff --git a/src/pages/shop/product/variant/[slug].jsx b/src/pages/shop/product/variant/[slug].jsx
index cb335e0a..42f38774 100644
--- a/src/pages/shop/product/variant/[slug].jsx
+++ b/src/pages/shop/product/variant/[slug].jsx
@@ -69,6 +69,8 @@ export default function ProductDetail({ product }) {
<Seo
title={product?.name || '' + ' - Indoteknik.com' || ''}
description='Temukan pilihan produk B2B Industri &amp; Alat Teknik untuk Perusahaan, UMKM &amp; Pemerintah dengan lengkap, mudah dan transparan.'
+ noindex={true}
+ nofollow={true}
openGraph={{
url: process.env.NEXT_PUBLIC_SELF_HOST + router.asPath,
images: [
diff --git a/src/pages/shop/promo/[slug].jsx b/src/pages/shop/promo/[slug].jsx
new file mode 100644
index 00000000..cfb2c841
--- /dev/null
+++ b/src/pages/shop/promo/[slug].jsx
@@ -0,0 +1,394 @@
+import dynamic from 'next/dynamic'
+import NextImage from 'next/image';
+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 LogoSpinner from '../../../core/components/elements/Spinner/LogoSpinner.jsx'
+import ProductPromoCard from '../../../../src-migrate/modules/product-promo/components/Card.tsx'
+import React from 'react'
+import DesktopView from '../../../core/components/views/DesktopView.jsx';
+import MobileView from '../../../core/components/views/MobileView.jsx';
+import 'swiper/swiper-bundle.css';
+import useDevice from '../../../core/hooks/useDevice.js'
+import ProductFilterDesktop from '../../../lib/product/components/ProductFilterDesktopPromotion.jsx';
+import ProductFilter from '../../../lib/product/components/ProductFilter.jsx';
+import { HStack, Image, Tag, TagCloseButton, TagLabel } from '@chakra-ui/react';
+import { formatCurrency } from '../../../core/utils/formatValue.js';
+import Pagination from '../../../core/components/elements/Pagination/Pagination.js';
+import whatsappUrl from '../../../core/utils/whatsappUrl.js';
+import _ from 'lodash';
+import useActive from '../../../core/hooks/useActive.js';
+import useProductSearch from '../../../lib/promo/hooks/usePromotionSearch.js';
+
+const BasicLayout = dynamic(() => import('../../../core/components/layouts/BasicLayout.jsx'))
+
+export default function PromoDetail() {
+ const router = useRouter()
+ const { slug = '', brand ='', category='', page = '1' } = router.query
+ const [currentPage, setCurrentPage] = useState(parseInt(10) || 1);
+ const [orderBy, setOrderBy] = useState(router.query?.orderBy);
+ const popup = useActive();
+ const prefixUrl = `/shop/promo/${slug}`
+ const [queryFinal, setQueryFinal] = useState({});
+ const [limit, setLimit] = useState(30);
+ const [q, setQ] = useState('*');
+ const [finalQuery, setFinalQuery] = useState({});
+ const [products, setProducts] = useState(null);
+ const [brandValues, setBrand] = useState(
+ !router.pathname.includes('brands')
+ ? router.query.brand
+ ? router.query.brand.split(',')
+ : []
+ : []
+ );
+
+ const [categoryValues, setCategory] = useState(
+ router.query?.category?.split(',') || router.query?.category?.split(',')
+ );
+
+ const [priceFrom, setPriceFrom] = useState(router.query?.priceFrom || null);
+ const [priceTo, setPriceTo] = useState(router.query?.priceTo || null);
+
+
+ useEffect(() => {
+ const newQuery = {
+ fq: `type_value_s:${slug}`,
+ page : router.query.page? router.query.page : 1,
+ brand : router.query.brand? router.query.brand : '',
+ category : router.query.category? router.query.category : '',
+ priceFrom : router.query.priceFrom? router.query.priceFrom : '',
+ priceTo : router.query.priceTo? router.query.priceTo : '',
+ limit : router.query.limit? router.query.limit : '',
+ orderBy : router.query.orderBy? router.query.orderBy : ''
+ };
+ setFinalQuery(newQuery);
+}, [router.query, prefixUrl, slug, brand, category, priceFrom, priceTo, currentPage]);
+ useEffect(() => {
+ setQueryFinal({ ...finalQuery, q, limit, orderBy });
+ }, [router.query, prefixUrl, slug, brand, category, priceFrom, priceTo, currentPage, finalQuery]);
+
+ const { productSearch } = useProductSearch({
+ query: queryFinal,
+ operation: 'OR',
+ });
+
+
+ const pageCount = Math.ceil(productSearch.data?.response.numFound / limit);
+ const productStart = productSearch.data?.responseHeader.params.start;
+ const productRows = limit;
+ const productFound = productSearch.data?.response.numFound;
+
+ useEffect(() => {
+ setProducts(productSearch.data?.response?.products);
+ }, [productSearch]);
+
+ const brands = [];
+ for (
+ let i = 0;
+ i < productSearch.data?.facet_counts?.facet_fields?.manufacture_name_s.length;
+ i += 2
+ ) {
+ const brand =
+ productSearch.data?.facet_counts?.facet_fields?.manufacture_name_s[i];
+ const qty =
+ productSearch.data?.facet_counts?.facet_fields?.manufacture_name_s[i + 1];
+ if (qty > 0) {
+ brands.push({ brand, qty });
+ }
+ }
+
+ const categories = [];
+ for (
+ let i = 0;
+ i < productSearch.data?.facet_counts?.facet_fields?.category_name.length;
+ i += 2
+ ) {
+ const name = productSearch.data?.facet_counts?.facet_fields?.category_name[i];
+ const qty =
+ productSearch.data?.facet_counts?.facet_fields?.category_name[i + 1];
+ if (qty > 0) {
+ categories.push({ name, qty });
+ }
+ }
+
+ function capitalizeFirstLetter(string) {
+ string = string.replace(/_/g, ' ');
+ return string.replace(/(^\w|\s\w)/g, function(match) {
+ return match.toUpperCase();
+ });
+ }
+
+ const handleDeleteFilter = async (source, value) => {
+ let params = {
+ q: router.query.q,
+ orderBy: '',
+ brand: brandValues.join(','),
+ category: categoryValues?.join(','),
+ priceFrom: priceFrom || '',
+ priceTo: priceTo || '',
+ };
+
+ let brands = brandValues;
+ let catagories = categoryValues;
+ switch (source) {
+ case 'brands':
+ brands = brandValues.filter((item) => item !== value);
+ params.brand = brands.join(',');
+ await setBrandValues(brands);
+ break;
+ case 'category':
+ catagories = categoryValues.filter((item) => item !== value);
+ params.category = catagories.join(',');
+ await setCategoryValues(catagories);
+ break;
+ case 'price':
+ params.priceFrom = '';
+ params.priceTo = '';
+ break;
+ case 'delete':
+ params = {
+ q: router.query.q,
+ orderBy: '',
+ brand: '',
+ category: '',
+ priceFrom: '',
+ priceTo: '',
+ };
+ break;
+ }
+
+ handleSubmitFilter(params);
+ };
+ const handleSubmitFilter = (params) => {
+ params = _.pickBy(params, _.identity);
+ params = toQuery(params);
+ router.push(`${slug}?${params}`);
+ };
+
+
+ const toQuery = (obj) => {
+ const str = Object.keys(obj)
+ .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`)
+ .join('&')
+ return str
+ }
+
+ const whatPromo = capitalizeFirstLetter(slug)
+ const queryWithoutSlug = _.omit(router.query, ['slug'])
+
+ 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={whatPromo} />
+ <MobileView>
+ <div className='p-4 pt-0'>
+ <h1 className='mb-2 font-semibold text-h-sm'>Promo {whatPromo}</h1>
+
+ <FilterChoicesComponent
+ brandValues={brandValues}
+ categoryValues={categoryValues}
+ priceFrom={priceFrom}
+ priceTo={priceTo}
+ handleDeleteFilter={handleDeleteFilter}
+ />
+ {products?.length >= 1 && (
+ <div className='flex items-center gap-x-2 mb-5 justify-between'>
+ <div>
+ <button
+ className='btn-light py-2 px-5 h-[40px]'
+ onClick={popup.activate}
+ >
+ Filter
+ </button>
+ </div>
+ </div>
+ )}
+ {productSearch.isLoading && <div className='container flex justify-center my-4'>
+ <LogoSpinner width={48} height={48} />
+ </div>}
+ {products && (
+ <>
+ <div className='grid grid-cols-1 gap-x-1 gap-y-1'>
+ {products?.map((promotion) => (
+ <div key={promotion.id} className="min-w-36 max-w-[400px] mb-[20px] sm:w-full md:w-1/2 lg:w-1/3 xl:w-1/4 ">
+ <ProductPromoCard promotion={promotion}/>
+ </div>
+ ))}
+ </div>
+ </>
+ ) }
+
+ <Pagination
+ pageCount={pageCount}
+ currentPage={parseInt(page)}
+ url={`${prefixUrl}?${toQuery(_.omit(queryWithoutSlug, ['page']))}`}
+ className='mt-6 mb-2'
+ />
+ <ProductFilter
+ active={popup.active}
+ close={popup.deactivate}
+ brands={brands || []}
+ categories={categories || []}
+ prefixUrl={router.asPath.includes('?') ? `${router.asPath}` : `${router.asPath}?`}
+ defaultBrand={null}
+ />
+ </div>
+
+ </MobileView>
+ <DesktopView>
+ <div className='container mx-auto flex mb-3 flex-col'>
+ <div className='w-full pl-6'>
+ <h1 className='text-2xl mb-2 font-semibold'>Promo {whatPromo}</h1>
+ <div className=' w-full h-full flex flex-row items-center '>
+
+ <div className='detail-filter w-1/2 flex justify-start items-center mt-4'>
+
+ <FilterChoicesComponent
+ brandValues={brandValues}
+ categoryValues={categoryValues}
+ priceFrom={priceFrom}
+ priceTo={priceTo}
+ handleDeleteFilter={handleDeleteFilter}
+ />
+ </div>
+ <div className='Filter w-1/2 flex flex-col'>
+
+ <ProductFilterDesktop
+ brands={brands || []}
+ categories={categories || []}
+ prefixUrl={'/shop/promo'}
+ // defaultBrand={null}
+ />
+ </div>
+ </div>
+ {productSearch.isLoading ? (
+ <div className='container flex justify-center my-4'>
+ <LogoSpinner width={48} height={48} />
+ </div>
+ ) : products && products.length >= 1 ? (
+ <>
+ <div className='grid grid-cols-1 gap-x-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3'>
+ {products?.map((promotion) => (
+ <div key={promotion.id} className="min-w-[400px] max-w-[400px] mb-[20px] sm:min-w-[350px] md:min-w-[380px] lg:min-w-[400px] xl:min-w-[400px] ">
+ <ProductPromoCard promotion={promotion}/>
+ </div>
+ ))}
+ </div>
+ </>
+ ) : (
+ <div className="text-center my-8">
+ <p>Belum ada promo pada kategori ini</p>
+ </div>
+ )}
+ <div className='flex justify-between items-center mt-6 mb-2'>
+ <div className='pt-2 pb-6 flex items-center gap-x-3'>
+ <NextImage
+ src='/images/logo-question.png'
+ alt='Logo Question Indoteknik'
+ width={60}
+ height={60}
+ />
+ <div className='text-gray_r-12/90'>
+ <span>
+ Barang yang anda cari tidak ada?{' '}
+ <a
+ href={
+ router.query?.q
+ ? whatsappUrl('productSearch', {
+ name: router.query.q,
+ })
+ : whatsappUrl()
+ }
+ className='text-danger-500'
+ >
+ Hubungi Kami
+ </a>
+ </span>
+ </div>
+ </div>
+
+
+
+ <Pagination
+ pageCount={pageCount}
+ currentPage={parseInt(page)}
+ url={`${prefixUrl}?${toQuery(_.omit(queryWithoutSlug, ['page']))}`}
+ className='mt-6 mb-2'
+ />
+ </div>
+
+ </div>
+ </div>
+ </DesktopView>
+ </BasicLayout>
+ )
+ }
+
+const FilterChoicesComponent = ({
+ brandValues,
+ categoryValues,
+ priceFrom,
+ priceTo,
+ handleDeleteFilter,
+ }) => (
+ <div className='flex items-center mb-4'>
+ <HStack spacing={2} className='flex-wrap'>
+ {brandValues?.map((value, index) => (
+ <Tag
+ size='lg'
+ key={index}
+ borderRadius='lg'
+ variant='outline'
+ colorScheme='gray'
+ >
+ <TagLabel>{value}</TagLabel>
+ <TagCloseButton onClick={() => handleDeleteFilter('brands', value)} />
+ </Tag>
+ ))}
+
+ {categoryValues?.map((value, index) => (
+ <Tag
+ size='lg'
+ key={index}
+ borderRadius='lg'
+ variant='outline'
+ colorScheme='gray'
+ >
+ <TagLabel>{value}</TagLabel>
+ <TagCloseButton
+ onClick={() => handleDeleteFilter('category', value)}
+ />
+ </Tag>
+ ))}
+ {priceFrom && priceTo && (
+ <Tag size='lg' borderRadius='lg' variant='outline' colorScheme='gray'>
+ <TagLabel>
+ {formatCurrency(priceFrom) + '-' + formatCurrency(priceTo)}
+ </TagLabel>
+ <TagCloseButton
+ onClick={() => handleDeleteFilter('price', priceFrom)}
+ />
+ </Tag>
+ )}
+ {brandValues?.length > 0 ||
+ categoryValues?.length > 0 ||
+ priceFrom ||
+ priceTo ? (
+ <span>
+ <button
+ className='btn-transparent py-2 px-5 h-[40px] text-red-700'
+ onClick={() => handleDeleteFilter('delete')}
+ >
+ Hapus Semua
+ </button>
+ </span>
+ ) : (
+ ''
+ )}
+ </HStack>
+ </div>
+);
diff --git a/src/pages/shop/promo/[slug].tsx b/src/pages/shop/promo/[slug].tsx
deleted file mode 100644
index aaee1249..00000000
--- a/src/pages/shop/promo/[slug].tsx
+++ /dev/null
@@ -1,523 +0,0 @@
-import dynamic from 'next/dynamic'
-import NextImage from 'next/image';
-import { useEffect, useState } from 'react'
-import { useRouter } from 'next/router'
-import Seo from '../../../core/components/Seo'
-import Promocrumb from '../../../lib/promo/components/Promocrumb'
-import { fetchPromoItemsSolr, fetchVariantSolr } from '../../../api/promoApi'
-import LogoSpinner from '../../../core/components/elements/Spinner/LogoSpinner.jsx'
-import ProductPromoCard from '../../../../src-migrate/modules/product-promo/components/Card'
-import { IPromotion } from '../../../../src-migrate/types/promotion'
-import React from 'react'
-import { SolrResponse } from "../../../../src-migrate/types/solr.ts";
-import DesktopView from '../../../core/components/views/DesktopView';
-import MobileView from '../../../core/components/views/MobileView';
-import 'swiper/swiper-bundle.css';
-import useDevice from '../../../core/hooks/useDevice'
-import ProductFilterDesktop from '../../../lib/product/components/ProductFilterDesktopPromotion';
-import ProductFilter from '../../../lib/product/components/ProductFilter';
-import { HStack, Image, Tag, TagCloseButton, TagLabel } from '@chakra-ui/react';
-import { formatCurrency } from '../../../core/utils/formatValue';
-import Pagination from '../../../core/components/elements/Pagination/Pagination';
-import SideBanner from '../../../../src-migrate/modules/side-banner';
-import whatsappUrl from '../../../core/utils/whatsappUrl';
-import { cons, toQuery } from 'lodash-contrib';
-import _ from 'lodash';
-import useActive from '../../../core/hooks/useActive';
-
-const BasicLayout = dynamic(() => import('../../../core/components/layouts/BasicLayout'))
-
-export default function PromoDetail() {
- const router = useRouter()
- const { slug = '', brand ='', category='', priceFrom = '', priceTo = '', page = '1' } = router.query
- const [promoItems, setPromoItems] = useState<any[]>([])
- const [promoData, setPromoData] = useState<IPromotion[] | null>(null)
- const [currentPage, setCurrentPage] = useState(parseInt(page as string, 10) || 1);
- const itemsPerPage = 12; // Jumlah item yang ingin ditampilkan per halaman
- const [loading, setLoading] = useState(true);
- const { isMobile, isDesktop } = useDevice()
- const [brands, setBrands] = useState<Brand[]>([]);
- const [categories, setCategories] = useState<Category[]>([]);
- const [brandValues, setBrandValues] = useState<string[]>([]);
- const [categoryValues, setCategoryValues] = useState<string[]>([]);
- const [orderBy, setOrderBy] = useState(router.query?.orderBy || 'popular');
- const popup = useActive();
- const prefixUrl = `/shop/promo/${slug}`
-
- useEffect(() => {
- if (router.query.brand) {
- let brandsArray: string[] = [];
- if (Array.isArray(router.query.brand)) {
- brandsArray = router.query.brand;
- } else if (typeof router.query.brand === 'string') {
- brandsArray = router.query.brand.split(',').map((brand) => brand.trim());
- }
- setBrandValues(brandsArray);
- } else {
- setBrandValues([]);
- }
-
- if (router.query.category) {
- let categoriesArray: string[] = [];
-
- if (Array.isArray(router.query.category)) {
- categoriesArray = router.query.category;
- } else if (typeof router.query.category === 'string') {
- categoriesArray = router.query.category.split(',').map((category) => category.trim());
- }
- setCategoryValues(categoriesArray);
- } else {
- setCategoryValues([]);
- }
- }, [router.query.brand, router.query.category]);
-
- interface Brand {
- brand: string;
- qty: number;
- }
-
- interface Category {
- name: string;
- qty: number;
- }
-
- useEffect(() => {
- const loadPromo = async () => {
- setLoading(true);
- const brandsData: Brand[] = [];
- const categoriesData: Category[] = [];
-
- const pageNumber = Array.isArray(page) ? parseInt(page[0], 10) : parseInt(page, 10);
- setCurrentPage(pageNumber)
-
- try {
- const items = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug}`,0,100);
- setPromoItems(items);
-
- if (items.length === 0) {
- setPromoData([])
- setLoading(false);
- return;
- }
-
- const brandArray = Array.isArray(brand) ? brand : brand.split(',');
- const categoryArray = Array.isArray(category) ? category : category.split(',');
-
- const promoDataPromises = items.map(async (item) => {
-
- try {
- let brandQuery = '';
- if (brand) {
- brandQuery = brandArray.map(b => `manufacture_name_s:${b}`).join(' OR ');
- brandQuery = `(${brandQuery})`;
- }
-
- let categoryQuery = '';
- if (category) {
- categoryQuery = categoryArray.map(c => `category_name:${c}`).join(' OR ');
- categoryQuery = `(${categoryQuery})`;
- }
-
- let priceQuery = '';
- if (priceFrom && priceTo) {
- priceQuery = `price_f:[${priceFrom} TO ${priceTo}]`;
- } else if (priceFrom) {
- priceQuery = `price_f:[${priceFrom} TO *]`;
- } else if (priceTo) {
- priceQuery = `price_f:[* TO ${priceTo}]`;
- }
-
- let combinedQuery = '';
- let combinedQueryPrice = `${priceQuery}`;
- if (brand && category && priceFrom || priceTo) {
- combinedQuery = `${brandQuery} AND ${categoryQuery} `;
- } else if (brand && category) {
- combinedQuery = `${brandQuery} AND ${categoryQuery}`;
- } else if (brand && priceFrom || priceTo) {
- combinedQuery = `${brandQuery}`;
- } else if (category && priceFrom || priceTo) {
- combinedQuery = `${categoryQuery}`;
- } else if (brand) {
- combinedQuery = brandQuery;
- } else if (category) {
- combinedQuery = categoryQuery;
- }
-
- if (combinedQuery && priceFrom || priceTo) {
- 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}`,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} `,0,100);
- return response2;
- } else {
- const response = await fetchPromoItemsSolr(`id:${item.id}`,0,100);
- return response;
- }
- } catch (fetchError) {
- return [];
- }
- });
-
- const promoDataArray = await Promise.all(promoDataPromises);
- const mergedPromoData = promoDataArray.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
- setPromoData(mergedPromoData);
-
- const dataBrandCategoryPromises = promoDataArray.map(async (promoData) => {
- if (promoData) {
- const dataBrandCategory = promoData.map(async (item) => {
- let response;
- if(category){
- const categoryQuery = categoryArray.map(c => `category_name:${c}`).join(' OR ');
- response = await fetchVariantSolr(`id:${item.products[0].product_id} AND (${categoryQuery})`);
- }else{
- response = await fetchVariantSolr(`id:${item.products[0].product_id}`)
- }
-
-
- if (response.response?.docs?.length > 0) {
- const product = response.response.docs[0];
- const manufactureNameS = product.manufacture_name;
- if (Array.isArray(manufactureNameS)) {
- for (let i = 0; i < manufactureNameS.length; i += 2) {
- const brand = manufactureNameS[i];
- const qty = 1;
- const existingBrandIndex = brandsData.findIndex(b => b.brand === brand);
- if (existingBrandIndex !== -1) {
- brandsData[existingBrandIndex].qty += qty;
- } else {
- brandsData.push({ brand, qty });
- }
- }
- }
-
- const categoryNameS = product.category_name;
- if (Array.isArray(categoryNameS)) {
- for (let i = 0; i < categoryNameS.length; i += 2) {
- const name = categoryNameS[i];
- const qty = 1;
- const existingCategoryIndex = categoriesData.findIndex(c => c.name === name);
- if (existingCategoryIndex !== -1) {
- categoriesData[existingCategoryIndex].qty += qty;
- } else {
- categoriesData.push({ name, qty });
- }
- }
- }
- }
- });
-
- return Promise.all(dataBrandCategory);
- }
- });
-
- await Promise.all(dataBrandCategoryPromises);
- setBrands(brandsData);
- setCategories(categoriesData);
- setLoading(false);
-
- } catch (loadError) {
- // console.error("Error loading promo items:", loadError)
- setLoading(false);
- }
- }
-
- if (slug) {
- loadPromo()
- }
- },[slug, brand, category, priceFrom, priceTo, currentPage]);
-
-
- function capitalizeFirstLetter(string) {
- string = string.replace(/_/g, ' ');
- return string.replace(/(^\w|\s\w)/g, function(match) {
- return match.toUpperCase();
- });
- }
-
- const handleDeleteFilter = async (source, value) => {
- let params = {
- q: router.query.q,
- orderBy: '',
- brand: brandValues.join(','),
- category: categoryValues.join(','),
- priceFrom: priceFrom || '',
- priceTo: priceTo || '',
- };
-
- let brands = brandValues;
- let catagories = categoryValues;
- switch (source) {
- case 'brands':
- brands = brandValues.filter((item) => item !== value);
- params.brand = brands.join(',');
- await setBrandValues(brands);
- break;
- case 'category':
- catagories = categoryValues.filter((item) => item !== value);
- params.category = catagories.join(',');
- await setCategoryValues(catagories);
- break;
- case 'price':
- params.priceFrom = '';
- params.priceTo = '';
- break;
- case 'delete':
- params = {
- q: router.query.q,
- orderBy: '',
- brand: '',
- category: '',
- priceFrom: '',
- priceTo: '',
- };
- break;
- }
-
- handleSubmitFilter(params);
- };
- const handleSubmitFilter = (params) => {
- params = _.pickBy(params, _.identity);
- params = toQuery(params);
- router.push(`${slug}?${params}`);
- };
-
- const visiblePromotions = promoData?.slice( (currentPage-1) * itemsPerPage, currentPage * 12)
-
- const toQuery = (obj) => {
- const str = Object.keys(obj)
- .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`)
- .join('&')
- return str
- }
-
- const whatPromo = capitalizeFirstLetter(slug)
- const queryWithoutSlug = _.omit(router.query, ['slug'])
- const queryString = toQuery(queryWithoutSlug)
-
- 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={whatPromo} />
- <MobileView>
- <div className='p-4 pt-0'>
- <h1 className='mb-2 font-semibold text-h-sm'>Promo {whatPromo}</h1>
-
- <FilterChoicesComponent
- brandValues={brandValues}
- categoryValues={categoryValues}
- priceFrom={priceFrom}
- priceTo={priceTo}
- handleDeleteFilter={handleDeleteFilter}
- />
- {promoItems.length >= 1 && (
- <div className='flex items-center gap-x-2 mb-5 justify-between'>
- <div>
- <button
- className='btn-light py-2 px-5 h-[40px]'
- onClick={popup.activate}
- >
- Filter
- </button>
- </div>
- </div>
- )}
-
- {loading ? (
- <div className='container flex justify-center my-4'>
- <LogoSpinner width={48} height={48} />
- </div>
- ) : promoData && promoItems.length >= 1 ? (
- <>
- <div className='grid grid-cols-1 gap-x-1 gap-y-1'>
- {visiblePromotions?.map((promotion) => (
- <div key={promotion.id} className="min-w-36 max-w-[400px] mb-[20px] sm:w-full md:w-1/2 lg:w-1/3 xl:w-1/4 ">
- <ProductPromoCard promotion={promotion}/>
- </div>
- ))}
- </div>
- </>
- ) : (
- <div className="text-center my-8">
- <p>Belum ada promo pada kategori ini</p>
- </div>
- )}
-
- <Pagination
- pageCount={Math.ceil((promoData?.length ?? 0) / itemsPerPage)}
- currentPage={currentPage}
- url={`${prefixUrl}?${toQuery(_.omit(queryWithoutSlug, ['page']))}`}
- className='mt-6 mb-2'
- />
- <ProductFilter
- active={popup.active}
- close={popup.deactivate}
- brands={brands || []}
- categories={categories || []}
- prefixUrl={router.asPath.includes('?') ? `${router.asPath}` : `${router.asPath}?`}
- defaultBrand={null}
- />
- </div>
-
- </MobileView>
- <DesktopView>
- <div className='container mx-auto flex mb-3 flex-col'>
- <div className='w-full pl-6'>
- <h1 className='text-2xl mb-2 font-semibold'>Promo {whatPromo}</h1>
- <div className=' w-full h-full flex flex-row items-center '>
-
- <div className='detail-filter w-1/2 flex justify-start items-center mt-4'>
-
- <FilterChoicesComponent
- brandValues={brandValues}
- categoryValues={categoryValues}
- priceFrom={priceFrom}
- priceTo={priceTo}
- handleDeleteFilter={handleDeleteFilter}
- />
- </div>
- <div className='Filter w-1/2 flex flex-col'>
-
- <ProductFilterDesktop
- brands={brands || []}
- categories={categories || []}
- prefixUrl={'/shop/promo'}
- // defaultBrand={null}
- />
- </div>
- </div>
- {loading ? (
- <div className='container flex justify-center my-4'>
- <LogoSpinner width={48} height={48} />
- </div>
- ) : promoData && promoItems.length >= 1 ? (
- <>
- <div className='grid grid-cols-1 gap-x-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3'>
- {visiblePromotions?.map((promotion) => (
- <div key={promotion.id} className="min-w-[400px] max-w-[400px] mb-[20px] sm:min-w-[350px] md:min-w-[380px] lg:min-w-[400px] xl:min-w-[400px] ">
- <ProductPromoCard promotion={promotion}/>
- </div>
- ))}
- </div>
- </>
- ) : (
- <div className="text-center my-8">
- <p>Belum ada promo pada kategori ini</p>
- </div>
- )}
- <div className='flex justify-between items-center mt-6 mb-2'>
- <div className='pt-2 pb-6 flex items-center gap-x-3'>
- <NextImage
- src='/images/logo-question.png'
- alt='Logo Question Indoteknik'
- width={60}
- height={60}
- />
- <div className='text-gray_r-12/90'>
- <span>
- Barang yang anda cari tidak ada?{' '}
- <a
- href={
- router.query?.q
- ? whatsappUrl('productSearch', {
- name: router.query.q,
- })
- : whatsappUrl()
- }
- className='text-danger-500'
- >
- Hubungi Kami
- </a>
- </span>
- </div>
- </div>
-
-
-
- <Pagination
- pageCount={Math.ceil((promoData?.length ?? 0) / itemsPerPage)}
- currentPage={currentPage}
- url={`${prefixUrl}?${toQuery(_.omit(queryWithoutSlug, ['page']))}`}
- className='mt-6 mb-2'
- />
- </div>
-
- </div>
- </div>
- </DesktopView>
- </BasicLayout>
- )
- }
-
-const FilterChoicesComponent = ({
- brandValues,
- categoryValues,
- priceFrom,
- priceTo,
- handleDeleteFilter,
- }) => (
- <div className='flex items-center mb-4'>
- <HStack spacing={2} className='flex-wrap'>
- {brandValues?.map((value, index) => (
- <Tag
- size='lg'
- key={index}
- borderRadius='lg'
- variant='outline'
- colorScheme='gray'
- >
- <TagLabel>{value}</TagLabel>
- <TagCloseButton onClick={() => handleDeleteFilter('brands', value)} />
- </Tag>
- ))}
-
- {categoryValues?.map((value, index) => (
- <Tag
- size='lg'
- key={index}
- borderRadius='lg'
- variant='outline'
- colorScheme='gray'
- >
- <TagLabel>{value}</TagLabel>
- <TagCloseButton
- onClick={() => handleDeleteFilter('category', value)}
- />
- </Tag>
- ))}
- {priceFrom && priceTo && (
- <Tag size='lg' borderRadius='lg' variant='outline' colorScheme='gray'>
- <TagLabel>
- {formatCurrency(priceFrom) + '-' + formatCurrency(priceTo)}
- </TagLabel>
- <TagCloseButton
- onClick={() => handleDeleteFilter('price', priceFrom)}
- />
- </Tag>
- )}
- {brandValues?.length > 0 ||
- categoryValues?.length > 0 ||
- priceFrom ||
- priceTo ? (
- <span>
- <button
- className='btn-transparent py-2 px-5 h-[40px] text-red-700'
- onClick={() => handleDeleteFilter('delete')}
- >
- Hapus Semua
- </button>
- </span>
- ) : (
- ''
- )}
- </HStack>
- </div>
-);
diff --git a/src/pages/sitemap/brands.xml.js b/src/pages/sitemap/brands.xml.js
index c85c40e9..65a84e97 100644
--- a/src/pages/sitemap/brands.xml.js
+++ b/src/pages/sitemap/brands.xml.js
@@ -15,8 +15,8 @@ export async function getServerSideProps({ res }) {
const url = sitemap.ele('url')
url.ele('loc', createSlug(baseUrl, brand.name, brand.id))
url.ele('lastmod', date.toISOString().slice(0, 10))
- url.ele('changefreq', 'weekly')
- url.ele('priority', '0.6')
+ url.ele('changefreq', 'daily')
+ url.ele('priority', '1.0')
})
res.setHeader('Content-Type', 'text/xml')
diff --git a/src/pages/sitemap/products/[page].js b/src/pages/sitemap/products/[page].js
index 2f9c3198..e39755d6 100644
--- a/src/pages/sitemap/products/[page].js
+++ b/src/pages/sitemap/products/[page].js
@@ -19,7 +19,7 @@ export async function getServerSideProps({ query, res }) {
const url = sitemap.ele('url')
url.ele('loc', createSlug(baseUrl, product.name, product.id))
url.ele('lastmod', date.toISOString().slice(0, 10))
- url.ele('changefreq', 'weekly')
+ url.ele('changefreq', 'daily')
url.ele('priority', '0.8')
})
diff --git a/src/pages/tracking-order.jsx b/src/pages/tracking-order.jsx
new file mode 100644
index 00000000..002acd42
--- /dev/null
+++ b/src/pages/tracking-order.jsx
@@ -0,0 +1,27 @@
+import Seo from '@/core/components/Seo'
+import dynamic from 'next/dynamic'
+import SimpleFooter from '@/core/components/elements/Footer/SimpleFooter';
+import BasicLayout from '@/core/components/layouts/BasicLayout';
+import DesktopView from '@/core/components/views/DesktopView';
+import MobileView from '@/core/components/views/MobileView';
+const PageTrackingOrder = dynamic(() => import('@/lib/tracking-order/component/TrackingOrder'))
+
+export default function TrackingOrder() {
+ return (
+ <>
+ <Seo title='Tracking Order - Indoteknik.com' />
+
+ <DesktopView>
+ <BasicLayout>
+ <PageTrackingOrder/>
+ </BasicLayout>
+ </DesktopView>
+
+ <MobileView>
+ <BasicLayout>
+ <PageTrackingOrder/>
+ </BasicLayout>
+ </MobileView>
+ </>
+ );
+}
diff --git a/src/styles/globals.css b/src/styles/globals.css
index f6561b00..b3ca85f4 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -563,7 +563,8 @@ button {
}
.category-mega-box > div:hover .category-mega-box__parent {
- @apply bg-gray_r-5;
+ @apply bg-gray_r-5
+ w-full;
}
.category-mega-box > div:hover .category-mega-box__child-wrapper {
@@ -583,12 +584,11 @@ button {
@apply absolute
left-[100%]
top-12
- w-[40vw]
bg-gray_r-1/90
backdrop-blur-md
border
border-gray_r-6
- p-6
+ p-6
opacity-0
h-full
transition-all
@@ -604,6 +604,7 @@ button {
transition-colors
ease-linear
duration-100
+ w-fit
font-semibold;
}
@@ -613,6 +614,7 @@ button {
transition-colors
ease-linear
duration-100
+ w-full
font-normal;
}
diff --git a/src/utils/solrMapping.js b/src/utils/solrMapping.js
index d4694eb2..0d50b99b 100644
--- a/src/utils/solrMapping.js
+++ b/src/utils/solrMapping.js
@@ -1,3 +1,29 @@
+export const promoMappingSolr = (promotions) => {
+ return promotions.map((promotion) =>{
+ let productMapped = {
+ 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,
+ sequence: promotion.sequence_i,
+ total_qty: promotion.total_qty_i,
+ products: JSON.parse(promotion.products_s) || '',
+ product_id: promotion.product_ids[0],
+ qty_sold_f:promotion.total_qty_sold_f,
+ free_products: JSON.parse(promotion.free_products_s)
+ };
+ return productMapped;
+ })
+};
+
+
export const productMappingSolr = (products, pricelist) => {
return products.map((product) => {
let price = product.price_tier1_v2_f || 0;
@@ -38,6 +64,7 @@ export const productMappingSolr = (products, pricelist) => {
qtySold: product?.qty_sold_f || 0,
isTkdn:product?.tkdn_b || false,
isSni:product?.sni_b || false,
+ is_in_bu:product?.is_in_bu_b || false,
voucherPastiHemat:product?.voucher_pastihemat || []
};
@@ -122,3 +149,4 @@ const flashsaleTime = (endDate) => {
isFlashSale: flashsaleEndDate > currentTime,
};
};
+
diff --git a/tsconfig.json b/tsconfig.json
index 8613c022..3afcd9ea 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -34,7 +34,7 @@
"**/*.tsx",
"**/*.jsx",
".next/types/**/*.ts"
-, "src/pages/shop/promo/index.tsx", "src/pages/shop/promo/[slug].jsx" ],
+, "src/pages/shop/promo/index.tsx", "src/pages/shop/promo/[slug].jsx", "src/lib/promo/hooks/usePromotionSearch.js" ],
"exclude": [
"node_modules",
"src"