diff options
| author | it-fixcomart <it@fixcomart.co.id> | 2024-08-31 09:25:53 +0700 |
|---|---|---|
| committer | it-fixcomart <it@fixcomart.co.id> | 2024-08-31 09:25:53 +0700 |
| commit | 6a0c477de3df773f2a818b904029624c212f083f (patch) | |
| tree | e1f7a09eb83509de594fe7dbb015a71dd18cec26 /src | |
| parent | 3de1a412bba31b19b8b443dd91df8aff8d6eda07 (diff) | |
| parent | c6e970598c6c23f0606d1bc19036f0decd57cc05 (diff) | |
Merge branch 'release' into Feature/new-cart-popup
Diffstat (limited to 'src')
34 files changed, 1020 insertions, 384 deletions
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/core/components/elements/Footer/BasicFooter.jsx b/src/core/components/elements/Footer/BasicFooter.jsx index 6129143d..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> ); @@ -259,7 +264,7 @@ const InformationCenter = () => ( <li className='text-gray_r-12/80 flex items-center'> <PhoneArrowUpRightIcon className='w-[18px] mr-2' /> <a href='tel:02129338828' target='_blank' rel='noreferrer'> - (021) 2933-8828 / 29 + (021) 2933-8828 </a> </li> <li className='text-gray_r-12/80 flex items-center'> diff --git a/src/core/components/elements/Navbar/NavbarDesktop.jsx b/src/core/components/elements/Navbar/NavbarDesktop.jsx index 1163aff3..0d0e8550 100644 --- a/src/core/components/elements/Navbar/NavbarDesktop.jsx +++ b/src/core/components/elements/Navbar/NavbarDesktop.jsx @@ -12,11 +12,12 @@ import { ChevronDownIcon, DocumentCheckIcon, HeartIcon, + ArrowUpRightIcon, } from '@heroicons/react/24/outline'; import dynamic from 'next/dynamic'; import Image from 'next/image'; import { useRouter } from 'next/router'; -import { useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import DesktopView from '../../views/DesktopView'; import Link from '../Link/Link'; import NavbarUserDropdown from './NavbarUserDropdown'; @@ -62,6 +63,46 @@ const NavbarDesktop = () => { (transaction) => transaction.status === 'draft' ); + const [showPopup, setShowPopup] = useState(false); + const [isTop, setIsTop] = useState(true); + + const handleTopBannerLoad = useCallback(() => { + const showTimer = setTimeout(() => { + setShowPopup(true); + }, 500); + + const hideTimer = setTimeout(() => { + // setShowPopup(false); + }, 9500); + + return () => { + clearTimeout(showTimer); + clearTimeout(hideTimer); + }; + }, []); + + useEffect(() => { + const handleScroll = () => { + setIsTop(window.scrollY < 100); + }; + + window.addEventListener('scroll', handleScroll); + return () => { + window.removeEventListener('scroll', handleScroll); + }; + }, []); + + useEffect(() => { + const handleScroll = () => { + setIsTop(window.scrollY < 100); + }; + + window.addEventListener('scroll', handleScroll); + return () => { + window.removeEventListener('scroll', handleScroll); + }; + }, []); + useEffect(() => { setPendingTransactions(data); }, [transactions.data]); @@ -115,7 +156,7 @@ const NavbarDesktop = () => { return ( <DesktopView> - <TopBanner /> + <TopBanner onLoad={handleTopBannerLoad} /> <div className='py-2 bg-warning-400' id='desktop-nav-top'> <div className='container mx-auto flex justify-between'> <div className='flex items-start gap-5'> @@ -230,6 +271,7 @@ const NavbarDesktop = () => { </div> </button> <div className='w-6/12 flex px-1 divide-x divide-gray_r-6'> + <Link href="/shop/promo" className={`${ @@ -238,19 +280,29 @@ const NavbarDesktop = () => { target="_blank" rel="noreferrer" > - <p className="absolute inset-0 flex justify-center items-center group-hover:scale-105 group-hover:text-red-500 transition-transform duration-200 z-10">Semua Promo</p> - {/* <div className='w-full h-full flex justify-end items-start'> - <Image - src='/images/ICON PROMO DISKON.svg' - alt='promo' - width={100} - height={100} + {showPopup && ( + <div className='w-full h-full relative justify-end items-start'> + <Image + src='/images/penawaran-terbatas.jpg' + alt='penawaran terbatas' + width={1440} + height={160} quality={100} - className={`inline-block z-20`} + // className={`fixed ${isTop ? 'md:top-[145px] lg:top-[160px] ' : 'lg:top-[85px] top-[80px]'} rounded-3xl md:left-1/4 lg:left-1/4 xl:left-1/4 left-2/3 w-40 h-12 p-2 z-50 transition-all duration-300 animate-pulse`} + className={`inline-block relative -top-8 transition-all duration-300 z-20 animate-pulse`} /> - </div> */} + </div> + )} + <p className="absolute inset-0 flex justify-center items-center group-hover:scale-105 group-hover:text-red-500 transition-transform duration-200 z-10">Semua Promo</p> </Link> - + {/* {showPopup && router.pathname === '/' && ( + <div className={`fixed ${isTop ? 'top-[170px]' : 'top-[90px]'} rounded-3xl left-[700px] w-fit object-center bg-green-50 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20 text-center p-2 z-50 transition-all duration-300`}> + <p className='w-36 h-3'> + Penawaran Terbatas + </p> + </div> + )} */} + <Link href='/shop/brands' @@ -265,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/TopBanner.jsx b/src/core/components/elements/Navbar/TopBanner.jsx index df47e87d..f438ae67 100644 --- a/src/core/components/elements/Navbar/TopBanner.jsx +++ b/src/core/components/elements/Navbar/TopBanner.jsx @@ -4,8 +4,9 @@ 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 = () => { +const TopBanner = ({ onLoad = () => {} }) => { const { isDesktop, isMobile } = useDevice() const topBanner = useQuery({ queryKey: 'topBanner', @@ -17,6 +18,12 @@ const TopBanner = () => { const hasData = topBanner.data?.length > 0; const data = topBanner.data?.[0] || null; + useEffect(() => { + if (hasData) { + onLoad(); + } + }, [hasData, onLoad]); + return ( <SmoothRender isLoaded={hasData} diff --git a/src/core/components/layouts/BasicLayout.jsx b/src/core/components/layouts/BasicLayout.jsx index a4f3a856..c4674344 100644 --- a/src/core/components/layouts/BasicLayout.jsx +++ b/src/core/components/layouts/BasicLayout.jsx @@ -1,12 +1,13 @@ import dynamic from 'next/dynamic'; import Image from 'next/image'; import { useRouter } from 'next/router'; -import { useEffect, useState } from 'react'; +import { useEffect, useState, useRef } from 'react'; import { useProductContext } from '@/contexts/ProductContext'; import odooApi from '@/core/api/odooApi'; import whatsappUrl from '@/core/utils/whatsappUrl'; import Navbar from '../elements/Navbar/Navbar'; +import styles from './BasicLayout.module.css'; // Import modul CSS const AnimationLayout = dynamic(() => import('./AnimationLayout'), { ssr: false, @@ -19,10 +20,15 @@ const BasicLayout = ({ children }) => { const [templateWA, setTemplateWA] = useState(null); const [payloadWA, setPayloadWa] = useState(null); const [urlPath, setUrlPath] = useState(null); + const [highlight, setHighlight] = useState(false); + const [buttonPosition, setButtonPosition] = useState(null); + const [wobble, setWobble] = useState(false); const router = useRouter(); + const buttonRef = useRef(null); const { product } = useProductContext(); + useEffect(() => { if ( router.pathname === '/shop/product/[slug]' || @@ -39,6 +45,32 @@ const BasicLayout = ({ children }) => { } }, [product, router]); + useEffect(() => { + const handleMouseOut = (event) => { + const rect = buttonRef.current.getBoundingClientRect(); + if (event.clientY <= 0) { + setButtonPosition(rect) + setHighlight(true); + } else { + setHighlight(false); + } + }; + + window.addEventListener('mouseout', handleMouseOut); + + return () => { + window.removeEventListener('mouseout', handleMouseOut); + }; + }, []); + + useEffect(() => { + if (highlight) { + // Set wobble animation after overlay highlight animation completes + const timer = setTimeout(() => setWobble(true), 1000); // Adjust timing if needed + return () => clearTimeout(timer); + } + }, [highlight]); + const recordActivity = async (pathname) => { const ONLY_ON_PATH = false; const recordedPath = []; @@ -60,32 +92,50 @@ const BasicLayout = ({ children }) => { return ( <> + {highlight && buttonPosition && ( + <div + className={styles['overlay-highlight']} + style={{ + '--button-x': `${buttonPosition.x + buttonPosition.width / 2}px`, + '--button-y': `${buttonPosition.y + buttonPosition.height / 2}px`, + '--button-radius': `${Math.max(buttonPosition.width, buttonPosition.height) / 2}px` + }} + onAnimationEnd={() => setHighlight(false)} + /> + )} <Navbar /> <AnimationLayout> {children} <div className='fixed bottom-4 right-4 sm:bottom-14 sm:right-10 z-50'> - <a - href={whatsappUrl(templateWA, payloadWA, urlPath)} - className='py-2 pl-3 pr-4 rounded-full bg-[#4FB84A] border border-green-300 flex items-center' - rel='noopener noreferrer' - target='_blank' - > - <Image - src='/images/socials/WHATSAPP.svg' - alt='Whatsapp' - className='block sm:hidden' - width={36} - height={36} - /> - <Image - src='/images/socials/WHATSAPP.svg' - alt='Whatsapp' - className='hidden sm:block' - width={44} - height={44} - /> - <span className='text-white font-bold ml-1.5'>Whatsapp</span> - </a> + <div className='flex flex-row items-center'> + <a href={whatsappUrl(templateWA, payloadWA, urlPath)} className='flex flex-row items-center' rel='noopener noreferrer' target='_blank'> + <span className={`text-green-300 text-lg font-bold mr-4 ${wobble ? 'animate-wobble' : ''}`} onAnimationEnd={() => setWobble(false)}> + Whatsapp + </span> + </a> + <a + href={whatsappUrl(templateWA, payloadWA, urlPath)} + className='elemen-whatsapp p-4 rounded-full bg-[#4FB84A] border border-green-300 flex items-center' + rel='noopener noreferrer' + target='_blank' + ref={buttonRef} + > + <Image + src='/images/socials/WHATSAPP.svg' + alt='Whatsapp' + className='block sm:hidden' + width={36} + height={36} + /> + <Image + src='/images/socials/WHATSAPP.svg' + alt='Whatsapp' + className='hidden sm:block' + width={44} + height={44} + /> + </a> + </div> </div> </AnimationLayout> <BasicFooter /> diff --git a/src/core/components/layouts/BasicLayout.module.css b/src/core/components/layouts/BasicLayout.module.css new file mode 100644 index 00000000..4945c420 --- /dev/null +++ b/src/core/components/layouts/BasicLayout.module.css @@ -0,0 +1,13 @@ +.overlay-highlight { + @apply fixed top-0 left-0 w-full h-full bg-[#4FB84A]/30 z-[900]; + animation: closeOverlay 1s forwards; + } + + @keyframes closeOverlay { + from { + clip-path: circle(100% at 50% 50%); + } + to { + clip-path: circle(var(--button-radius) at var(--button-x) var(--button-y)); + } + } 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 c147a3b3..f76e6e42 100644 --- a/src/lib/category/components/Category.jsx +++ b/src/lib/category/components/Category.jsx @@ -5,12 +5,17 @@ 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') @@ -30,7 +35,6 @@ const Category = () => { } loadCategories() }, []) - return ( <DesktopView> <div className='category-mega-box'> @@ -38,8 +42,11 @@ const Category = () => { <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'> @@ -80,36 +87,12 @@ const Category = () => { ))} </div> <div className='category-mega-box__child-wrapper !w-[260px] !flex !flex-col !gap-4'> - <div className='flex flex-col'> - <div className='grid grid-cols-2 max-h-full w-full gap-2'> - {category.childs.map((brand, index) => ( - (index < 8 ) && ( - <div key={brand.id} className='w-full flex items-center justify-center pb-2'> - <Link - href={createSlug('/shop/category/', brand.name, brand.id)} - className='category-mega-box__child-one w-fit h-full flex items-center justify-center ' - > - <Image src='https://erp.indoteknik.com/api/image/x_manufactures/x_logo_manufacture/661' alt='' width={104} height={44} objectFit='cover' /> - </Link> - </div> - ) - ))} - </div> - {category.childs.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> - <div className='flex w-60 h-20 object-cover'> - <Image src='https://erp.indoteknik.com/api/image/x_banner.banner/x_banner_image/397' alt='' width={275} height={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..4777fded --- /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..54acdf7c 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,91 @@ 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]); + 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]); + 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 +239,7 @@ const Checkout = () => { }, [bottomPopup]); useEffect(() => { - voucher(); + // voucher(); const loadExpedisi = async () => { let dataExpedisi = await ExpedisiList(); dataExpedisi = dataExpedisi.map((expedisi) => ({ @@ -210,13 +262,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 +295,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'); - const countDiscount = hitungDiscountVoucher(activeVoucher); + // SetDiscountVoucher(countDiscount); + // }, [activeVoucher, listVouchers]); - SetDiscountVoucher(countDiscount); - }, [activeVoucher, listVouchers]); + // useEffect(() => { + // if (!listVoucherShippings) return; + // if (!activeVoucherShipping) return; + + // const countDiscount = hitungDiscountVoucher(activeVoucherShipping, 'voucher_shipping'); + + // SetDiscountVoucherOngkir(countDiscount); + // }, [activeVoucherShipping, listVoucherShippings]); useEffect(() => { if (qVoucher === 'PASTIHEMAT' && listVouchers) { @@ -389,12 +461,38 @@ 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; - } + } 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, + }); - gtagPurchase(products, biayaKirim, isCheckouted.name); + 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 midtrans = async () => { for (const product of products) deleteItemCart({ productId: product.id }); @@ -413,16 +511,6 @@ 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 handlingActivateCode = async () => { @@ -483,6 +571,10 @@ const Checkout = () => { const finalShippingAmt = biayaKirim - discShippingAmt; + const totalDiscountVoucher = + cartCheckout?.discountVoucher + + (cartCheckout?.discountVoucherShipping || 0); + return ( <> <BottomPopup @@ -593,10 +685,23 @@ 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 +836,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> @@ -1004,7 +1100,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 +1168,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> )} @@ -1295,7 +1396,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 +1468,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> )} @@ -1431,10 +1537,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/components/CategoryDynamic.jsx b/src/lib/home/components/CategoryDynamic.jsx index f2d1a16f..b7798a24 100644 --- a/src/lib/home/components/CategoryDynamic.jsx +++ b/src/lib/home/components/CategoryDynamic.jsx @@ -33,9 +33,8 @@ const CategoryDynamic = () => { }; fetchCategoryData(); - }, [categoryManagement, categoryData]); + }, [categoryManagement.isLoading]); - return ( <div> {categoryManagement && categoryManagement.data?.map((category) => { @@ -83,6 +82,7 @@ const CategoryDynamic = () => { <NextImage src={childCategory.image ? childCategory.image : "/images/noimage.jpeg"} alt={childCategory.name} + className='p-2 ml-1' width={40} height={40} /> diff --git a/src/lib/home/components/CategoryPilihan.jsx b/src/lib/home/components/CategoryPilihan.jsx index 6568621c..6dbf771e 100644 --- a/src/lib/home/components/CategoryPilihan.jsx +++ b/src/lib/home/components/CategoryPilihan.jsx @@ -16,6 +16,7 @@ const CategoryPilihan = ({ id, categories }) => { const { categoryPilihan } = useCategoryPilihan(); const heroBanner = useQuery('categoryPilihan', bannerApi({ type: 'banner-category-list' })); return ( + categoryPilihan.length > 0 && ( <section> {isDesktop && ( <div> @@ -114,6 +115,8 @@ const CategoryPilihan = ({ id, categories }) => { </div> )} </section> + + ) ) } diff --git a/src/lib/home/components/PreferredBrand.jsx b/src/lib/home/components/PreferredBrand.jsx index ae12505d..b30fa5c9 100644 --- a/src/lib/home/components/PreferredBrand.jsx +++ b/src/lib/home/components/PreferredBrand.jsx @@ -66,7 +66,7 @@ const PreferredBrand = () => { </Link> )} </div> - <div className='border rounded border-gray_r-6'> + <div className=''> {manufactures.isLoading && <PreferredBrandSkeleton />} {!manufactures.isLoading && ( <Swiper {...swiperBanner}> diff --git a/src/lib/home/components/PromotionProgram.jsx b/src/lib/home/components/PromotionProgram.jsx index 99258d94..c2f76069 100644 --- a/src/lib/home/components/PromotionProgram.jsx +++ b/src/lib/home/components/PromotionProgram.jsx @@ -3,11 +3,16 @@ import Image from 'next/image' import { bannerApi } from '@/api/bannerApi'; import useDevice from '@/core/hooks/useDevice' import { Swiper, SwiperSlide } from 'swiper/react'; +import BannerPromoSkeleton from '../components/Skeleton/BannerPromoSkeleton'; const { useQuery } = require('react-query') const BannerSection = () => { const promotionProgram = useQuery('promotionProgram', bannerApi({ type: 'banner-promotion' })); const { isMobile, isDesktop } = useDevice() + if (promotionProgram.isLoading) { + return <BannerPromoSkeleton />; + } + return ( <div className='px-4 sm:px-0'> <div className='flex justify-between items-center mb-4 '> diff --git a/src/lib/home/components/Skeleton/BannerPromoSkeleton.jsx b/src/lib/home/components/Skeleton/BannerPromoSkeleton.jsx new file mode 100644 index 00000000..c5f39f19 --- /dev/null +++ b/src/lib/home/components/Skeleton/BannerPromoSkeleton.jsx @@ -0,0 +1,16 @@ +import useDevice from '@/core/hooks/useDevice' +import Skeleton from 'react-loading-skeleton' + +const BannerPromoSkeleton = () => { + const { isDesktop } = useDevice() + + return ( + <div className='grid grid-cols-1 md:grid-cols-3 gap-x-3'> + {Array.from({ length: isDesktop ? 3 : 1.2 }, (_, index) => ( + <Skeleton count={1} height={isDesktop ? 60 : 36} key={index} /> + ))} + </div> + ) +} + +export default BannerPromoSkeleton diff --git a/src/lib/product/components/CategorySection.jsx b/src/lib/product/components/CategorySection.jsx index 2af3db10..e8ebb095 100644 --- a/src/lib/product/components/CategorySection.jsx +++ b/src/lib/product/components/CategorySection.jsx @@ -35,7 +35,7 @@ const CategorySection = ({ categories }) => { <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" src={category?.image1920 ? category?.image1920 : '/images/noimage.jpeg'} width={56} height={48} alt={category?.name} /> + <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> diff --git a/src/lib/product/components/LobSectionCategory.jsx b/src/lib/product/components/LobSectionCategory.jsx index 03d6e8c0..5cd467e9 100644 --- a/src/lib/product/components/LobSectionCategory.jsx +++ b/src/lib/product/components/LobSectionCategory.jsx @@ -24,7 +24,6 @@ const LobSectionCategory = ({ categories }) => { }; const displayedCategories = categories[0]?.categoryIds; - return ( <section> {isDesktop && ( diff --git a/src/lib/product/components/ProductCard.jsx b/src/lib/product/components/ProductCard.jsx index 94db144d..22ce0533 100644 --- a/src/lib/product/components/ProductCard.jsx +++ b/src/lib/product/components/ProductCard.jsx @@ -17,8 +17,8 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => { const [discount, setDiscount] = useState(0); let voucherPastiHemat = 0; - - if (product?.voucherPastiHemat?.length > 0) { + + if (product?.voucherPastiHemat ? product?.voucherPastiHemat.length : voucherPastiHemat > 0) { const stringVoucher = product?.voucherPastiHemat[0]; const validJsonString = stringVoucher.replace(/'/g, '"'); voucherPastiHemat = JSON.parse(validJsonString); @@ -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 1933c5f0..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) diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx index 09534a5d..eaeac71a 100644 --- a/src/lib/product/components/ProductSearch.jsx +++ b/src/lib/product/components/ProductSearch.jsx @@ -42,7 +42,7 @@ const ProductSearch = ({ const [q, setQ] = useState(query?.q || '*'); const [search, setSearch] = useState(query?.q || '*'); const [limit, setLimit] = useState(router.query?.limit || 30); - const [orderBy, setOrderBy] = useState(router.query?.orderBy || 'popular'); + const [orderBy, setOrderBy] = useState(router.query?.orderBy); const [finalQuery, setFinalQuery] = useState({}); const [queryFinal, setQueryFinal] = useState({}); const [dataCategoriesProduct, setDataCategoriesProduct] = useState([]) @@ -267,6 +267,7 @@ const ProductSearch = ({ const orderOptions = [ + { value: '', label: 'Pilih Filter' }, { value: 'price-asc', label: 'Harga Terendah' }, { value: 'price-desc', label: 'Harga Tertinggi' }, { value: 'popular', label: 'Populer' }, 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/transaction/components/Transaction.jsx b/src/lib/transaction/components/Transaction.jsx index 9bef895a..4d401037 100644 --- a/src/lib/transaction/components/Transaction.jsx +++ b/src/lib/transaction/components/Transaction.jsx @@ -1,6 +1,6 @@ import Spinner from '@/core/components/elements/Spinner/Spinner'; import NextImage from 'next/image'; -import rejectImage from "../../../../public/images/reject.png" +import rejectImage from '../../../../public/images/reject.png'; import useTransaction from '../hooks/useTransaction'; import TransactionStatusBadge from './TransactionStatusBadge'; import Divider from '@/core/components/elements/Divider/Divider'; @@ -40,7 +40,7 @@ import rejectProductApi from '../api/rejectProductApi'; import { useRouter } from 'next/router'; const Transaction = ({ id }) => { - const router = useRouter() + const router = useRouter(); const [isModalOpen, setIsModalOpen] = useState(false); const [selectedProduct, setSelectedProduct] = useState(null); const [reason, setReason] = useState(''); @@ -152,7 +152,10 @@ const Transaction = ({ id }) => { const memoizeVariantGroupCardReject = useMemo( () => ( <div className='p-4 pt-0 flex flex-col gap-y-3'> - <VariantGroupCard variants={transaction.data?.productsRejectLine} buyMore /> + <VariantGroupCard + variants={transaction.data?.productsRejectLine} + buyMore + /> </div> ), [transaction.data] @@ -182,26 +185,25 @@ const Transaction = ({ id }) => { }; const handleRejectProduct = async () => { - try{ + try { if (!reason.trim()) { toast.error('Masukkan alasan terlebih dahulu'); return; - }else{ - let idSo = transaction?.data.id - let idProduct = selectedProduct?.id - await rejectProductApi({ idSo, idProduct, reason}); + } else { + let idSo = transaction?.data.id; + let idProduct = selectedProduct?.id; + await rejectProductApi({ idSo, idProduct, reason }); closeModal(); - toast.success("Produk berhasil di reject") + toast.success('Produk berhasil di reject'); setTimeout(() => { window.location.reload(); - }, 1500); + }, 1500); } - }catch(error){ + } catch (error) { toast.error('Gagal reject produk. Silakan coba lagi.'); } }; - return ( transaction.data?.name && ( <> @@ -390,14 +392,20 @@ const Transaction = ({ id }) => { <p className='text-gray_r-11 leading-none'>Dokumen PO</p> <button type='button' - className='btn-light py-1.5 px-3 ml-auto' + className='inline-block text-danger-500' onClick={ transaction.data?.purchaseOrderFile ? () => downloadPurchaseOrder(transaction.data) - : openUploadPo + : transaction?.data.invoices.length < 1 + ? openUploadPo + : '' } > - {transaction.data?.purchaseOrderFile ? 'Download' : 'Upload'} + {transaction?.data?.purchaseOrderFile + ? 'Download' + : transaction?.data.invoices.length < 1 + ? 'Upload' + : '-'} </button> </div> </div> @@ -406,13 +414,13 @@ const Transaction = ({ id }) => { <Divider /> <div className='font-medium p-4'>Detail Produk</div> - {transaction?.data?.products.length > 0? ( - <div> - {memoizeVariantGroupCard} - </div> + {transaction?.data?.products.length > 0 ? ( + <div>{memoizeVariantGroupCard}</div> ) : ( - <div className='badge-red text-sm px-2 ml-4'>Semua produk telah di reject</div> - )} + <div className='badge-red text-sm px-2 ml-4'> + Semua produk telah di reject + </div> + )} {transaction?.data?.productsRejectLine.length > 0 && ( <div> @@ -594,12 +602,16 @@ const Transaction = ({ id }) => { onClick={ transaction.data?.purchaseOrderFile ? () => downloadPurchaseOrder(transaction.data) - : openUploadPo + : transaction?.data.invoices.length < 1 + ? openUploadPo + : '' } > {transaction?.data?.purchaseOrderFile ? 'Download' - : 'Upload'} + : transaction?.data.invoices.length < 1 + ? 'Upload' + : '-'} </button> </div> </> @@ -628,9 +640,11 @@ const Transaction = ({ id }) => { <div className='text-h-sm font-semibold mt-10 mb-4'> Pengiriman </div> - {transaction?.data?.pickings.length == 0 && ( - <div className='badge-red text-sm'>Belum ada pengiriman</div> - )} + {transaction?.data?.pickings.length == 0 && ( + <div className='badge-red text-sm'> + Belum ada pengiriman + </div> + )} <div className='grid grid-cols-1 gap-1 w-2/3'> {transaction?.data?.pickings?.map((airway) => ( <button @@ -646,7 +660,9 @@ const Transaction = ({ id }) => { </div> <div className='flex gap-x-2'> <div className='text-sm text-gray-600 badge-green leading-[1.5] mt-1'> - {airway?.delivered ? 'Pesanan Tiba' : 'Sedang Dikirim'} + {airway?.delivered + ? 'Pesanan Tiba' + : 'Sedang Dikirim'} </div> <ChevronRightIcon className='w-5 stroke-2' /> </div> @@ -655,51 +671,53 @@ const Transaction = ({ id }) => { </div> </div> <div className='invoice w-1/2 '> - <div className='text-h-sm font-semibold mt-10 mb-4 '>Invoice</div> - {transaction.data?.invoices?.length === 0 && ( - <div className='badge-red text-sm'>Belum ada invoice</div> - )} - <div className='grid grid-cols-1 gap-1 w-2/3 '> - {transaction.data?.invoices?.map((invoice, index) => ( - <Link href={`/my/invoices/${invoice.id}`} key={index}> - <div className='shadow rounded-md p-4 text-gray_r-12 font-normal flex justify-between'> - <div> - <p className='mb-1'>{invoice?.name}</p> - <div className='flex items-center gap-x-1'> - {invoice.amountResidual > 0 ? ( - <div className='badge-red'>Belum Lunas</div> - ) : ( - <div className='badge-green'>Lunas</div> - )} - <p className='text-caption-2 text-gray_r-11'> - {currencyFormat(invoice.amountTotal)} - </p> - </div> + <div className='text-h-sm font-semibold mt-10 mb-4 '> + Invoice + </div> + {transaction.data?.invoices?.length === 0 && ( + <div className='badge-red text-sm'>Belum ada invoice</div> + )} + <div className='grid grid-cols-1 gap-1 w-2/3 '> + {transaction.data?.invoices?.map((invoice, index) => ( + <Link href={`/my/invoices/${invoice.id}`} key={index}> + <div className='shadow rounded-md p-4 text-gray_r-12 font-normal flex justify-between'> + <div> + <p className='mb-1'>{invoice?.name}</p> + <div className='flex items-center gap-x-1'> + {invoice.amountResidual > 0 ? ( + <div className='badge-red'>Belum Lunas</div> + ) : ( + <div className='badge-green'>Lunas</div> + )} + <p className='text-caption-2 text-gray_r-11'> + {currencyFormat(invoice.amountTotal)} + </p> </div> - <ChevronRightIcon className='w-5 stroke-2' /> </div> - </Link> - ))} - </div> + <ChevronRightIcon className='w-5 stroke-2' /> + </div> + </Link> + ))} + </div> </div> </div> <div className='text-h-sm font-semibold mt-4 mb-4'> Rincian Pembelian </div> - {transaction?.data?.products?.length > 0? ( - <table className='table-data'> - <thead> - <tr> - <th>Nama Produk</th> - {/* <th>Diskon</th> */} - <th>Jumlah</th> - <th>Harga</th> - <th>Subtotal</th> - <th></th> - </tr> - </thead> - <tbody> + {transaction?.data?.products?.length > 0 ? ( + <table className='table-data'> + <thead> + <tr> + <th>Nama Produk</th> + {/* <th>Diskon</th> */} + <th>Jumlah</th> + <th>Harga</th> + <th>Subtotal</th> + <th></th> + </tr> + </thead> + <tbody> {transaction?.data?.products?.map((product) => ( <tr key={product.id}> <td className='flex'> @@ -711,37 +729,37 @@ const Transaction = ({ id }) => { )} className='w-[20%] flex-shrink-0' > - <div className='relative'> + <div className='relative'> <Image src={product?.parent?.image} alt={product?.name} className='object-contain object-center border border-gray_r-6 h-32 w-full rounded-md' /> - <div className='absolute top-0 right-4 flex mt-3'> - <div className='gambarB '> - {product.isSni && ( - <ImageNext - src='/images/sni-logo.png' - alt='SNI Logo' - className='w-2 h-4 object-contain object-top sm:h-4' - width={50} - height={50} - /> - )} - </div> - <div className='gambarC '> - {product.isTkdn && ( - <ImageNext - src='/images/TKDN.png' - alt='TKDN' - className='w-5 h-4 object-contain object-top ml-1 sm:h-4' - width={50} - height={50} - /> - )} + <div className='absolute top-0 right-4 flex mt-3'> + <div className='gambarB '> + {product.isSni && ( + <ImageNext + src='/images/sni-logo.png' + alt='SNI Logo' + className='w-2 h-4 object-contain object-top sm:h-4' + width={50} + height={50} + /> + )} + </div> + <div className='gambarC '> + {product.isTkdn && ( + <ImageNext + src='/images/TKDN.png' + alt='TKDN' + className='w-5 h-4 object-contain object-top ml-1 sm:h-4' + width={50} + height={50} + /> + )} + </div> </div> </div> - </div> </Link> <div className='px-2 text-left'> <Link @@ -774,33 +792,42 @@ const Transaction = ({ id }) => { {currencyFormat(product.price.price)} </div> )} */} - <div>{currencyFormat(product.price.priceDiscount)}</div> + <div> + {currencyFormat(product.price.priceDiscount)} + </div> </td> <td>{currencyFormat(product.price.subtotal)}</td> {/* {auth?.feature.soApproval && (auth.webRole == 2 || auth.webRole == 3) && (transaction.data.isReaject == false) && ( */} - {auth?.feature.soApproval && (auth.webRole == 2 || auth.webRole == 3) && (router.asPath.includes("/my/quotations/")) && transaction.data?.status == 'draft' && ( - <td> - <button - className="bg-red-500 text-white py-1 px-3 rounded" - onClick={() => openModal(product)} - > - Reject - </button> - </td> - )} + {auth?.feature.soApproval && + (auth.webRole == 2 || auth.webRole == 3) && + router.asPath.includes('/my/quotations/') && + transaction.data?.status == 'draft' && ( + <td> + <button + className='bg-red-500 text-white py-1 px-3 rounded' + onClick={() => openModal(product)} + > + Reject + </button> + </td> + )} </tr> ))} </tbody> </table> ) : ( - <div className='badge-red text-sm'>Semua produk telah di reject</div> + <div className='badge-red text-sm'> + Semua produk telah di reject + </div> )} - + {isModalOpen && ( <div className='fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center'> - <div className='bg-white p-4 rounded w-96 + <div + className='bg-white p-4 rounded w-96 ease-in-out opacity-100 - transform transition-transform duration-300 scale-100'> + transform transition-transform duration-300 scale-100' + > <h2 className='text-lg mb-2'>Berikan Alasan</h2> <textarea value={reason} @@ -826,117 +853,116 @@ const Transaction = ({ id }) => { </div> )} - {transaction?.data?.products?.map((product) => ( - <div className='flex justify-end mt-4' key={product.id}> - <div className='w-1/4 grid grid-cols-2 gap-y-3 text-gray_r-12/80'> - <div className='text-right'>Subtotal</div> - <div className='text-right font-medium'> - {currencyFormat(transaction.data?.amountUntaxed)} - </div> + {transaction?.data?.products?.length > 0 && ( + <div className='flex justify-end mt-4'> + <div className='w-1/4 grid grid-cols-2 gap-y-3 text-gray_r-12/80'> + <div className='text-right'>Subtotal</div> + <div className='text-right font-medium'> + {currencyFormat(transaction.data?.amountUntaxed)} + </div> - <div className='text-right'>PPN 11%</div> - <div className='text-right font-medium'> - {currencyFormat(transaction.data?.amountTax)} - </div> + <div className='text-right'>PPN 11%</div> + <div className='text-right font-medium'> + {currencyFormat(transaction.data?.amountTax)} + </div> - <div className='text-right whitespace-nowrap'> - Biaya Pengiriman - </div> - <div className='text-right font-medium'> - {currencyFormat(transaction.data?.deliveryAmount)} - </div> + <div className='text-right whitespace-nowrap'> + Biaya Pengiriman + </div> + <div className='text-right font-medium'> + {currencyFormat(transaction.data?.deliveryAmount)} + </div> - <div className='text-right'>Grand Total</div> - <div className='text-right font-medium text-gray_r-12'> - {currencyFormat(transaction.data?.amountTotal)} + <div className='text-right'>Grand Total</div> + <div className='text-right font-medium text-gray_r-12'> + {currencyFormat(transaction.data?.amountTotal)} + </div> </div> </div> - </div> - ))} - - + )} {transaction?.data?.productsRejectLine.length > 0 && ( - <div className='text-h-sm font-semibold mt-10 mb-4'> - Rincian Produk Reject - </div> + <div className='text-h-sm font-semibold mt-10 mb-4'> + Rincian Produk Reject + </div> )} {transaction?.data?.productsRejectLine.length > 0 && ( - <table className='table-data'> - <thead> - <tr> - <th>Nama Produk</th> - {/* <th>Diskon</th> */} - <th>Jumlah</th> - <th>Harga</th> - <th>Subtotal</th> - </tr> - </thead> - <tbody> - {transaction?.data?.productsRejectLine?.map((product) => ( - <tr key={product.id}> - <td className='flex'> + <table className='table-data'> + <thead> + <tr> + <th>Nama Produk</th> + {/* <th>Diskon</th> */} + <th>Jumlah</th> + <th>Harga</th> + <th>Subtotal</th> + </tr> + </thead> + <tbody> + {transaction?.data?.productsRejectLine?.map((product) => ( + <tr key={product.id}> + <td className='flex'> + <Link + href={createSlug( + '/shop/product/', + product?.parent.name, + product?.parent.id + )} + className='w-[20%] flex-shrink-0' + > + <Image + src={product?.parent?.image} + alt={product?.name} + className='object-contain object-center border border-gray_r-6 h-32 w-full rounded-md' + /> + </Link> + <div className='px-2 text-left'> <Link href={createSlug( '/shop/product/', product?.parent.name, product?.parent.id )} - className='w-[20%] flex-shrink-0' + 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-32 w-full rounded-md' - /> + {product?.parent?.name} </Link> - <div className='px-2 text-left'> - <Link - href={createSlug( - '/shop/product/', - product?.parent.name, - product?.parent.id - )} - className='line-clamp-2 leading-6 !text-gray_r-12 font-normal' - > - {product?.parent?.name} - </Link> - <div className='text-gray_r-11 mt-2'> - {product?.code}{' '} - {product?.attributes.length > 0 - ? `| ${product?.attributes.join(', ')}` - : ''} - </div> + <div className='text-gray_r-11 mt-2'> + {product?.code}{' '} + {product?.attributes.length > 0 + ? `| ${product?.attributes.join(', ')}` + : ''} </div> - </td> - {/* <td> + </div> + </td> + {/* <td> {product.price.discountPercentage > 0 ? `${product.price.discountPercentage}%` : ''} </td> */} - <td>{product.quantity}</td> - <td> - {/* {product.price.discountPercentage > 0 && ( + <td>{product.quantity}</td> + <td> + {/* {product.price.discountPercentage > 0 && ( <div className='line-through mb-1 text-caption-1 text-gray_r-12/70'> {currencyFormat(product.price.price)} </div> )} */} - <div>{currencyFormat(product.price.priceDiscount)}</div> - </td> - <td className='flex justify-center'> - <NextImage - src={rejectImage} - alt='Reject' - width={90} - height={30} - /> - </td> - </tr> - ))} - </tbody> - </table> + <div> + {currencyFormat(product.price.priceDiscount)} + </div> + </td> + <td className='flex justify-center'> + <NextImage + src={rejectImage} + alt='Reject' + width={90} + height={30} + /> + </td> + </tr> + ))} + </tbody> + </table> )} - </div> </div> </DesktopView> diff --git a/src/lib/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/search.js b/src/pages/api/shop/search.js index b6b8c795..6f98efcb 100644 --- a/src/pages/api/shop/search.js +++ b/src/pages/api/shop/search.js @@ -39,10 +39,13 @@ export default async function handler(req, res) { paramOrderBy += 'flashsale_price_f ASC'; break; default: - paramOrderBy += 'product_rating_f DESC, price_discount_f DESC'; + 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', @@ -51,12 +54,12 @@ export default async function handler(req, res) { 'indent=true', `facet.query=${escapeSolrQuery(q)}`, `q.op=${operation}`, - `q=${escapeSolrQuery(q)}`, + `q=${newQ}`, 'qf=name_s', `start=${parseInt(offset)}`, `rows=${limit}`, `sort=${paramOrderBy}`, - `fq=-publish_b:false`, + `fq=-publish_b:false, product_rating_f:[8 TO *], price_tier1_v2_f:[1 TO *]`, ]; if (priceFrom > 0 || priceTo > 0) { @@ -77,7 +80,10 @@ export default async function handler(req, res) { parameter.push( `fq=${brand .split(',') - .map((manufacturer) => `manufacture_name:"${encodeURIComponent(manufacturer)}"`) + .map( + (manufacturer) => + `manufacture_name:"${encodeURIComponent(manufacturer)}"` + ) .join(' OR ')}` ); if (category) @@ -120,12 +126,14 @@ export default async function handler(req, res) { const escapeSolrQuery = (query) => { if (query == '*') return query; + + query = query.replace(/-/g, ' '); - const specialChars = /([\+\-\!\(\)\{\}\[\]\^"~\*\?:\\\/])/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.replace(specialChars, '\\$1'); } return word; }); @@ -133,6 +141,7 @@ const escapeSolrQuery = (query) => { return escapedWords.join(' '); }; + /*const productResponseMap = (products, pricelist) => { return products.map((product) => { let price = product.price_tier1_v2_f || 0 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 d649ee17..613950a6 100644 --- a/src/pages/index.jsx +++ b/src/pages/index.jsx @@ -8,12 +8,14 @@ import DesktopView from '@/core/components/views/DesktopView'; import MobileView from '@/core/components/views/MobileView'; import { FlashSaleSkeleton } from '@/lib/flashSale/skeleton/FlashSaleSkeleton'; import PreferredBrandSkeleton from '@/lib/home/components/Skeleton/PreferredBrandSkeleton'; +import BannerPromoSkeleton from '@/lib/home/components/Skeleton/BannerPromoSkeleton'; import PromotinProgram from '@/lib/promotinProgram/components/HomePage'; import PagePopupIformation from '~/modules/popup-information'; 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') @@ -45,9 +47,12 @@ const FlashSale = dynamic( } ); -// const ProgramPromotion = dynamic(() => -// import('@/lib/home/components/PromotionProgram') -// ); +const ProgramPromotion = dynamic(() => + import('@/lib/home/components/PromotionProgram'), +{ + loading: () => <BannerPromoSkeleton />, +} +); const BannerSection = dynamic(() => import('@/lib/home/components/BannerSection') @@ -93,8 +98,8 @@ export default function Home({categoryId}) { }, []) const [dataCategories, setDataCategories] = useState([]) - return ( + <> <BasicLayout> <Seo title='Indoteknik.com: B2B Industrial Supply & Solution' @@ -102,11 +107,9 @@ export default function Home({categoryId}) { 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 /> @@ -131,28 +134,32 @@ export default function Home({categoryId}) { </div> <div className='my-16 flex flex-col gap-y-8'> - <div className='my-16 flex flex-col gap-y-8'> <ServiceList /> <div id='flashsale'> <PreferredBrand /> </div> {!auth?.feature?.soApproval && ( <> - {/* <ProgramPromotion /> <FlashSale /> */} + <DelayRender renderAfter={200}> + <ProgramPromotion /> + </DelayRender> + <DelayRender renderAfter={200}> + <FlashSale /> + </DelayRender> </> )} <PromotinProgram /> - <CategoryPilihan categories={dataCategories}/> - <CategoryDynamic/> + {dataCategories &&( + <CategoryPilihan categories={dataCategories} /> + )} + <CategoryDynamic /> <CategoryHomeId /> <BannerSection /> <CustomerReviews /> </div> - </div> </div> - </DesktopView> - - <MobileView> + </DesktopView> + <MobileView> <DelayRender renderAfter={200}> <HeroBanner /> </DelayRender> @@ -168,7 +175,7 @@ export default function Home({categoryId}) { {!auth?.feature?.soApproval && ( <> <DelayRender renderAfter={400}> - {/* <ProgramPromotion /> */} + <ProgramPromotion /> </DelayRender> <DelayRender renderAfter={600}> <FlashSale /> @@ -179,8 +186,10 @@ export default function Home({categoryId}) { <PromotinProgram /> </DelayRender> <DelayRender renderAfter={600}> - <CategoryPilihan categories={dataCategories}/> - <CategoryDynamicMobile/> + {dataCategories &&( + <CategoryPilihan categories={dataCategories} /> + )} + <CategoryDynamicMobile /> </DelayRender> <DelayRender renderAfter={800}> <PopularProduct /> @@ -195,5 +204,6 @@ export default function Home({categoryId}) { </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/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 505dcab4..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 { diff --git a/src/utils/solrMapping.js b/src/utils/solrMapping.js index d4694eb2..fee474be 100644 --- a/src/utils/solrMapping.js +++ b/src/utils/solrMapping.js @@ -38,6 +38,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 || [] }; |
