summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/address/api/cityApi.js4
-rw-r--r--src/lib/address/api/stateApi.js8
-rw-r--r--src/lib/address/components/CreateAddress.jsx51
-rw-r--r--src/lib/address/components/EditAddress.jsx61
-rw-r--r--src/lib/cart/components/Cartheader.jsx354
-rw-r--r--src/lib/checkout/components/Checkout.jsx43
-rw-r--r--src/lib/checkout/components/FinishCheckout.jsx95
-rw-r--r--src/lib/flashSale/components/FlashSale.jsx94
-rw-r--r--src/lib/flashSale/components/FlashSaleNonDisplay.jsx68
-rw-r--r--src/lib/home/api/categoryManagementApi.js8
-rw-r--r--src/lib/home/components/BannerSection.jsx40
-rw-r--r--src/lib/home/components/CategoryDynamic.jsx236
-rw-r--r--src/lib/home/components/CategoryDynamicMobile.jsx56
-rw-r--r--src/lib/home/components/PreferredBrand.jsx81
-rw-r--r--src/lib/home/components/PromotionProgram.jsx87
-rw-r--r--src/lib/home/components/ServiceList.jsx4
-rw-r--r--src/lib/product/components/Product/ProductDesktopVariant.jsx419
-rw-r--r--src/lib/product/components/Product/ProductMobileVariant.jsx209
-rw-r--r--src/lib/product/components/ProductCard.jsx15
-rw-r--r--src/lib/product/components/ProductFilter.jsx127
-rw-r--r--src/lib/product/components/ProductFilterDesktop.jsx165
-rw-r--r--src/lib/product/components/ProductSearch.jsx20
-rw-r--r--src/lib/quotation/components/Quotation.jsx5
-rw-r--r--src/lib/review/components/CustomerReviews.jsx25
-rw-r--r--src/lib/shipment/components/Shipments.jsx204
-rw-r--r--src/lib/transaction/api/transactionsApi.js3
-rw-r--r--src/lib/transaction/components/Transaction.jsx6
-rw-r--r--src/lib/treckingAwb/component/Manifest.jsx124
-rw-r--r--src/lib/variant/components/VariantCard.jsx22
29 files changed, 1720 insertions, 914 deletions
diff --git a/src/lib/address/api/cityApi.js b/src/lib/address/api/cityApi.js
index 7873435b..0b0201e6 100644
--- a/src/lib/address/api/cityApi.js
+++ b/src/lib/address/api/cityApi.js
@@ -1,7 +1,7 @@
import odooApi from '@/core/api/odooApi'
-const cityApi = async () => {
- const dataCities = await odooApi('GET', '/api/v1/city')
+const cityApi = async ({stateId}) => {
+ const dataCities = await odooApi('GET', '/api/v1/city?state_id='+stateId)
return dataCities
}
diff --git a/src/lib/address/api/stateApi.js b/src/lib/address/api/stateApi.js
new file mode 100644
index 00000000..cea49e7e
--- /dev/null
+++ b/src/lib/address/api/stateApi.js
@@ -0,0 +1,8 @@
+import odooApi from '@/core/api/odooApi'
+
+const stateApi = async () => {
+ const dataState = await odooApi('GET', '/api/v1/state')
+ return dataState
+}
+
+export default stateApi \ No newline at end of file
diff --git a/src/lib/address/components/CreateAddress.jsx b/src/lib/address/components/CreateAddress.jsx
index e315affe..9d70e8fc 100644
--- a/src/lib/address/components/CreateAddress.jsx
+++ b/src/lib/address/components/CreateAddress.jsx
@@ -12,6 +12,7 @@ import { toast } from 'react-hot-toast';
import { yupResolver } from '@hookform/resolvers/yup';
import Menu from '@/lib/auth/components/Menu';
import useAddresses from '../hooks/useAddresses';
+import stateApi from '../api/stateApi';
const CreateAddress = () => {
const auth = useAuth();
@@ -28,23 +29,40 @@ const CreateAddress = () => {
defaultValues,
});
const { addresses = [] } = useAddresses(); // Ensure addresses is an array
+ const [states, setState] = useState([]);
const [cities, setCities] = useState([]);
const [districts, setDistricts] = useState([]);
const [subDistricts, setSubDistricts] = useState([]);
const [filteredTypes, setFilteredTypes] = useState(types); // State to manage filtered types
useEffect(() => {
- const loadCities = async () => {
- let dataCities = await cityApi();
- dataCities = dataCities.map((city) => ({
- value: city.id,
- label: city.name,
+ const loadState = async () => {
+ let dataState = await stateApi();
+ dataState = dataState.map((state) => ({
+ value: state.id,
+ label: state.name,
}));
- setCities(dataCities);
+ setState(dataState);
};
- loadCities();
+ loadState();
}, []);
+ const watchState = watch('state');
+ useEffect(() => {
+ setValue('city', '');
+ if (watchState) {
+ const loadCities = async () => {
+ let dataCities = await cityApi({stateId: watchState});
+ dataCities = dataCities.map((city) => ({
+ value: city.id,
+ label: city.name,
+ }));
+ setCities(dataCities);
+ };
+ loadCities();
+ }
+ }, [watchState, setValue]);
+
useEffect(() => {
if (addresses) {
let hasContactAddress = false;
@@ -100,6 +118,7 @@ const CreateAddress = () => {
const onSubmitHandler = async (values) => {
const data = {
...values,
+ state_id: values.state,
city_id: values.city,
district_id: values.district,
sub_district_id: values.subDistrict,
@@ -205,12 +224,26 @@ const CreateAddress = () => {
</div>
<div>
+ <label className='form-label mb-2'>Provinsi</label>
+ <Controller
+ name='state'
+ control={control}
+ render={(props) => (
+ <HookFormSelect {...props} options={states} />
+ )}
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>
+ {errors.state?.message}
+ </div>
+ </div>
+
+ <div>
<label className='form-label mb-2'>Kota</label>
<Controller
name='city'
control={control}
render={(props) => (
- <HookFormSelect {...props} options={cities} />
+ <HookFormSelect {...props} options={cities} disabled={!watchState}/>
)}
/>
<div className='text-caption-2 text-danger-500 mt-1'>
@@ -270,6 +303,7 @@ const validationSchema = Yup.object().shape({
mobile: Yup.string().required('Harus di-isi'),
street: Yup.string().required('Harus di-isi'),
zip: Yup.string().required('Harus di-isi'),
+ state: Yup.string().required('Harus di-pilih'),
city: Yup.string().required('Harus di-pilih'),
district: Yup.string().required('Harus di-pilih'),
});
@@ -280,6 +314,7 @@ const defaultValues = {
email: '',
mobile: '',
street: '',
+ state: '',
city: '',
district: '',
subDistrict: '',
diff --git a/src/lib/address/components/EditAddress.jsx b/src/lib/address/components/EditAddress.jsx
index 182c8a31..23cf72a9 100644
--- a/src/lib/address/components/EditAddress.jsx
+++ b/src/lib/address/components/EditAddress.jsx
@@ -13,6 +13,7 @@ import { toast } from 'react-hot-toast';
import Menu from '@/lib/auth/components/Menu';
import useAuth from '@/core/hooks/useAuth';
import odooApi from '@/core/api/odooApi';
+import stateApi from '../api/stateApi';
const EditAddress = ({ id, defaultValues }) => {
const auth = useAuth();
@@ -29,9 +30,11 @@ const EditAddress = ({ id, defaultValues }) => {
resolver: yupResolver(validationSchema),
defaultValues,
});
+
+ const [states, setStates] = useState([]);
const [cities, setCities] = useState([]);
const [districts, setDistricts] = useState([]);
- const [subDistricts, setSubDistricts] = useState([]);
+ const [subDistricts, setSubDistricts] = useState([]);
useEffect(() => {
const loadProfile = async () => {
@@ -48,16 +51,38 @@ const EditAddress = ({ id, defaultValues }) => {
}, [auth?.parentId]);
useEffect(() => {
- const loadCities = async () => {
- let dataCities = await cityApi();
- dataCities = dataCities.map((city) => ({
- value: city.id,
- label: city.name,
+ const loadStates = async () => {
+ let dataStates = await stateApi();
+ dataStates = dataStates.map((state) => ({
+ value: state.id,
+ label: state.name,
}));
- setCities(dataCities);
+ setStates(dataStates);
};
- loadCities();
- }, []);
+ loadStates();
+ },[])
+
+ const watchState = watch('state');
+ useEffect(() => {
+ setValue('city', '');
+ if(watchState) {
+ const loadCities = async () => {
+ let dataCities = await cityApi({ stateId: watchState });
+ dataCities = dataCities.map((city) => ({
+ value: city.id,
+ label: city.name,
+ }));
+ setCities(dataCities);
+ let oldCity = getValues('oldCity');
+ if (oldCity) {
+ setValue('city', oldCity);
+ setValue('oldCity', '');
+ }
+ };
+ loadCities();
+ }
+
+ }, [watchState, setValue, getValues]);
const watchCity = watch('city');
useEffect(() => {
@@ -107,6 +132,7 @@ const EditAddress = ({ id, defaultValues }) => {
const data = {
...values,
phone: values.mobile,
+ state_id: values.state,
city_id: values.city,
district_id: values.district,
sub_district_id: values.subDistrict,
@@ -242,12 +268,26 @@ const EditAddress = ({ id, defaultValues }) => {
</div>
<div>
+ <label className='form-label mb-2'>Provinsi</label>
+ <Controller
+ name='state'
+ control={control}
+ render={(props) => (
+ <HookFormSelect {...props} options={states} />
+ )}
+ />
+ <div className='text-caption-2 text-danger-500 mt-1'>
+ {errors.state?.message}
+ </div>
+ </div>
+
+ <div>
<label className='form-label mb-2'>Kota</label>
<Controller
name='city'
control={control}
render={(props) => (
- <HookFormSelect {...props} options={cities} />
+ <HookFormSelect {...props} options={cities} disabled={!watchState} />
)}
/>
<div className='text-caption-2 text-danger-500 mt-1'>
@@ -308,6 +348,7 @@ const validationSchema = Yup.object().shape({
mobile: Yup.string().required('Harus di-isi'),
street: Yup.string().required('Harus di-isi'),
zip: Yup.string().required('Harus di-isi'),
+ state : Yup.string().required('Harus di-pilih'),
city: Yup.string().required('Harus di-pilih'),
district: Yup.string().required('Harus di-pilih'),
});
diff --git a/src/lib/cart/components/Cartheader.jsx b/src/lib/cart/components/Cartheader.jsx
index ddb77c1f..1c30bb13 100644
--- a/src/lib/cart/components/Cartheader.jsx
+++ b/src/lib/cart/components/Cartheader.jsx
@@ -1,105 +1,115 @@
-import { useCallback, useEffect, useMemo, useState } from 'react'
-import { getCartApi } from '../api/CartApi'
-import currencyFormat from '@/core/utils/currencyFormat'
-import { createSlug } from '@/core/utils/slug'
-import useAuth from '@/core/hooks/useAuth'
-import { useRouter } from 'next/router'
-import odooApi from '@/core/api/odooApi'
-import { useProductCartContext } from '@/contexts/ProductCartContext'
-import Image from '@/core/components/elements/Image/Image'
-import whatsappUrl from '@/core/utils/whatsappUrl'
-import { AnimatePresence, motion } from 'framer-motion'
-import style from '../../../../src-migrate/modules/cart/styles/item-promo.module.css'
-const { ShoppingCartIcon, PhotoIcon } = require('@heroicons/react/24/outline')
-const { default: Link } = require('next/link')
+import { useCallback, useEffect, useMemo, useState } from 'react';
+import { getCartApi } from '../api/CartApi';
+import currencyFormat from '@/core/utils/currencyFormat';
+import { createSlug } from '@/core/utils/slug';
+import useAuth from '@/core/hooks/useAuth';
+import { useRouter } from 'next/router';
+import odooApi from '@/core/api/odooApi';
+import { useProductCartContext } from '@/contexts/ProductCartContext';
+import Image from '@/core/components/elements/Image/Image';
+import whatsappUrl from '@/core/utils/whatsappUrl';
+import { AnimatePresence, motion } from 'framer-motion';
+import style from '../../../../src-migrate/modules/cart/styles/item-promo.module.css';
+const { ShoppingCartIcon, PhotoIcon } = require('@heroicons/react/24/outline');
+const { default: Link } = require('next/link');
const Cardheader = (cartCount) => {
-
- const router = useRouter()
- const [subTotal, setSubTotal] = useState(null)
- const [buttonLoading, SetButtonTerapkan] = useState(false)
- const itemLoading = [1, 2, 3]
- const auth = useAuth()
- const [countCart, setCountCart] = useState(null)
- const { productCart, setRefreshCart, setProductCart, refreshCart, isLoading, setIsloading } =
- useProductCartContext()
+ const router = useRouter();
+ const [subTotal, setSubTotal] = useState(null);
+ const [buttonLoading, SetButtonTerapkan] = useState(false);
+ const itemLoading = [1, 2, 3];
+ const auth = useAuth();
+ const [countCart, setCountCart] = useState(null);
+ const {
+ productCart,
+ setRefreshCart,
+ setProductCart,
+ refreshCart,
+ isLoading,
+ setIsloading,
+ } = useProductCartContext();
- const [isHovered, setIsHovered] = useState(false)
- const [isTop, setIsTop] = useState(true)
+ const [isHovered, setIsHovered] = useState(false);
+ const [isTop, setIsTop] = useState(true);
const products = useMemo(() => {
- return productCart?.products || []
- }, [productCart])
+ return productCart?.products || [];
+ }, [productCart]);
const handleMouseEnter = () => {
- setIsHovered(true)
- getCart()
- }
+ setIsHovered(true);
+ getCart();
+ };
const handleMouseLeave = () => {
- setIsHovered(false)
- }
+ setIsHovered(false);
+ };
const getCart = () => {
if (!productCart && auth) {
- refreshCartf()
+ refreshCartf();
}
- }
+ };
const refreshCartf = useCallback(async () => {
- setIsloading(true)
- let cart = await getCartApi()
- setProductCart(cart)
- setCountCart(cart?.productTotal)
- setIsloading(false)
- }, [setProductCart, setIsloading])
+ setIsloading(true);
+ let cart = await getCartApi();
+ setProductCart(cart);
+ setCountCart(cart?.products?.length);
+ setIsloading(false);
+ }, [setProductCart, setIsloading]);
useEffect(() => {
- if (!products) return
+ if (!products) return;
- let calculateTotalPriceBeforeTax = 0
- let calculateTotalTaxAmount = 0
- let calculateTotalDiscountAmount = 0
+ let calculateTotalPriceBeforeTax = 0;
+ let calculateTotalTaxAmount = 0;
+ let calculateTotalDiscountAmount = 0;
for (const product of products) {
- if (product.quantity == '') continue
+ if (product.quantity == '') continue;
- let priceBeforeTax = product.price.price / 1.11
- calculateTotalPriceBeforeTax += priceBeforeTax * product.quantity
- calculateTotalTaxAmount += (product.price.price - priceBeforeTax) * product.quantity
+ let priceBeforeTax = product.price.price / 1.11;
+ calculateTotalPriceBeforeTax += priceBeforeTax * product.quantity;
+ calculateTotalTaxAmount +=
+ (product.price.price - priceBeforeTax) * product.quantity;
calculateTotalDiscountAmount +=
- (product.price.price - product.price.priceDiscount) * product.quantity
+ (product.price.price - product.price.priceDiscount) * product.quantity;
}
let subTotal =
- calculateTotalPriceBeforeTax - calculateTotalDiscountAmount + calculateTotalTaxAmount
- setSubTotal(subTotal)
- }, [products])
+ calculateTotalPriceBeforeTax -
+ calculateTotalDiscountAmount +
+ calculateTotalTaxAmount;
+ setSubTotal(subTotal);
+ }, [products]);
useEffect(() => {
if (refreshCart) {
- refreshCartf()
+ refreshCartf();
}
- setRefreshCart(false)
- }, [refreshCart, refreshCartf, setRefreshCart])
+ setRefreshCart(false);
+ }, [refreshCart, refreshCartf, setRefreshCart]);
useEffect(() => {
- setCountCart(cartCount.cartCount)
- setRefreshCart(false)
- }, [cartCount])
+ setCountCart(cartCount.cartCount);
+ setRefreshCart(false);
+ }, [cartCount]);
useEffect(() => {
const handleScroll = () => {
- setIsTop(window.scrollY === 0)
- }
- window.addEventListener('scroll', handleScroll)
+ setIsTop(window.scrollY === 0);
+ };
+ window.addEventListener('scroll', handleScroll);
return () => {
- window.removeEventListener('scroll', handleScroll)
- }
- }, [])
+ window.removeEventListener('scroll', handleScroll);
+ };
+ }, []);
const handleCheckout = async () => {
- SetButtonTerapkan(true)
- let checkoutAll = await odooApi('POST', `/api/v1/user/${auth.id}/cart/select-all`)
- router.push('/shop/checkout')
- }
-
+ SetButtonTerapkan(true);
+ let checkoutAll = await odooApi(
+ 'POST',
+ `/api/v1/user/${auth.id}/cart/select-all`
+ );
+ router.push('/shop/checkout');
+ };
return (
<div className='relative group'>
@@ -152,8 +162,13 @@ const Cardheader = (cartCount) => {
className='w-full max-w-md p-2 bg-white border border-gray-200 rounded-lg shadow overflow-hidden'
>
<div className='p-2 flex justify-between items-center'>
- <h5 className='text-base font-semibold leading-none'>Keranjang Belanja</h5>
- <Link href='/shop/cart' class='text-sm font-medium text-red-600 underline'>
+ <h5 className='text-base font-semibold leading-none'>
+ Keranjang Belanja
+ </h5>
+ <Link
+ href='/shop/cart'
+ class='text-sm font-medium text-red-600 underline'
+ >
Lihat Semua
</Link>
</div>
@@ -163,7 +178,10 @@ const Cardheader = (cartCount) => {
<div className='justify-center p-4'>
<p className='text-gray-500 text-center '>
Silahkan{' '}
- <Link href='/login' className='text-red-600 underline leading-6'>
+ <Link
+ href='/login'
+ className='text-red-600 underline leading-6'
+ >
Login
</Link>{' '}
Untuk Melihat Daftar Keranjang Belanja Anda
@@ -172,7 +190,11 @@ const Cardheader = (cartCount) => {
)}
{isLoading &&
itemLoading.map((item) => (
- <div key={item} role='status' className='max-w-sm animate-pulse'>
+ <div
+ key={item}
+ role='status'
+ className='max-w-sm animate-pulse'
+ >
<div className='flex items-center space-x-4 mb- 2'>
<div className='flex-shrink-0'>
<PhotoIcon className='h-16 w-16 text-gray-500' />
@@ -194,14 +216,17 @@ const Cardheader = (cartCount) => {
)}
{auth && products.length > 0 && !isLoading && (
<>
- <ul role='list' className='divide-y divide-gray-200 dark:divide-gray-700'>
+ <ul
+ role='list'
+ className='divide-y divide-gray-200 dark:divide-gray-700'
+ >
{products &&
products?.map((product, index) => (
<>
<li className='py-1 sm:py-2'>
<div className='flex items-center space-x-4'>
<div className='bagian gambar flex-shrink-0'>
- {product.cartType === 'promotion' && (
+ {product.cartType === 'promotion' && (
<Image
src={product.imageProgram[0]}
alt={product.name}
@@ -227,10 +252,10 @@ const Cardheader = (cartCount) => {
</div>
<div className='bagian tulisan dan harga flex-1 min-w-0'>
{product.cartType === 'promotion' && (
- <p className='text-caption-2 font-medium text-gray-900 truncate dark:text-white'>
- {product.name}
- </p>
- )}
+ <p className='text-caption-2 font-medium text-gray-900 truncate dark:text-white'>
+ {product.name}
+ </p>
+ )}
{product.cartType === 'product' && (
<Link
href={createSlug(
@@ -252,27 +277,32 @@ const Cardheader = (cartCount) => {
{product?.price?.discountPercentage}%
</div>
<div className='text-gray_r-11 line-through text-caption-2'>
- {currencyFormat(product?.price?.price)}
+ {currencyFormat(
+ product?.price?.price
+ )}
</div>
</div>
)}
-
+
<div className='flex justify-between items-center'>
<div className='font-semibold text-sm text-red-600'>
{product?.price?.priceDiscount > 0 ? (
- currencyFormat(product?.price?.priceDiscount)
+ currencyFormat(
+ product?.price?.priceDiscount
+ )
) : (
<span className='text-gray_r-12/90 font-normal text-caption-1'>
<a
href={whatsappUrl('product', {
name: product.name,
- manufacture: product.manufacture?.name,
+ manufacture:
+ product.manufacture?.name,
url: createSlug(
'/shop/product/',
product.name,
product.id,
true
- )
+ ),
})}
className='text-danger-500 underline'
rel='noopener noreferrer'
@@ -286,56 +316,112 @@ const Cardheader = (cartCount) => {
</div>
</div>
</div>
- <div className="flex flex-col w-3/4">
- {product.products?.map((product) =>
- <div key={product.id} className='md:ml-8 ml-4 mt-2 flex'>
- <Link href={createSlug('/shop/product/', product.parent.name, product.parent.id.toString())} className='md:h-12 md:w-12 md:min-w-[48px] h-10 w-10 min-w-[40px] border border-gray-300 rounded '>
- {product?.image && <Image src={product.image} alt={product.name} width={40} height={40} className='w-full h-full object-fill' />}
- </Link>
-
- <div className="ml-4 w-full flex flex-col gap-y-1">
- <Link href={createSlug('/shop/product/', product.parent.name, product.parent.id.toString())} className="text-caption-2 font-medium text-gray-900 truncate dark:text-white">
- {product.displayName}
- </Link>
-
- <div className='flex w-full'>
- <div className="flex flex-col">
- {/* <div className="text-gray-500 text-caption-1">{product.code}</div> */}
- <div>
- <span className="text-gray-500 text-caption-1">Berat Barang: </span>
- <span className="text-gray-500 text-caption-1">{product.packageWeight} Kg</span>
- </div>
- </div>
+ <div className='flex flex-col w-3/4'>
+ {product.products?.map((product) => (
+ <div
+ key={product.id}
+ className='md:ml-8 ml-4 mt-2 flex'
+ >
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product.parent.name,
+ product.parent.id.toString()
+ )}
+ className='md:h-12 md:w-12 md:min-w-[48px] h-10 w-10 min-w-[40px] border border-gray-300 rounded '
+ >
+ {product?.image && (
+ <Image
+ src={product.image}
+ alt={product.name}
+ width={40}
+ height={40}
+ className='w-full h-full object-fill'
+ />
+ )}
+ </Link>
+
+ <div className='ml-4 w-full flex flex-col gap-y-1'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product.parent.name,
+ product.parent.id.toString()
+ )}
+ className='text-caption-2 font-medium text-gray-900 truncate dark:text-white'
+ >
+ {product.displayName}
+ </Link>
+
+ <div className='flex w-full'>
+ <div className='flex flex-col'>
+ {/* <div className="text-gray-500 text-caption-1">{product.code}</div> */}
+ <div>
+ <span className='text-gray-500 text-caption-1'>
+ Berat Barang:{' '}
+ </span>
+ <span className='text-gray-500 text-caption-1'>
+ {product.packageWeight} Kg
+ </span>
</div>
</div>
-
</div>
+ </div>
+ </div>
+ ))}
+ {product.freeProducts?.map((product) => (
+ <div
+ key={product.id}
+ className='md:ml-8 ml-4 mt-2 flex'
+ >
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product.parent.name,
+ product.parent.id.toString()
)}
- {product.freeProducts?.map((product) =>
- <div key={product.id} className='md:ml-8 ml-4 mt-2 flex'>
- <Link href={createSlug('/shop/product/', product.parent.name, product.parent.id.toString())} className='md:h-12 md:w-12 md:min-w-[48px] h-10 w-10 min-w-[40px] border border-gray-300 rounded '>
- {product?.image && <Image src={product.image} alt={product.name} width={40} height={40} className='w-full h-full object-fill' />}
+ className='md:h-12 md:w-12 md:min-w-[48px] h-10 w-10 min-w-[40px] border border-gray-300 rounded '
+ >
+ {product?.image && (
+ <Image
+ src={product.image}
+ alt={product.name}
+ width={40}
+ height={40}
+ className='w-full h-full object-fill'
+ />
+ )}
+ </Link>
+
+ <div className='ml-4 w-full flex flex-col gap-y-1'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product.parent.name,
+ product.parent.id.toString()
+ )}
+ className='text-caption-2 font-medium text-gray-900 truncate dark:text-white'
+ >
+ {product.displayName}
</Link>
-
- <div className="ml-4 w-full flex flex-col gap-y-1">
- <Link href={createSlug('/shop/product/', product.parent.name, product.parent.id.toString())} className="text-caption-2 font-medium text-gray-900 truncate dark:text-white">
- {product.displayName}
- </Link>
-
- <div className='flex w-full'>
- <div className="flex flex-col">
- {/* <div className="text-gray-500 text-caption-1">{product.code}</div> */}
- <div>
- <span className="text-gray-500 text-caption-1">Berat Barang: </span>
- <span className="text-gray-500 text-caption-1">{product.packageWeight} Kg</span>
- </div>
- </div>
+
+ <div className='flex w-full'>
+ <div className='flex flex-col'>
+ {/* <div className="text-gray-500 text-caption-1">{product.code}</div> */}
+ <div>
+ <span className='text-gray-500 text-caption-1'>
+ Berat Barang:{' '}
+ </span>
+ <span className='text-gray-500 text-caption-1'>
+ {product.packageWeight} Kg
+ </span>
</div>
</div>
-
</div>
- )}
+ </div>
</div>
+ ))}
+ </div>
</li>
</>
))}
@@ -347,8 +433,12 @@ const Cardheader = (cartCount) => {
{auth && products.length > 0 && !isLoading && (
<>
<div className='mt-3'>
- <span className='text-gray-400 text-caption-2'>Subtotal Sebelum PPN : </span>
- <span className='font-semibold text-red-600'>{currencyFormat(subTotal)}</span>
+ <span className='text-gray-400 text-caption-2'>
+ Subtotal Sebelum PPN :{' '}
+ </span>
+ <span className='font-semibold text-red-600'>
+ {currencyFormat(subTotal)}
+ </span>
</div>
<div className='mt-5 mb-2'>
<button
@@ -357,7 +447,9 @@ const Cardheader = (cartCount) => {
onClick={handleCheckout}
disabled={buttonLoading}
>
- {buttonLoading ? 'Loading...' : 'Lanjutkan Ke Pembayaran'}
+ {buttonLoading
+ ? 'Loading...'
+ : 'Lanjutkan Ke Pembayaran'}
</button>
</div>
</>
@@ -368,7 +460,7 @@ const Cardheader = (cartCount) => {
)}
</AnimatePresence>
</div>
- )
-}
+ );
+};
-export default Cardheader
+export default Cardheader;
diff --git a/src/lib/checkout/components/Checkout.jsx b/src/lib/checkout/components/Checkout.jsx
index 4c7e852f..6fb5cdb4 100644
--- a/src/lib/checkout/components/Checkout.jsx
+++ b/src/lib/checkout/components/Checkout.jsx
@@ -37,6 +37,18 @@ const SELF_PICKUP_ID = 32;
const { checkoutApi } = require('../api/checkoutApi');
const { getProductsCheckout } = require('../api/checkoutApi');
+function convertToInternational(number) {
+ if (typeof number !== 'string') {
+ throw new Error("Input harus berupa string");
+ }
+
+ if (number.startsWith('08')) {
+ return '+62' + number.slice(2);
+ }
+
+ return number;
+}
+
const Checkout = () => {
const router = useRouter();
const query = router.query.source ?? null;
@@ -413,7 +425,12 @@ const Checkout = () => {
Math.round(parseInt(finalShippingAmt * 1.1) / 1000) * 1000;
const finalGT = GT < 0 ? 0 : GT;
setGrandTotal(finalGT);
- }, [biayaKirim, cartCheckout?.grandTotal, activeVoucher, activeVoucherShipping]);
+ }, [
+ biayaKirim,
+ cartCheckout?.grandTotal,
+ activeVoucher,
+ activeVoucherShipping,
+ ]);
const checkout = async () => {
const file = poFile.current.files[0];
@@ -442,6 +459,7 @@ const Checkout = () => {
const productOrder = products.map((product) => ({
product_id: product.id,
quantity: product.quantity,
+ available_quantity: product?.availableQuantity,
}));
let data = {
// partner_shipping_id: auth.partnerId,
@@ -483,6 +501,13 @@ const Checkout = () => {
transaction_id: isCheckouted.id,
});
+ gtag('set', 'user_data', {
+ email: auth.email,
+ phone_number: convertToInternational(auth.mobile) ?? convertToInternational(auth.phone),
+ });
+
+ gtag('config', 'AW-954540379', { ' allow_enhanced_conversions':true } ) ;
+
for (const product of products) deleteItemCart({ productId: product.id });
if (grandTotal > 0) {
const payment = await axios.post(
@@ -500,7 +525,7 @@ const Checkout = () => {
}
}
- /* const midtrans = async () => {
+ /* const midtrans = async () => {
for (const product of products) deleteItemCart({ productId: product.id });
if (grandTotal > 0) {
const payment = await axios.post(
@@ -1192,7 +1217,11 @@ const Checkout = () => {
<div className='text-gray_r-11'>
Biaya Kirim <p className='text-xs mt-1'>{etdFix}</p>
</div>
- <div>{currencyFormat(Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000)}</div>
+ <div>
+ {currencyFormat(
+ Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
+ )}
+ </div>
</div>
{activeVoucherShipping && voucherShippingAmt && (
<div className='flex gap-x-2 justify-between'>
@@ -1493,7 +1522,11 @@ const Checkout = () => {
Biaya Kirim
<p className='text-xs mt-1'>{etdFix}</p>
</div>
- <div>{currencyFormat(Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000) }</div>
+ <div>
+ {currencyFormat(
+ Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
+ )}
+ </div>
</div>
{activeVoucherShipping && voucherShippingAmt && (
<div className='flex gap-x-2 justify-between'>
@@ -1664,7 +1697,7 @@ const SectionAddress = ({ address, label, url }) => (
);
const SectionValidation = ({ address }) =>
- address?.rajaongkirCityId == 0 && (
+ address?.stateId == 0 && (
<BottomPopup active={true} title='Update Alamat'>
<div className='leading-7 text-gray_r-12/80'>
Mohon untuk memperbarui alamat Anda dengan mengklik tombol di bawah ini.{' '}
diff --git a/src/lib/checkout/components/FinishCheckout.jsx b/src/lib/checkout/components/FinishCheckout.jsx
index 92245e31..4a67b252 100644
--- a/src/lib/checkout/components/FinishCheckout.jsx
+++ b/src/lib/checkout/components/FinishCheckout.jsx
@@ -1,27 +1,86 @@
-import Link from '@/core/components/elements/Link/Link'
+import Link from 'next/link';
+import Image from '~/components/ui/image';
+import whatsappUrl from '@/core/utils/whatsappUrl';
+import { useEffect, useState } from 'react';
+import odooApi from '@/core/api/odooApi';
+import useDevice from '@/core/hooks/useDevice';
+import useAuth from '@/core/hooks/useAuth';
+import axios from 'axios';
+import { toast } from 'react-hot-toast';
const FinishCheckout = ({ query }) => {
+ const [data, setData] = useState();
+ const [transactionData, setTransactionData] = useState();
+ const { isDesktop, isMobile } = useDevice();
+ const auth = useAuth();
+
+ const so_order = query?.order_id?.replaceAll('-', '/');
+ useEffect(() => {
+ const fetchData = async () => {
+ const fetchedData = await odooApi(
+ 'GET',
+ `/api/v1/sale_order_number?sale_number=${so_order}`
+ );
+ setData(fetchedData[0]);
+ };
+ fetchData();
+ }, [query]);
+
+ // Kirim email ketika komponen ini dimount atau sesuai kondisi
+ const sendEmail = async () => {
+ try {
+ const send = await axios.post(
+ `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/finish-checkout?orderName=${query?.order_id}`,
+ {}
+ );
+ if (send.status === 200) {
+ toast.success('Berhasil mengirim rincian pesanan');
+ } else {
+ toast.error('Gagal mengirimkan rincian pesanan');
+ }
+ } catch (error) {
+ console.error(error);
+ toast.error('Gagal mengirimkan rincian pesanan');
+ }
+ };
+
return (
- <div className='mx-auto container p-4 md:p-0 mt-0 md:mt-10'>
- <div className='rounded-xl bg-warning-100 text-center border border-warning-300 w-full md:w-1/2 mx-auto'>
- <div className='px-4 py-6 text-warning-900'>
- <p className='font-semibold mb-2'>Terima Kasih atas Pembelian Anda</p>
- <p className='text-warning-800 mb-4 leading-6'>
- Rincian belanja sudah kami kirimkan ke email anda. Mohon dicek kembali. jika tidak
- menerima email, anda dapat menghubungi kami disini.
- </p>
- <p className='mb-2 font-medium'>{query?.order_id?.replaceAll('-', '/')}</p>
- <p className='text-caption-2 text-warning-800'>No. Transaksi</p>
- </div>
+ <div className='flex flex-col items-center'>
+ <Image
+ src='/images/CHECKOUT-PESANAN.svg'
+ alt='Checkout Pesanan'
+ width={isMobile ? 300 : 450}
+ height={isMobile ? 300 : 450}
+ />
+ <div className='text-title-sm md:text-title-lg text-center font-semibold'>
+ Terima Kasih atas Pembelian Kamu
+ </div>
+ <div className='flex flex-col justify-center items-center text-body-2 md:text-body-1 text-center mt-3 px-24 md:px-36 py-4 border-2 gap-y-2 rounded'>
+ <p className='font-bold'>No. Transaksi</p>
+ <p className='mb-2 font-medium text-red-500 text-xl'>
+ {query?.order_id?.replaceAll('-', '/')}
+ </p>
<Link
- href='/my/quotations'
- className='bg-warning-400 text-warning-900 rounded-b-xl py-4 block'
+ href={`/my/quotations/${data?.id}`}
+ className='btn-solid-red rounded-md text-base'
>
- Lihat detail pembelian Anda disini
+ Cek Detail Transaksi
</Link>
</div>
+ <div className='mt-2 text-center leading-6 text-base p-4 md:p-0 md:max-w-[700px]'>
+ Rincian pembelian sudah kami kirimkan ke email kamu. Mohon dicek
+ kembali. jika tidak menerima email, kamu dapat menghubungi kami{' '}
+ <a className='text-red-500' href={whatsappUrl()}>
+ di sini
+ </a>{' '}
+ atau{' '}
+ <span onClick={sendEmail} className='text-red-500 cursor-pointer'>
+ kirim rincian pesanan ulang
+ </span>
+ .
+ </div>
</div>
- )
-}
+ );
+};
-export default FinishCheckout
+export default FinishCheckout;
diff --git a/src/lib/flashSale/components/FlashSale.jsx b/src/lib/flashSale/components/FlashSale.jsx
index 5be6d4e3..6d90cad7 100644
--- a/src/lib/flashSale/components/FlashSale.jsx
+++ b/src/lib/flashSale/components/FlashSale.jsx
@@ -2,10 +2,8 @@ import Image from 'next/image';
import { useEffect, useState } from 'react';
import CountDown from '@/core/components/elements/CountDown/CountDown';
-import productSearchApi from '@/lib/product/api/productSearchApi';
import ProductSlider from '@/lib/product/components/ProductSlider';
-import flashSaleApi from '../api/flashSaleApi';
import { FlashSaleSkeleton } from '../skeleton/FlashSaleSkeleton';
const FlashSale = () => {
@@ -14,10 +12,14 @@ const FlashSale = () => {
useEffect(() => {
const loadFlashSales = async () => {
- const dataFlashSales = await flashSaleApi();
- setFlashSales(dataFlashSales);
+ const res = await fetch('/api/flashsale-header');
+ const { data } = await res.json();
+ if (data) {
+ setFlashSales(data);
+ }
setIsLoading(false);
};
+
loadFlashSales();
}, []);
@@ -26,54 +28,64 @@ const FlashSale = () => {
}
return (
- flashSales?.length > 0 && (
- <div className='px-4 sm:px-0 grid grid-cols-1 gap-y-8'>
- {flashSales.map((flashSale, index) => (
- <div key={index}>
- <div className='flex gap-x-3 mb-4 justify-between sm:justify-start'>
- <div className='font-medium sm:text-h-lg mt-1.5'>
- {flashSale.name}
+ <div className='sm:mt-4'>
+ {flashSales?.length > 0 && (
+ <div className='px-4 sm:px-0 grid grid-cols-1 gap-y-8 sm:mt-4'>
+ {flashSales.map((flashSale, index) => (
+ <div key={index}>
+ <div className='flex gap-x-3 mb-4 justify-between sm:justify-start'>
+ <div className='font-medium sm:text-h-lg mt-1.5'>
+ {flashSale.name}
+ </div>
+ <CountDown initialTime={flashSale.duration} />
</div>
- <CountDown initialTime={flashSale.duration} />
- </div>
- <div className='relative'>
- <Image
- src={flashSale.banner}
- alt={flashSale.name}
- width={1080}
- height={192}
- className='w-full rounded mb-4 hidden sm:block'
- />
- <Image
- src={flashSale.bannerMobile}
- alt={flashSale.name}
- width={256}
- height={48}
- className='w-full rounded mb-4 block sm:hidden'
- />
- <FlashSaleProduct flashSaleId={flashSale.pricelistId} />
+ <div className='relative'>
+ <Image
+ src={flashSale.banner}
+ alt={flashSale.name}
+ width={1080}
+ height={192}
+ className='w-full rounded mb-4 hidden sm:block'
+ />
+ <Image
+ src={flashSale.bannerMobile}
+ alt={flashSale.name}
+ width={256}
+ height={48}
+ className='w-full rounded mb-4 block sm:hidden'
+ />
+ <FlashSaleProduct
+ flashSaleId={flashSale.pricelistId}
+ duration={flashSale.duration}
+ />
+ </div>
</div>
- </div>
- ))}
- </div>
- )
+ ))}
+ </div>
+ )}
+ </div>
);
};
-const FlashSaleProduct = ({ flashSaleId }) => {
+const FlashSaleProduct = ({ flashSaleId, duration }) => {
const [products, setProducts] = useState(null);
-
useEffect(() => {
+ const data_search = new URLSearchParams({
+ query: `fq=flashsale_id_i:${flashSaleId}&fq=flashsale_price_f:[1 TO *]&limit=500&orderBy=flashsale-price-asc&source=similar`,
+ operation: 'AND',
+ duration: `${duration}`,
+ });
const loadProducts = async () => {
- const dataProducts = await productSearchApi({
- query: `fq=flashsale_id_i:${flashSaleId}&fq=flashsale_price_f:[1 TO *]&limit=500&orderBy=flashsale-price-asc`,
- operation: 'AND',
- });
- setProducts(dataProducts.response);
+ const res = await fetch(
+ `/api/search-flashsale?${data_search.toString()}`
+ );
+ const { data } = await res.json();
+ setProducts(data.response);
};
+
loadProducts();
- }, [flashSaleId]);
+ }, []);
return <ProductSlider products={products} />;
};
diff --git a/src/lib/flashSale/components/FlashSaleNonDisplay.jsx b/src/lib/flashSale/components/FlashSaleNonDisplay.jsx
new file mode 100644
index 00000000..4b420fac
--- /dev/null
+++ b/src/lib/flashSale/components/FlashSaleNonDisplay.jsx
@@ -0,0 +1,68 @@
+import Image from 'next/image';
+import { useEffect, useState } from 'react';
+import CountDown from '@/core/components/elements/CountDown/CountDown';
+import productSearchApi from '@/lib/product/api/productSearchApi';
+import ProductSlider from '@/lib/product/components/ProductSlider';
+import flashSaleApi from '../api/flashSaleApi';
+import { FlashSaleSkeleton } from '../skeleton/FlashSaleSkeleton';
+import Link from 'next/link';
+import { useRouter } from 'next/router';
+const FlashSaleNonDisplay = () => {
+ const [flashSales, setFlashSales] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+ const router = useRouter();
+ useEffect(() => {
+ const loadFlashSales = async () => {
+ const dataFlashSales = await flashSaleApi();
+ setFlashSales(dataFlashSales);
+ setIsLoading(false);
+ };
+ loadFlashSales();
+ }, []);
+ const handleSubmit = () => {
+ router.push(`/shop/search?penawaran=${flashSales[0]?.pricelistId}`);
+ };
+ if (isLoading) {
+ return <FlashSaleSkeleton />;
+ }
+
+ return (
+ flashSales?.length > 0 && (
+ <div className='px-4 sm:px-0 grid grid-cols-1 gap-y-8'>
+ {flashSales.map((flashSale, index) => (
+ <div key={index}>
+ <div className='flex items-center mb-4 justify-between '>
+ <div className='font-medium sm:text-h-lg mt-1.5'>
+ Penawaran Terbatas
+ </div>
+ <div
+ onClick={handleSubmit}
+ className='!text-red-500 font-semibold cursor-pointer'
+ >
+ Lihat Semua
+ </div>
+ </div>
+ <div className='relative'>
+ <FlashSaleProduct flashSaleId={flashSale.pricelistId} />
+ </div>
+ </div>
+ ))}
+ </div>
+ )
+ );
+};
+const FlashSaleProduct = ({ flashSaleId }) => {
+ const [products, setProducts] = useState(null);
+ useEffect(() => {
+ const loadProducts = async () => {
+ const dataProducts = await productSearchApi({
+ query: `fq=-flashsale_id_i:${flashSaleId}&fq=flashsale_price_f:[1 TO *]&limit=25&orderBy=flashsale-discount-desc&source=similar`,
+ operation: 'AND',
+ });
+ setProducts(dataProducts.response);
+ };
+ loadProducts();
+ }, [flashSaleId]);
+ return <ProductSlider products={products} />;
+};
+export default FlashSaleNonDisplay;
diff --git a/src/lib/home/api/categoryManagementApi.js b/src/lib/home/api/categoryManagementApi.js
index 2ff4fdfc..4101f87a 100644
--- a/src/lib/home/api/categoryManagementApi.js
+++ b/src/lib/home/api/categoryManagementApi.js
@@ -42,3 +42,11 @@ const map = async (promotions) => {
return productMapped;
});
};
+
+export const fetchCategoryManagementVersion = async () => {
+ const response = await fetch(
+ '/solr/admin/cores?action=STATUS&core=category_management'
+ );
+ const data = await response.json();
+ return data.status.category_management.index.version;
+};
diff --git a/src/lib/home/components/BannerSection.jsx b/src/lib/home/components/BannerSection.jsx
index f83c36fc..303b5c4b 100644
--- a/src/lib/home/components/BannerSection.jsx
+++ b/src/lib/home/components/BannerSection.jsx
@@ -1,18 +1,48 @@
import Link from '@/core/components/elements/Link/Link';
import Image from 'next/image';
+import { useEffect, useState } from 'react';
+import { bannerApi } from '../../../api/bannerApi';
const { useQuery } = require('react-query');
const { default: bannerSectionApi } = require('../api/bannerSectionApi');
const BannerSection = () => {
- const fetchBannerSection = async () => await bannerSectionApi();
- const bannerSection = useQuery('bannerSection', fetchBannerSection);
+ const [data, setData] = useState(null);
+ const [shouldFetch, setShouldFetch] = useState(false);
+ useEffect(() => {
+ const fetchCategoryData = async () => {
+ const res = await fetch('/api/banner-section');
+ const { data } = await res.json();
+ if (data) {
+ setData(data);
+ }
+ };
+
+ fetchCategoryData();
+ }, []);
+
+ // const fetchBannerSection = async () => await bannerSectionApi();
+ const getBannerSection = useQuery(
+ 'bannerSection',
+ bannerApi({ type: 'home-banner' }),
+ {
+ enabled: shouldFetch,
+ onSuccess: (data) => {
+ if (data) {
+ localStorage.setItem('Homepage_bannerSection', JSON.stringify(data));
+ setData(data);
+ }
+ },
+ }
+ );
+
+ const bannerSection = data;
return (
- bannerSection.data &&
- bannerSection.data?.length > 0 && (
+ bannerSection &&
+ bannerSection?.length > 0 && (
<div className='grid grid-cols-1 sm:grid-cols-2 gap-4'>
- {bannerSection.data?.map((banner) => (
+ {bannerSection?.map((banner) => (
<Link key={banner.id} href={banner.url}>
<Image
width={1024}
diff --git a/src/lib/home/components/CategoryDynamic.jsx b/src/lib/home/components/CategoryDynamic.jsx
index 49a9a93f..cc4f42b7 100644
--- a/src/lib/home/components/CategoryDynamic.jsx
+++ b/src/lib/home/components/CategoryDynamic.jsx
@@ -1,9 +1,9 @@
-import React, { useEffect, useState, useCallback } from 'react';
+import React, { useEffect, useState } from 'react';
import { fetchCategoryManagementSolr } from '../api/categoryManagementApi';
+import { Skeleton } from '@chakra-ui/react';
import NextImage from 'next/image';
import Link from 'next/link';
import { createSlug } from '@/core/utils/slug';
-import { Skeleton } from '@chakra-ui/react';
import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/css';
import 'swiper/css/navigation';
@@ -12,45 +12,21 @@ import { Pagination } from 'swiper';
const CategoryDynamic = () => {
const [categoryManagement, setCategoryManagement] = useState([]);
- const [isLoading, setIsLoading] = useState(false);
- const loadBrand = useCallback(async () => {
- setIsLoading(true);
- const items = await fetchCategoryManagementSolr();
-
- setIsLoading(false);
- setCategoryManagement(items);
- }, []);
+ const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
- loadBrand();
- }, [loadBrand]);
-
- // const [categoryData, setCategoryData] = useState({});
- // const [subCategoryData, setSubCategoryData] = useState({});
-
- // useEffect(() => {
- // const fetchCategoryData = async () => {
- // if (categoryManagement && categoryManagement.data) {
- // const updatedCategoryData = {};
- // const updatedSubCategoryData = {};
+ const fetchCategoryData = async () => {
+ setIsLoading(true);
+ const res = await fetch('/api/category-management');
+ const { data } = await res.json();
+ if (data) {
+ setCategoryManagement(data);
+ }
+ setIsLoading(false);
+ };
- // for (const category of categoryManagement.data) {
- // const countLevel1 = await odooApi('GET', `/api/v1/category/numFound?parent_id=${category.categoryIdI}`);
-
- // updatedCategoryData[category.categoryIdI] = countLevel1?.numFound;
-
- // for (const subCategory of countLevel1?.children) {
- // updatedSubCategoryData[subCategory.id] = subCategory?.numFound;
- // }
- // }
-
- // setCategoryData(updatedCategoryData);
- // setSubCategoryData(updatedSubCategoryData);
- // }
- // };
-
- // fetchCategoryData();
- // }, [categoryManagement.isLoading]);
+ fetchCategoryData();
+ }, []);
const swiperBanner = {
modules: [Pagination],
@@ -66,115 +42,99 @@ const CategoryDynamic = () => {
return (
<div>
{categoryManagement &&
- categoryManagement?.map((category) => {
- // const countLevel1 = categoryData[category.categoryIdI] || 0;
- return (
- <Skeleton key={category.id} isLoaded={!isLoading}>
- <div key={category.id}>
- <div className='bagian-judul flex flex-row justify-start items-center gap-3 mb-4 mt-4'>
- <h1 className='font-semibold text-[14px] sm:text-h-lg mr-2'>
- {category.name}
- </h1>
- {/* <Skeleton isLoaded={countLevel1 != 0}>
- <p className={`text-gray_r-10 text-sm`}>{countLevel1} Produk tersedia</p>
- </Skeleton> */}
- <Link
- href={createSlug(
- '/shop/category/',
- category?.name,
- category?.category_id
- )}
- className='!text-red-500 font-semibold'
- >
- Lihat Semua
- </Link>
- </div>
-
- {/* Swiper for SubCategories */}
- <Swiper {...swiperBanner}>
- {category.categories.map((subCategory) => {
- // const countLevel2 = subCategoryData[subCategory.idLevel2] || 0;
+ categoryManagement.map((category) => (
+ <Skeleton key={category.id} isLoaded={!isLoading}>
+ <div key={category.id}>
+ <div className='bagian-judul flex flex-row justify-start items-center gap-3 mb-4 mt-4'>
+ <h1 className='font-semibold text-[14px] sm:text-h-lg mr-2'>
+ {category.name}
+ </h1>
+ <Link
+ href={createSlug(
+ '/shop/category/',
+ category?.name,
+ category?.category_id
+ )}
+ className='!text-red-500 font-semibold'
+ >
+ Lihat Semua
+ </Link>
+ </div>
- return (
- <SwiperSlide key={subCategory.id}>
- <div className='border rounded justify-start items-start '>
- <div className='p-3'>
- <div className='flex flex-row border rounded mb-2 justify-start items-center'>
- <NextImage
- src={
- subCategory.image
- ? subCategory.image
- : '/images/noimage.jpeg'
- }
- alt={subCategory.name}
- width={90}
- height={30}
- className='object-fit p-4'
- />
- <div className='bagian-judul flex flex-col justify-center items-start gap-2 ml-2'>
- <h2 className='font-semibold text-lg mr-2'>
- {subCategory?.name}
- </h2>
- {/* <Skeleton isLoaded={countLevel2 != 0}>
- <p className={`text-gray_r-10 text-sm`}>
- {countLevel2} Produk tersedia
- </p>
- </Skeleton> */}
+ <Swiper {...swiperBanner}>
+ {category?.categories?.map((subCategory) => (
+ <SwiperSlide key={subCategory.id}>
+ <div className='border rounded justify-start items-start '>
+ <div className='p-3'>
+ <div className='flex flex-row border rounded mb-2 justify-start items-center'>
+ <NextImage
+ src={
+ subCategory.image
+ ? subCategory.image
+ : '/images/noimage.jpeg'
+ }
+ alt={subCategory.name}
+ width={90}
+ height={30}
+ className='object-fit p-4'
+ />
+ <div className='bagian-judul flex flex-col justify-center items-start gap-2 ml-2'>
+ <h2 className='font-semibold text-lg mr-2'>
+ {subCategory?.name}
+ </h2>
+ <Link
+ href={createSlug(
+ '/shop/category/',
+ subCategory?.name,
+ subCategory?.id_level_2
+ )}
+ className='!text-red-500 font-semibold'
+ >
+ Lihat Semua
+ </Link>
+ </div>
+ </div>
+ <div className='grid grid-cols-2 gap-2 overflow-y-auto max-h-[240px] min-h-[240px] content-start'>
+ {subCategory.child_frontend_id_i.map(
+ (childCategory) => (
+ <div key={childCategory.id} className=''>
<Link
href={createSlug(
'/shop/category/',
- subCategory?.name,
- subCategory?.id_level_2
+ childCategory?.name,
+ childCategory?.id_level_3
)}
- className='!text-red-500 font-semibold'
+ className='flex flex-row gap-2 border rounded group hover:border-red-500'
>
- Lihat Semua
+ <NextImage
+ src={
+ childCategory.image
+ ? childCategory.image
+ : '/images/noimage.jpeg'
+ }
+ alt={childCategory.name}
+ className='p-2 ml-1'
+ width={40}
+ height={40}
+ />
+ <div className='bagian-judul flex flex-col justify-center items-center gap-2 break-words line-clamp-2 group-hover:text-red-500'>
+ <h3 className='font-semibold line-clamp-2 group-hover:text-red-500 text-sm mr-2'>
+ {childCategory.name}
+ </h3>
+ </div>
</Link>
</div>
- </div>
- <div className='grid grid-cols-2 gap-2 overflow-y-auto max-h-[240px] min-h-[240px] content-start'>
- {subCategory.child_frontend_id_i.map(
- (childCategory) => (
- <div key={childCategory.id} className=''>
- <Link
- href={createSlug(
- '/shop/category/',
- childCategory?.name,
- childCategory?.id_level_3
- )}
- className='flex flex-row gap-2 border rounded group hover:border-red-500'
- >
- <NextImage
- src={
- childCategory.image
- ? childCategory.image
- : '/images/noimage.jpeg'
- }
- alt={childCategory.name}
- className='p-2 ml-1'
- width={40}
- height={40}
- />
- <div className='bagian-judul flex flex-col justify-center items-center gap-2 break-words line-clamp-2 group-hover:text-red-500'>
- <h3 className='font-semibold line-clamp-2 group-hover:text-red-500 text-sm mr-2'>
- {childCategory.name}
- </h3>
- </div>
- </Link>
- </div>
- )
- )}
- </div>
- </div>
+ )
+ )}
</div>
- </SwiperSlide>
- );
- })}
- </Swiper>
- </div>
- </Skeleton>
- );
- })}
+ </div>
+ </div>
+ </SwiperSlide>
+ ))}
+ </Swiper>
+ </div>
+ </Skeleton>
+ ))}
</div>
);
};
diff --git a/src/lib/home/components/CategoryDynamicMobile.jsx b/src/lib/home/components/CategoryDynamicMobile.jsx
index 4a8f13cf..67ae6f5f 100644
--- a/src/lib/home/components/CategoryDynamicMobile.jsx
+++ b/src/lib/home/components/CategoryDynamicMobile.jsx
@@ -4,52 +4,46 @@ import Link from 'next/link';
import { createSlug } from '@/core/utils/slug';
import { Swiper, SwiperSlide } from 'swiper/react';
import 'swiper/css';
-import { fetchCategoryManagementSolr } from '../api/categoryManagementApi';
+import {
+ fetchCategoryManagementSolr,
+ fetchCategoryManagementVersion,
+} from '../api/categoryManagementApi';
const CategoryDynamicMobile = () => {
const [selectedCategory, setSelectedCategory] = useState({});
const [categoryManagement, setCategoryManagement] = useState([]);
const [isLoading, setIsLoading] = useState(false);
- const loadBrand = useCallback(async () => {
- setIsLoading(true);
- const items = await fetchCategoryManagementSolr();
+ useEffect(() => {
+ const fetchCategoryData = async () => {
+ setIsLoading(true);
+ const res = await fetch('/api/category-management');
+ const { data } = await res.json();
+ if (data) {
+ setCategoryManagement(data);
+ }
+ setIsLoading(false);
+ };
- setIsLoading(false);
- setCategoryManagement(items);
+ fetchCategoryData();
}, []);
useEffect(() => {
- loadBrand();
- }, [loadBrand]);
-
- useEffect(() => {
- const loadPromo = async () => {
- try {
- if (categoryManagement?.length > 0) {
- const initialSelections = categoryManagement.reduce(
- (acc, category) => {
- if (category.categories.length > 0) {
- acc[category.id] = category.categories[0].id_level_2;
- }
- return acc;
- },
- {}
- );
- setSelectedCategory(initialSelections);
+ if (categoryManagement?.length > 0) {
+ const initialSelections = categoryManagement.reduce((acc, category) => {
+ if (category.categories.length > 0) {
+ acc[category.id] = category.categories[0].id_level_2;
}
- } catch (loadError) {
- // console.error("Error loading promo items:", loadError);
- }
- };
-
- loadPromo();
+ return acc;
+ }, {});
+ setSelectedCategory(initialSelections);
+ }
}, [categoryManagement]);
- const handleCategoryLevel2Click = (categoryIdI, idLevel2) => {
+ const handleCategoryLevel2Click = (categoryId, idLevel2) => {
setSelectedCategory((prev) => ({
...prev,
- [categoryIdI]: idLevel2,
+ [categoryId]: idLevel2,
}));
};
diff --git a/src/lib/home/components/PreferredBrand.jsx b/src/lib/home/components/PreferredBrand.jsx
index eefced60..b7a30503 100644
--- a/src/lib/home/components/PreferredBrand.jsx
+++ b/src/lib/home/components/PreferredBrand.jsx
@@ -1,49 +1,50 @@
-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'
-import BrandCard from '@/lib/brand/components/BrandCard'
-import useDevice from '@/core/hooks/useDevice'
-import Link from '@/core/components/elements/Link/Link'
-import axios from 'axios'
+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';
+import BrandCard from '@/lib/brand/components/BrandCard';
+import useDevice from '@/core/hooks/useDevice';
+import Link from '@/core/components/elements/Link/Link';
+import axios from 'axios';
const PreferredBrand = () => {
- let query = ''
- let params = 'prioritas'
- const [isLoading, setIsLoading] = useState(true)
- const [startWith, setStartWith] = useState(null)
- const [manufactures, setManufactures] = useState([])
+ let query = '';
+ let params = 'prioritas';
+ const [isLoading, setIsLoading] = useState(true);
+ const [startWith, setStartWith] = useState(null);
+ const [manufactures, setManufactures] = useState([]);
const loadBrand = useCallback(async () => {
- setIsLoading(true)
- const name = startWith ? `${startWith}*` : ''
- const result = await axios(`${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/brands?rows=20`)
-
- setIsLoading(false)
- setManufactures((manufactures) => [...result.data])
- }, [startWith])
+ setIsLoading(true);
+ const name = startWith ? `${startWith}*` : '';
+ const result = await axios(
+ `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/preferredBrand?rows=20`
+ );
+ setIsLoading(false);
+ setManufactures((manufactures) => [...result.data]);
+ }, [startWith]);
const toggleStartWith = (alphabet) => {
- setManufactures([])
+ setManufactures([]);
if (alphabet == startWith) {
- setStartWith(null)
- return
+ setStartWith(null);
+ return;
}
- setStartWith(alphabet)
- }
+ setStartWith(alphabet);
+ };
useEffect(() => {
- loadBrand()
- }, [])
+ loadBrand();
+ }, []);
// const { preferredBrands } = usePreferredBrand(query)
- const { isMobile, isDesktop } = useDevice()
+ const { isMobile, isDesktop } = useDevice();
const swiperBanner = {
- modules:[Navigation, Pagination, Autoplay],
+ modules: [Navigation, Pagination, Autoplay],
autoplay: {
delay: 4000,
- disableOnInteraction: false
+ disableOnInteraction: false,
},
loop: true,
className: 'h-[70px] md:h-[100px] w-full',
@@ -53,13 +54,17 @@ const PreferredBrand = () => {
dynamicBullets: true,
dynamicMainBullets: isMobile ? 6 : 8,
clickable: true,
- }
- }
- const preferredBrandsData = manufactures ? manufactures.slice(0, 20) : []
+ },
+ };
+ const preferredBrandsData = manufactures ? manufactures.slice(0, 20) : [];
return (
<div className='px-4 sm:px-0'>
<div className='flex justify-between items-center mb-4'>
- <h1 className='font-semibold text-[14px] sm:text-h-lg'><Link href='/shop/brands' className='!text-black font-semibold'>Brand Pilihan</Link></h1>
+ <h1 className='font-semibold text-[14px] sm:text-h-lg'>
+ <Link href='/shop/brands' className='!text-black font-semibold'>
+ Brand Pilihan
+ </Link>
+ </h1>
{isDesktop && (
<Link href='/shop/brands' className='!text-red-500 font-semibold'>
Lihat Semua
@@ -79,7 +84,7 @@ const PreferredBrand = () => {
)}
</div>
</div>
- )
-}
+ );
+};
-export default PreferredBrand \ No newline at end of file
+export default PreferredBrand;
diff --git a/src/lib/home/components/PromotionProgram.jsx b/src/lib/home/components/PromotionProgram.jsx
index ae06bd4d..562fa138 100644
--- a/src/lib/home/components/PromotionProgram.jsx
+++ b/src/lib/home/components/PromotionProgram.jsx
@@ -4,15 +4,56 @@ import { bannerApi } from '@/api/bannerApi';
import useDevice from '@/core/hooks/useDevice';
import { Swiper, SwiperSlide } from 'swiper/react';
import BannerPromoSkeleton from '../components/Skeleton/BannerPromoSkeleton';
+import { useEffect, useState } from 'react';
const { useQuery } = require('react-query');
const BannerSection = () => {
- const promotionProgram = useQuery(
- 'promotionProgram',
- bannerApi({ type: 'banner-promotion' })
- );
const { isMobile, isDesktop } = useDevice();
+ const [data, setData] = useState(null);
+ const [shouldFetch, setShouldFetch] = useState(false);
+ useEffect(() => {
+ const fetchData = async () => {
+ const res = await fetch(`/api/hero-banner?type=banner-promotion`);
+ const { data } = await res.json();
+ if (data) {
+ setData(data);
+ }
+ };
+
+ fetchData();
+ }, []);
+
+ // useEffect(() => {
+ // const localData = localStorage.getItem('Homepage_promotionProgram');
+ // if (localData) {
+ // setData(JSON.parse(localData));
+ // } else {
+ // setShouldFetch(true);
+ // }
+ // }, []);
+
+ // const getPromotionProgram = useQuery(
+ // 'promotionProgram',
+ // bannerApi({ type: 'banner-promotion' }),
+ // {
+ // enabled: shouldFetch,
+ // onSuccess: (data) => {
+ // if (data) {
+ // localStorage.setItem(
+ // 'Homepage_promotionProgram',
+ // JSON.stringify(data)
+ // );
+ // setData(data);
+ // }
+ // },
+ // }
+ // );
+
+ const promotionProgram = data;
- if (promotionProgram.isLoading) {
+ // if (getPromotionProgram?.isLoading && !data) {
+ // return <BannerPromoSkeleton />;
+ // }
+ if (!data) {
return <BannerPromoSkeleton />;
}
@@ -39,28 +80,26 @@ const BannerSection = () => {
</Link>
)}
</div>
- {isDesktop &&
- promotionProgram.data &&
- promotionProgram.data?.length > 0 && (
- <div className='grid grid-cols-3 sm:grid-cols-3 gap-4 rounded-md'>
- {promotionProgram.data?.map((banner) => (
- <Link key={banner.id} href={banner.url}>
- <Image
- width={439}
- height={150}
- quality={85}
- src={banner.image}
- alt={banner.name}
- className='h-auto w-full rounded hover:scale-105 transition duration-500 ease-in-out'
- />
- </Link>
- ))}
- </div>
- )}
+ {isDesktop && promotionProgram && promotionProgram?.length > 0 && (
+ <div className='grid grid-cols-3 sm:grid-cols-3 gap-4 rounded-md'>
+ {promotionProgram?.map((banner) => (
+ <Link key={banner.id} href={banner.url}>
+ <Image
+ width={439}
+ height={150}
+ quality={85}
+ src={banner.image}
+ alt={banner.name}
+ className='h-auto w-full rounded hover:scale-105 transition duration-500 ease-in-out'
+ />
+ </Link>
+ ))}
+ </div>
+ )}
{isMobile && (
<Swiper slidesPerView={1.1} spaceBetween={8} freeMode>
- {promotionProgram.data?.map((banner) => (
+ {promotionProgram?.map((banner) => (
<SwiperSlide key={banner.id}>
<Link key={banner.id} href={banner.url}>
<Image
diff --git a/src/lib/home/components/ServiceList.jsx b/src/lib/home/components/ServiceList.jsx
index 5b16915d..b3cc8fe5 100644
--- a/src/lib/home/components/ServiceList.jsx
+++ b/src/lib/home/components/ServiceList.jsx
@@ -32,7 +32,7 @@ const ServiceList = () => {
</div>
<div className='w-full'>
<Link
- href='/tentang-kami'
+ href='/garansi-resmi'
className='border border-gray-200 p-2 flex items-center gap-x-2 rounded-lg'
>
<div className=''>
@@ -57,7 +57,7 @@ const ServiceList = () => {
</div>
<div className='w-full '>
<Link
- href='/tentang-kami'
+ href='/pembayaran-tempo'
className='border border-gray-200 p-2 flex items-center gap-x-2 rounded-lg'
>
<div className=''>
diff --git a/src/lib/product/components/Product/ProductDesktopVariant.jsx b/src/lib/product/components/Product/ProductDesktopVariant.jsx
index 09b30a44..5dfd452b 100644
--- a/src/lib/product/components/Product/ProductDesktopVariant.jsx
+++ b/src/lib/product/components/Product/ProductDesktopVariant.jsx
@@ -1,12 +1,12 @@
-
-import { Box, Skeleton, Tooltip } from '@chakra-ui/react';
+import { Box, Button, Skeleton, Tooltip } from '@chakra-ui/react';
import { HeartIcon } from '@heroicons/react/24/outline';
-import { Info } from 'lucide-react';
+import { Info, MessageCircleIcon, Share2Icon } from 'lucide-react';
import { useRouter } from 'next/router';
import { useCallback, useEffect, useRef, useState } from 'react';
import { toast } from 'react-hot-toast';
+import AddToWishlist from '../../../../../src-migrate/modules/product-detail/components/AddToWishlist';
+import { RWebShare } from 'react-web-share';
import LazyLoad from 'react-lazy-load';
-
import { useProductCartContext } from '@/contexts/ProductCartContext';
import odooApi from '@/core/api/odooApi';
import Image from '@/core/components/elements/Image/Image';
@@ -18,10 +18,16 @@ import { updateItemCart } from '@/core/utils/cart';
import currencyFormat from '@/core/utils/currencyFormat';
import { createSlug } from '@/core/utils/slug';
import whatsappUrl from '@/core/utils/whatsappUrl';
+import { getAuth } from '~/libs/auth';
+import ImageNext from 'next/image';
import productSimilarApi from '../../api/productSimilarApi';
import ProductCard from '../ProductCard';
import ProductSimilar from '../ProductSimilar';
+import ProductPromoSection from '~/modules/product-promo/components/Section';
+import SimilarBottom from '~/modules/product-detail/components/SimilarBottom';
+
+const SELF_HOST = process.env.NEXT_PUBLIC_SELF_HOST;
const ProductDesktopVariant = ({
product,
@@ -30,9 +36,10 @@ const ProductDesktopVariant = ({
isVariant,
}) => {
const router = useRouter();
- const auth = useAuth();
+ let auth = useAuth();
const { slug } = router.query;
-
+ const { srsltid } = router.query;
+ const [askAdminUrl, setAskAdminUrl, isApproval] = useState();
const [lowestPrice, setLowestPrice] = useState(null);
const [addCartAlert, setAddCartAlert] = useState(false);
@@ -40,11 +47,20 @@ const ProductDesktopVariant = ({
const { setRefreshCart } = useProductCartContext();
+ const [quantityInput, setQuantityInput] = useState(1);
+
+ const createdAskUrl = whatsappUrl({
+ template: 'product',
+ payload: {
+ manufacture: product.manufacture.name,
+ productName: product.name,
+ url: process.env.NEXT_PUBLIC_SELF_HOST + router.asPath,
+ },
+ fallbackUrl: router.asPath,
+ });
+
const getLowestPrice = useCallback(() => {
const lowest = product.price;
- /* const lowest = prices.reduce((lowest, price) => {
- return price.priceDiscount < lowest.priceDiscount ? price : lowest
- }, prices[0])*/
return lowest;
}, [product]);
@@ -74,10 +90,10 @@ const ProductDesktopVariant = ({
const handleAddToCart = (variant) => {
if (!auth) {
- router.push(`/login?next=/shop/product/${slug}`);
+ router.push(`/login?next=/shop/product/${slug}?srsltid=${srsltid}`);
return;
}
- const quantity = variantQuantityRefs.current[product.id].value;
+ const quantity = quantityInput;
if (!validQuantity(quantity)) return;
updateItemCart({
productId: product.id,
@@ -91,8 +107,34 @@ const ProductDesktopVariant = ({
setAddCartAlert(true);
};
- const handleBuy = (variant) => {
- const quantity = variantQuantityRefs.current[product.id].value;
+ const handleBuy = async (variant) => {
+ const quantity = variantQuantityRefs?.current[product.id]?.value;
+ let isLoggedIn = typeof auth === 'object';
+
+ if (!isLoggedIn) {
+ const currentUrl = encodeURIComponent(router.asPath);
+ await router.push(`/login?next=${currentUrl}`);
+
+ // Tunggu login berhasil, misalnya dengan memantau perubahan status auth.
+ const authCheckInterval = setInterval(() => {
+ const newAuth = getAuth();
+ if (typeof newAuth === 'object') {
+ isLoggedIn = true;
+ auth = newAuth; // Update nilai auth setelah login
+ clearInterval(authCheckInterval);
+ }
+ }, 500); // Periksa status login setiap 500ms
+
+ await new Promise((resolve) => {
+ const checkLogin = setInterval(() => {
+ if (isLoggedIn) {
+ clearInterval(checkLogin);
+ resolve(null);
+ }
+ }, 500);
+ });
+ }
+
if (!validQuantity(quantity)) return;
updateItemCart({
@@ -105,6 +147,45 @@ const ProductDesktopVariant = ({
router.push(`/shop/checkout?source=buy`);
};
+ const handleButton = async (variant) => {
+ const quantity = quantityInput;
+ let isLoggedIn = typeof auth === 'object';
+
+ if (!isLoggedIn) {
+ const currentUrl = encodeURIComponent(router.asPath);
+ await router.push(`/login?next=${currentUrl}`);
+
+ // Tunggu login berhasil, misalnya dengan memantau perubahan status auth.
+ const authCheckInterval = setInterval(() => {
+ const newAuth = getAuth();
+ if (typeof newAuth === 'object') {
+ isLoggedIn = true;
+ auth = newAuth; // Update nilai auth setelah login
+ clearInterval(authCheckInterval);
+ }
+ }, 500); // Periksa status login setiap 500ms
+
+ await new Promise((resolve) => {
+ const checkLogin = setInterval(() => {
+ if (isLoggedIn) {
+ clearInterval(checkLogin);
+ resolve(null);
+ }
+ }, 500);
+ });
+ }
+ if (!validQuantity(quantity)) return;
+
+ updateItemCart({
+ productId: variant,
+ quantity,
+ programLineId: null,
+ selected: true,
+ source: 'buy',
+ });
+ router.push('/shop/quotation?source=buy');
+ };
+
const variantSectionRef = useRef(null);
const goToVariantSection = () => {
if (variantSectionRef.current) {
@@ -160,87 +241,45 @@ const ProductDesktopVariant = ({
<Image
src={product.image + '?variant=True'}
alt={product.name}
- className='h-[430px] object-contain object-center w-full border border-gray_r-4'
+ className='w-full h-[350px]'
/>
</div>
- <div className='w-7/12 px-4'>
+ <div className='w-7/12 px-6'>
<h1 className='text-title-md leading-10 font-medium'>
{product?.name}
</h1>
<div className='mt-10'>
- <div className='flex p-3'>
- <div className='w-4/12 text-gray_r-12/70'>Nomor SKU</div>
- <div className='w-8/12'>SKU-{product.id}</div>
- </div>
<div className='flex p-3 bg-gray_r-4'>
- <div className='w-4/12 text-gray_r-12/70'>Part Number</div>
- <div className='w-8/12'>{product.code || '-'}</div>
+ <div className='w-4/12 text-gray_r-12/70'>Item Code</div>
+ <div className='w-8/12'>{product.code}</div>
</div>
- <div className='flex p-3'>
+ <div className='flex p-3 items-center '>
<div className='w-4/12 text-gray_r-12/70'>Manufacture</div>
<div className='w-8/12'>
- {product.manufacture?.name ? (
- <Link
- href={createSlug(
- '/shop/brands/',
- product.manufacture?.name,
- product.manufacture?.id
- )}
- >
- {product.manufacture?.name}
- </Link>
- ) : (
- <div>-</div>
- )}
- </div>
- </div>
-
- <div className='flex p-3 items-center bg-gray_r-4'>
- <div className='w-4/12 text-gray_r-12/70'>
- Persiapan Barang
- </div>
- <div className='w-8/12'>
- {!product?.sla && <Skeleton width='20%' height='16px' />}
- {product?.sla && (
- <Tooltip
- placement='top'
- label={`Masa Persiapan Barang ${product?.sla?.slaDate}`}
- >
- <Box className='w-fit flex items-center gap-x-2'>
- {product?.sla?.slaDate}
- <Info size={16} />
- </Box>
- </Tooltip>
- )}
+ <Link
+ href={createSlug(
+ '/shop/brands/',
+ product.manufacture.name,
+ product.manufacture.id.toString()
+ )}
+ >
+ {product?.manufacture.logo ? (
+ <Image
+ width={100}
+ src={product.manufacture.logo}
+ alt={product.manufacture.name}
+ />
+ ) : (
+ <p className='font-bold text-red-500'>
+ {product.manufacture.name}
+ </p>
+ )}
+ </Link>
</div>
</div>
- <div className='flex p-3'>
- <div className='w-4/12 text-gray_r-12/70'>Stock</div>
- <div className='w-8/12'>
- {!product?.sla && <Skeleton width='10%' height='16px' />}
- {product?.sla?.qty > 0 && <span>{product?.sla?.qty}</span>}
- {product?.sla?.qty == 0 && (
- <a
- href={whatsappUrl('product', {
- name: product.name,
- manufacture: product?.manufacture?.name,
- url: createSlug(
- '/shop/product/',
- product.name,
- product.id,
- true
- ),
- })}
- className='text-danger-500 font-medium'
- >
- Tanya Admin
- </a>
- )}
- </div>
- </div>
- <div className='flex p-3 bg-gray_r-4'>
+ <div className='flex p-3 bg-gray_r-4 '>
<div className='w-4/12 text-gray_r-12/70'>Berat Barang</div>
<div className='w-8/12'>
{product?.weight > 0 && <span>{product?.weight} KG</span>}
@@ -262,24 +301,55 @@ const ProductDesktopVariant = ({
)}
</div>
</div>
+ <div className='flex p-3 items-center '>
+ <div className='w-4/12 text-gray_r-12/70'>Terjual</div>
+ <div className='w-8/12'>-</div>
+ </div>
+
+ <div className='flex p-3 items-center bg-gray_r-4 '>
+ <div className='w-4/12 text-gray_r-12/70'>
+ Persiapan Barang
+ </div>
+ <div className='w-8/12'>
+ {!product?.sla && <Skeleton width='20%' height='16px' />}
+ {product?.sla && (
+ <Tooltip
+ placement='top'
+ label={`Masa Persiapan Barang ${product?.sla?.slaDate}`}
+ >
+ <Box className='w-fit flex items-center gap-x-2'>
+ {product?.sla?.slaDate}
+ <Info size={16} />
+ </Box>
+ </Tooltip>
+ )}
+ </div>
+ </div>
</div>
</div>
- <div className='p-4 md:p-6 md:bg-gray-50 rounded-xl'>
- <h2 className='text-h-md md:text-h-lg font-medium'>Informasi Produk</h2>
- <div className='h-4' />
- <div
- className='leading-relaxed text-gray-700'
- dangerouslySetInnerHTML={{
- __html:
- !product.parent.description || product.parent.description == '<p><br></p>'
- ? 'Belum ada deskripsi'
- : product.parent.description,
- }}
- />
+ <div className='p-4 md:p-6 w-full'>
+ <ProductPromoSection product={product} productId={product.id} />
+
+ <div className='p-4 md:p-6 md:bg-gray-50 rounded-xl'>
+ <h2 className='text-h-md md:text-h-lg font-medium'>
+ Informasi Produk
+ </h2>
+ <div className='h-4' />
+ <div
+ className='leading-relaxed text-gray-700'
+ dangerouslySetInnerHTML={{
+ __html:
+ !product.parent.description ||
+ product.parent.description == '<p><br></p>'
+ ? 'Belum ada deskripsi'
+ : product.parent.description,
+ }}
+ />
+ </div>
</div>
</div>
- <div className='w-[25%]'>
+ <div className='w-[33%]'>
{product?.isFlashsale > 0 &&
product?.price?.discountPercentage > 0 ? (
<>
@@ -337,46 +407,143 @@ const ProductDesktopVariant = ({
)}
</h3>
)}
- <div className='flex gap-x-3 mt-4'>
- <input
- type='number'
- className='form-input w-16 py-2 text-center bg-gray_r-1'
- ref={setVariantQuantityRef(product.id)}
- defaultValue={1}
- />
- <button
- type='button'
+ <div className='flex justify-between items-center py-5 px-3'>
+ <div className='relative flex items-center'>
+ <button
+ type='button'
+ className='absolute left-0 px-2 py-1 h-full text-gray-500'
+ onClick={() =>
+ setQuantityInput(
+ String(Math.max(1, Number(quantityInput) - 1))
+ )
+ }
+ >
+ -
+ </button>
+ <input
+ type='number'
+ id='quantity'
+ min={1}
+ value={quantityInput}
+ onChange={(e) => setQuantityInput(e.target.value)}
+ className=' w-24 h-10 text-center border border-gray-300 rounded focus:outline-none'
+ />
+ <button
+ type='button'
+ className='absolute right-0 px-2 py-1 h-full text-gray-500'
+ onClick={() =>
+ setQuantityInput(String(Number(quantityInput) + 1))
+ }
+ >
+ +
+ </button>
+ </div>
+ <div>
+ <Skeleton
+ isLoaded={!isLoadingSLA}
+ h='21px'
+ // w={16}
+ className={
+ product?.sla?.qty < 10 ? 'text-red-600 font-medium' : ''
+ }
+ >
+ Stock : {product?.sla?.qty}{' '}
+ </Skeleton>
+ </div>
+ <div>
+ {product?.sla?.qty > 0 && (
+ <Link href='/panduan-pick-up-service' className='group'>
+ <Image
+ src='/images/PICKUP-NOW.png'
+ className='group-hover:scale-105 transition-transform duration-200 w-28'
+ alt='pickup now'
+ />
+ </Link>
+ )}
+ </div>
+ </div>
+ <div className='flex gap-x-3'>
+ <Button
onClick={() => handleAddToCart(product.id)}
- className='flex-1 py-2 btn-yellow'
+ className='w-full'
+ colorScheme='yellow'
>
Keranjang
- </button>
- <button
- type='button'
+ </Button>
+ <Button
onClick={() => handleBuy(product.id)}
- className='flex-1 py-2 btn-solid-red'
+ className='w-full'
+ colorScheme='red'
>
Beli
- </button>
+ </Button>
</div>
- <div className='flex mt-4'>
- <button
- className='flex items-center gap-x-1'
- onClick={toggleWishlist}
- >
- {wishlist.data?.productTotal > 0 ? (
- <HeartIcon className='w-6 fill-danger-500 text-danger-500' />
- ) : (
- <HeartIcon className='w-6' />
- )}
- Wishlist
- </button>
+ <Button
+ onClick={() => handleButton(product.id)}
+ color={'red'}
+ colorScheme='white'
+ className='w-full border-2 p-2 gap-1 mt-2 hover:bg-slate-100 flex items-center'
+ >
+ <ImageNext
+ src='/images/writing.png'
+ alt='penawaran instan'
+ className=''
+ width={25}
+ height={25}
+ />
+ Penawaran Harga Instan
+ </Button>
+ <div className='flex py-5'>
+ <div className='flex gap-x-5 items-center justify-center'>
+ <Button
+ as={Link}
+ href={createdAskUrl}
+ variant='link'
+ target='_blank'
+ colorScheme='gray'
+ leftIcon={<MessageCircleIcon size={18} />}
+ >
+ Ask Admin
+ </Button>
+
+ <span>|</span>
+
+ <button
+ className='flex items-center gap-x-1'
+ onClick={toggleWishlist}
+ >
+ {wishlist.data?.productTotal > 0 ? (
+ <HeartIcon className='w-6 fill-danger-500 text-danger-500' />
+ ) : (
+ <HeartIcon className='w-6' />
+ )}
+ Wishlist
+ </button>
+
+ <span>|</span>
+
+ <RWebShare
+ data={{
+ text: 'Check out this product',
+ title: `${product.name} - Indoteknik.com`,
+ url: SELF_HOST + router.asPath,
+ }}
+ >
+ <Button
+ variant='link'
+ colorScheme='gray'
+ leftIcon={<Share2Icon size={18} />}
+ >
+ Share
+ </Button>
+ </RWebShare>
+ </div>
</div>
<div className='border border-gray_r-6 overflow-auto mt-4'>
<div className='font-medium text-center p-4 bg-gray_r-1 border-b border-gray_r-6 sticky top-0 z-10'>
Produk Serupa
</div>
- <div className='h-full divide-y divide-gray_r-6 max-h-96'>
+ <div className='h-full divide-y divide-gray_r-6 max-h-[500px]'>
{productSimilarInBrand &&
productSimilarInBrand?.map((product) => (
<div className='py-2' key={product.id}>
@@ -393,8 +560,11 @@ const ProductDesktopVariant = ({
Kamu Mungkin Juga Suka
</div>
<LazyLoad>
- <ProductSimilar query={productSimilarQuery} />
+ <SimilarBottom product={product} />
</LazyLoad>
+ {/* <LazyLoad>
+ <ProductSimilar query={productSimilarQuery} />
+ </LazyLoad> */}
</div>
<BottomPopup
@@ -429,8 +599,11 @@ const ProductDesktopVariant = ({
Kamu Mungkin Juga Suka
</div>
<LazyLoad>
- <ProductSimilar query={productSimilarQuery} />
+ <SimilarBottom product={product} />
</LazyLoad>
+ {/* <LazyLoad>
+ <ProductSimilar query={productSimilarQuery} />
+ </LazyLoad> */}
</div>
</BottomPopup>
</div>
diff --git a/src/lib/product/components/Product/ProductMobileVariant.jsx b/src/lib/product/components/Product/ProductMobileVariant.jsx
index af9e52bb..de5c3f10 100644
--- a/src/lib/product/components/Product/ProductMobileVariant.jsx
+++ b/src/lib/product/components/Product/ProductMobileVariant.jsx
@@ -1,10 +1,10 @@
-import { Skeleton } from '@chakra-ui/react';
+import { Button, Skeleton } from '@chakra-ui/react';
import { HeartIcon } from '@heroicons/react/24/outline';
import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import { toast } from 'react-hot-toast';
import LazyLoad from 'react-lazy-load';
-
+import ImageNext from 'next/image';
import odooApi from '@/core/api/odooApi';
import Divider from '@/core/components/elements/Divider/Divider';
import Image from '@/core/components/elements/Image/Image';
@@ -16,12 +16,15 @@ import currencyFormat from '@/core/utils/currencyFormat';
import { gtagAddToCart } from '@/core/utils/googleTag';
import { createSlug } from '@/core/utils/slug';
import whatsappUrl from '@/core/utils/whatsappUrl';
-
+import { getAuth } from '~/libs/auth';
+import SimilarBottom from '~/modules/product-detail/components/SimilarBottom';
import ProductSimilar from '../ProductSimilar';
const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => {
const router = useRouter();
-
+ const { slug } = router.query;
+ const { srsltid } = router.query;
+ let auth = getAuth();
const [quantity, setQuantity] = useState('1');
const [selectedVariant, setSelectedVariant] = useState(product.id);
const [informationTab, setInformationTab] = useState(
@@ -73,11 +76,16 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => {
return isValid;
};
- const handleClickCart = () => {
+ const handleClickCart = async () => {
+ if (!auth) {
+ router.push(`/login?next=/shop/product/${slug}?srsltid=${srsltid}`);
+ return;
+ }
+
if (!validAction()) return;
gtagAddToCart(activeVariant, quantity);
updateItemCart({
- productId: variant,
+ productId: product.id,
quantity,
programLineId: null,
selected: true,
@@ -86,7 +94,33 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => {
setAddCartAlert(true);
};
- const handleClickBuy = () => {
+ const handleClickBuy = async () => {
+ let isLoggedIn = typeof auth === 'object';
+
+ if (!isLoggedIn) {
+ const currentUrl = encodeURIComponent(router.asPath);
+ await router.push(`/login?next=${currentUrl}`);
+
+ // Tunggu login berhasil, misalnya dengan memantau perubahan status auth.
+ const authCheckInterval = setInterval(() => {
+ const newAuth = getAuth();
+ if (typeof newAuth === 'object') {
+ isLoggedIn = true;
+ auth = newAuth; // Update nilai auth setelah login
+ clearInterval(authCheckInterval);
+ }
+ }, 500); // Periksa status login setiap 500ms
+
+ await new Promise((resolve) => {
+ const checkLogin = setInterval(() => {
+ if (isLoggedIn) {
+ clearInterval(checkLogin);
+ resolve(null);
+ }
+ }, 500);
+ });
+ }
+
if (!validAction()) return;
updateItemCart({
@@ -99,6 +133,20 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => {
router.push(`/shop/checkout?source=buy`);
};
+ const handleButton = (variant) => {
+ const quantity = quantityInput;
+ if (!validQuantity(quantity)) return;
+
+ updateItemCart({
+ productId: variant,
+ quantity,
+ programLineId: null,
+ selected: true,
+ source: 'buy',
+ });
+ router.push('/shop/quotation?source=buy');
+ };
+
const productSimilarQuery = [
product?.name,
`fq=-product_id_i:${product.id}`,
@@ -120,42 +168,14 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => {
return (
<MobileView>
- <Image
- src={product.image + '?variant=True'}
- alt={product.name}
- className='h-72 object-contain object-center w-full border-b border-gray_r-4'
- />
-
- <div className='p-4'>
- <div className='flex items-end mb-2'>
- {product.manufacture?.name ? (
- <Link
- href={createSlug(
- '/shop/brands/',
- product.manufacture?.name,
- product.manufacture?.id
- )}
- >
- {product.manufacture?.name}
- </Link>
- ) : (
- <div>-</div>
- )}
- <button type='button' className='ml-auto' onClick={toggleWishlist}>
- {wishlist.data?.productTotal > 0 ? (
- <HeartIcon className='w-6 fill-danger-500 text-danger-500' />
- ) : (
- <HeartIcon className='w-6' />
- )}
- </button>
- </div>
- <h1 className='leading-6 font-medium mb-3'>{activeVariant?.name}</h1>
-
+ <div
+ className={`px-4 block md:sticky md:top-[150px] md:py-6 fixed bottom-0 left-0 right-0 bg-white p-2 z-10 pb-6 pt-6 rounded-lg shadow-[rgba(0,0,4,0.1)_0px_-4px_4px_0px] `}
+ >
{activeVariant.isFlashSale &&
activeVariant?.price?.discountPercentage > 0 ? (
<>
<div className='flex gap-x-1 items-center'>
- <div className='badge-solid-red'>
+ <div className='bg-danger-500 px-2 py-1.5 rounded text-white text-caption-2'>
{activeVariant?.price?.discountPercentage}%
</div>
<div className='text-gray_r-11 line-through text-caption-1'>
@@ -173,7 +193,7 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => {
</div>
</>
) : (
- <h3 className='text-danger-500 font-semibold mt-1'>
+ <div className='text-danger-500 font-semibold mt-1 text-3xl'>
{activeVariant?.price?.price > 0 ? (
<>
{currencyFormat(activeVariant?.price?.price)}
@@ -203,39 +223,84 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => {
</a>
</span>
)}
- </h3>
+ </div>
)}
+ <div className=''>
+ <div className='mt-4 mb-2'>Jumlah</div>
+ <div className='flex gap-x-3'>
+ <div className='w-2/12'>
+ <input
+ name='quantity'
+ type='number'
+ className='form-input'
+ value={quantity}
+ onChange={(e) => setQuantity(e.target.value)}
+ />
+ </div>
+ <button
+ type='button'
+ className='btn-yellow flex-1'
+ onClick={handleClickCart}
+ >
+ Keranjang
+ </button>
+ <button
+ type='button'
+ className='btn-solid-red flex-1'
+ onClick={handleClickBuy}
+ >
+ Beli
+ </button>
+ </div>
+ <Button
+ onClick={() => handleButton(product.id)}
+ color={'red'}
+ colorScheme='white'
+ className='w-full border-2 p-2 gap-1 mt-2 hover:bg-slate-100 flex items-center'
+ >
+ <ImageNext
+ src='/images/writing.png'
+ alt='penawaran instan'
+ className=''
+ width={25}
+ height={25}
+ />
+ Penawaran Harga Instan
+ </Button>
+ </div>
</div>
-
- <Divider />
+ <Image
+ src={product.image + '?variant=True'}
+ alt={product.name}
+ className='h-72 object-contain object-center w-full border-b border-gray_r-4'
+ />
<div className='p-4'>
- <div className='mt-4 mb-2'>Jumlah</div>
- <div className='flex gap-x-3'>
- <div className='w-2/12'>
- <input
- name='quantity'
- type='number'
- className='form-input'
- value={quantity}
- onChange={(e) => setQuantity(e.target.value)}
- />
- </div>
- <button
- type='button'
- className='btn-yellow flex-1'
- onClick={handleClickCart}
- >
- Keranjang
- </button>
- <button
- type='button'
- className='btn-solid-red flex-1'
- onClick={handleClickBuy}
- >
- Beli
+ <div className='flex items-end mb-2'>
+ {product.manufacture?.name ? (
+ <Link
+ href={createSlug(
+ '/shop/brands/',
+ product.manufacture?.name,
+ product.manufacture?.id
+ )}
+ >
+ {product.manufacture?.name}
+ </Link>
+ ) : (
+ <div>-</div>
+ )}
+ <button type='button' className='ml-auto' onClick={toggleWishlist}>
+ {wishlist.data?.productTotal > 0 ? (
+ <HeartIcon className='w-6 fill-danger-500 text-danger-500' />
+ ) : (
+ <HeartIcon className='w-6' />
+ )}
</button>
</div>
+ <h1 className='font-medium text-h-lg leading-8 md:text-title-md md:leading-10 mb-3'>
+ {activeVariant?.name}
+ </h1>
</div>
<Divider />
@@ -375,8 +440,11 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => {
<div className='p-4'>
<h2 className='font-semibold mb-4'>Kamu Mungkin Juga Suka</h2>
<LazyLoad>
- <ProductSimilar query={productSimilarQuery} />
+ <SimilarBottom product={product} />
</LazyLoad>
+ {/* <LazyLoad>
+ <ProductSimilar query={productSimilarQuery} />
+ </LazyLoad> */}
</div>
<BottomPopup
@@ -409,8 +477,11 @@ const ProductMobileVariant = ({ product, wishlist, toggleWishlist }) => {
Kamu Mungkin Juga Suka
</div>
<LazyLoad>
- <ProductSimilar query={productSimilarQuery} />
+ <SimilarBottom product={product} />
</LazyLoad>
+ {/* <LazyLoad>
+ <ProductSimilar query={productSimilarQuery} />
+ </LazyLoad> */}
</div>
</BottomPopup>
</MobileView>
diff --git a/src/lib/product/components/ProductCard.jsx b/src/lib/product/components/ProductCard.jsx
index d3b50302..3e6a6913 100644
--- a/src/lib/product/components/ProductCard.jsx
+++ b/src/lib/product/components/ProductCard.jsx
@@ -10,12 +10,13 @@ import { sellingProductFormat } from '@/core/utils/formatValue';
import { createSlug } from '@/core/utils/slug';
import whatsappUrl from '@/core/utils/whatsappUrl';
import useUtmSource from '~/hooks/useUtmSource';
+import useDevice from '@/core/hooks/useDevice';
const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
const router = useRouter();
const utmSource = useUtmSource();
const [discount, setDiscount] = useState(0);
-
+ const { isDesktop, isMobile } = useDevice();
let voucherPastiHemat = 0;
voucherPastiHemat = product?.newVoucherPastiHemat[0];
@@ -26,9 +27,13 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
});
const image = useMemo(() => {
- if (product.image) return product.image + '?ratio=square';
- return '/images/noimage.jpeg';
- }, [product.image]);
+ if (!isDesktop && product.image_mobile) {
+ return product.image_mobile + '?ratio=square';
+ } else {
+ if (product.image) return product.image + '?ratio=square';
+ return '/images/noimage.jpeg';
+ }
+ }, [product.image, product.image_mobile]);
const URL = {
product:
@@ -143,7 +148,7 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
<div className='p-2 sm:p-3 pb-3 text-caption-2 sm:text-body-2 leading-5'>
<div className='flex justify-between '>
{product?.manufacture?.name ? (
- <Link href={URL.manufacture} className='mb-1 mt-1'>
+ <Link href={URL.manufacture} className='mb-1 mt-1 truncate'>
{product.manufacture.name}
</Link>
) : (
diff --git a/src/lib/product/components/ProductFilter.jsx b/src/lib/product/components/ProductFilter.jsx
index d52fcb90..947550b7 100644
--- a/src/lib/product/components/ProductFilter.jsx
+++ b/src/lib/product/components/ProductFilter.jsx
@@ -1,88 +1,96 @@
-import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
-import { useRouter } from 'next/router'
-import { useState } from 'react'
-import _ from 'lodash'
-import { toQuery } from 'lodash-contrib'
-import { Checkbox } from '@chakra-ui/react'
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup';
+import { useRouter } from 'next/router';
+import { useState } from 'react';
+import _ from 'lodash';
+import { toQuery } from 'lodash-contrib';
+import { Checkbox } from '@chakra-ui/react';
const orderOptions = [
{ value: 'price-asc', label: 'Harga Terendah' },
{ value: 'price-desc', label: 'Harga Tertinggi' },
{ value: 'popular', label: 'Populer' },
- { value: 'stock', label: 'Ready Stock' }
-]
+ { value: 'stock', label: 'Ready Stock' },
+];
-const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBrand = null }) => {
- const router = useRouter()
- const { query } = router
- const [order, setOrder] = useState(query?.orderBy || 'popular')
- const [brand, setBrand] = useState(query?.brand)
- const [category, setCategory] = useState(query?.category)
- const [priceFrom, setPriceFrom] = useState(query?.priceFrom)
- const [priceTo, setPriceTo] = useState(query?.priceTo)
+const ProductFilter = ({
+ active,
+ close,
+ brands,
+ categories,
+ prefixUrl,
+ defaultBrand = null,
+}) => {
+ const router = useRouter();
+ const { query } = router;
+ const [order, setOrder] = useState(query?.orderBy || 'popular');
+ const [brand, setBrand] = useState(query?.brand);
+ const [category, setCategory] = useState(query?.category);
+ const [priceFrom, setPriceFrom] = useState(query?.priceFrom);
+ const [priceTo, setPriceTo] = useState(query?.priceTo);
- const [stock, setStock] = useState(query?.stock)
+ const [stock, setStock] = useState(query?.stock);
- const [activeRange, setActiveRange] = useState(null)
+ const [activeRange, setActiveRange] = useState(null);
const priceRange = [
{
priceFrom: 100000,
- priceTo: 200000
+ priceTo: 200000,
},
{
priceFrom: 200000,
- priceTo: 300000
+ priceTo: 300000,
},
{
priceFrom: 300000,
- priceTo: 400000
+ priceTo: 400000,
},
{
priceFrom: 400000,
- priceTo: 500000
- }
- ]
+ priceTo: 500000,
+ },
+ ];
const handlePriceFromChange = async (priceFromr, priceTor, index) => {
- await setPriceFrom(priceFromr)
- await setPriceTo(priceTor)
- setActiveRange(index)
- }
+ await setPriceFrom(priceFromr);
+ await setPriceTo(priceTor);
+ setActiveRange(index);
+ };
const handleReadyStockChange = (event) => {
- const value = event.target.value
- const isChecked = event.target.checked
+ const value = event.target.value;
+ const isChecked = event.target.checked;
if (isChecked) {
- setStock(value)
+ setStock(value);
} else {
- setStock(null)
+ setStock(null);
}
- }
+ };
const handleSubmit = () => {
let params = {
+ penawaran: router.query.penawaran,
q: router.query.q,
orderBy: order,
brand,
category,
priceFrom,
priceTo,
- stock: stock
- }
- params = _.pickBy(params, _.identity)
- params = toQuery(params)
- router.push(`${prefixUrl}?${params}`)
- }
+ stock: stock,
+ };
+ params = _.pickBy(params, _.identity);
+ params = toQuery(params);
+ router.push(`${prefixUrl}?${params}`);
+ };
const formatCurrency = (value) => {
if (value >= 1000) {
- const thousands = Math.floor(value / 1000) // Menghitung ribuan
- return `Rp${thousands}k`
+ const thousands = Math.floor(value / 1000); // Menghitung ribuan
+ return `Rp${thousands}k`;
} else {
- return `Rp${value}`
+ return `Rp${value}`;
}
- }
+ };
return (
<BottomPopup active={active} close={close} title='Filter Produk'>
@@ -101,7 +109,10 @@ const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBr
<option value=''>Pilih Brand...</option>
{brands.map((brand, index) => (
<option value={brand.brand} key={index}>
- {brand.brand} <span className='text-sm text-gray-200'>({brand.qty})</span>
+ {brand.brand}{' '}
+ <span className='text-sm text-gray-200'>
+ ({brand.qty})
+ </span>
</option>
))}
</>
@@ -125,7 +136,10 @@ const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBr
<option value=''>Pilih Kategori...</option>
{categories.map((category, index) => (
<option value={category.name} key={index}>
- {category.name} <span className='text-sm text-gray-200'>({category.qty})</span>
+ {category.name}{' '}
+ <span className='text-sm text-gray-200'>
+ ({category.qty})
+ </span>
</option>
))}
</>
@@ -141,7 +155,9 @@ const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBr
<button
key={orderOption.value}
className={`btn-light px-3 font-normal flex-shrink-0 ${
- order == orderOption.value ? 'bg-warning-500' : 'bg-transparent'
+ order == orderOption.value
+ ? 'bg-warning-500'
+ : 'bg-transparent'
}`}
onClick={() => setOrder(orderOption.value)}
>
@@ -173,13 +189,16 @@ const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBr
{priceRange.map((price, i) => (
<button
key={i}
- onClick={() => handlePriceFromChange(price.priceFrom, price.priceTo, i)}
+ onClick={() =>
+ handlePriceFromChange(price.priceFrom, price.priceTo, i)
+ }
className={`w-full border ${
i === activeRange ? 'border-red-600' : 'border-gray-400'
}
py-2 p-3 rounded-full text-sm whitespace-nowrap`}
>
- {formatCurrency(price.priceFrom)} - {formatCurrency(price.priceTo)}
+ {formatCurrency(price.priceFrom)} -{' '}
+ {formatCurrency(price.priceTo)}
</button>
))}
</div>
@@ -197,12 +216,16 @@ const ProductFilter = ({ active, close, brands, categories, prefixUrl, defaultBr
</Checkbox>
</div>
</div> */}
- <button type='button' className='btn-solid-red w-full mt-2' onClick={handleSubmit}>
+ <button
+ type='button'
+ className='btn-solid-red w-full mt-2'
+ onClick={handleSubmit}
+ >
Terapkan Filter
</button>
</div>
</BottomPopup>
- )
-}
+ );
+};
-export default ProductFilter
+export default ProductFilter;
diff --git a/src/lib/product/components/ProductFilterDesktop.jsx b/src/lib/product/components/ProductFilterDesktop.jsx
index 73fecab5..d2ecb4d9 100644
--- a/src/lib/product/components/ProductFilterDesktop.jsx
+++ b/src/lib/product/components/ProductFilterDesktop.jsx
@@ -1,7 +1,7 @@
-import { useRouter } from 'next/router'
-import { useEffect, useState } from 'react'
-import _ from 'lodash'
-import { toQuery } from 'lodash-contrib'
+import { useRouter } from 'next/router';
+import { useEffect, useState } from 'react';
+import _ from 'lodash';
+import { toQuery } from 'lodash-contrib';
import {
Accordion,
AccordionButton,
@@ -15,110 +15,119 @@ import {
InputGroup,
InputLeftAddon,
Stack,
- VStack
-} from '@chakra-ui/react'
-import Image from '@/core/components/elements/Image/Image'
-import { formatCurrency } from '@/core/utils/formatValue'
+ VStack,
+} from '@chakra-ui/react';
+import Image from '@/core/components/elements/Image/Image';
+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)
- const [brandValues, setBrand] = useState(query?.brand?.split(',') || [])
- const [categoryValues, setCategory] = useState(query?.category?.split(',') || [])
- const [priceFrom, setPriceFrom] = useState(query?.priceFrom)
- const [priceTo, setPriceTo] = useState(query?.priceTo)
- const [stock, setStock] = useState(query?.stock)
- const [activeRange, setActiveRange] = useState(null)
- const [activeIndeces, setActiveIndeces] = useState([])
+const ProductFilterDesktop = ({
+ brands,
+ categories,
+ prefixUrl,
+ defaultBrand = null,
+}) => {
+ const router = useRouter();
+ const { query } = router;
+ const [order, setOrder] = useState(query?.orderBy);
+ const [brandValues, setBrand] = useState(query?.brand?.split(',') || []);
+ const [categoryValues, setCategory] = useState(
+ query?.category?.split(',') || []
+ );
+ const [priceFrom, setPriceFrom] = useState(query?.priceFrom);
+ const [priceTo, setPriceTo] = useState(query?.priceTo);
+ const [stock, setStock] = useState(query?.stock);
+ const [activeRange, setActiveRange] = useState(null);
+ const [activeIndeces, setActiveIndeces] = useState([]);
const priceRange = [
{
priceFrom: 100000,
- priceTo: 200000
+ priceTo: 200000,
},
{
priceFrom: 200000,
- priceTo: 300000
+ priceTo: 300000,
},
{
priceFrom: 300000,
- priceTo: 400000
+ priceTo: 400000,
},
{
priceFrom: 400000,
- priceTo: 500000
- }
- ]
+ priceTo: 500000,
+ },
+ ];
const indexRange = priceRange.findIndex((range) => {
- return range.priceFrom === parseInt(priceFrom) && range.priceTo == parseInt(priceTo)
- })
+ return (
+ range.priceFrom === parseInt(priceFrom) &&
+ range.priceTo == parseInt(priceTo)
+ );
+ });
const handleCategoriesChange = (event) => {
- const value = event.target.value
- const isChecked = event.target.checked
+ const value = event.target.value;
+ const isChecked = event.target.checked;
if (isChecked) {
- setCategory([...categoryValues, value])
+ setCategory([...categoryValues, value]);
} else {
- setCategory(categoryValues.filter((val) => val !== value))
+ setCategory(categoryValues.filter((val) => val !== value));
}
- }
+ };
const handleBrandsChange = (event) => {
- const value = event.target.value
- const isChecked = event.target.checked
+ const value = event.target.value;
+ const isChecked = event.target.checked;
if (isChecked) {
- setBrand([...brandValues, value])
+ setBrand([...brandValues, value]);
} else {
- setBrand(brandValues.filter((val) => val !== value))
+ setBrand(brandValues.filter((val) => val !== value));
}
- }
+ };
const handleReadyStockChange = (event) => {
- const value = event.target.value
- const isChecked = event.target.checked
+ const value = event.target.value;
+ const isChecked = event.target.checked;
if (isChecked) {
- setStock(value)
+ setStock(value);
} else {
- setStock(null)
+ setStock(null);
}
- }
+ };
const handlePriceFromChange = async (priceFromr, priceTor, index) => {
- await setPriceFrom(priceFromr)
- await setPriceTo(priceTor)
- setActiveRange(index)
- }
+ await setPriceFrom(priceFromr);
+ await setPriceTo(priceTor);
+ setActiveRange(index);
+ };
const handleSubmit = () => {
let params = {
+ penawaran: router.query.penawaran,
q: router.query.q,
orderBy: order,
brand: brandValues.join(','),
category: categoryValues.join(','),
priceFrom,
priceTo,
- stock: stock
- }
- params = _.pickBy(params, _.identity)
- params = toQuery(params)
+ stock: stock,
+ };
+ params = _.pickBy(params, _.identity);
+ params = toQuery(params);
- const slug = Array.isArray(router.query.slug) ? router.query.slug[0] : router.query.slug;
+ const slug = Array.isArray(router.query.slug)
+ ? router.query.slug[0]
+ : router.query.slug;
if (slug) {
- if(prefixUrl.includes('category') || prefixUrl.includes('lob')){
- router.push(`${prefixUrl}?${params}`)
- }else{
- router.push(`${prefixUrl}/${slug}?${params}`)
+ if (prefixUrl.includes('category') || prefixUrl.includes('lob')) {
+ router.push(`${prefixUrl}?${params}`);
+ } else {
+ router.push(`${prefixUrl}/${slug}?${params}`);
}
} else {
- router.push(`${prefixUrl}?${params}`)
+ router.push(`${prefixUrl}?${params}`);
}
- }
-
-
+ };
/*const handleIndexAccordion = async () => {
if (brandValues) {
@@ -136,9 +145,8 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu
}*/
useEffect(() => {
- setActiveRange(indexRange)
- }, [])
-
+ setActiveRange(indexRange);
+ }, []);
return (
<>
@@ -165,13 +173,17 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu
>
<div className='flex items-center gap-2'>
<span>{brand.brand} </span>
- <span className='text-sm text-gray-600'>({brand.qty})</span>
+ <span className='text-sm text-gray-600'>
+ ({brand.qty})
+ </span>
</div>
</Checkbox>
</div>
))
) : (
- <div className='flex items-center gap-2'>Brands tidak tersedia</div>
+ <div className='flex items-center gap-2'>
+ Brands tidak tersedia
+ </div>
)}
</Stack>
</AccordionPanel>
@@ -199,13 +211,17 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu
>
<div className='flex items-center gap-2'>
<span>{category.name} </span>
- <span className='text-sm text-gray-600'>({category.qty})</span>
+ <span className='text-sm text-gray-600'>
+ ({category.qty})
+ </span>
</div>
</Checkbox>
</div>
))
) : (
- <div className='flex items-center gap-2'>Kategori tidak tersedia</div>
+ <div className='flex items-center gap-2'>
+ Kategori tidak tersedia
+ </div>
)}
</Stack>
</AccordionPanel>
@@ -243,13 +259,16 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu
{priceRange.map((price, i) => (
<button
key={i}
- onClick={() => handlePriceFromChange(price.priceFrom, price.priceTo, i)}
+ onClick={() =>
+ handlePriceFromChange(price.priceFrom, price.priceTo, i)
+ }
className={`w-full border ${
i === activeRange ? 'border-red-600' : 'border-gray-400'
}
py-2 p-3 rounded-full text-sm whitespace-nowrap`}
>
- {formatCurrency(price.priceFrom)} - {formatCurrency(price.priceTo)}
+ {formatCurrency(price.priceFrom)} -{' '}
+ {formatCurrency(price.priceTo)}
</button>
))}
</div>
@@ -282,7 +301,7 @@ const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = nu
Terapkan
</Button>
</>
- )
-}
+ );
+};
-export default ProductFilterDesktop
+export default ProductFilterDesktop;
diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx
index 26114acf..f7b044aa 100644
--- a/src/lib/product/components/ProductSearch.jsx
+++ b/src/lib/product/components/ProductSearch.jsx
@@ -79,6 +79,24 @@ const ProductSearch = ({
}
}, [categoryId]);
+ useEffect(() => {
+ const checkIfPenawaran = async () => {
+ if (router.asPath.includes('penawaran')) {
+ query = {
+ ...query,
+ fq: [
+ `-flashsale_id_i:${router.query.penawaran}`,
+ `flashsale_price_f:[1 TO *]`,
+ ],
+ orderBy: 'flashsale-discount-desc',
+ };
+ setFinalQuery(query);
+ setOrderBy('flashsale-discount-desc');
+ }
+ };
+ checkIfPenawaran();
+ }, [router.query]);
+
const collectIds = (category) => {
const ids = [];
function recurse(cat) {
@@ -337,6 +355,7 @@ const ProductSearch = ({
const handleDeleteFilter = async (source, value) => {
let params = {
+ penawaran: router.query.penawaran,
q: router.query.q,
orderBy: orderBy,
brand: brandValues.join(','),
@@ -364,6 +383,7 @@ const ProductSearch = ({
break;
case 'delete':
params = {
+ penawaran: router.query.penawaran,
q: router.query.q,
orderBy: orderBy,
};
diff --git a/src/lib/quotation/components/Quotation.jsx b/src/lib/quotation/components/Quotation.jsx
index cf0ad41f..5a2f63a5 100644
--- a/src/lib/quotation/components/Quotation.jsx
+++ b/src/lib/quotation/components/Quotation.jsx
@@ -39,9 +39,12 @@ const { getProductsCheckout } = require('@/lib/checkout/api/checkoutApi');
const Quotation = () => {
const router = useRouter();
const auth = useAuth();
+ const query = router.query.source ?? null;
const { data: cartCheckout } = useQuery('cartCheckout', () =>
- getProductsCheckout()
+ getProductsCheckout({
+ source: query,
+ })
);
const { setRefreshCart } = useProductCartContext();
diff --git a/src/lib/review/components/CustomerReviews.jsx b/src/lib/review/components/CustomerReviews.jsx
index a6e697f0..6ca0fa7b 100644
--- a/src/lib/review/components/CustomerReviews.jsx
+++ b/src/lib/review/components/CustomerReviews.jsx
@@ -3,16 +3,37 @@ import MobileView from '@/core/components/views/MobileView';
import Image from 'next/image';
import { Swiper, SwiperSlide } from 'swiper/react';
import { Autoplay } from 'swiper';
+import { useEffect, useState } from 'react';
const { useQuery } = require('react-query');
const { getCustomerReviews } = require('../api/customerReviewsApi');
const CustomerReviews = () => {
- const { data: customerReviews } = useQuery(
+ const [data, setData] = useState(null);
+
+ useEffect(() => {
+ const localData = localStorage.getItem('Homepage_customerReviews');
+ if (localData) {
+ setData(JSON.parse(localData));
+ }
+ },[])
+
+
+ const { data: fetchCustomerReviews } = useQuery(
'customerReviews',
- getCustomerReviews
+ getCustomerReviews,{
+ enabled: !data,
+ onSuccess: (data) => {
+ if (data) {
+ localStorage.setItem('Homepage_customerReviews', JSON.stringify(data));
+ setData(data);
+ }
+ }
+ }
);
+ const customerReviews = data
+
return (
<div className='px-4 sm:px-0'>
<h1 className='font-semibold text-[14px] sm:text-h-lg mb-4'>
diff --git a/src/lib/shipment/components/Shipments.jsx b/src/lib/shipment/components/Shipments.jsx
index 115bbd3a..20dbb013 100644
--- a/src/lib/shipment/components/Shipments.jsx
+++ b/src/lib/shipment/components/Shipments.jsx
@@ -1,62 +1,83 @@
-import DesktopView from '@/core/components/views/DesktopView'
-import MobileView from '@/core/components/views/MobileView'
-import Menu from '@/lib/auth/components/Menu'
-import { EllipsisVerticalIcon, MagnifyingGlassIcon } from '@heroicons/react/24/outline'
-import ImageNext from 'next/image'
-import { useRouter } from 'next/router'
-import { useQuery } from 'react-query'
-import _, { forEach } from 'lodash-contrib'
-import Spinner from '@/core/components/elements/Spinner/Spinner'
-import Manifest from '@/lib/treckingAwb/component/Manifest'
-import { useState } from 'react'
-import Pagination from '@/core/components/elements/Pagination/Pagination'
-import Link from 'next/link'
-import TransactionStatusBadge from '@/lib/transaction/components/TransactionStatusBadge'
+import DesktopView from '@/core/components/views/DesktopView';
+import MobileView from '@/core/components/views/MobileView';
+import Menu from '@/lib/auth/components/Menu';
+import {
+ EllipsisVerticalIcon,
+ MagnifyingGlassIcon,
+} from '@heroicons/react/24/outline';
+import ImageNext from 'next/image';
+import { useEffect } from 'react';
+import { useRouter } from 'next/router';
+import { useQuery } from 'react-query';
+import _, { forEach } from 'lodash-contrib';
+import Spinner from '@/core/components/elements/Spinner/Spinner';
+import Manifest from '@/lib/treckingAwb/component/Manifest';
+import { useState } from 'react';
+import Pagination from '@/core/components/elements/Pagination/Pagination';
+import Link from 'next/link';
+import TransactionStatusBadge from '@/lib/transaction/components/TransactionStatusBadge';
-const { listShipments } = require('../api/listShipment')
+const { listShipments } = require('../api/listShipment');
const Shipments = () => {
- const router = useRouter()
- const { q = '', page = 1 } = router.query
- const [paramStatus, setParamStatus] = useState(null)
-
- const limit = 15
+ const router = useRouter();
+ const { q = '', page = 1, status = null } = router.query;
+ const [paramStatus, setParamStatus] = useState(status);
+ const limit = 15;
const query = {
q: q,
status: paramStatus,
offset: (page - 1) * limit,
- limit
- }
- const [inputQuery, setInputQuery] = useState(q)
- const queryString = _.toQuery(query)
+ limit,
+ };
+ const [inputQuery, setInputQuery] = useState(q);
+ const queryString = _.toQuery(query);
const { data: shipments } = useQuery('shipments' + queryString, () =>
listShipments({ query: queryString })
- )
- const [idAWB, setIdAWB] = useState(null)
+ );
+ const [idAWB, setIdAWB] = useState(null);
- const pageCount = Math.ceil(shipments?.pickingTotal / limit)
- let pageQuery = _.omit(query, ['limit', 'offset', 'context'])
- pageQuery = _.pickBy(pageQuery, _.identity)
- pageQuery = _.toQuery(pageQuery)
+ const pageCount = Math.ceil(shipments?.pickingTotal / limit);
+ let pageQuery = _.omit(query, ['limit', 'offset', 'context']);
+ pageQuery = _.pickBy(pageQuery, _.identity);
+ pageQuery = _.toQuery(pageQuery);
const closePopup = () => {
- setIdAWB(null)
- }
+ setIdAWB(null);
+ };
const handleSubmit = async (e) => {
- e.preventDefault()
- router.push(`${router.pathname}?q=${inputQuery}`)
- }
+ e.preventDefault();
+ router.push(`${router.pathname}?q=${inputQuery}`);
+ };
const filterStatus = async (status) => {
if (status === paramStatus) {
- setParamStatus(null)
+ setParamStatus(null);
} else {
- setParamStatus(status)
+ setParamStatus(status);
}
- }
+ };
+
+ useEffect(() => {
+ const resetQuery = () => {
+ const newQuery = {
+ status: paramStatus || undefined,
+ q: '',
+ page: 1,
+ };
+ router.push({
+ pathname: router.pathname,
+ query: newQuery,
+ });
+ };
+
+ if (paramStatus !== status) {
+ resetQuery();
+ }
+ }, [paramStatus]);
return (
<>
<MobileView>
@@ -84,7 +105,10 @@ const Shipments = () => {
</form>
{shipments?.pickings.map((shipment) => (
- <div className='p-4 shadow border border-gray_r-3 rounded-md' key={shipment.id}>
+ <div
+ className='p-4 shadow border border-gray_r-3 rounded-md'
+ key={shipment.id}
+ >
<div className='flex justify-between items-center mb-3'>
<div className='text-caption-2 text-gray_r-11'>
<p>
@@ -93,7 +117,9 @@ const Shipments = () => {
{shipment.carrierName || '-'}
</span>
</p>
- <p className='mt-2'>No. Resi : {shipment.trackingNumber || '-'}</p>
+ <p className='mt-2'>
+ No. Resi : {shipment.trackingNumber || '-'}
+ </p>
</div>
<div className='flex justify-between'>
{shipment?.status === 'completed' && (
@@ -116,11 +142,17 @@ const Shipments = () => {
<hr />
<div className='flex justify-between mt-2 items-center mb-5'>
<div>
- <span className='text-caption-2 text-gray_r-11'>No. Transaksi</span>
+ <span className='text-caption-2 text-gray_r-11'>
+ No. Transaksi
+ </span>
<Link href={`/my/transactions/${shipment.saleOrder.id}`}>
- <h2 className='text-danger-500 mt-1 mb-2'>{shipment.saleOrder.name}</h2>
+ <h2 className='text-danger-500 mt-1 mb-2'>
+ {shipment.saleOrder.name}
+ </h2>
</Link>
- <span className='text-caption-2 text-gray_r-11'>{shipment.date}</span>
+ <span className='text-caption-2 text-gray_r-11'>
+ {shipment.date}
+ </span>
</div>
<div>
<button
@@ -136,7 +168,11 @@ const Shipments = () => {
onClick={() => setIdAWB(shipment.id)}
className='flex items-center mt-1 gap-x-1 min-w-full'
>
- <ImageNext src={`/images/BOX_DELIVERY_GREEN.svg`} width={20} height={20} />
+ <ImageNext
+ src={`/images/BOX_DELIVERY_GREEN.svg`}
+ width={20}
+ height={20}
+ />
<p className='text-sm text-green-700 truncate'>
{shipment.lastManifest.description}
</p>
@@ -148,7 +184,7 @@ const Shipments = () => {
<Pagination
pageCount={pageCount}
currentPage={parseInt(page)}
- url={router.pathname + pageQuery}
+ url={`${router.pathname}${pageQuery ? '?' + pageQuery : ''}`}
className='mt-2 mb-2'
/>
</div>
@@ -176,7 +212,8 @@ const Shipments = () => {
<path d='M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z' />
</svg>
<div>
- Lacak pengiriman untuk setiap transaksi anda semakin mudah di Indoteknik.com
+ Lacak pengiriman untuk setiap transaksi anda semakin mudah di
+ Indoteknik.com
</div>
</div>
<div className='flex justify-between gap-x-5'>
@@ -190,7 +227,9 @@ const Shipments = () => {
</div>
<div className='p-4 bg-white border border-gray_r-6 rounded'>
<div className='flex mb-6 items-center justify-between'>
- <h1 className='text-title-sm font-semibold'>Detail Pengiriman</h1>
+ <h1 className='text-title-sm font-semibold'>
+ Detail Pengiriman
+ </h1>
<form className='flex gap-x-2' onSubmit={handleSubmit}>
<input
type='text'
@@ -199,7 +238,10 @@ const Shipments = () => {
value={inputQuery}
onChange={(e) => setInputQuery(e.target.value)}
/>
- <button className='btn-light bg-transparent px-3' type='submit'>
+ <button
+ className='btn-light bg-transparent px-3'
+ type='submit'
+ >
<MagnifyingGlassIcon className='w-6' />
</button>
</form>
@@ -254,7 +296,7 @@ const Shipments = () => {
<Pagination
pageCount={pageCount}
currentPage={parseInt(page)}
- url={router.pathname + pageQuery}
+ url={`${router.pathname}${pageQuery ? '?' + pageQuery : ''}`}
className='mt-2 mb-2'
/>
</div>
@@ -263,16 +305,16 @@ const Shipments = () => {
<Manifest idAWB={idAWB} closePopup={closePopup} />
</DesktopView>
</>
- )
-}
+ );
+};
const CardStatus = ({ device, paramStatus, shipments, filterStatus }) => {
- const status = [`pending`, `shipment`, `completed`]
+ const status = [`pending`, `shipment`, `completed`];
return (
<>
{status.map((value) => {
- const statusData = getStatusLabel(device, value, shipments)
+ const statusData = getStatusLabel(device, value, shipments);
if (device === 'desktop') {
return (
<div
@@ -282,13 +324,15 @@ const CardStatus = ({ device, paramStatus, shipments, filterStatus }) => {
}`}
onClick={() => filterStatus(value)}
>
- <h2 className='mb-2 text-lg font-bold tracking-tight'>{statusData.label}</h2>
+ <h2 className='mb-2 text-lg font-bold tracking-tight'>
+ {statusData.label}
+ </h2>
{statusData.image}
<h1 className='text-xl font-bold'>
{statusData.shipCount} <span className='text-sm'>Pesanan</span>
</h1>
</div>
- )
+ );
} else {
return (
<div
@@ -305,15 +349,15 @@ const CardStatus = ({ device, paramStatus, shipments, filterStatus }) => {
<span className='truncate'>{statusData.shipCount}</span> {'>'}
</h1>
</div>
- )
+ );
}
})}
</>
- )
-}
+ );
+};
const getStatusLabel = (device, status, shipments) => {
- let images = null
+ let images = null;
switch (status) {
case 'pending':
if (device === 'desktop') {
@@ -328,40 +372,48 @@ const getStatusLabel = (device, status, shipments) => {
/>
</div>
</div>
- )
+ );
} else {
images = (
<div>
<ImageNext src='/images/BOX(1).svg' width={15} height={20} />
</div>
- )
+ );
}
return {
label: 'Pending',
shipCount: shipments?.summary?.pendingCount,
- image: images
- }
+ image: images,
+ };
case 'shipment':
if (device === 'desktop') {
images = (
<div className='bg-yellow-100 border border-yellow-200 rounded-sm p-1 w-20 mb-2'>
<div>
- <ImageNext src='/images/BOX_DELIVER_(1).svg' width={30} height={20} />
+ <ImageNext
+ src='/images/BOX_DELIVER_(1).svg'
+ width={30}
+ height={20}
+ />
</div>
</div>
- )
+ );
} else {
images = (
<div>
- <ImageNext src='/images/BOX_DELIVER_(1).svg' width={18} height={20} />
+ <ImageNext
+ src='/images/BOX_DELIVER_(1).svg'
+ width={18}
+ height={20}
+ />
</div>
- )
+ );
}
return {
label: 'Pengiriman',
shipCount: shipments?.summary?.shipmentCount,
- image: images
- }
+ image: images,
+ };
case 'completed':
if (device === 'desktop') {
images = (
@@ -375,22 +427,22 @@ const getStatusLabel = (device, status, shipments) => {
/>
</div>
</div>
- )
+ );
} else {
images = (
<div>
<ImageNext src='/images/open-box(1).svg' width={16} height={20} />
</div>
- )
+ );
}
return {
label: 'Pesanan Tiba',
shipCount: shipments?.summary?.completedCount,
- image: images
- }
+ image: images,
+ };
default:
- return 'Status Tidak Dikenal'
+ return 'Status Tidak Dikenal';
}
-}
+};
-export default Shipments
+export default Shipments;
diff --git a/src/lib/transaction/api/transactionsApi.js b/src/lib/transaction/api/transactionsApi.js
index f4e36e6f..5ea2b5b0 100644
--- a/src/lib/transaction/api/transactionsApi.js
+++ b/src/lib/transaction/api/transactionsApi.js
@@ -3,6 +3,9 @@ import { getAuth } from '@/core/utils/auth'
const transactionsApi = async ({ query }) => {
const auth = getAuth()
+ if (!auth) {
+ return null
+ }
const dataTransactions = await odooApi(
'GET',
`/api/v1/partner/${auth.partnerId}/sale_order?${query}`
diff --git a/src/lib/transaction/components/Transaction.jsx b/src/lib/transaction/components/Transaction.jsx
index 4d401037..d001c7f4 100644
--- a/src/lib/transaction/components/Transaction.jsx
+++ b/src/lib/transaction/components/Transaction.jsx
@@ -778,6 +778,10 @@ const Transaction = ({ id }) => {
? `| ${product?.attributes.join(', ')}`
: ''}
</div>
+ <div className='text-[10px] text-red-500 italic mt-2'>
+ {product.availableQuantity} barang ini bisa di
+ pickup maksimal pukul 16.00
+ </div>
</div>
</td>
{/* <td>
@@ -879,7 +883,7 @@ const Transaction = ({ id }) => {
</div>
</div>
</div>
- )}
+ )}
{transaction?.data?.productsRejectLine.length > 0 && (
<div className='text-h-sm font-semibold mt-10 mb-4'>
diff --git a/src/lib/treckingAwb/component/Manifest.jsx b/src/lib/treckingAwb/component/Manifest.jsx
index fbc95702..02d0bc7a 100644
--- a/src/lib/treckingAwb/component/Manifest.jsx
+++ b/src/lib/treckingAwb/component/Manifest.jsx
@@ -1,16 +1,16 @@
-import odooApi from '@/core/api/odooApi'
-import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
-import LogoSpinner from '@/core/components/elements/Spinner/LogoSpinner'
-import { getAuth } from '@/core/utils/auth'
-import { useEffect, useState } from 'react'
-import { toast } from 'react-hot-toast'
-import ImageNext from 'next/image'
-import { list } from 'postcss'
+import odooApi from '@/core/api/odooApi';
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup';
+import LogoSpinner from '@/core/components/elements/Spinner/LogoSpinner';
+import { getAuth } from '@/core/utils/auth';
+import { useEffect, useState } from 'react';
+import { toast } from 'react-hot-toast';
+import ImageNext from 'next/image';
+import { list } from 'postcss';
const Manifest = ({ idAWB, closePopup }) => {
- const [manifests, setManifests] = useState(null)
- const [isLoading, setIsLoading] = useState(false)
-
+ const [manifests, setManifests] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ console.log('manifests', manifests);
const formatCustomDate = (date) => {
const months = [
'Jan',
@@ -24,61 +24,60 @@ const Manifest = ({ idAWB, closePopup }) => {
'Sep',
'Oct',
'Nov',
- 'Dec'
- ]
+ 'Dec',
+ ];
- const parts = date.split(' ') // Pisahkan tanggal dan waktu
- const [datePart, timePart] = parts
- const [yyyy, mm, dd] = datePart.split('-')
- const [hh, min] = timePart.split(':')
+ const parts = date.split(' '); // Pisahkan tanggal dan waktu
+ const [datePart, timePart] = parts;
+ const [yyyy, mm, dd] = datePart.split('-');
+ const [hh, min] = timePart.split(':');
- const monthAbbreviation = months[parseInt(mm, 10) - 1]
+ const monthAbbreviation = months[parseInt(mm, 10) - 1];
- return `${dd} ${monthAbbreviation} ${hh}:${min}`
- }
+ return `${dd} ${monthAbbreviation} ${hh}:${min}`;
+ };
const getManifest = async () => {
- setIsLoading(true)
- const auth = getAuth()
- let list
- if(auth){
+ setIsLoading(true);
+ const auth = getAuth();
+ 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`
- )
+ );
+ } else {
+ list = await odooApi('GET', `/api/v1/stock-picking/${idAWB}/tracking`);
}
- setManifests(list)
- setIsLoading(false)
- }
+ setManifests(list);
+ setIsLoading(false);
+ };
useEffect(() => {
if (idAWB) {
- getManifest()
+ getManifest();
} else {
- setManifests(null)
+ setManifests(null);
}
- }, [idAWB])
+ }, [idAWB]);
- const [copied, setCopied] = useState(false)
+ const [copied, setCopied] = useState(false);
const handleCopyClick = () => {
- const textToCopy = manifests?.waybillNumber
- navigator.clipboard.writeText(textToCopy)
- setCopied(true)
- toast.success('No Resi Berhasil di Copy')
- setTimeout(() => setCopied(false), 2000) // Reset copied state after 2 seconds
- }
+ const textToCopy = manifests?.waybillNumber;
+ navigator.clipboard.writeText(textToCopy);
+ setCopied(true);
+ toast.success('No Resi Berhasil di Copy');
+ setTimeout(() => setCopied(false), 2000); // Reset copied state after 2 seconds
+ };
return (
<>
{isLoading && (
<BottomPopup active={true} close=''>
- <div className='leading-7 text-gray_r-12/80 flex justify-center'>Mohon Tunggu</div>
+ <div className='leading-7 text-gray_r-12/80 flex justify-center'>
+ Mohon Tunggu
+ </div>
<div className='container flex justify-center my-4'>
<LogoSpinner width={48} height={48} />
</div>
@@ -111,11 +110,14 @@ const Manifest = ({ idAWB, closePopup }) => {
</div>
<div className=''>
<h1 className='text-body-1'>
- Estimasi tiba pada <span className='text-gray_r-11 text-sm'>({manifests?.eta})</span>
+ Estimasi tiba pada{' '}
+ <span className='text-gray_r-11 text-sm'>({manifests?.eta})</span>
</h1>
<h1 className='text-sm mt-2 mb-3'>
Dikirim Menggunakan{' '}
- <span className='text-red-500 font-semibold'>{manifests?.deliveryOrder.carrier}</span>
+ <span className='text-red-500 font-semibold'>
+ {manifests?.deliveryOrder.carrier}
+ </span>
</h1>
{manifests?.waybillNumber && (
<div className='flex justify-between items-center'>
@@ -154,10 +156,16 @@ const Manifest = ({ idAWB, closePopup }) => {
{manifests.delivered == true && index == 0 ? (
<div
class={`absolute w-6 h-6 rounded-full mt-1.5 -left-3 border ${
- index == 0 ? 'bg-green-100 border-green-100' : 'bg-gray_r-7 border-white'
+ index == 0
+ ? 'bg-green-100 border-green-100'
+ : 'bg-gray_r-7 border-white'
}`}
>
- <ImageNext src='/images/open-box(1).svg' width={30} height={20} />
+ <ImageNext
+ src='/images/open-box(1).svg'
+ width={30}
+ height={20}
+ />
</div>
) : (
<div
@@ -167,7 +175,9 @@ const Manifest = ({ idAWB, closePopup }) => {
{manifests.delivered != true && (
<div
class={`absolute w-3 h-3 rounded-full mt-1.5 -left-1.5 border ${
- index == 0 ? 'bg-green-600 border-green-600' : 'bg-gray_r-7 border-white'
+ index == 0
+ ? 'bg-green-600 border-green-600'
+ : 'bg-gray_r-7 border-white'
} `}
/>
)}
@@ -176,9 +186,15 @@ const Manifest = ({ idAWB, closePopup }) => {
{formatCustomDate(manifest.datetime)}
</time>
{manifests.delivered == true && index == 0 && (
- <p class={`leading-6 font-semibold text-sm text-green-600 `}>Sudah Sampai</p>
+ <p
+ class={`leading-6 font-semibold text-sm text-green-600 `}
+ >
+ Sudah Sampai
+ </p>
)}
- <p class={`leading-6 text-[12px] text-gray_r-11`}>{manifest.description}</p>
+ <p class={`leading-6 text-[12px] text-gray_r-11`}>
+ {manifest.description}
+ </p>
</li>
</>
))}
@@ -187,7 +203,7 @@ const Manifest = ({ idAWB, closePopup }) => {
</BottomPopup>
)}
</>
- )
-}
+ );
+};
-export default Manifest
+export default Manifest;
diff --git a/src/lib/variant/components/VariantCard.jsx b/src/lib/variant/components/VariantCard.jsx
index 68cdf54f..08b7a97e 100644
--- a/src/lib/variant/components/VariantCard.jsx
+++ b/src/lib/variant/components/VariantCard.jsx
@@ -103,30 +103,42 @@ const VariantCard = ({ product, openOnClick = true, buyMore = false }) => {
</div>
</div>
</div>
-
</div>
<div className='w-8/12 flex flex-col'>
- <p className='product-card__title wrap-line-ellipsis-2'>{product.parent.name}</p>
+ <p className='product-card__title wrap-line-ellipsis-2'>
+ {product.parent.name}
+ </p>
<p className='text-caption-2 text-gray_r-11 mt-1'>
{product.code || '-'}
- {product.attributes.length > 0 ? ` ・ ${product.attributes.join(', ')}` : ''}
+ {product.attributes.length > 0
+ ? ` ・ ${product.attributes.join(', ')}`
+ : ''}
</p>
<p className='text-caption-2 text-gray_r-11 mt-1'>
Berat Item : {product?.weight} Kg x {product?.quantity} Barang
</p>
+ <p className='text-[10px] text-red-500 italic mt-2'>
+ {product.availableQuantity} barang ini bisa di pickup maksimal pukul
+ 16.00
+ </p>
<div className='flex flex-wrap gap-x-1 items-center mt-auto'>
{product.hasFlashsale && (
<>
<p className='text-caption-2 text-gray_r-11 line-through'>
{currencyFormat(product.price.price)}
</p>
- <span className='badge-red'>{product.price.discountPercentage}%</span>
+ <span className='badge-red'>
+ {product.price.discountPercentage}%
+ </span>
</>
)}
</div>
<p className='text-caption-2 text-gray_r-11 mt-1'>
{product.price.priceDiscount > 0
- ? currencyFormat(product.price.priceDiscount) + ' × ' + product.quantity + ' Barang'
+ ? currencyFormat(product.price.priceDiscount) +
+ ' × ' +
+ product.quantity +
+ ' Barang'
: ''}
</p>
<p className='text-caption-2 text-gray_r-12 font-bold mt-2'>