summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/promoApi.js8
-rw-r--r--src/core/components/elements/Footer/PromoOffer.tsx112
-rw-r--r--src/core/components/elements/Footer/style/promoOffer.module.css39
-rw-r--r--src/core/components/elements/Navbar/NavbarDesktop.jsx45
-rw-r--r--src/core/components/elements/Navbar/style/NavbarDesktop.module.css14
-rw-r--r--src/core/components/elements/Sidebar/Sidebar.jsx7
-rw-r--r--src/lib/brand/components/BrandCard.jsx8
-rw-r--r--src/lib/category/components/Category.jsx79
-rw-r--r--src/lib/checkout/api/checkoutApi.js34
-rw-r--r--src/lib/checkout/api/getVoucher.js37
-rw-r--r--src/lib/checkout/components/Checkout.jsx219
-rw-r--r--src/lib/home/api/categoryManagementApi.js8
-rw-r--r--src/lib/home/components/CategoryDynamic.jsx84
-rw-r--r--src/lib/home/components/CategoryDynamicMobile.jsx101
-rw-r--r--src/lib/home/components/CategoryPilihan.jsx66
-rw-r--r--src/lib/home/components/PreferredBrand.jsx43
-rw-r--r--src/lib/home/components/PromotionProgram.jsx12
-rw-r--r--src/lib/home/hooks/useCategoryManagement.js13
-rw-r--r--src/lib/product/components/CategorySection.jsx111
-rw-r--r--src/lib/product/components/ProductCard.jsx152
-rw-r--r--src/lib/product/components/ProductSearch.jsx18
-rw-r--r--src/lib/quotation/components/Quotation.jsx62
-rw-r--r--src/lib/transaction/api/rejectProductApi.js9
-rw-r--r--src/lib/transaction/components/Transaction.jsx499
-rw-r--r--src/lib/variant/components/VariantCard.jsx99
-rw-r--r--src/pages/index.jsx42
-rw-r--r--src/pages/shop/category/[slug].jsx9
-rw-r--r--src/pages/shop/promo/[slug].tsx8
-rw-r--r--src/pages/shop/promo/index.jsx40
-rw-r--r--src/pages/shop/promo/index.tsx186
-rw-r--r--src/styles/globals.css5
-rw-r--r--src/utils/solrMapping.js1
32 files changed, 1620 insertions, 550 deletions
diff --git a/src/api/promoApi.js b/src/api/promoApi.js
index 0e82c8b9..3f85db8e 100644
--- a/src/api/promoApi.js
+++ b/src/api/promoApi.js
@@ -13,11 +13,13 @@ export const fetchPromoItems = async (type) => {
}
};
-export const fetchPromoItemsSolr = async (type) => {
+export const fetchPromoItemsSolr = async (type, start, rows) => {
// let query = type ? `type_value_s:${type}` : '*:*';
let sort ='sort=if(exists(sequence_i),0,1) asc, sequence_i asc, if(exists(total_qty_sold_f), total_qty_sold_f, -1) desc';
- let start = 0
- let rows = 100
+ // let start = 0
+ // let rows = 100
+ // let start = 0
+ // let rows = 10
try {
const queryParams = new URLSearchParams({ q: type });
const response = await fetch(`/solr/promotion_program_lines/select?${queryParams.toString()}&rows=${rows}&start=${start}&${sort}`);
diff --git a/src/core/components/elements/Footer/PromoOffer.tsx b/src/core/components/elements/Footer/PromoOffer.tsx
new file mode 100644
index 00000000..a5432b6a
--- /dev/null
+++ b/src/core/components/elements/Footer/PromoOffer.tsx
@@ -0,0 +1,112 @@
+
+import React from "react";
+// import { useGeneralSetting } from "@/common/state-management/general-setting";
+import { FormEvent, useEffect, useState } from "react";
+import toast from "react-hot-toast";
+import style from "../Footer/style/promoOffer.module.css"
+const PromoOffer = ()=>{
+ // const { data, isLoading, fetchData } = useGeneralSetting();
+ const [formData, setFormData] = useState<FormData>({
+ email: "",
+ name: "",
+ telephone: "",
+ message: "",
+ });
+
+ useEffect(() => {
+ // fetchData();
+ }, []);
+
+ type FormData = {
+ email: string;
+ name: string;
+ telephone: string;
+ message: string;
+ };
+
+ const [errors, setErrors] = useState({
+ email: false,
+ name: false,
+ message: false,
+ });
+
+
+const handleGetOffer = async (e: FormEvent) => {
+ e.preventDefault();
+ let loadingToast;
+ try {
+ loadingToast = toast.loading("Mengirimkan formulir...");
+ const response = await fetch("/api/contactus", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({ ...formData, from: "newsletter" }),
+ });
+
+ if (response.ok) {
+ toast.dismiss(loadingToast);
+ toast.success("Terima kasih telah menghubungi kami");
+ setFormData({
+ email: "",
+ name: "",
+ telephone: "",
+ message: "",
+ });
+ } else {
+ toast.dismiss(loadingToast);
+ toast.error("Gagal mengirimkan formulir. Silakan coba lagi nanti.");
+ }
+ } catch (error) {
+ toast.dismiss(loadingToast);
+ console.error("Gagal mengirimkan formulir", error);
+ toast.error("Terjadi kesalahan. Silakan coba lagi nanti.");
+ }
+ };
+
+
+
+
+ return(
+ <div className=" py-3 mb-3">
+ <div className="md:flex container mx-auto md:justify-between md:items-center md:gap-x-20">
+ <div className="">
+ <span className="text-black font-semibold text-sm md:text-lg">
+ Dapatkan Promo Menarik Setiap Bulan{" "}
+ </span>
+ <p>
+ Promo produk dengan penawaran terbatas setiap bulannya!
+ </p>
+ </div>
+ <div className=" flex-1 flex items-center h-full justify-end text-sm text-slate-950">
+ <form onSubmit={handleGetOffer} className={style['form-input']}>
+ <div className="flex justify-start w-full">
+ <div className="flex justify-end">
+ <input
+ type="email"
+ value={formData.email}
+ onChange={(e) =>
+ setFormData({ ...formData, email: e.target.value })
+ }
+ className={style['input']}
+ placeholder="Masukkan email anda disini"
+ />
+ <button
+ type="submit"
+ className={style['button']}
+ >
+ Dapatkan
+ </button>
+ </div>
+
+
+ </div>
+ </form>
+
+ </div>
+ </div>
+ </div>
+ )
+ };
+
+ export default PromoOffer;
diff --git a/src/core/components/elements/Footer/style/promoOffer.module.css b/src/core/components/elements/Footer/style/promoOffer.module.css
new file mode 100644
index 00000000..3184182d
--- /dev/null
+++ b/src/core/components/elements/Footer/style/promoOffer.module.css
@@ -0,0 +1,39 @@
+.form-input {
+ @apply
+ h-full
+ w-[514px]
+ text-slate-950
+ flex
+ justify-center;
+}
+
+.input{
+ @apply w-[320px]
+ sm:w-[320px]
+ md:w-[500px]
+ xl:w-[514px]
+ lg:w-[514px]
+ 2xl:w-[514px]
+ text-black
+ py-2
+ h-11
+ md:py-3
+ px-4
+ bg-[#FDF1C7]
+ rounded-3xl
+ focus:outline-none
+ ;
+}
+
+.button{
+ @apply bg-[#FAD147]
+ absolute
+ py-1.5
+ rounded-3xl
+ text-black
+ md:py-2.5
+ px-4
+ h-11
+ z-0
+ ;
+} \ No newline at end of file
diff --git a/src/core/components/elements/Navbar/NavbarDesktop.jsx b/src/core/components/elements/Navbar/NavbarDesktop.jsx
index 9de761a2..f157297e 100644
--- a/src/core/components/elements/Navbar/NavbarDesktop.jsx
+++ b/src/core/components/elements/Navbar/NavbarDesktop.jsx
@@ -19,6 +19,7 @@ import DesktopView from '../../views/DesktopView';
import Link from '../Link/Link';
import NavbarUserDropdown from './NavbarUserDropdown';
import NextImage from 'next/image';
+import style from "./style/NavbarDesktop.module.css";
import {
Button,
Menu,
@@ -203,45 +204,65 @@ const NavbarDesktop = () => {
</div>
</button>
<div className='w-6/12 flex px-1 divide-x divide-gray_r-6'>
+ <Link
+ href="/shop/promo"
+ className={`${
+ router.asPath === '/shop/promo' && 'bg-gray_r-3'
+ } flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition group relative`} // Added relative position
+ target="_blank"
+ rel="noreferrer"
+ >
+ <p className="absolute inset-0 flex justify-center items-center group-hover:scale-105 group-hover:text-red-500 transition-transform duration-200 z-10">Semua Promo</p>
+ <div className='w-full h-full flex justify-end items-start'>
+ <Image
+ src='/images/ICON PROMO DISKON.svg'
+ alt='promo'
+ width={100}
+ height={100}
+ quality={100}
+ className={`inline-block z-20`}
+ />
+ </div>
+ </Link>
+
+
<Link
href='/shop/brands'
- className={`${
+ className={`${
router.asPath === '/shop/brands' && 'bg-gray_r-3'
- } p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition`}
+ } p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition group`}
target='_blank'
rel='noreferrer'
>
- Semua Brand
+ <p className="group-hover:scale-105 group-hover:text-red-500 transition-transform duration-200">Semua Brand</p>
</Link>
<Link
href='/shop/search?orderBy=stock'
className={`${
router.asPath === '/shop/search?orderBy=stock' &&
'bg-gray_r-3'
- } p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition`}
+ } p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition group`}
target='_blank'
rel='noreferrer'
>
- Ready Stock
+ <p className="group-hover:scale-105 group-hover:text-red-500 transition-transform duration-200">Ready Stock</p>
</Link>
<Link
href='https://blog.indoteknik.com/'
- className='p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition'
+ className='p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition group'
target='_blank'
rel='noreferrer noopener'
>
- Blog Indoteknik
+ <p className="group-hover:scale-105 group-hover:text-red-500 transition-transform duration-200">Blog Indoteknik</p>
</Link>
- <Link
+ {/* <Link
href='/video'
- className={`${
- router.asPath === '/video' && 'bg-gray_r-3'
- } p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition`}
+ className='p-4 flex-1 flex justify-center items-center !text-gray_r-12/80 hover:bg-gray_r-3 idt-transition'
target='_blank'
rel='noreferrer'
>
Indoteknik TV
- </Link>
+ </Link> */}
</div>
<div className='w-3/12 flex gap-x-1 relative'>
diff --git a/src/core/components/elements/Navbar/style/NavbarDesktop.module.css b/src/core/components/elements/Navbar/style/NavbarDesktop.module.css
new file mode 100644
index 00000000..9cddb127
--- /dev/null
+++ b/src/core/components/elements/Navbar/style/NavbarDesktop.module.css
@@ -0,0 +1,14 @@
+/* navbarDesktop.module.css */
+@keyframes blink {
+ 0%, 100% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 0;
+ }
+}
+
+.blink {
+ animation: blink 0.8s infinite;
+}
+
diff --git a/src/core/components/elements/Sidebar/Sidebar.jsx b/src/core/components/elements/Sidebar/Sidebar.jsx
index 38fcdef8..55838890 100644
--- a/src/core/components/elements/Sidebar/Sidebar.jsx
+++ b/src/core/components/elements/Sidebar/Sidebar.jsx
@@ -117,6 +117,9 @@ const Sidebar = ({ active, close }) => {
</>
)}
</div>
+ <SidebarLink className={itemClassName} href='/shop/promo'>
+ Semua Promo
+ </SidebarLink>
<SidebarLink className={itemClassName} href='/shop/brands'>
Semua Brand
</SidebarLink>
@@ -128,9 +131,9 @@ const Sidebar = ({ active, close }) => {
>
Blog Indoteknik
</SidebarLink>
- <SidebarLink className={itemClassName} href='/video'>
+ {/* <SidebarLink className={itemClassName} href='/video'>
Indoteknik TV
- </SidebarLink>
+ </SidebarLink> */}
<SidebarLink className={itemClassName} href='/tentang-kami'>
Tentang Indoteknik
</SidebarLink>
diff --git a/src/lib/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/category/components/Category.jsx b/src/lib/category/components/Category.jsx
index e6ea5acf..c147a3b3 100644
--- a/src/lib/category/components/Category.jsx
+++ b/src/lib/category/components/Category.jsx
@@ -2,10 +2,14 @@ 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'
const Category = () => {
const [categories, setCategories] = useState([])
+ const [openCategories, setOpenCategory] = useState([]);
+
useEffect(() => {
const loadCategories = async () => {
@@ -31,7 +35,7 @@ const Category = () => {
<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'
@@ -39,33 +43,74 @@ const Category = () => {
{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'>
+ <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} />
+ </div>
+ </div>
</div>
</div>
))}
diff --git a/src/lib/checkout/api/checkoutApi.js b/src/lib/checkout/api/checkoutApi.js
index 24f1868a..fd982fff 100644
--- a/src/lib/checkout/api/checkoutApi.js
+++ b/src/lib/checkout/api/checkoutApi.js
@@ -1,28 +1,20 @@
-import odooApi from '@/core/api/odooApi'
-import { getAuth } from '@/core/utils/auth'
+import odooApi from '@/core/api/odooApi';
+import { getAuth } from '@/core/utils/auth';
export const checkoutApi = async ({ data }) => {
- const auth = getAuth()
+ const auth = getAuth();
const dataCheckout = await odooApi(
'POST',
`/api/v1/partner/${auth.partnerId}/sale_order/checkout`,
data
- )
- return dataCheckout
-}
+ );
+ return dataCheckout;
+};
-export const getProductsCheckout = async (voucher, query) => {
- const id = getAuth()?.id
- let products
- if(voucher && query){
- products = await odooApi('GET',`/api/v1/user/${id}/sale_order/checkout?voucher=${voucher}&source=buy`)
- }else if (voucher){
- products = await odooApi('GET',`/api/v1/user/${id}/sale_order/checkout?voucher=${voucher}`)
- }else if (query) {
- products = await odooApi('GET',`/api/v1/user/${id}/sale_order/checkout?source=buy`)
- }else{
- products = await odooApi('GET',`/api/v1/user/${id}/sale_order/checkout`)
- }
-
- return products
-}
+export const getProductsCheckout = async (query) => {
+ const queryParam = new URLSearchParams(query);
+ const userId = getAuth()?.id;
+ const url = `/api/v1/user/${userId}/sale_order/checkout?${queryParam.toString()}`;
+ const result = await odooApi('GET', url);
+ return result;
+};
diff --git a/src/lib/checkout/api/getVoucher.js b/src/lib/checkout/api/getVoucher.js
index 07cf376e..54c8cce5 100644
--- a/src/lib/checkout/api/getVoucher.js
+++ b/src/lib/checkout/api/getVoucher.js
@@ -1,21 +1,24 @@
-import odooApi from '@/core/api/odooApi'
+import odooApi from '@/core/api/odooApi';
-export const getVoucher = async (id, source) => {
- let dataVoucher = null
- if(source){
- dataVoucher = await odooApi('GET', `/api/v1/user/${id}/voucher?source=${source}`)
- }else {
- dataVoucher = await odooApi('GET', `/api/v1/user/${id}/voucher`)
- }
- return dataVoucher
-}
+export const getVoucher = async (id, query) => {
+ const queryParam = new URLSearchParams(query);
+ const url = `/api/v1/user/${id}/voucher?${queryParam.toString()}`;
+ const dataVoucher = await odooApi('GET', url);
+ return dataVoucher;
+};
export const findVoucher = async (code, id, source) => {
- let dataVoucher = null
- if(source){
- dataVoucher = await odooApi('GET', `/api/v1/user/${id}/voucher?code=${code}&source=${source}`)
- }else{
- dataVoucher = await odooApi('GET', `/api/v1/user/${id}/voucher?code=${code}`)
+ let dataVoucher = null;
+ if (source) {
+ dataVoucher = await odooApi(
+ 'GET',
+ `/api/v1/user/${id}/voucher?code=${code}&source=${source}`
+ );
+ } else {
+ dataVoucher = await odooApi(
+ 'GET',
+ `/api/v1/user/${id}/voucher?code=${code}`
+ );
}
- return dataVoucher
-}
+ return dataVoucher;
+};
diff --git a/src/lib/checkout/components/Checkout.jsx b/src/lib/checkout/components/Checkout.jsx
index 92e14db6..fde6f6de 100644
--- a/src/lib/checkout/components/Checkout.jsx
+++ b/src/lib/checkout/components/Checkout.jsx
@@ -43,9 +43,16 @@ const Checkout = () => {
const auth = useAuth();
const [activeVoucher, SetActiveVoucher] = useState(null);
-
- const { data: cartCheckout } = useQuery('cartCheckout-' + activeVoucher, () =>
- getProductsCheckout(activeVoucher, query)
+ const [activeVoucherShipping, setActiveVoucherShipping] = useState(null);
+
+ const { data: cartCheckout } = useQuery(
+ ['cartCheckout', activeVoucher, activeVoucherShipping],
+ () =>
+ getProductsCheckout({
+ source: query,
+ voucher: activeVoucher,
+ voucher_shipping: activeVoucherShipping,
+ })
);
const [selectedAddress, setSelectedAddress] = useState({
@@ -103,6 +110,7 @@ const Checkout = () => {
const [bottomPopupTnC, SetBottomPopupTnC] = useState(null);
const [itemTnC, setItemTnC] = useState(null);
const [listVouchers, SetListVoucher] = useState(null);
+ const [listVoucherShippings, SetListVoucherShipping] = useState(null);
const [discountVoucher, SetDiscountVoucher] = useState(0);
const [codeVoucher, SetCodeVoucher] = useState(null);
const [findCodeVoucher, SetFindVoucher] = useState(null);
@@ -119,13 +127,23 @@ const Checkout = () => {
const voucher = async () => {
if (!listVouchers) {
try {
- let dataVoucher = await getVoucher(auth?.id, query);
+ setLoadingVoucher(true);
+ let dataVoucher = await getVoucher(auth?.id, {
+ source: query,
+ });
SetListVoucher(dataVoucher);
+
+ let dataVoucherShipping = await getVoucher(auth?.id, {
+ source: query,
+ type: 'shipping',
+ });
+ SetListVoucherShipping(dataVoucherShipping);
} finally {
setLoadingVoucher(false);
}
}
};
+
const VoucherCode = async (code) => {
let dataVoucher = await findVoucher(code, auth.id, query);
if (dataVoucher.length <= 0) {
@@ -228,7 +246,6 @@ const Checkout = () => {
const hasFlashSale = cartCheckout?.products.some(product => product.hasFlashsale);
setHasFlashSale(hasFlashSale);
}, [cartCheckout]);
-
useEffect(() => {
setCheckoutValidation(false);
@@ -303,7 +320,7 @@ const Checkout = () => {
useEffect(() => {
const GT =
cartCheckout?.grandTotal +
- Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000;
+ Math.round(parseInt(finalShippingAmt * 1.1) / 1000) * 1000;
const finalGT = GT < 0 ? 0 : GT;
setGrandTotal(finalGT);
}, [biayaKirim, cartCheckout?.grandTotal, activeVoucher]);
@@ -349,6 +366,7 @@ const Checkout = () => {
delivery_service_type: selectedExpedisiService,
flash_sale : hasFlashSale, // dibuat negasi untuk ngetest kebalikan nilai false
voucher: activeVoucher,
+ voucher_shipping: activeVoucherShipping,
type: 'sale_order',
};
@@ -444,6 +462,11 @@ const Checkout = () => {
return false;
}, [products]);
+ const voucherShippingAmt = cartCheckout?.discountVoucherShipping || 0;
+ const discShippingAmt = Math.min(biayaKirim, voucherShippingAmt);
+
+ const finalShippingAmt = biayaKirim - discShippingAmt;
+
return (
<>
<BottomPopup
@@ -553,8 +576,145 @@ const Checkout = () => {
</div>
)}
- <hr className='mt-10 my-4 border-gray_r-10' />
- <div className=''>
+ <hr className='mt-8 mb-4 border-gray_r-8' />
+
+ {listVoucherShippings && listVoucherShippings?.length > 0 && (
+ <div>
+ <h3 className='font-semibold mb-4'>Promo Gratis Ongkir</h3>
+ {listVoucherShippings?.map((item) => (
+ <div key={item.id} className='relative'>
+ <div
+ className={`border border-solid mb-5 w-full hover:cursor-pointer p-2 pl-4 pr-4 `}
+ >
+ {item.canApply && (
+ <div
+ class='p-2 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:text-green-400'
+ role='alert'
+ >
+ <p>
+ Potensi potongan sebesar{' '}
+ <span className='text-green font-bold'>
+ {currencyFormat(item.discountVoucher)}
+ </span>
+ </p>
+ </div>
+ )}
+ {!item.canApply && (
+ <div
+ class='p-2 mb-4 text-sm text-red-800 rounded-lg bg-red-50'
+ role='alert'
+ onClick={() => handlingTnC(item)}
+ >
+ <p>
+ Voucher tidak bisa di gunakan,{' '}
+ <span className='text-red font-bold'>
+ Baca Selengkapnya !
+ </span>
+ </p>
+ </div>
+ )}
+
+ <div className={`flex gap-x-3 relative`}>
+ {item.canApply === false && (
+ <div className='absolute w-full h-full bg-gray_r-3/40 top-0 left-0 z-50' />
+ )}
+ <div className='hidden md:w-[250px] md:block'>
+ <Image
+ src={item.image}
+ alt={item.name}
+ className={`object-cover`}
+ />
+ </div>
+ <div className='w-full'>
+ <div className='flex justify-between gap-x-2 mb-1 items-center'>
+ <div className=''>
+ <h3 className='font-semibold'>{item.name}</h3>
+ <div className='mt-1'>
+ <span className='text-sm line-clamp-3'>
+ {item.description}{' '}
+ </span>
+ </div>
+ </div>
+ <div className='flex justify-end'>
+ <label class='relative inline-flex items-center cursor-pointer'>
+ <input
+ type='checkbox'
+ value=''
+ class='sr-only peer'
+ checked={activeVoucherShipping === item.code}
+ onChange={() =>
+ setActiveVoucherShipping(
+ activeVoucherShipping === item.code
+ ? null
+ : item.code
+ )
+ }
+ />
+ <div class="w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-focus:ring-4 peer-focus:ring-green-300 dark:peer-focus:ring-green-800 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-green-600"></div>
+ </label>
+ </div>
+ </div>
+ <hr className='mt-2 my-2 border-gray_r-8' />
+ <div className='flex justify-between items-center'>
+ <p className='text-justify text-sm md:text-xs'>
+ Kode Voucher :{' '}
+ <span className='text-red-500 font-semibold'>
+ {item.code}
+ </span>
+ </p>
+ <p className='text-sm md:text-xs'>
+ {activeVoucher === item.code && (
+ <span className=' text-green-600'>
+ Voucher digunakan{' '}
+ </span>
+ )}
+ </p>
+ </div>
+ <div className='flex items-center mt-3'>
+ <svg
+ aria-hidden='true'
+ fill='none'
+ stroke='currentColor'
+ stroke-width='1.5'
+ viewBox='0 0 24 24'
+ className='w-5 text-black'
+ >
+ <path
+ d='M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z'
+ stroke-linecap='round'
+ stroke-linejoin='round'
+ ></path>
+ </svg>
+ <div className='flex justify-between items-center'>
+ <div className='text-left ml-3 text-sm '>
+ Berakhir dalam{' '}
+ <span className='text-red-600'>
+ {item.remainingTime}
+ </span>{' '}
+ lagi,{' '}
+ </div>
+ <div
+ className='text-sm ml-2 text-red-600'
+ onClick={() => handlingTnC(item)}
+ >
+ Baca S&K
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div className='mt-3'>
+ <p className='text-justify text-sm '></p>
+ </div>
+ </div>
+ </div>
+ ))}
+ </div>
+ )}
+
+ <hr className='mt-8 mb-4 border-gray_r-8' />
+
+ <div>
{!loadingVoucher && listVouchers?.length === 0 ? (
<div className='flex items-center justify-center mt-4 mb-4'>
<div className='text-center'>
@@ -739,14 +899,7 @@ const Checkout = () => {
</div>
</div>
<div className='mt-3'>
- <p className='text-justify text-sm '>
- {/* {item.canApply === false
- ? 'Tambah ' +
- currencyFormat(item.differenceToApply) +
- ' untuk pakai promo ini'
- : 'Potensi potongan sebesar ' +
- currencyFormat(hitungDiscountVoucher(item.code))} */}
- </p>
+ <p className='text-justify text-sm '></p>
</div>
</div>
</div>
@@ -912,14 +1065,18 @@ const Checkout = () => {
</div>
<div className='flex gap-x-2 justify-between'>
<div className='text-gray_r-11'>
- Biaya Kirim <p className='text-xs mt-3'>{etdFix}</p>
- </div>
- <div>
- {currencyFormat(
- Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
- )}
+ Biaya Kirim <p className='text-xs mt-1'>{etdFix}</p>
</div>
+ <div>{currencyFormat(biayaKirim)}</div>
</div>
+ {activeVoucherShipping && voucherShippingAmt && (
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>Diskon Kirim</div>
+ <div className='text-danger-500'>
+ - {currencyFormat(discShippingAmt)}
+ </div>
+ </div>
+ )}
</div>
)}
@@ -948,7 +1105,7 @@ const Checkout = () => {
<div className='mt-4 mb-4'>
<button
type='button'
- onClick={() => {
+ onClick={async () => {
SetBottomPopup(true);
voucher();
}}
@@ -1204,14 +1361,18 @@ const Checkout = () => {
<div className='flex gap-x-2 justify-between'>
<div className='text-gray_r-11'>
Biaya Kirim
- <p className='text-xs mt-3'>{etdFix}</p>
- </div>
- <div>
- {currencyFormat(
- Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
- )}
+ <p className='text-xs mt-1'>{etdFix}</p>
</div>
+ <div>{currencyFormat(biayaKirim)}</div>
</div>
+ {activeVoucherShipping && voucherShippingAmt && (
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>Diskon Kirim</div>
+ <div className='text-danger-500'>
+ - {currencyFormat(discShippingAmt)}
+ </div>
+ </div>
+ )}
</div>
)}
diff --git a/src/lib/home/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..fa1df286
--- /dev/null
+++ b/src/lib/home/components/CategoryDynamic.jsx
@@ -0,0 +1,84 @@
+import React, { useEffect, useState } from 'react';
+import useCategoryManagement from '../hooks/useCategoryManagement';
+import NextImage from 'next/image';
+import Link from "next/link";
+import router from 'next/router';
+import { createSlug } from '@/core/utils/slug';
+
+const CategoryDynamic = () => {
+ const { categoryManagement } = useCategoryManagement();
+
+ const calculateLevel3Products = (category) => {
+ return category.childFrontendIdI.reduce((total, child) => total + (child.numFound || 0), 0);
+ };
+
+ const calculateLevel2Products = (category) => {
+ return category.categories.reduce((total, subCategory) => {
+ const level3Products = calculateLevel3Products(subCategory);
+ return total + (subCategory.numFound || 0) + level3Products;
+ }, 0);
+ };
+
+ return (
+ <div>
+ {categoryManagement && categoryManagement.data?.map((category) => {
+ const countLevel2 = calculateLevel2Products(category);
+
+ return (
+ <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>
+ <p className='text-gray_r-10 text-sm'>{countLevel2} Produk tersedia</p>
+ <Link href={createSlug('/shop/category/', category?.name, category?.categoryIdI)} className="!text-red-500 font-semibold">Lihat Semua</Link>
+ </div>
+ <div className='grid grid-cols-3 gap-2'>
+ {category.categories.map((subCategory) => {
+ const countLevel3 = calculateLevel3Products(subCategory);
+
+ return (
+ <div key={subCategory.id} 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'
+ />
+ <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>
+ <p className='text-gray_r-10 text-sm'>{(subCategory.numFound || 0) + countLevel3} Produk</p>
+ <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]'>
+ {subCategory.childFrontendIdI.map((childCategory) => (
+ <div key={childCategory.id}>
+ <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}
+ 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>
+ );
+ })}
+ </div>
+ </div>
+ );
+ })}
+ </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..c1433a2d
--- /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='object-'
+ />
+ <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>
+ <p className='text-gray_r-10 text-[10px]'>999 rb+ Produk</p>
+ </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}
+ />
+ <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..5aa1fbcc
--- /dev/null
+++ b/src/lib/home/components/CategoryPilihan.jsx
@@ -0,0 +1,66 @@
+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';
+
+
+const CategoryPilihan = ({ id, categories }) => {
+ const heroBanner = useQuery('categoryPilihan', bannerApi({ type: 'banner-category-list' }));
+
+ return (
+ <section>
+ <div className='flex flex-row items-center mb-4'>
+ <div className='font-semibold sm:text-h-lg mr-2'>Kategori Pilihan</div>
+ <p className='text-gray_r-10 text-sm'>200 Rb+ Produk Unggulan & 800+ Brand Rekomendasi tersedia!</p>
+ </div>
+ <div className='flex w-full h-48 bg-red-300 mb-4'>
+ {heroBanner.data &&
+ heroBanner.data?.length > 0 && (
+ <div key={heroBanner.data.id} className='px-4 sm:px-0 object-fill '>
+ {/* {heroBanner.data?.map((banner) => ( */}
+ <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='h-48 object-fill w-full'
+ />
+ </Link>
+ {/* ))} */}
+ </div>
+
+ )}
+ </div>
+ <div className="group/item grid grid-cols-6 gap-y-2 w-full h-full col-span-2 ">
+ {categories.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?.image1920? category?.image1920 : '/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?.name}</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/category/', category?.name, category?.id)}
+ className='category-mega-box__parent text-white rounded-lg'
+ >
+ Lihat semua
+ </Link>
+ </div>
+ </div>
+ ))}
+ </div>
+ </section>
+ )
+}
+
+export default CategoryPilihan
diff --git a/src/lib/home/components/PreferredBrand.jsx b/src/lib/home/components/PreferredBrand.jsx
index ec09aa4e..ae12505d 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'>
@@ -49,18 +66,20 @@ const PreferredBrand = () => {
</Link>
)}
</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 className='border rounded border-gray_r-6'>
+ {manufactures.isLoading && <PreferredBrandSkeleton />}
+ {!manufactures.isLoading && (
+ <Swiper {...swiperBanner}>
+ {preferredBrandsData.map((manufacture) => (
+ <SwiperSlide key={manufacture.id}>
+ <BrandCard brand={manufacture} />
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ )}
+ </div>
</div>
)
}
-export default PreferredBrand
+export default PreferredBrand \ No newline at end of file
diff --git a/src/lib/home/components/PromotionProgram.jsx b/src/lib/home/components/PromotionProgram.jsx
index b204df8e..99258d94 100644
--- a/src/lib/home/components/PromotionProgram.jsx
+++ b/src/lib/home/components/PromotionProgram.jsx
@@ -13,10 +13,14 @@ const BannerSection = () => {
<div className='flex justify-between items-center mb-4 '>
<div className='font-semibold sm:text-h-lg'>Promo Tersedia</div>
{isDesktop && (
- <div></div>
- // <Link href='/shop/promo' className='!text-red-500 font-semibold'>
- // Lihat Semua
- // </Link>
+ <Link href='/shop/promo' className='!text-red-500 font-semibold'>
+ Lihat Semua
+ </Link>
+ )}
+ {isMobile && (
+ <Link href='/shop/promo' className='!text-red-500 font-semibold sm:text-h-sm'>
+ Lihat Semua
+ </Link>
)}
</div>
{isDesktop && (promotionProgram.data &&
diff --git a/src/lib/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/product/components/CategorySection.jsx b/src/lib/product/components/CategorySection.jsx
new file mode 100644
index 00000000..14a39e7e
--- /dev/null
+++ b/src/lib/product/components/CategorySection.jsx
@@ -0,0 +1,111 @@
+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
+
+const CategorySection = ({ categories }) => {
+ const { isDesktop, isMobile } = useDevice();
+ const router = useRouter();
+ const [isOpenCategory, setIsOpenCategory] = useState(false); // State to manage category visibility
+
+ let teks = router.query.slug;
+ let hasil = teks?.match(/(\d+)$/)[0];
+
+ const breadcrumbs = useQuery(
+ `category-breadcrumbs/${hasil}`,
+ async () => await odooApi('GET', `/api/v1/category/${hasil}/category-breadcrumb`)
+ );
+
+ 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" 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}>
+ {displayedCategories.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}
+ />
+ <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/ProductCard.jsx b/src/lib/product/components/ProductCard.jsx
index 98732407..3b5fab28 100644
--- a/src/lib/product/components/ProductCard.jsx
+++ b/src/lib/product/components/ProductCard.jsx
@@ -14,7 +14,15 @@ import useUtmSource from '~/hooks/useUtmSource';
const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
const router = useRouter();
const utmSource = useUtmSource();
+ const [discount, setDiscount] = useState(0);
+ let voucherPastiHemat = 0;
+
+ if (product?.voucherPastiHemat?.length > 0) {
+ const stringVoucher = product?.voucherPastiHemat[0];
+ const validJsonString = stringVoucher.replace(/'/g, '"');
+ voucherPastiHemat = JSON.parse(validJsonString);
+ }
const callForPriceWhatsapp = whatsappUrl('product', {
name: product.name,
@@ -38,42 +46,59 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
),
};
+ const hitungDiscountVoucher = () => {
+ let countDiscount = 0;
+ if (voucherPastiHemat.discount_type === 'percentage') {
+ countDiscount = product?.lowestPrice.priceDiscount * (voucherPastiHemat.discount_amount / 100);
+ if (voucherPastiHemat.max_discount > 0 && countDiscount > voucherPastiHemat.max_discount) {
+ countDiscount = voucherPastiHemat.max_discount;
+ }
+ } else {
+ countDiscount = voucherPastiHemat.discount_amount;
+ }
+
+ setDiscount(countDiscount);
+ };
+
+ useEffect(() => {
+ hitungDiscountVoucher();
+ }, []);
+
if (variant == 'vertical') {
return (
- <div className='rounded shadow-sm border border-gray_r-4 bg-white h-[300px] md:h-[350px]'>
+ <div className='rounded shadow-sm border border-gray_r-4 bg-white h-[310px] md:h-[380px]'>
<Link href={URL.product} className='border-b border-gray_r-4 relative'>
- <div className="relative">
- <Image
- src={image}
- alt={product?.name}
- className="gambarA w-full object-contain object-center h-36 sm:h-48"
- />
- <div className="absolute top-0 right-0 flex mt-3">
- <div className="gambarB ">
- {product?.isSni && (
- <ImageNext
- src="/images/sni-logo.png"
- alt="SNI Logo"
- className="w-4 h-5 object-contain object-top sm:h-6"
- width={50}
- height={50}
- />
- )}
- </div>
- <div className="gambarC ">
- {product?.isTkdn && (
- <ImageNext
- src="/images/TKDN.png"
- alt="TKDN"
- className="w-11 h-6 object-contain object-top ml-1 mr-1 sm:h-6"
- width={50}
- height={50}
- />
- )}
+ <div className='relative'>
+ <Image
+ src={image}
+ alt={product?.name}
+ className='gambarA w-full object-contain object-center h-36 sm:h-48'
+ />
+ <div className='absolute top-0 right-0 flex mt-3'>
+ <div className='gambarB '>
+ {product?.isSni && (
+ <ImageNext
+ src='/images/sni-logo.png'
+ alt='SNI Logo'
+ className='w-4 h-5 object-contain object-top sm:h-6'
+ width={50}
+ height={50}
+ />
+ )}
+ </div>
+ <div className='gambarC '>
+ {product?.isTkdn && (
+ <ImageNext
+ src='/images/TKDN.png'
+ alt='TKDN'
+ className='w-11 h-6 object-contain object-top ml-1 mr-1 sm:h-6'
+ width={50}
+ height={50}
+ />
+ )}
+ </div>
</div>
</div>
- </div>
-
{router.pathname != '/' && product?.flashSale?.id > 0 && (
<div className='absolute bottom-0 w-full grid'>
@@ -178,6 +203,13 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
)}
</div>
)}
+ {discount > 0 && (
+ <div className='flex gap-x-1 mb-1 text-sm'>
+ <div className='inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20'>
+ Hemat : {currencyFormat(discount)}
+ </div>
+ </div>
+ )}
<div className='flex w-full items-center gap-x-1 '>
{product?.stockTotal > 0 && (
@@ -200,37 +232,37 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
<div className='flex bg-white'>
<div className='w-4/12'>
<Link href={URL.product} className='relative'>
- <div className="relative">
- <Image
- src={image}
- alt={product?.name}
- className="gambarA w-full object-contain object-center h-36 sm:h-48"
- />
- <div className="absolute top-0 right-0 flex mt-3">
- <div className="gambarB ">
- {product?.isSni && (
- <ImageNext
- src="/images/sni-logo.png"
- alt="SNI Logo"
- className="w-4 h-5 object-contain object-top sm:h-6"
- width={50}
- height={50}
- />
- )}
- </div>
- <div className="gambarC ">
- {product?.isTkdn && (
- <ImageNext
- src="/images/TKDN.png"
- alt="TKDN"
- className="w-11 h-6 object-contain object-top ml-1 sm:h-6"
- width={50}
- height={50}
- />
- )}
+ <div className='relative'>
+ <Image
+ src={image}
+ alt={product?.name}
+ className='gambarA w-full object-contain object-center h-36 sm:h-48'
+ />
+ <div className='absolute top-0 right-0 flex mt-3'>
+ <div className='gambarB '>
+ {product?.isSni && (
+ <ImageNext
+ src='/images/sni-logo.png'
+ alt='SNI Logo'
+ className='w-4 h-5 object-contain object-top sm:h-6'
+ width={50}
+ height={50}
+ />
+ )}
+ </div>
+ <div className='gambarC '>
+ {product?.isTkdn && (
+ <ImageNext
+ src='/images/TKDN.png'
+ alt='TKDN'
+ className='w-11 h-6 object-contain object-top ml-1 sm:h-6'
+ width={50}
+ height={50}
+ />
+ )}
+ </div>
</div>
</div>
- </div>
{product.variantTotal > 1 && (
<div className='absolute badge-gray bottom-1.5 left-1.5'>
{product.variantTotal} Varian
diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx
index b1a5d409..34018ffe 100644
--- a/src/lib/product/components/ProductSearch.jsx
+++ b/src/lib/product/components/ProductSearch.jsx
@@ -26,6 +26,8 @@ import ProductSearchSkeleton from './Skeleton/ProductSearchSkeleton';
import SideBanner from '~/modules/side-banner';
import FooterBanner from '~/modules/footer-banner';
+import CategorySection from './CategorySection';
+import { getIdFromSlug } from '@/core/utils/slug'
const ProductSearch = ({
query,
@@ -68,6 +70,9 @@ const ProductSearch = ({
const productStart = productSearch.data?.responseHeader.params.start;
const productRows = limit;
const productFound = productSearch.data?.response.numFound;
+ const [dataCategories, setDataCategories] = useState([])
+
+ const categoryId = getIdFromSlug(prefixUrl)
useEffect(() => {
if (productFound == 0 && query.q && !spellings) {
@@ -115,6 +120,16 @@ const ProductSearch = ({
checkIfBrand();
}
}, [q]);
+
+ useEffect(() => {
+ const loadCategories = async () => {
+ const getCategories = await odooApi('GET', `/api/v1/category/child?parent_id=${categoryId}`)
+ if(getCategories){
+ setDataCategories(getCategories)
+ }
+ }
+ loadCategories()
+ }, [])
const brands = [];
for (
@@ -323,6 +338,7 @@ const ProductSearch = ({
SpellingComponent
)}
</div>
+ <CategorySection categories={dataCategories}/>
{productFound > 0 && (
<div className='flex items-center gap-x-2 mb-5 justify-between'>
@@ -411,7 +427,9 @@ const ProductSearch = ({
<SideBanner />
</div>
+
<div className='w-9/12 pl-6'>
+ <CategorySection categories={dataCategories}/>
{bannerPromotionHeader && bannerPromotionHeader?.image && (
<div className='mb-3'>
<Image
diff --git a/src/lib/quotation/components/Quotation.jsx b/src/lib/quotation/components/Quotation.jsx
index 8855c6c4..df234dc2 100644
--- a/src/lib/quotation/components/Quotation.jsx
+++ b/src/lib/quotation/components/Quotation.jsx
@@ -67,17 +67,19 @@ const Quotation = () => {
const [selectedExpedisiService, setselectedExpedisiService] = useState(null);
const [etd, setEtd] = useState(null);
const [etdFix, setEtdFix] = useState(null);
-
+
const [isApproval, setIsApproval] = useState(false);
-
+
const expedisiValidation = useRef(null);
-
+
const [selectedAddress, setSelectedAddress] = useState({
shipping: null,
invoicing: null,
});
-
+
const [addresses, setAddresses] = useState(null);
+
+ const [note_websiteText, setselectedNote_websiteText] = useState('');
useEffect(() => {
if (!auth) return;
@@ -262,6 +264,12 @@ const Quotation = () => {
}
if (!products || products.length == 0) return;
+
+ if (isApproval && note_websiteText == '') {
+ toast.error('Maaf, Note wajib dimasukkan.');
+ return;
+ }
+
setIsLoading(true);
const productOrder = products.map((product) => ({
product_id: product.id,
@@ -276,16 +284,18 @@ const Quotation = () => {
carrier_id: selectedCarrierId,
estimated_arrival_days: splitDuration(etd),
delivery_service_type: selectedExpedisiService,
+ note_website : note_websiteText,
};
- console.log('data checkout', data);
+
const isSuccess = await checkoutApi({ data });
- console.log('isSuccess', isSuccess);
+ ;
setIsLoading(false);
if (isSuccess?.id) {
for (const product of products) deleteItemCart({ productId: product.id });
router.push(`/shop/quotation/finish?id=${isSuccess.id}`);
return;
}
+
toast.error('Gagal melakukan transaksi, terjadi kesalahan internal');
};
@@ -442,8 +452,25 @@ const Quotation = () => {
</Link>{' '}
yang berlaku
</p>
+ <hr className='my-4 border-gray_r-6' />
+
+ <div className='flex gap-x-2 justify-start mb-4'>
+ <div className=''>Note</div>
+ {isApproval && (
+ <div className='text-caption-1 text-red-500 items-center flex'>*harus diisi</div>
+ )}
+ </div>
+ <div className='text-caption-2 text-gray_r-11'>
+ <textarea
+ rows="4"
+ cols="50"
+ className={`w-full p-1 rounded border border-gray_r-6`}
+ onChange={(e) => setselectedNote_websiteText(e.target.value)}
+ />
+ </div>
</div>
-
+
+
<Divider />
<div className='flex gap-x-3 p-4'>
@@ -576,6 +603,27 @@ const Quotation = () => {
yang berlaku
</p>
+ <div>
+ <hr className='my-4 border-gray_r-6' />
+
+ <div className='flex gap-x-1 flex-col mb-4'>
+ <div className='flex flex-row gap-x-1'>
+ <div className=''>Note</div>
+ {isApproval && (
+ <div className='text-caption-1 text-red-500 items-center flex'>*harus diisi</div>
+ )}
+ </div>
+ <div className='text-caption-2 text-gray_r-11'>
+ <textarea
+ rows="4"
+ cols="50"
+ className={`w-full p-1 rounded border border-gray_r-6`}
+ onChange={(e) => setselectedNote_websiteText(e.target.value)}
+ />
+ </div>
+ </div>
+ </div>
+
<hr className='my-4 border-gray_r-6' />
<button
diff --git a/src/lib/transaction/api/rejectProductApi.js b/src/lib/transaction/api/rejectProductApi.js
new file mode 100644
index 00000000..e03c7975
--- /dev/null
+++ b/src/lib/transaction/api/rejectProductApi.js
@@ -0,0 +1,9 @@
+import odooApi from '@/core/api/odooApi'
+
+const rejectProductApi = async ({ idSo, idProduct, reason }) => {
+ const dataCheckout = await odooApi('POST', `/api/v1/sale_order/${idSo}/reject/${idProduct}`, {
+ reason_reject: reason
+ });
+ return dataCheckout
+}
+export default rejectProductApi \ No newline at end of file
diff --git a/src/lib/transaction/components/Transaction.jsx b/src/lib/transaction/components/Transaction.jsx
index c6152ca9..9bef895a 100644
--- a/src/lib/transaction/components/Transaction.jsx
+++ b/src/lib/transaction/components/Transaction.jsx
@@ -1,4 +1,6 @@
import Spinner from '@/core/components/elements/Spinner/Spinner';
+import NextImage from 'next/image';
+import rejectImage from "../../../../public/images/reject.png"
import useTransaction from '../hooks/useTransaction';
import TransactionStatusBadge from './TransactionStatusBadge';
import Divider from '@/core/components/elements/Divider/Divider';
@@ -34,8 +36,14 @@ import useAuth from '@/core/hooks/useAuth';
import StepApproval from './stepper';
import aprpoveApi from '../api/approveApi';
import rejectApi from '../api/rejectApi';
+import rejectProductApi from '../api/rejectProductApi';
+import { useRouter } from 'next/router';
const Transaction = ({ id }) => {
+ const router = useRouter()
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [selectedProduct, setSelectedProduct] = useState(null);
+ const [reason, setReason] = useState('');
const auth = useAuth();
const { transaction } = useTransaction({ id });
@@ -141,6 +149,15 @@ const Transaction = ({ id }) => {
[transaction.data]
);
+ const memoizeVariantGroupCardReject = useMemo(
+ () => (
+ <div className='p-4 pt-0 flex flex-col gap-y-3'>
+ <VariantGroupCard variants={transaction.data?.productsRejectLine} buyMore />
+ </div>
+ ),
+ [transaction.data]
+ );
+
if (transaction.isLoading) {
return (
<div className='flex justify-center my-6'>
@@ -153,6 +170,38 @@ const Transaction = ({ id }) => {
setIdAWB(null);
};
+ const openModal = (product) => {
+ setSelectedProduct(product);
+ setIsModalOpen(true);
+ };
+
+ const closeModal = () => {
+ setIsModalOpen(false);
+ setSelectedProduct(null);
+ setReason('');
+ };
+
+ const handleRejectProduct = async () => {
+ try{
+ if (!reason.trim()) {
+ toast.error('Masukkan alasan terlebih dahulu');
+ return;
+ }else{
+ let idSo = transaction?.data.id
+ let idProduct = selectedProduct?.id
+ await rejectProductApi({ idSo, idProduct, reason});
+ closeModal();
+ toast.success("Produk berhasil di reject")
+ setTimeout(() => {
+ window.location.reload();
+ }, 1500);
+ }
+ }catch(error){
+ toast.error('Gagal reject produk. Silakan coba lagi.');
+ }
+ };
+
+
return (
transaction.data?.name && (
<>
@@ -301,6 +350,37 @@ const Transaction = ({ id }) => {
<Divider />
+ <div className='p-4'>
+ <p className='font-medium'>Invoice</p>
+ <div className='flex flex-col gap-y-3 mt-4'>
+ {transaction.data?.invoices?.map((invoice, index) => (
+ <Link href={`/my/invoices/${invoice.id}`} key={index}>
+ <div className='shadow rounded-md p-4 text-gray_r-12 font-normal flex justify-between'>
+ <div>
+ <p className='mb-2'>{invoice?.name}</p>
+ <div className='flex items-center gap-x-1'>
+ {invoice.amountResidual > 0 ? (
+ <div className='badge-red'>Belum Lunas</div>
+ ) : (
+ <div className='badge-green'>Lunas</div>
+ )}
+ <p className='text-caption-2 text-gray_r-11'>
+ {currencyFormat(invoice.amountTotal)}
+ </p>
+ </div>
+ </div>
+ <ChevronRightIcon className='w-5 stroke-2' />
+ </div>
+ </Link>
+ ))}
+ {transaction.data?.invoices?.length === 0 && (
+ <div className='badge-red text-sm px-2'>Belum ada invoice</div>
+ )}
+ </div>
+ </div>
+
+ <Divider />
+
{!auth?.feature.soApproval && (
<div className='p-4 flex flex-col gap-y-4'>
<DescriptionRow label='Purchase Order'>
@@ -326,8 +406,20 @@ const Transaction = ({ id }) => {
<Divider />
<div className='font-medium p-4'>Detail Produk</div>
+ {transaction?.data?.products.length > 0? (
+ <div>
+ {memoizeVariantGroupCard}
+ </div>
+ ) : (
+ <div className='badge-red text-sm px-2 ml-4'>Semua produk telah di reject</div>
+ )}
- {memoizeVariantGroupCard}
+ {transaction?.data?.productsRejectLine.length > 0 && (
+ <div>
+ <div className='font-medium p-4'>Detail Produk Reject</div>
+ {memoizeVariantGroupCardReject}
+ </div>
+ )}
<Divider />
@@ -335,37 +427,6 @@ const Transaction = ({ id }) => {
<Divider />
- <div className='p-4'>
- <p className='font-medium'>Invoice</p>
- <div className='flex flex-col gap-y-3 mt-4'>
- {transaction.data?.invoices?.map((invoice, index) => (
- <Link href={`/my/invoices/${invoice.id}`} key={index}>
- <div className='shadow rounded-md p-4 text-gray_r-12 font-normal flex justify-between'>
- <div>
- <p className='mb-2'>{invoice?.name}</p>
- <div className='flex items-center gap-x-1'>
- {invoice.amountResidual > 0 ? (
- <div className='badge-red'>Belum Lunas</div>
- ) : (
- <div className='badge-green'>Lunas</div>
- )}
- <p className='text-caption-2 text-gray_r-11'>
- {currencyFormat(invoice.amountTotal)}
- </p>
- </div>
- </div>
- <ChevronRightIcon className='w-5 stroke-2' />
- </div>
- </Link>
- ))}
- {transaction.data?.invoices?.length === 0 && (
- <div className='badge-red text-sm px-2'>Belum ada invoice</div>
- )}
- </div>
- </div>
-
- <Divider />
-
<div className='p-4 pt-0'>
{transaction.data?.status == 'draft' &&
auth?.feature.soApproval && (
@@ -562,67 +623,100 @@ const Transaction = ({ id }) => {
/>
</div>
</div>
-
- <div className='text-h-sm font-semibold mt-10 mb-4'>
- Pengiriman
- </div>
- <div className='grid grid-cols-3 gap-1'>
- {transaction?.data?.pickings?.map((airway) => (
- <button
- className='shadow rounded-md p-3 text-gray_r-12 font-normal flex justify-between items-center text-left'
- key={airway?.id}
- onClick={() => setIdAWB(airway?.id)}
- >
- <div>
- <span className='text-sm text-gray_r-11'>
- No Resi : {airway?.trackingNumber || '-'}{' '}
- </span>
- <p className='mt-1 font-medium'>{airway?.name}</p>
- </div>
- <div className='flex gap-x-2'>
- <div className='text-sm text-gray-600 badge-green leading-[1.5] mt-1'>
- {airway?.delivered ? 'Pesanan Tiba' : 'Sedang Dikirim'}
- </div>
- <ChevronRightIcon className='w-5 stroke-2' />
+ <div className='flex '>
+ <div className='w-1/2'>
+ <div className='text-h-sm font-semibold mt-10 mb-4'>
+ Pengiriman
+ </div>
+ {transaction?.data?.pickings.length == 0 && (
+ <div className='badge-red text-sm'>Belum ada pengiriman</div>
+ )}
+ <div className='grid grid-cols-1 gap-1 w-2/3'>
+ {transaction?.data?.pickings?.map((airway) => (
+ <button
+ className='shadow rounded-md p-3 text-gray_r-12 font-normal flex justify-between items-center text-left h-20'
+ key={airway?.id}
+ onClick={() => setIdAWB(airway?.id)}
+ >
+ <div>
+ <span className='text-sm text-gray_r-11'>
+ No Resi : {airway?.trackingNumber || '-'}{' '}
+ </span>
+ <p className='mt-1 font-medium'>{airway?.name}</p>
+ </div>
+ <div className='flex gap-x-2'>
+ <div className='text-sm text-gray-600 badge-green leading-[1.5] mt-1'>
+ {airway?.delivered ? 'Pesanan Tiba' : 'Sedang Dikirim'}
+ </div>
+ <ChevronRightIcon className='w-5 stroke-2' />
+ </div>
+ </button>
+ ))}
+ </div>
+ </div>
+ <div className='invoice w-1/2 '>
+ <div className='text-h-sm font-semibold mt-10 mb-4 '>Invoice</div>
+ {transaction.data?.invoices?.length === 0 && (
+ <div className='badge-red text-sm'>Belum ada invoice</div>
+ )}
+ <div className='grid grid-cols-1 gap-1 w-2/3 '>
+ {transaction.data?.invoices?.map((invoice, index) => (
+ <Link href={`/my/invoices/${invoice.id}`} key={index}>
+ <div className='shadow rounded-md p-4 text-gray_r-12 font-normal flex justify-between'>
+ <div>
+ <p className='mb-1'>{invoice?.name}</p>
+ <div className='flex items-center gap-x-1'>
+ {invoice.amountResidual > 0 ? (
+ <div className='badge-red'>Belum Lunas</div>
+ ) : (
+ <div className='badge-green'>Lunas</div>
+ )}
+ <p className='text-caption-2 text-gray_r-11'>
+ {currencyFormat(invoice.amountTotal)}
+ </p>
+ </div>
+ </div>
+ <ChevronRightIcon className='w-5 stroke-2' />
+ </div>
+ </Link>
+ ))}
</div>
- </button>
- ))}
+ </div>
</div>
- {transaction?.data?.pickings.length == 0 && (
- <div className='badge-red text-sm'>Belum ada pengiriman</div>
- )}
- <div className='text-h-sm font-semibold mt-10 mb-4'>
+ <div className='text-h-sm font-semibold mt-4 mb-4'>
Rincian Pembelian
</div>
- <table className='table-data'>
- <thead>
- <tr>
- <th>Nama Produk</th>
- {/* <th>Diskon</th> */}
- <th>Jumlah</th>
- <th>Harga</th>
- <th>Subtotal</th>
- </tr>
- </thead>
- <tbody>
- {transaction?.data?.products?.map((product) => (
- <tr key={product.id}>
- <td className='flex'>
- <Link
- href={createSlug(
- '/shop/product/',
- product?.parent.name,
- product?.parent.id
- )}
- className='w-[20%] flex-shrink-0'
- >
+ {transaction?.data?.products?.length > 0? (
+ <table className='table-data'>
+ <thead>
+ <tr>
+ <th>Nama Produk</th>
+ {/* <th>Diskon</th> */}
+ <th>Jumlah</th>
+ <th>Harga</th>
+ <th>Subtotal</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ {transaction?.data?.products?.map((product) => (
+ <tr key={product.id}>
+ <td className='flex'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='w-[20%] flex-shrink-0'
+ >
<div className='relative'>
- <Image
- src={product?.parent?.image}
- alt={product?.name}
- className='object-contain object-center border border-gray_r-6 h-32 w-full rounded-md'
- />
+ <Image
+ src={product?.parent?.image}
+ alt={product?.name}
+ className='object-contain object-center border border-gray_r-6 h-32 w-full rounded-md'
+ />
<div className='absolute top-0 right-4 flex mt-3'>
<div className='gambarB '>
{product.isSni && (
@@ -648,47 +742,92 @@ const Transaction = ({ id }) => {
</div>
</div>
</div>
- </Link>
- <div className='px-2 text-left'>
- <Link
- href={createSlug(
- '/shop/product/',
- product?.parent.name,
- product?.parent.id
- )}
- className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
- >
- {product?.parent?.name}
</Link>
- <div className='text-gray_r-11 mt-2'>
- {product?.code}{' '}
- {product?.attributes.length > 0
- ? `| ${product?.attributes.join(', ')}`
- : ''}
- </div>
- </div>
- </td>
- {/* <td>
- {product.price.discountPercentage > 0
- ? `${product.price.discountPercentage}%`
- : ''}
- </td> */}
- <td>{product.quantity}</td>
- <td>
- {/* {product.price.discountPercentage > 0 && (
- <div className='line-through mb-1 text-caption-1 text-gray_r-12/70'>
- {currencyFormat(product.price.price)}
+ <div className='px-2 text-left'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
+ >
+ {product?.parent?.name}
+ </Link>
+ <div className='text-gray_r-11 mt-2'>
+ {product?.code}{' '}
+ {product?.attributes.length > 0
+ ? `| ${product?.attributes.join(', ')}`
+ : ''}
+ </div>
</div>
- )} */}
- <div>{currencyFormat(product.price.priceDiscount)}</div>
- </td>
- <td>{currencyFormat(product.price.subtotal)}</td>
- </tr>
- ))}
- </tbody>
- </table>
-
- <div className='flex justify-end mt-4'>
+ </td>
+ {/* <td>
+ {product.price.discountPercentage > 0
+ ? `${product.price.discountPercentage}%`
+ : ''}
+ </td> */}
+ <td>{product.quantity}</td>
+ <td>
+ {/* {product.price.discountPercentage > 0 && (
+ <div className='line-through mb-1 text-caption-1 text-gray_r-12/70'>
+ {currencyFormat(product.price.price)}
+ </div>
+ )} */}
+ <div>{currencyFormat(product.price.priceDiscount)}</div>
+ </td>
+ <td>{currencyFormat(product.price.subtotal)}</td>
+ {/* {auth?.feature.soApproval && (auth.webRole == 2 || auth.webRole == 3) && (transaction.data.isReaject == false) && ( */}
+ {auth?.feature.soApproval && (auth.webRole == 2 || auth.webRole == 3) && (router.asPath.includes("/my/quotations/")) && transaction.data?.status == 'draft' && (
+ <td>
+ <button
+ className="bg-red-500 text-white py-1 px-3 rounded"
+ onClick={() => openModal(product)}
+ >
+ Reject
+ </button>
+ </td>
+ )}
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ ) : (
+ <div className='badge-red text-sm'>Semua produk telah di reject</div>
+ )}
+
+ {isModalOpen && (
+ <div className='fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center'>
+ <div className='bg-white p-4 rounded w-96
+ ease-in-out opacity-100
+ transform transition-transform duration-300 scale-100'>
+ <h2 className='text-lg mb-2'>Berikan Alasan</h2>
+ <textarea
+ value={reason}
+ onChange={(e) => setReason(e.target.value)}
+ className='w-full p-2 border rounded'
+ rows='4'
+ ></textarea>
+ <div className='mt-4 flex justify-end'>
+ <button
+ className='bg-gray-300 text-black py-1 px-3 rounded mr-2'
+ onClick={closeModal}
+ >
+ Batal
+ </button>
+ <button
+ className='bg-red-500 text-white py-1 px-3 rounded'
+ onClick={handleRejectProduct}
+ >
+ Reject
+ </button>
+ </div>
+ </div>
+ </div>
+ )}
+
+ {transaction?.data?.products?.map((product) => (
+ <div className='flex justify-end mt-4' key={product.id}>
<div className='w-1/4 grid grid-cols-2 gap-y-3 text-gray_r-12/80'>
<div className='text-right'>Subtotal</div>
<div className='text-right font-medium'>
@@ -713,33 +852,91 @@ const Transaction = ({ id }) => {
</div>
</div>
</div>
+ ))}
- <div className='text-h-sm font-semibold mt-10 mb-4'>Invoice</div>
- <div className='grid grid-cols-3 gap-4'>
- {transaction.data?.invoices?.map((invoice, index) => (
- <Link href={`/my/invoices/${invoice.id}`} key={index}>
- <div className='shadow rounded-md p-4 text-gray_r-12 font-normal flex justify-between'>
- <div>
- <p className='mb-2'>{invoice?.name}</p>
- <div className='flex items-center gap-x-1'>
- {invoice.amountResidual > 0 ? (
- <div className='badge-red'>Belum Lunas</div>
- ) : (
- <div className='badge-green'>Lunas</div>
- )}
- <p className='text-caption-2 text-gray_r-11'>
- {currencyFormat(invoice.amountTotal)}
- </p>
- </div>
- </div>
- <ChevronRightIcon className='w-5 stroke-2' />
- </div>
- </Link>
- ))}
- </div>
- {transaction.data?.invoices?.length === 0 && (
- <div className='badge-red text-sm'>Belum ada invoice</div>
+
+
+ {transaction?.data?.productsRejectLine.length > 0 && (
+ <div className='text-h-sm font-semibold mt-10 mb-4'>
+ Rincian Produk Reject
+ </div>
+ )}
+ {transaction?.data?.productsRejectLine.length > 0 && (
+ <table className='table-data'>
+ <thead>
+ <tr>
+ <th>Nama Produk</th>
+ {/* <th>Diskon</th> */}
+ <th>Jumlah</th>
+ <th>Harga</th>
+ <th>Subtotal</th>
+ </tr>
+ </thead>
+ <tbody>
+ {transaction?.data?.productsRejectLine?.map((product) => (
+ <tr key={product.id}>
+ <td className='flex'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='w-[20%] flex-shrink-0'
+ >
+ <Image
+ src={product?.parent?.image}
+ alt={product?.name}
+ className='object-contain object-center border border-gray_r-6 h-32 w-full rounded-md'
+ />
+ </Link>
+ <div className='px-2 text-left'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
+ >
+ {product?.parent?.name}
+ </Link>
+ <div className='text-gray_r-11 mt-2'>
+ {product?.code}{' '}
+ {product?.attributes.length > 0
+ ? `| ${product?.attributes.join(', ')}`
+ : ''}
+ </div>
+ </div>
+ </td>
+ {/* <td>
+ {product.price.discountPercentage > 0
+ ? `${product.price.discountPercentage}%`
+ : ''}
+ </td> */}
+ <td>{product.quantity}</td>
+ <td>
+ {/* {product.price.discountPercentage > 0 && (
+ <div className='line-through mb-1 text-caption-1 text-gray_r-12/70'>
+ {currencyFormat(product.price.price)}
+ </div>
+ )} */}
+ <div>{currencyFormat(product.price.priceDiscount)}</div>
+ </td>
+ <td className='flex justify-center'>
+ <NextImage
+ src={rejectImage}
+ alt='Reject'
+ width={90}
+ height={30}
+ />
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
)}
+
</div>
</div>
</DesktopView>
diff --git a/src/lib/variant/components/VariantCard.jsx b/src/lib/variant/components/VariantCard.jsx
index 9f65fc3c..68cdf54f 100644
--- a/src/lib/variant/components/VariantCard.jsx
+++ b/src/lib/variant/components/VariantCard.jsx
@@ -1,18 +1,26 @@
import { useRouter } from 'next/router'
import { toast } from 'react-hot-toast'
-
+import useAuth from '@/core/hooks/useAuth';
import Image from '@/core/components/elements/Image/Image'
import Link from '@/core/components/elements/Link/Link'
import { createSlug } from '@/core/utils/slug'
import currencyFormat from '@/core/utils/currencyFormat'
import { updateItemCart } from '@/core/utils/cart'
import whatsappUrl from '@/core/utils/whatsappUrl'
+import {useState } from 'react';
+import rejectProductApi from '../../../lib/transaction/api/rejectProductApi'
+// import {useTransaction} from 'C:\Users\Indoteknik\next-indoteknik\src\lib\transaction\hooks\useTransaction.js'
+import useTransaction from '../../../lib/transaction/hooks/useTransaction';
import ImageNext from 'next/image';
-import { useMemo, useEffect, useState } from 'react';
const VariantCard = ({ product, openOnClick = true, buyMore = false }) => {
const router = useRouter()
-
+ const id = router.query.id
+ const auth = useAuth();
+ const { transaction } = useTransaction({id});
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [selectedProduct, setSelectedProduct] = useState(null);
+ const [reason, setReason] = useState('');
@@ -24,11 +32,42 @@ const VariantCard = ({ product, openOnClick = true, buyMore = false }) => {
})
return
}
-
+
const checkoutItem = () => {
router.push(`/shop/checkout?product_id=${product.id}&qty=${product.quantity}`)
}
-
+
+ const openModal = (product) => {
+ setSelectedProduct(product);
+ setIsModalOpen(true);
+ };
+
+ const closeModal = () => {
+ setIsModalOpen(false);
+ setSelectedProduct(null);
+ setReason('');
+ };
+
+ const handleRejectProduct = async () => {
+ try {
+ if (!reason.trim()) {
+ toast.error('Masukkan alasan terlebih dahulu');
+ return;
+ }else{
+ let idSo = transaction?.data.id
+ let idProduct = selectedProduct.id
+ await rejectProductApi({ idSo, idProduct, reason});
+ closeModal();
+ toast.success("Produk berhasil di reject")
+ setTimeout(() => {
+ window.location.reload();
+ }, 1500);
+ }
+ } catch (error) {
+ toast.error('Gagal reject produk. Silakan coba lagi.');
+ }
+ };
+
const Card = () => (
<div className='flex gap-x-3'>
<div className='w-4/12 flex items-center gap-x-2'>
@@ -115,7 +154,7 @@ const VariantCard = ({ product, openOnClick = true, buyMore = false }) => {
<Link href={createSlug('/shop/product/', product.parent.name, product.parent.id)}>
<Card />
</Link>
- {buyMore && (
+ {buyMore && (!transaction?.data?.productsRejectLine.some(pr => pr.id === product.id)) && (
<div className='flex justify-end gap-x-2 mb-2'>
<button
type='button'
@@ -124,14 +163,48 @@ const VariantCard = ({ product, openOnClick = true, buyMore = false }) => {
>
Tambah Keranjang
</button>
- <button
- type='button'
- onClick={checkoutItem}
- className='btn-solid-red py-2 px-3 text-caption-1'
- >
- Beli Lagi
- </button>
+ {/* {auth?.feature.soApproval && (auth.webRole == 2 || auth.webRole == 3) && (transaction.data.isReaject == false) && ( */}
+ {auth?.feature.soApproval && (auth.webRole == 2 || auth.webRole == 3) && (router.asPath.includes("/my/quotations/")) && (
+ !transaction?.data?.productsRejectLine.some(pr => pr.id === product.id) && (
+ <button
+ className="bg-red-500 text-white py-1 px-3 rounded"
+ onClick={() => openModal(product)}
+ >
+ Reject
+ </button>
+ )
+ )}
+ {isModalOpen && (
+ <div className='fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center'>
+ <div className='bg-white p-4 rounded w-96
+ ease-in-out opacity-100
+ transform transition-transform duration-300 scale-100'>
+ <h2 className='text-lg mb-2'>Berikan Alasan</h2>
+ <textarea
+ value={reason}
+ onChange={(e) => setReason(e.target.value)}
+ className='w-full p-2 border rounded'
+ rows='4'
+ ></textarea>
+ <div className='mt-4 flex justify-end'>
+ <button
+ className='bg-gray-300 text-black py-1 px-3 rounded mr-2'
+ onClick={closeModal}
+ >
+ Batal
+ </button>
+ <button
+ className='bg-red-500 text-white py-1 px-3 rounded'
+ onClick={handleRejectProduct}
+ >
+ Reject
+ </button>
+ </div>
+ </div>
+ </div>
+ )}
</div>
+
)}
</>
)
diff --git a/src/pages/index.jsx b/src/pages/index.jsx
index 8af963fb..30a7ac1f 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';
@@ -11,8 +10,10 @@ import { FlashSaleSkeleton } from '@/lib/flashSale/skeleton/FlashSaleSkeleton';
import PreferredBrandSkeleton from '@/lib/home/components/Skeleton/PreferredBrandSkeleton';
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';
const BasicLayout = dynamic(() =>
import('@/core/components/layouts/BasicLayout')
@@ -54,12 +55,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);
@@ -70,6 +82,18 @@ 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
@@ -107,6 +131,7 @@ export default function Home() {
</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 />
@@ -117,10 +142,13 @@ export default function Home() {
</>
)}
<PromotinProgram />
+ <CategoryPilihan categories={dataCategories}/>
+ <CategoryDynamic/>
<CategoryHomeId />
<BannerSection />
<CustomerReviews />
</div>
+ </div>
</div>
</DesktopView>
@@ -150,6 +178,10 @@ export default function Home() {
<DelayRender renderAfter={600}>
<PromotinProgram />
</DelayRender>
+ <DelayRender renderAfter={600}>
+ <CategoryPilihan categories={dataCategories}/>
+ <CategoryDynamicMobile/>
+ </DelayRender>
<DelayRender renderAfter={800}>
<PopularProduct />
</DelayRender>
@@ -164,4 +196,4 @@ export default function Home() {
</MobileView>
</BasicLayout>
);
-}
+} \ No newline at end of file
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/promo/[slug].tsx b/src/pages/shop/promo/[slug].tsx
index bd69c071..aaee1249 100644
--- a/src/pages/shop/promo/[slug].tsx
+++ b/src/pages/shop/promo/[slug].tsx
@@ -91,7 +91,7 @@ export default function PromoDetail() {
setCurrentPage(pageNumber)
try {
- const items = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug}`);
+ const items = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug}`,0,100);
setPromoItems(items);
if (items.length === 0) {
@@ -147,16 +147,16 @@ export default function PromoDetail() {
const response = await fetchVariantSolr(`id:${item.product_id} AND ${combinedQuery}`);
const product = response.response.docs[0];
const product_id = product.id;
- const response2 = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug} AND product_ids:${product_id} AND ${combinedQueryPrice}`);
+ const response2 = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug} AND product_ids:${product_id} AND ${combinedQueryPrice}`,0,100);
return response2;
}else if(combinedQuery){
const response = await fetchVariantSolr(`id:${item.product_id} AND ${combinedQuery}`);
const product = response.response.docs[0];
const product_id = product.id;
- const response2 = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug} AND product_ids:${product_id} `);
+ const response2 = await fetchPromoItemsSolr(`type_value_s:${Array.isArray(slug) ? slug[0] : slug} AND product_ids:${product_id} `,0,100);
return response2;
} else {
- const response = await fetchPromoItemsSolr(`id:${item.id}`);
+ const response = await fetchPromoItemsSolr(`id:${item.id}`,0,100);
return response;
}
} catch (fetchError) {
diff --git a/src/pages/shop/promo/index.jsx b/src/pages/shop/promo/index.jsx
new file mode 100644
index 00000000..01a11aad
--- /dev/null
+++ b/src/pages/shop/promo/index.jsx
@@ -0,0 +1,40 @@
+import Seo from '@/core/components/Seo'
+import BasicLayout from '@/core/components/layouts/BasicLayout';
+import { Breadcrumb, BreadcrumbItem, BreadcrumbLink } from '@chakra-ui/react';
+import Link from 'next/link';
+import Promo from '~/pages/shop/promo';
+
+import React from 'react';
+
+const PromoPage = () => {
+ return (
+ <BasicLayout>
+ <Seo title='Promo Indoteknik.com' />
+ <div className='container mx-auto py-4 md:py-6 pb-0'>
+ <Breadcrumb>
+ <BreadcrumbItem>
+ <BreadcrumbLink
+ as={Link}
+ href='/'
+ className='!text-danger-500 whitespace-nowrap'
+ >
+ Home
+ </BreadcrumbLink>
+ </BreadcrumbItem>
+
+ <BreadcrumbItem isCurrentPage>
+ <BreadcrumbLink className='whitespace-nowrap'>
+ Promo
+ </BreadcrumbLink>
+ </BreadcrumbItem>
+ </Breadcrumb>
+
+ <div className='h-10' />
+
+ <Promo />
+ </div>
+ </BasicLayout>
+ );
+};
+
+export default PromoPage;
diff --git a/src/pages/shop/promo/index.tsx b/src/pages/shop/promo/index.tsx
deleted file mode 100644
index 7ec4f6b0..00000000
--- a/src/pages/shop/promo/index.tsx
+++ /dev/null
@@ -1,186 +0,0 @@
-import dynamic from 'next/dynamic'
-import { useEffect, useState } from 'react'
-import { useRouter } from 'next/router'
-import Seo from '../../../core/components/Seo.jsx'
-import Promocrumb from '../../../lib/promo/components/Promocrumb.jsx'
-import { fetchPromoItemsSolr } from '../../../api/promoApi.js'
-import LogoSpinner from '../../../core/components/elements/Spinner/LogoSpinner.jsx'
-import ProductPromoCard from '../../../../src-migrate/modules/product-promo/components/Card.tsx'
-import { IPromotion } from '../../../../src-migrate/types/promotion.ts'
-import React from 'react'
-import { SolrResponse } from "../../../../src-migrate/types/solr.ts";
-
-const BasicLayout = dynamic(() => import('../../../core/components/layouts/BasicLayout.jsx'))
-
-export default function Promo() {
- const router = useRouter()
- const { slug = '' } = router.query
- const [promoItems, setPromoItems] = useState<any[]>([])
- const [promoData, setPromoData] = useState<IPromotion[] | null>(null)
- const [loading, setLoading] = useState(true)
- const [currentPage, setCurrentPage] = useState(1)
- const [fetchingData, setFetchingData] = useState(false)
-
- useEffect(() => {
- const loadPromo = async () => {
- try {
- const items = await fetchPromoItemsSolr(`*:*`)
-
-
- setPromoItems(items)
-
-
- if (items.length === 0) {
- setPromoData([])
- setLoading(false);
- return;
- }
-
- const promoDataPromises = items.map(async (item) => {
- const queryParams = new URLSearchParams({ q: `id:${item.id}` })
-
-
- try {
- const response = await fetch(`/solr/promotion_program_lines/select?${queryParams.toString()}`)
-
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`)
- }
-
- const data: SolrResponse<any[]> = await response.json()
-
-
- const promotions = await map(data.response.docs)
- return promotions;
- } catch (fetchError) {
- console.error("Error fetching promotion data:", fetchError)
- return [];
- }
- });
-
- const promoDataArray = await Promise.all(promoDataPromises);
- const mergedPromoData = promoDataArray.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
- setPromoData(mergedPromoData);
- setTimeout(() => setLoading(false), 120); // Menambahkan delay 200ms sebelum mengubah status loading
- } catch (loadError) {
- console.error("Error loading promo items:", loadError)
- setLoading(false);
- }
- }
-
- if (slug) {
- loadPromo()
- }
- }, [slug])
-
- const map = async (promotions: any[]): Promise<IPromotion[]> => {
- const result: IPromotion[] = []
-
- for (const promotion of promotions) {
- const data: IPromotion = {
- id: promotion.id,
- program_id: promotion.program_id_i,
- name: promotion.name_s,
- type: {
- value: promotion.type_value_s,
- label: promotion.type_label_s,
- },
- limit: promotion.package_limit_i,
- limit_user: promotion.package_limit_user_i,
- limit_trx: promotion.package_limit_trx_i,
- price: promotion.price_f,
- total_qty: promotion.total_qty_i,
- products: JSON.parse(promotion.products_s),
- free_products: JSON.parse(promotion.free_products_s),
- }
-
- result.push(data)
- }
-
- return result
- }
-
-
-
-
- useEffect(() => {
- const handleScroll = () => {
- if (
- !fetchingData &&
- window.innerHeight + document.documentElement.scrollTop >= 0.95 * document.documentElement.offsetHeight
- ) {
- // User has scrolled to 95% of page height
-
- setTimeout(() => setFetchingData(true), 120);
- setCurrentPage((prevPage) => prevPage + 1)
- }
- }
-
- window.addEventListener('scroll', handleScroll)
- return () => window.removeEventListener('scroll', handleScroll)
- }, [fetchingData])
-
- useEffect(() => {
- if (fetchingData) {
- // Fetch more data
- // You may need to adjust this logic according to your API
- fetchMoreData()
- }
- }, [fetchingData])
-
- const fetchMoreData = async () => {
- try {
- // Add a delay of approximately 150ms
- setTimeout(async () => {
- // Fetch more data
- // Update promoData state with the new data
- }, 150)
- } catch (error) {
- console.error('Error fetching more data:', error)
- } finally {
- setTimeout(() => setFetchingData(false), 120);
-
- }
- }
-
- const visiblePromotions = promoData?.slice(0, currentPage * 12)
-
- return (
- <BasicLayout>
- <Seo
- title={`Promo ${Array.isArray(slug) ? slug[0] : slug} Terkini`}
- description='B2B Marketplace MRO & Industri dengan Layanan Pembayaran Tempo, Faktur Pajak, Online Quotation, Garansi Resmi & Harga Kompetitif'
- />
- {/* <Promocrumb brandName={capitalizeFirstLetter(Array.isArray(slug) ? slug[0] : slug)} /> */}
- <div className='container mx-auto mt-1 flex mb-1'>
- <div className=''>
- <h1 className='font-semibold'>Semua Promo di Indoteknik</h1>
- </div>
- </div>
- {loading ? (
- <div className='container flex justify-center my-4'>
- <LogoSpinner width={48} height={48} />
- </div>
- ) : promoData && promoItems.length >= 1 ? (
- <>
- <div className='flex flex-wrap justify-center'>
- {visiblePromotions?.map((promotion) => (
- <div key={promotion.id} className="min-w-[40px] max-w-[400px] mr-[20px] mb-[20px] sm:w-full md:w-1/2 lg:w-1/3 xl:w-1/4">
- <ProductPromoCard promotion={promotion} />
- </div>
- ))}
- </div>
- {fetchingData && (
- <div className='container flex justify-center my-4'>
- <LogoSpinner width={48} height={48} />
- </div>
- )}
- </>
- ) : (
- <div className="text-center my-8">
- <p>Belum ada promo pada kategori ini</p>
- </div>
- )}
- </BasicLayout>
- )
-}
diff --git a/src/styles/globals.css b/src/styles/globals.css
index f6561b00..505dcab4 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -583,12 +583,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 +603,7 @@ button {
transition-colors
ease-linear
duration-100
+ w-fit
font-semibold;
}
@@ -613,6 +613,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 dd90ac7d..d4694eb2 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,
+ voucherPastiHemat:product?.voucher_pastihemat || []
};
if (product.manufacture_id_i && product.manufacture_name_s) {