summaryrefslogtreecommitdiff
path: root/src/lib
diff options
context:
space:
mode:
authorit-fixcomart <it@fixcomart.co.id>2024-08-20 16:12:25 +0700
committerit-fixcomart <it@fixcomart.co.id>2024-08-20 16:12:25 +0700
commit5c5eef9d62efd83f52f7c37dacb94d50ff7cb915 (patch)
tree7fdef4f99f0f42e2d99a40bfd5b81f1ca5f4ef30 /src/lib
parent004a9a644aed65d5c02263f19cce8b7c3000f354 (diff)
parent6d302bb338e26810a7f90326b84086217f1f4ae0 (diff)
Merge branch 'release' into Feature/category-management
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/auth/components/LoginDesktop.jsx5
-rw-r--r--src/lib/auth/components/LoginMobile.jsx4
-rw-r--r--src/lib/auth/hooks/useLogin.js2
-rw-r--r--src/lib/category/api/popularProduct.js1
-rw-r--r--src/lib/checkout/api/checkoutApi.js34
-rw-r--r--src/lib/checkout/api/getVoucher.js45
-rw-r--r--src/lib/checkout/components/Checkout.jsx264
-rw-r--r--src/lib/checkout/components/CheckoutOld.jsx6
-rw-r--r--src/lib/checkout/components/CheckoutSection.jsx7
-rw-r--r--src/lib/home/components/CategoryPilihan.jsx3
-rw-r--r--src/lib/home/components/PreferredBrand.jsx5
-rw-r--r--src/lib/home/components/PromotionProgram.jsx75
-rw-r--r--src/lib/home/components/Skeleton/BannerPromoSkeleton.jsx16
-rw-r--r--src/lib/product/components/LobSectionCategory.jsx1
-rw-r--r--src/lib/product/components/ProductCard.jsx124
-rw-r--r--src/lib/product/components/ProductFilterDesktop.jsx1
-rw-r--r--src/lib/product/components/ProductFilterDesktopPromotion.jsx132
-rw-r--r--src/lib/product/components/ProductSearch.jsx1
-rw-r--r--src/lib/promo/components/Promocrumb.jsx40
-rw-r--r--src/lib/quotation/components/Quotation.jsx118
-rw-r--r--src/lib/transaction/api/listSiteApi.js10
-rw-r--r--src/lib/transaction/api/rejectProductApi.js9
-rw-r--r--src/lib/transaction/components/Transaction.jsx708
-rw-r--r--src/lib/transaction/components/Transactions.jsx328
-rw-r--r--src/lib/variant/components/VariantCard.jsx130
25 files changed, 1643 insertions, 426 deletions
diff --git a/src/lib/auth/components/LoginDesktop.jsx b/src/lib/auth/components/LoginDesktop.jsx
index 1333db14..9a68dc53 100644
--- a/src/lib/auth/components/LoginDesktop.jsx
+++ b/src/lib/auth/components/LoginDesktop.jsx
@@ -8,6 +8,7 @@ import { useRouter } from 'next/router';
import { useEffect, useState } from 'react';
import BottomPopup from '@/core/components/elements/Popup/BottomPopup';
import LogoSpinner from '@/core/components/elements/Spinner/LogoSpinner';
+import Image from 'next/image';
const LoginDesktop = () => {
const {
@@ -108,7 +109,7 @@ const LoginDesktop = () => {
{!isLoading ? 'Masuk' : 'Loading...'}
</button>
</form>
- {/* <div className='flex items-center mt-3 mb-3'>
+ <div className='flex items-center mt-3 mb-3'>
<hr className='flex-1' />
<p className='text-gray-400'>ATAU</p>
<hr className='flex-1' />
@@ -127,7 +128,7 @@ const LoginDesktop = () => {
height={10}
/>
<p>Masuk dengan Google</p>
- </button> */}
+ </button>
<div className='text-gray_r-11 mt-10'>
Belum punya akun Indoteknik?{' '}
diff --git a/src/lib/auth/components/LoginMobile.jsx b/src/lib/auth/components/LoginMobile.jsx
index 40924fbe..d2bf704f 100644
--- a/src/lib/auth/components/LoginMobile.jsx
+++ b/src/lib/auth/components/LoginMobile.jsx
@@ -117,7 +117,7 @@ const LoginMobile = () => {
{!isLoading ? 'Masuk' : 'Loading...'}
</button>
</form>
- {/* <div className='flex items-center mt-3 mb-3'>
+ <div className='flex items-center mt-3 mb-3'>
<hr className='flex-1' />
<p className='text-gray-400'>ATAU</p>
<hr className='flex-1' />
@@ -136,7 +136,7 @@ const LoginMobile = () => {
height={10}
/>
<p>Masuk dengan Google</p>
- </button> */}
+ </button>
<div className='text-gray_r-11 mt-4'>
Belum punya akun Indoteknik?{' '}
diff --git a/src/lib/auth/hooks/useLogin.js b/src/lib/auth/hooks/useLogin.js
index dc9580ea..dd5a4b03 100644
--- a/src/lib/auth/hooks/useLogin.js
+++ b/src/lib/auth/hooks/useLogin.js
@@ -74,7 +74,7 @@ const useLogin = () => {
if (data.isAuth) {
session.odooUser = data.user;
setCookie('auth', JSON.stringify(session?.odooUser));
- router.push(decodeURIComponent(router?.query?.next) ?? '/');
+ router.push(router?.query?.next || '/');
return;
}
};
diff --git a/src/lib/category/api/popularProduct.js b/src/lib/category/api/popularProduct.js
index 146c9449..48f8a2a0 100644
--- a/src/lib/category/api/popularProduct.js
+++ b/src/lib/category/api/popularProduct.js
@@ -4,7 +4,6 @@ export const fetchPopulerProductSolr = async (category_id_ids) => {
try {
const queryParams = new URLSearchParams({ q: category_id_ids });
const response = await fetch(`/solr/product/select?${queryParams.toString()}&rows=2000&fl=manufacture_name_s,manufacture_id_i,id,display_name_s,qty_sold_f&${sort}`);
- console.log("response",response)
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
diff --git a/src/lib/checkout/api/checkoutApi.js b/src/lib/checkout/api/checkoutApi.js
index 24f1868a..fd982fff 100644
--- a/src/lib/checkout/api/checkoutApi.js
+++ b/src/lib/checkout/api/checkoutApi.js
@@ -1,28 +1,20 @@
-import odooApi from '@/core/api/odooApi'
-import { getAuth } from '@/core/utils/auth'
+import odooApi from '@/core/api/odooApi';
+import { getAuth } from '@/core/utils/auth';
export const checkoutApi = async ({ data }) => {
- const auth = getAuth()
+ const auth = getAuth();
const dataCheckout = await odooApi(
'POST',
`/api/v1/partner/${auth.partnerId}/sale_order/checkout`,
data
- )
- return dataCheckout
-}
+ );
+ return dataCheckout;
+};
-export const getProductsCheckout = async (voucher, query) => {
- const id = getAuth()?.id
- let products
- if(voucher && query){
- products = await odooApi('GET',`/api/v1/user/${id}/sale_order/checkout?voucher=${voucher}&source=buy`)
- }else if (voucher){
- products = await odooApi('GET',`/api/v1/user/${id}/sale_order/checkout?voucher=${voucher}`)
- }else if (query) {
- products = await odooApi('GET',`/api/v1/user/${id}/sale_order/checkout?source=buy`)
- }else{
- products = await odooApi('GET',`/api/v1/user/${id}/sale_order/checkout`)
- }
-
- return products
-}
+export const getProductsCheckout = async (query) => {
+ const queryParam = new URLSearchParams(query);
+ const userId = getAuth()?.id;
+ const url = `/api/v1/user/${userId}/sale_order/checkout?${queryParam.toString()}`;
+ const result = await odooApi('GET', url);
+ return result;
+};
diff --git a/src/lib/checkout/api/getVoucher.js b/src/lib/checkout/api/getVoucher.js
index 07cf376e..779cef43 100644
--- a/src/lib/checkout/api/getVoucher.js
+++ b/src/lib/checkout/api/getVoucher.js
@@ -1,21 +1,34 @@
-import odooApi from '@/core/api/odooApi'
+import odooApi from '@/core/api/odooApi';
+import { getAuth } from '@/core/utils/auth'
-export const getVoucher = async (id, source) => {
- let dataVoucher = null
- if(source){
- dataVoucher = await odooApi('GET', `/api/v1/user/${id}/voucher?source=${source}`)
- }else {
- dataVoucher = await odooApi('GET', `/api/v1/user/${id}/voucher`)
- }
- return dataVoucher
-}
+export const getVoucher = async (id, query) => {
+ const queryParam = new URLSearchParams(query);
+ const url = `/api/v1/user/${id}/voucher?${queryParam.toString()}`;
+ const dataVoucher = await odooApi('GET', url);
+ return dataVoucher;
+};
export const findVoucher = async (code, id, source) => {
- let dataVoucher = null
- if(source){
- dataVoucher = await odooApi('GET', `/api/v1/user/${id}/voucher?code=${code}&source=${source}`)
- }else{
- dataVoucher = await odooApi('GET', `/api/v1/user/${id}/voucher?code=${code}`)
+ let dataVoucher = null;
+ if (source) {
+ dataVoucher = await odooApi(
+ 'GET',
+ `/api/v1/user/${id}/voucher?code=${code}&source=${source}`
+ );
+ } else {
+ dataVoucher = await odooApi(
+ 'GET',
+ `/api/v1/user/${id}/voucher?code=${code}`
+ );
}
+ return dataVoucher;
+};
+
+
+export const getVoucherNew = async (source) => {
+ const id = getAuth()?.id;
+ const dataVoucher = await odooApi('GET', `/api/v1/user/${id}/voucher?${source}`)
+
return dataVoucher
-}
+
+} \ No newline at end of file
diff --git a/src/lib/checkout/components/Checkout.jsx b/src/lib/checkout/components/Checkout.jsx
index 7a456d70..09a791ee 100644
--- a/src/lib/checkout/components/Checkout.jsx
+++ b/src/lib/checkout/components/Checkout.jsx
@@ -30,7 +30,7 @@ import whatsappUrl from '@/core/utils/whatsappUrl';
import addressesApi from '@/lib/address/api/addressesApi';
import CartItem from '~/modules/cart/components/Item.tsx';
import ExpedisiList from '../api/ExpedisiList';
-import { findVoucher, getVoucher } from '../api/getVoucher';
+import { findVoucher, getVoucher, getVoucherNew } from '../api/getVoucher';
const SELF_PICKUP_ID = 32;
@@ -40,12 +40,20 @@ const { getProductsCheckout } = require('../api/checkoutApi');
const Checkout = () => {
const router = useRouter();
const query = router.query.source ?? null;
+ const qVoucher = router.query.voucher ?? null;
const auth = useAuth();
const [activeVoucher, SetActiveVoucher] = useState(null);
-
- const { data: cartCheckout } = useQuery('cartCheckout-' + activeVoucher, () =>
- getProductsCheckout(activeVoucher, query)
+ const [activeVoucherShipping, setActiveVoucherShipping] = useState(null);
+
+ const { data: cartCheckout } = useQuery(
+ ['cartCheckout', activeVoucher, activeVoucherShipping],
+ () =>
+ getProductsCheckout({
+ source: query,
+ voucher: activeVoucher,
+ voucher_shipping: activeVoucherShipping,
+ })
);
const [selectedAddress, setSelectedAddress] = useState({
@@ -103,6 +111,7 @@ const Checkout = () => {
const [bottomPopupTnC, SetBottomPopupTnC] = useState(null);
const [itemTnC, setItemTnC] = useState(null);
const [listVouchers, SetListVoucher] = useState(null);
+ const [listVoucherShippings, SetListVoucherShipping] = useState(null);
const [discountVoucher, SetDiscountVoucher] = useState(0);
const [codeVoucher, SetCodeVoucher] = useState(null);
const [findCodeVoucher, SetFindVoucher] = useState(null);
@@ -112,28 +121,41 @@ const Checkout = () => {
const [loadingVoucher, setLoadingVoucher] = useState(true);
const [loadingRajaOngkir, setLoadingRajaOngkir] = useState(false);
const [grandTotal, setGrandTotal] = useState(0);
+ const [hasFlashSale, setHasFlashSale] = useState(false);
const expedisiValidation = useRef(null);
const voucher = async () => {
if (!listVouchers) {
try {
- let dataVoucher = await getVoucher(auth?.id, query);
+ setLoadingVoucher(true);
+ let dataVoucher = await getVoucher(auth?.id, {
+ source: query,
+ });
SetListVoucher(dataVoucher);
+
+ let dataVoucherShipping = await getVoucher(auth?.id, {
+ source: query,
+ type: 'shipping',
+ });
+ SetListVoucherShipping(dataVoucherShipping);
} finally {
setLoadingVoucher(false);
}
}
};
+
const VoucherCode = async (code) => {
- let dataVoucher = await findVoucher(code, auth.id, query);
+ const source = 'code=' + code + '&source=' + query;
+ // let dataVoucher = await findVoucher(code, auth.id, query);
+ let dataVoucher = await getVoucherNew(source);
if (dataVoucher.length <= 0) {
SetFindVoucher(1);
return;
}
let addNewLine = dataVoucher[0];
- let checkList = listVouchers.findIndex(
+ let checkList = listVouchers?.findIndex(
(voucher) => voucher.code == addNewLine.code
);
if (checkList >= 0) {
@@ -165,6 +187,7 @@ const Checkout = () => {
}, [bottomPopup]);
useEffect(() => {
+ voucher();
const loadExpedisi = async () => {
let dataExpedisi = await ExpedisiList();
dataExpedisi = dataExpedisi.map((expedisi) => ({
@@ -185,7 +208,6 @@ const Checkout = () => {
return () => {
window.onpopstate = null;
};
- // voucher()
}, []);
const hitungDiscountVoucher = (code) => {
@@ -221,9 +243,20 @@ const Checkout = () => {
}, [activeVoucher, listVouchers]);
useEffect(() => {
+ if (qVoucher === 'PASTIHEMAT' && listVouchers) {
+ let code = qVoucher;
+ VoucherCode(code);
+ }
+ }, [listVouchers]);
+
+ useEffect(() => {
setProducts(cartCheckout?.products);
setCheckWeight(cartCheckout?.hasProductWithoutWeight);
setTotalWeight(cartCheckout?.totalWeight.g);
+ const hasFlashSale = cartCheckout?.products.some(
+ (product) => product.hasFlashsale
+ );
+ setHasFlashSale(hasFlashSale);
}, [cartCheckout]);
useEffect(() => {
@@ -299,7 +332,7 @@ const Checkout = () => {
useEffect(() => {
const GT =
cartCheckout?.grandTotal +
- Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000;
+ Math.round(parseInt(finalShippingAmt * 1.1) / 1000) * 1000;
const finalGT = GT < 0 ? 0 : GT;
setGrandTotal(finalGT);
}, [biayaKirim, cartCheckout?.grandTotal, activeVoucher]);
@@ -333,15 +366,19 @@ const Checkout = () => {
quantity: product.quantity,
}));
let data = {
- partner_shipping_id: auth.partnerId,
- partner_invoice_id: auth.partnerId,
+ // partner_shipping_id: auth.partnerId,
+ // partner_invoice_id: auth.partnerId,
+ partner_shipping_id: selectedAddress?.shipping?.id || auth.partnerId,
+ partner_invoice_id: selectedAddress?.invoicing?.id || auth.partnerId,
user_id: auth.id,
order_line: JSON.stringify(productOrder),
delivery_amount: biayaKirim,
carrier_id: selectedCarrierId,
estimated_arrival_days: splitDuration(etd),
delivery_service_type: selectedExpedisiService,
+ flash_sale: hasFlashSale, // dibuat negasi untuk ngetest kebalikan nilai false
voucher: activeVoucher,
+ voucher_shipping: activeVoucherShipping,
type: 'sale_order',
};
@@ -400,13 +437,17 @@ const Checkout = () => {
} else {
SetActiveVoucher(code);
SetFindVoucher(null);
- document.getElementById('uniqCode').value = '';
+ document.getElementById('uniqCode')
+ ? (document.getElementById('uniqCode').value = '')
+ : '';
SetButtonTerapkan(false);
}
} else {
SetActiveVoucher(code);
SetFindVoucher(null);
- document.getElementById('uniqCode').value = '';
+ document.getElementById('uniqCode')
+ ? (document.getElementById('uniqCode').value = '')
+ : '';
SetButtonTerapkan(false);
}
};
@@ -437,6 +478,11 @@ const Checkout = () => {
return false;
}, [products]);
+ const voucherShippingAmt = cartCheckout?.discountVoucherShipping || 0;
+ const discShippingAmt = Math.min(biayaKirim, voucherShippingAmt);
+
+ const finalShippingAmt = biayaKirim - discShippingAmt;
+
return (
<>
<BottomPopup
@@ -546,8 +592,145 @@ const Checkout = () => {
</div>
)}
- <hr className='mt-10 my-4 border-gray_r-10' />
- <div className=''>
+ <hr className='mt-8 mb-4 border-gray_r-8' />
+
+ {listVoucherShippings && listVoucherShippings?.length > 0 && (
+ <div>
+ <h3 className='font-semibold mb-4'>Promo Gratis Ongkir</h3>
+ {listVoucherShippings?.map((item) => (
+ <div key={item.id} className='relative'>
+ <div
+ className={`border border-solid mb-5 w-full hover:cursor-pointer p-2 pl-4 pr-4 `}
+ >
+ {item.canApply && (
+ <div
+ class='p-2 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:text-green-400'
+ role='alert'
+ >
+ <p>
+ Potensi potongan sebesar{' '}
+ <span className='text-green font-bold'>
+ {currencyFormat(item.discountVoucher)}
+ </span>
+ </p>
+ </div>
+ )}
+ {!item.canApply && (
+ <div
+ class='p-2 mb-4 text-sm text-red-800 rounded-lg bg-red-50'
+ role='alert'
+ onClick={() => handlingTnC(item)}
+ >
+ <p>
+ Voucher tidak bisa di gunakan,{' '}
+ <span className='text-red font-bold'>
+ Baca Selengkapnya !
+ </span>
+ </p>
+ </div>
+ )}
+
+ <div className={`flex gap-x-3 relative`}>
+ {item.canApply === false && (
+ <div className='absolute w-full h-full bg-gray_r-3/40 top-0 left-0 z-50' />
+ )}
+ <div className='hidden md:w-[250px] md:block'>
+ <Image
+ src={item.image}
+ alt={item.name}
+ className={`object-cover`}
+ />
+ </div>
+ <div className='w-full'>
+ <div className='flex justify-between gap-x-2 mb-1 items-center'>
+ <div className=''>
+ <h3 className='font-semibold'>{item.name}</h3>
+ <div className='mt-1'>
+ <span className='text-sm line-clamp-3'>
+ {item.description}{' '}
+ </span>
+ </div>
+ </div>
+ <div className='flex justify-end'>
+ <label class='relative inline-flex items-center cursor-pointer'>
+ <input
+ type='checkbox'
+ value=''
+ class='sr-only peer'
+ checked={activeVoucherShipping === item.code}
+ onChange={() =>
+ setActiveVoucherShipping(
+ activeVoucherShipping === item.code
+ ? null
+ : item.code
+ )
+ }
+ />
+ <div class="w-11 h-6 bg-gray-200 rounded-full peer dark:bg-gray-700 peer-focus:ring-4 peer-focus:ring-green-300 dark:peer-focus:ring-green-800 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-0.5 after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-green-600"></div>
+ </label>
+ </div>
+ </div>
+ <hr className='mt-2 my-2 border-gray_r-8' />
+ <div className='flex justify-between items-center'>
+ <p className='text-justify text-sm md:text-xs'>
+ Kode Voucher :{' '}
+ <span className='text-red-500 font-semibold'>
+ {item.code}
+ </span>
+ </p>
+ <p className='text-sm md:text-xs'>
+ {activeVoucher === item.code && (
+ <span className=' text-green-600'>
+ Voucher digunakan{' '}
+ </span>
+ )}
+ </p>
+ </div>
+ <div className='flex items-center mt-3'>
+ <svg
+ aria-hidden='true'
+ fill='none'
+ stroke='currentColor'
+ stroke-width='1.5'
+ viewBox='0 0 24 24'
+ className='w-5 text-black'
+ >
+ <path
+ d='M12 6v6h4.5m4.5 0a9 9 0 11-18 0 9 9 0 0118 0z'
+ stroke-linecap='round'
+ stroke-linejoin='round'
+ ></path>
+ </svg>
+ <div className='flex justify-between items-center'>
+ <div className='text-left ml-3 text-sm '>
+ Berakhir dalam{' '}
+ <span className='text-red-600'>
+ {item.remainingTime}
+ </span>{' '}
+ lagi,{' '}
+ </div>
+ <div
+ className='text-sm ml-2 text-red-600'
+ onClick={() => handlingTnC(item)}
+ >
+ Baca S&K
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div className='mt-3'>
+ <p className='text-justify text-sm '></p>
+ </div>
+ </div>
+ </div>
+ ))}
+ </div>
+ )}
+
+ <hr className='mt-8 mb-4 border-gray_r-8' />
+
+ <div>
{!loadingVoucher && listVouchers?.length === 0 ? (
<div className='flex items-center justify-center mt-4 mb-4'>
<div className='text-center'>
@@ -732,14 +915,7 @@ const Checkout = () => {
</div>
</div>
<div className='mt-3'>
- <p className='text-justify text-sm '>
- {/* {item.canApply === false
- ? 'Tambah ' +
- currencyFormat(item.differenceToApply) +
- ' untuk pakai promo ini'
- : 'Potensi potongan sebesar ' +
- currencyFormat(hitungDiscountVoucher(item.code))} */}
- </p>
+ <p className='text-justify text-sm '></p>
</div>
</div>
</div>
@@ -905,14 +1081,18 @@ const Checkout = () => {
</div>
<div className='flex gap-x-2 justify-between'>
<div className='text-gray_r-11'>
- Biaya Kirim <p className='text-xs mt-3'>{etdFix}</p>
- </div>
- <div>
- {currencyFormat(
- Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
- )}
+ Biaya Kirim <p className='text-xs mt-1'>{etdFix}</p>
</div>
+ <div>{currencyFormat(biayaKirim)}</div>
</div>
+ {activeVoucherShipping && voucherShippingAmt && (
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>Diskon Kirim</div>
+ <div className='text-danger-500'>
+ - {currencyFormat(discShippingAmt)}
+ </div>
+ </div>
+ )}
</div>
)}
@@ -941,7 +1121,7 @@ const Checkout = () => {
<div className='mt-4 mb-4'>
<button
type='button'
- onClick={() => {
+ onClick={async () => {
SetBottomPopup(true);
voucher();
}}
@@ -1197,14 +1377,18 @@ const Checkout = () => {
<div className='flex gap-x-2 justify-between'>
<div className='text-gray_r-11'>
Biaya Kirim
- <p className='text-xs mt-3'>{etdFix}</p>
- </div>
- <div>
- {currencyFormat(
- Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000
- )}
+ <p className='text-xs mt-1'>{etdFix}</p>
</div>
+ <div>{currencyFormat(biayaKirim)}</div>
</div>
+ {activeVoucherShipping && voucherShippingAmt && (
+ <div className='flex gap-x-2 justify-between'>
+ <div className='text-gray_r-11'>Diskon Kirim</div>
+ <div className='text-danger-500'>
+ - {currencyFormat(discShippingAmt)}
+ </div>
+ </div>
+ )}
</div>
)}
@@ -1454,7 +1638,7 @@ const SectionExpedisi = ({
dengan menghubungi admin melalui{' '}
<a
className='text-danger-500 inline'
- href='https://api.whatsapp.com/send?phone=628128080622'
+ href='https://api.whatsapp.com/send?phone=6281717181922'
>
tautan ini
</a>
@@ -1582,7 +1766,11 @@ const PickupAddress = ({ label }) => (
Kodepos : 14440
</p>
<p className='mt-1 text-gray_r-11'>Telp : 021-2933 8828/29</p>
- <p className='mt-1 text-gray_r-11'>Mobile : 0813 9000 7430</p>
+ <p className='mt-1 text-gray_r-11 hover:text-red-500'>
+ <a href={whatsappUrl()} target='_blank' rel='noreferrer'>
+ Mobile : 0817-1718-1922
+ </a>
+ </p>
</div>
</div>
);
diff --git a/src/lib/checkout/components/CheckoutOld.jsx b/src/lib/checkout/components/CheckoutOld.jsx
index d57fbd66..5b479a73 100644
--- a/src/lib/checkout/components/CheckoutOld.jsx
+++ b/src/lib/checkout/components/CheckoutOld.jsx
@@ -696,7 +696,7 @@ const SectionExpedisi = ({ address, listExpedisi, setSelectedExpedisi, checkWeig
diatur beratnya. Mohon atur berat barang dengan menghubungi admin melalui{' '}
<a
className='text-danger-500 inline'
- href='https://api.whatsapp.com/send?phone=628128080622'
+ href='https://api.whatsapp.com/send?phone=6281717181922'
>
tautan ini
</a>
@@ -802,7 +802,9 @@ const PickupAddress = ({ label }) => (
Daerah Khusus Ibukota Jakarta, Indonesia Kodepos : 14440
</p>
<p className='mt-1 text-gray_r-11'>Telp : 021-2933 8828/29</p>
- <p className='mt-1 text-gray_r-11'>Mobile : 0813 9000 7430</p>
+ <p className='mt-1 text-gray_r-11 hover:text-red-500'><a href={whatsappUrl()} target='_blank' rel='noreferrer'>
+ Mobile : 0817-1718-1922
+ </a></p>
</div>
</div>
)
diff --git a/src/lib/checkout/components/CheckoutSection.jsx b/src/lib/checkout/components/CheckoutSection.jsx
index 7f9ea08a..623152c6 100644
--- a/src/lib/checkout/components/CheckoutSection.jsx
+++ b/src/lib/checkout/components/CheckoutSection.jsx
@@ -2,6 +2,7 @@ import Link from 'next/link';
import BottomPopup from '@/core/components/elements/Popup/BottomPopup';
import { AnimatePresence, motion } from 'framer-motion';
import { Divider, Spinner } from '@chakra-ui/react';
+import whatsappUrl from '@/core/utils/whatsappUrl';
export const SectionAddress = ({ address, label, url }) => {
return (
@@ -120,7 +121,7 @@ export const SectionExpedisi = ({
dengan menghubungi admin melalui{' '}
<a
className='text-danger-500 inline'
- href='https://api.whatsapp.com/send?phone=628128080622'
+ href='https://api.whatsapp.com/send?phone=6281717181922'
>
tautan ini
</a>
@@ -185,7 +186,9 @@ export const PickupAddress = ({ label }) => (
Kodepos : 14440
</p>
<p className='mt-1 text-gray_r-11'>Telp : 021-2933 8828/29</p>
- <p className='mt-1 text-gray_r-11'>Mobile : 0813 9000 7430</p>
+ <p className='mt-1 text-gray_r-11 hover:text-red-500'><a href={whatsappUrl()} target='_blank' rel='noreferrer'>
+ Mobile : 0817-1718-1922
+ </a></p>
</div>
</div>
);
diff --git a/src/lib/home/components/CategoryPilihan.jsx b/src/lib/home/components/CategoryPilihan.jsx
index 6568621c..6dbf771e 100644
--- a/src/lib/home/components/CategoryPilihan.jsx
+++ b/src/lib/home/components/CategoryPilihan.jsx
@@ -16,6 +16,7 @@ const CategoryPilihan = ({ id, categories }) => {
const { categoryPilihan } = useCategoryPilihan();
const heroBanner = useQuery('categoryPilihan', bannerApi({ type: 'banner-category-list' }));
return (
+ categoryPilihan.length > 0 && (
<section>
{isDesktop && (
<div>
@@ -114,6 +115,8 @@ const CategoryPilihan = ({ id, categories }) => {
</div>
)}
</section>
+
+ )
)
}
diff --git a/src/lib/home/components/PreferredBrand.jsx b/src/lib/home/components/PreferredBrand.jsx
index ae12505d..fdefb526 100644
--- a/src/lib/home/components/PreferredBrand.jsx
+++ b/src/lib/home/components/PreferredBrand.jsx
@@ -65,6 +65,11 @@ const PreferredBrand = () => {
Lihat Semua
</Link>
)}
+ {isMobile && (
+ <Link href='/shop/brands' className='!text-red-500 font-semibold sm:text-h-sm'>
+ Lihat Semua
+ </Link>
+ )}
</div>
<div className='border rounded border-gray_r-6'>
{manufactures.isLoading && <PreferredBrandSkeleton />}
diff --git a/src/lib/home/components/PromotionProgram.jsx b/src/lib/home/components/PromotionProgram.jsx
new file mode 100644
index 00000000..c2f76069
--- /dev/null
+++ b/src/lib/home/components/PromotionProgram.jsx
@@ -0,0 +1,75 @@
+import Link from '@/core/components/elements/Link/Link'
+import Image from 'next/image'
+import { bannerApi } from '@/api/bannerApi';
+import useDevice from '@/core/hooks/useDevice'
+import { Swiper, SwiperSlide } from 'swiper/react';
+import BannerPromoSkeleton from '../components/Skeleton/BannerPromoSkeleton';
+const { useQuery } = require('react-query')
+const BannerSection = () => {
+ const promotionProgram = useQuery('promotionProgram', bannerApi({ type: 'banner-promotion' }));
+ const { isMobile, isDesktop } = useDevice()
+
+ if (promotionProgram.isLoading) {
+ return <BannerPromoSkeleton />;
+ }
+
+ return (
+ <div className='px-4 sm:px-0'>
+ <div className='flex justify-between items-center mb-4 '>
+ <div className='font-semibold sm:text-h-lg'>Promo Tersedia</div>
+ {isDesktop && (
+ <Link href='/shop/promo' className='!text-red-500 font-semibold'>
+ Lihat Semua
+ </Link>
+ )}
+ {isMobile && (
+ <Link href='/shop/promo' className='!text-red-500 font-semibold sm:text-h-sm'>
+ Lihat Semua
+ </Link>
+ )}
+ </div>
+ {isDesktop && (promotionProgram.data &&
+ 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={100}
+ 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) => (
+ <SwiperSlide key={banner.id}>
+ <Link key={banner.id} href={banner.url}>
+ <Image
+ width={439}
+ height={150}
+ quality={100}
+ src={banner.image}
+ alt={banner.name}
+ className='h-auto w-full rounded '
+ />
+ </Link>
+ </SwiperSlide>
+ ))}
+ </Swiper>
+
+ )}
+ </div>
+
+ )
+}
+
+export default BannerSection
diff --git a/src/lib/home/components/Skeleton/BannerPromoSkeleton.jsx b/src/lib/home/components/Skeleton/BannerPromoSkeleton.jsx
new file mode 100644
index 00000000..c5f39f19
--- /dev/null
+++ b/src/lib/home/components/Skeleton/BannerPromoSkeleton.jsx
@@ -0,0 +1,16 @@
+import useDevice from '@/core/hooks/useDevice'
+import Skeleton from 'react-loading-skeleton'
+
+const BannerPromoSkeleton = () => {
+ const { isDesktop } = useDevice()
+
+ return (
+ <div className='grid grid-cols-1 md:grid-cols-3 gap-x-3'>
+ {Array.from({ length: isDesktop ? 3 : 1.2 }, (_, index) => (
+ <Skeleton count={1} height={isDesktop ? 60 : 36} key={index} />
+ ))}
+ </div>
+ )
+}
+
+export default BannerPromoSkeleton
diff --git a/src/lib/product/components/LobSectionCategory.jsx b/src/lib/product/components/LobSectionCategory.jsx
index 03d6e8c0..5cd467e9 100644
--- a/src/lib/product/components/LobSectionCategory.jsx
+++ b/src/lib/product/components/LobSectionCategory.jsx
@@ -24,7 +24,6 @@ const LobSectionCategory = ({ categories }) => {
};
const displayedCategories = categories[0]?.categoryIds;
-
return (
<section>
{isDesktop && (
diff --git a/src/lib/product/components/ProductCard.jsx b/src/lib/product/components/ProductCard.jsx
index 38ed35f8..35e2a665 100644
--- a/src/lib/product/components/ProductCard.jsx
+++ b/src/lib/product/components/ProductCard.jsx
@@ -1,7 +1,7 @@
import clsx from 'clsx';
import ImageNext from 'next/image';
import { useRouter } from 'next/router';
-import { useMemo } from 'react';
+import { useMemo, useEffect, useState } from 'react';
import Image from '@/core/components/elements/Image/Image';
import Link from '@/core/components/elements/Link/Link';
@@ -14,6 +14,15 @@ import useUtmSource from '~/hooks/useUtmSource';
const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
const router = useRouter();
const utmSource = useUtmSource();
+ const [discount, setDiscount] = useState(0);
+
+ let voucherPastiHemat = 0;
+
+ if (product?.voucherPastiHemat ? product?.voucherPastiHemat.length : voucherPastiHemat > 0) {
+ const stringVoucher = product?.voucherPastiHemat[0];
+ const validJsonString = stringVoucher.replace(/'/g, '"');
+ voucherPastiHemat = JSON.parse(validJsonString);
+ }
const callForPriceWhatsapp = whatsappUrl('product', {
name: product.name,
@@ -37,15 +46,65 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
),
};
+ const hitungDiscountVoucher = () => {
+ let countDiscount = 0;
+ if (voucherPastiHemat.discount_type === 'percentage') {
+ countDiscount =
+ product?.lowestPrice.priceDiscount *
+ (voucherPastiHemat.discount_amount / 100);
+ if (
+ voucherPastiHemat.max_discount > 0 &&
+ countDiscount > voucherPastiHemat.max_discount
+ ) {
+ countDiscount = voucherPastiHemat.max_discount;
+ }
+ } else {
+ countDiscount = voucherPastiHemat.discount_amount;
+ }
+
+ setDiscount(countDiscount);
+ };
+
+ useEffect(() => {
+ hitungDiscountVoucher();
+ }, []);
+
if (variant == 'vertical') {
return (
- <div className='rounded shadow-sm border border-gray_r-4 bg-white h-[300px] md:h-[350px]'>
+ <div className='rounded shadow-sm border border-gray_r-4 bg-white h-[330px] md:h-[380px]'>
<Link href={URL.product} className='border-b border-gray_r-4 relative'>
- <Image
- src={image}
- alt={product?.name}
- className='w-full object-contain object-center h-36 sm:h-48'
- />
+ <div className='relative'>
+ <Image
+ src={image}
+ alt={product?.name}
+ className='gambarA w-full object-contain object-center h-36 sm:h-48'
+ />
+ <div className='absolute top-0 right-0 flex mt-3'>
+ <div className='gambarB '>
+ {product?.isSni && (
+ <ImageNext
+ src='/images/sni-logo.png'
+ alt='SNI Logo'
+ className='w-4 h-5 object-contain object-top sm:h-6'
+ width={50}
+ height={50}
+ />
+ )}
+ </div>
+ <div className='gambarC '>
+ {product?.isTkdn && (
+ <ImageNext
+ src='/images/TKDN.png'
+ alt='TKDN'
+ className='w-11 h-6 object-contain object-top ml-1 mr-1 sm:h-6'
+ width={50}
+ height={50}
+ />
+ )}
+ </div>
+ </div>
+ </div>
+
{router.pathname != '/' && product?.flashSale?.id > 0 && (
<div className='absolute bottom-0 w-full grid'>
<div className='absolute bottom-0 w-full h-full'>
@@ -149,6 +208,13 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
)}
</div>
)}
+ {discount > 0 && product?.flashSale?.id < 1 && (
+ <div className='flex gap-x-1 mb-1 text-sm'>
+ <div className='inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20'>
+ Voucher : {currencyFormat(discount)}
+ </div>
+ </div>
+ )}
<div className='flex w-full items-center gap-x-1 '>
{product?.stockTotal > 0 && (
@@ -171,11 +237,37 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
<div className='flex bg-white'>
<div className='w-4/12'>
<Link href={URL.product} className='relative'>
- <Image
- src={image}
- alt={product?.name}
- className='w-full object-contain object-center h-36'
- />
+ <div className='relative'>
+ <Image
+ src={image}
+ alt={product?.name}
+ className='gambarA w-full object-contain object-center h-36 sm:h-48'
+ />
+ <div className='absolute top-0 right-0 flex mt-3'>
+ <div className='gambarB '>
+ {product?.isSni && (
+ <ImageNext
+ src='/images/sni-logo.png'
+ alt='SNI Logo'
+ className='w-4 h-5 object-contain object-top sm:h-6'
+ width={50}
+ height={50}
+ />
+ )}
+ </div>
+ <div className='gambarC '>
+ {product?.isTkdn && (
+ <ImageNext
+ src='/images/TKDN.png'
+ alt='TKDN'
+ className='w-11 h-6 object-contain object-top ml-1 sm:h-6'
+ width={50}
+ height={50}
+ />
+ )}
+ </div>
+ </div>
+ </div>
{product.variantTotal > 1 && (
<div className='absolute badge-gray bottom-1.5 left-1.5'>
{product.variantTotal} Varian
@@ -264,6 +356,14 @@ const ProductCard = ({ product, simpleTitle, variant = 'vertical' }) => {
</div>
)}
+ {discount > 0 && product?.flashSale?.id < 1 && (
+ <div className='flex gap-x-1 mb-1 text-sm'>
+ <div className='inline-flex items-center rounded-md bg-green-50 px-2 py-1 text-xs font-medium text-green-700 ring-1 ring-inset ring-green-600/20'>
+ Voucher : {currencyFormat(discount)}
+ </div>
+ </div>
+ )}
+
<div className='flex w-full items-center gap-x-1 '>
{product?.stockTotal > 0 && (
<div className='badge-solid-red'>Ready Stock</div>
diff --git a/src/lib/product/components/ProductFilterDesktop.jsx b/src/lib/product/components/ProductFilterDesktop.jsx
index 1933c5f0..73fecab5 100644
--- a/src/lib/product/components/ProductFilterDesktop.jsx
+++ b/src/lib/product/components/ProductFilterDesktop.jsx
@@ -22,6 +22,7 @@ import { formatCurrency } from '@/core/utils/formatValue'
const ProductFilterDesktop = ({ brands, categories, prefixUrl, defaultBrand = null }) => {
+
const router = useRouter()
const { query } = router
const [order, setOrder] = useState(query?.orderBy)
diff --git a/src/lib/product/components/ProductFilterDesktopPromotion.jsx b/src/lib/product/components/ProductFilterDesktopPromotion.jsx
new file mode 100644
index 00000000..0815b881
--- /dev/null
+++ b/src/lib/product/components/ProductFilterDesktopPromotion.jsx
@@ -0,0 +1,132 @@
+import { useRouter } from 'next/router';
+import { useEffect, useState } from 'react';
+import _ from 'lodash';
+import { toQuery } from 'lodash-contrib';
+import { Button } from '@chakra-ui/react';
+import { MultiSelect } from 'react-multi-select-component';
+
+const ProductFilterDesktop = ({ brands, categories, prefixUrl }) => {
+ const router = useRouter();
+ const { query } = router;
+ const [order, setOrder] = useState(query?.orderBy);
+ const [brandValues, setBrand] = useState([]);
+ const [categoryValues, setCategory] = useState([]);
+ const [priceFrom, setPriceFrom] = useState(query?.priceFrom);
+ const [priceTo, setPriceTo] = useState(query?.priceTo);
+ const [stock, setStock] = useState(query?.stock);
+ const [activeRange, setActiveRange] = useState(null);
+ const [isBrandDropdownClicked, setIsBrandDropdownClicked] = useState(false);
+ const [isCategoryDropdownClicked, setIsCategoryDropdownClicked] = useState(false);
+
+ // Effect to set brandValues from query parameter 'brand'
+ useEffect(() => {
+ const brandParam = query?.brand;
+ if (brandParam) {
+ const brandsArray = brandParam.split(',').map((b) => ({
+ label: b,
+ value: b,
+ }));
+ setBrand(brandsArray);
+ }
+
+ }, [query.brand]); // Trigger effect whenever query.brand changes
+
+ useEffect(() => {
+ const categoryParam = query?.category;
+ if (categoryParam) {
+ const categoriesArray = categoryParam.split(',').map((c) => ({
+ label: c,
+ value: c,
+ }));
+ setCategory(categoriesArray);
+ }
+ }, [query.category]); // Trigger effect whenever query.category changes
+
+ const handleSubmit = () => {
+ let params = {
+ q: router.query.q,
+ orderBy: order,
+ brand: brandValues.map((b) => b.value).join(','),
+ category: categoryValues.map((c) => c.value).join(','),
+ priceFrom,
+ priceTo,
+ stock: stock,
+ };
+ params = _.pickBy(params, _.identity);
+ params = toQuery(params);
+
+ const slug = Array.isArray(router.query.slug)
+ ? router.query.slug[0]
+ : router.query.slug;
+
+ if (slug) {
+ router.push(`${prefixUrl}/${slug}?${params}`);
+ } else {
+ router.push(`${prefixUrl}?${params}`);
+ }
+ };
+
+
+ const brandOptions = brands.map((brand) => ({
+ label: `${brand.brand} (${brand.qty})`,
+ value: brand.brand,
+ }));
+
+ const categoryOptions = categories.map((category) => ({
+ label: `${category.name} (${category.qty})`,
+ value: category.name,
+ }));
+
+ return (
+ <>
+ <div className='flex h-full w-[100%] justify-end '>
+ {/* Brand MultiSelect */}
+ <div className='mb-[20px] mr-4 w-64 h-full flex justify-start '>
+ <div className='relative'>
+ <label>Brand</label>
+ <div className='h-auto z-50 w-64 '>
+ <MultiSelect
+ options={brandOptions}
+ value={brandValues}
+ onChange={setBrand}
+ labelledBy='Select Brand'
+ onMenuToggle={(isOpen) => setIsBrandDropdownClicked(isOpen)}
+ hasSelectAll={false}
+ />
+ </div>
+ </div>
+ </div>
+
+ {/* Category MultiSelect */}
+ <div className='mb-[20px] mr-4 w-64 h-full flex justify-start '>
+ <div className='relative'>
+ <label>Kategori</label>
+ <div className=' h-auto w-64'>
+ <MultiSelect
+ options={categoryOptions}
+ value={categoryValues}
+ onChange={setCategory}
+ labelledBy='Select Kategori'
+ onMenuToggle={() =>
+ setIsCategoryDropdownClicked(!isCategoryDropdownClicked)
+ }
+ hasSelectAll={false}
+ />
+ </div>
+ </div>
+ </div>
+
+ {/* Apply Button */}
+ <div className='TOMBOL mb-1 h-24 flex justify-center items-center w-24'>
+ <div className=' bottom-1 pb-1 left-0 right-0 flex justify-center rounded' >
+ <Button colorScheme='red' width={"full"} onClick={handleSubmit}>
+ Terapkan
+ </Button>
+ </div>
+ </div>
+ </div>
+ </>
+ );
+};
+
+export default ProductFilterDesktop;
diff --git a/src/lib/product/components/ProductSearch.jsx b/src/lib/product/components/ProductSearch.jsx
index 09534a5d..9f19aced 100644
--- a/src/lib/product/components/ProductSearch.jsx
+++ b/src/lib/product/components/ProductSearch.jsx
@@ -267,6 +267,7 @@ const ProductSearch = ({
const orderOptions = [
+ { value: '', label: 'Pilih Filter' },
{ value: 'price-asc', label: 'Harga Terendah' },
{ value: 'price-desc', label: 'Harga Tertinggi' },
{ value: 'popular', label: 'Populer' },
diff --git a/src/lib/promo/components/Promocrumb.jsx b/src/lib/promo/components/Promocrumb.jsx
new file mode 100644
index 00000000..4f5cf346
--- /dev/null
+++ b/src/lib/promo/components/Promocrumb.jsx
@@ -0,0 +1,40 @@
+import { Breadcrumb as ChakraBreadcrumb, BreadcrumbItem, BreadcrumbLink } from '@chakra-ui/react'
+import Link from 'next/link'
+import React from 'react'
+
+/**
+ * Renders a breadcrumb component with links to navigate through different pages.
+ *
+ * @param {Object} props - The props object containing the brand name.
+ * @param {string} props.brandName - The name of the brand to display in the breadcrumb.
+ * @return {JSX.Element} The rendered breadcrumb component.
+ */
+const Breadcrumb = ({ brandName }) => {
+ return (
+ <div className='container mx-auto py-4 md:py-6'>
+ <ChakraBreadcrumb>
+ <BreadcrumbItem>
+ <BreadcrumbLink as={Link} href='/' className='!text-danger-500 whitespace-nowrap'>
+ Shop
+ </BreadcrumbLink>
+ </BreadcrumbItem>
+
+ {/* <BreadcrumbItem>
+ <BreadcrumbLink
+ as={Link}
+ href='/shop/promo'
+ className='!text-danger-500 whitespace-nowrap'
+ >
+ Promo
+ </BreadcrumbLink>
+ </BreadcrumbItem> */}
+
+ <BreadcrumbItem isCurrentPage>
+ <BreadcrumbLink className='whitespace-nowrap'>{brandName}</BreadcrumbLink>
+ </BreadcrumbItem>
+ </ChakraBreadcrumb>
+ </div>
+ )
+}
+
+export default Breadcrumb
diff --git a/src/lib/quotation/components/Quotation.jsx b/src/lib/quotation/components/Quotation.jsx
index 09d55e92..df234dc2 100644
--- a/src/lib/quotation/components/Quotation.jsx
+++ b/src/lib/quotation/components/Quotation.jsx
@@ -67,15 +67,19 @@ const Quotation = () => {
const [selectedExpedisiService, setselectedExpedisiService] = useState(null);
const [etd, setEtd] = useState(null);
const [etdFix, setEtdFix] = useState(null);
-
+
+ const [isApproval, setIsApproval] = useState(false);
+
const expedisiValidation = useRef(null);
-
+
const [selectedAddress, setSelectedAddress] = useState({
shipping: null,
invoicing: null,
});
-
+
const [addresses, setAddresses] = useState(null);
+
+ const [note_websiteText, setselectedNote_websiteText] = useState('');
useEffect(() => {
if (!auth) return;
@@ -86,6 +90,7 @@ const Quotation = () => {
};
getAddresses();
+ setIsApproval(auth?.feature?.soApproval);
}, [auth]);
useEffect(() => {
@@ -185,6 +190,13 @@ const Quotation = () => {
if (etd) setEtdFix(calculateEstimatedArrival(etd));
}, [etd]);
+ useEffect(() => {
+ if (isApproval) {
+ setselectedCarrierId(1);
+ setselectedExpedisiService('indoteknik');
+ }
+ }, [isApproval]);
+
// end set up address and carrier
useEffect(() => {
@@ -235,7 +247,7 @@ const Quotation = () => {
const checkout = async () => {
// validation checkout
- if (selectedExpedisi === 0) {
+ if (selectedExpedisi === 0 && !isApproval) {
setCheckoutValidation(true);
if (expedisiValidation.current) {
const position = expedisiValidation.current.getBoundingClientRect();
@@ -246,12 +258,18 @@ const Quotation = () => {
}
return;
}
- if (selectedCarrier != 1 && biayaKirim == 0) {
+ if (selectedCarrier != 1 && biayaKirim == 0 && !isApproval) {
toast.error('Maaf, layanan tidak tersedia. Mohon pilih expedisi lain.');
return;
}
if (!products || products.length == 0) return;
+
+ if (isApproval && note_websiteText == '') {
+ toast.error('Maaf, Note wajib dimasukkan.');
+ return;
+ }
+
setIsLoading(true);
const productOrder = products.map((product) => ({
product_id: product.id,
@@ -266,14 +284,18 @@ const Quotation = () => {
carrier_id: selectedCarrierId,
estimated_arrival_days: splitDuration(etd),
delivery_service_type: selectedExpedisiService,
+ note_website : note_websiteText,
};
+
const isSuccess = await checkoutApi({ data });
+ ;
setIsLoading(false);
if (isSuccess?.id) {
for (const product of products) deleteItemCart({ productId: product.id });
router.push(`/shop/quotation/finish?id=${isSuccess.id}`);
return;
}
+
toast.error('Gagal melakukan transaksi, terjadi kesalahan internal');
};
@@ -343,16 +365,21 @@ const Quotation = () => {
)}
<Divider />
<SectionValidation address={selectedAddress.invoicing} />
- <SectionExpedisi
- address={selectedAddress.shipping}
- listExpedisi={listExpedisi}
- setSelectedExpedisi={setSelectedExpedisi}
- checkWeigth={checkWeigth}
- checkoutValidation={checkoutValidation}
- expedisiValidation={expedisiValidation}
- loadingRajaOngkir={loadingRajaOngkir}
- />
- <Divider />
+ {!isApproval && (
+ <>
+ <SectionExpedisi
+ address={selectedAddress.shipping}
+ listExpedisi={listExpedisi}
+ setSelectedExpedisi={setSelectedExpedisi}
+ checkWeigth={checkWeigth}
+ checkoutValidation={checkoutValidation}
+ expedisiValidation={expedisiValidation}
+ loadingRajaOngkir={loadingRajaOngkir}
+ />
+ <Divider />
+ </>
+ )}
+
<SectionListService
listserviceExpedisi={listserviceExpedisi}
setSelectedServiceType={setSelectedServiceType}
@@ -425,8 +452,25 @@ const Quotation = () => {
</Link>{' '}
yang berlaku
</p>
+ <hr className='my-4 border-gray_r-6' />
+
+ <div className='flex gap-x-2 justify-start mb-4'>
+ <div className=''>Note</div>
+ {isApproval && (
+ <div className='text-caption-1 text-red-500 items-center flex'>*harus diisi</div>
+ )}
+ </div>
+ <div className='text-caption-2 text-gray_r-11'>
+ <textarea
+ rows="4"
+ cols="50"
+ className={`w-full p-1 rounded border border-gray_r-6`}
+ onChange={(e) => setselectedNote_websiteText(e.target.value)}
+ />
+ </div>
</div>
-
+
+
<Divider />
<div className='flex gap-x-3 p-4'>
@@ -468,15 +512,18 @@ const Quotation = () => {
)}
<Divider />
<SectionValidation address={selectedAddress.invoicing} />
- <SectionExpedisi
- address={selectedAddress.shipping}
- listExpedisi={listExpedisi}
- setSelectedExpedisi={setSelectedExpedisi}
- checkWeigth={checkWeigth}
- checkoutValidation={checkoutValidation}
- expedisiValidation={expedisiValidation}
- loadingRajaOngkir={loadingRajaOngkir}
- />
+ {!isApproval && (
+ <SectionExpedisi
+ address={selectedAddress.shipping}
+ listExpedisi={listExpedisi}
+ setSelectedExpedisi={setSelectedExpedisi}
+ checkWeigth={checkWeigth}
+ checkoutValidation={checkoutValidation}
+ expedisiValidation={expedisiValidation}
+ loadingRajaOngkir={loadingRajaOngkir}
+ />
+ )}
+
<Divider />
<SectionListService
listserviceExpedisi={listserviceExpedisi}
@@ -556,6 +603,27 @@ const Quotation = () => {
yang berlaku
</p>
+ <div>
+ <hr className='my-4 border-gray_r-6' />
+
+ <div className='flex gap-x-1 flex-col mb-4'>
+ <div className='flex flex-row gap-x-1'>
+ <div className=''>Note</div>
+ {isApproval && (
+ <div className='text-caption-1 text-red-500 items-center flex'>*harus diisi</div>
+ )}
+ </div>
+ <div className='text-caption-2 text-gray_r-11'>
+ <textarea
+ rows="4"
+ cols="50"
+ className={`w-full p-1 rounded border border-gray_r-6`}
+ onChange={(e) => setselectedNote_websiteText(e.target.value)}
+ />
+ </div>
+ </div>
+ </div>
+
<hr className='my-4 border-gray_r-6' />
<button
diff --git a/src/lib/transaction/api/listSiteApi.js b/src/lib/transaction/api/listSiteApi.js
new file mode 100644
index 00000000..8b7740c5
--- /dev/null
+++ b/src/lib/transaction/api/listSiteApi.js
@@ -0,0 +1,10 @@
+import odooApi from '@/core/api/odooApi'
+import { getAuth } from '@/core/utils/auth'
+
+const getSite = async () => {
+ const auth = getAuth()
+ const dataSite = await odooApi('GET', `/api/v1/partner/${auth?.partnerId}/list/site`)
+ return dataSite
+}
+
+export default getSite \ No newline at end of file
diff --git a/src/lib/transaction/api/rejectProductApi.js b/src/lib/transaction/api/rejectProductApi.js
new file mode 100644
index 00000000..e03c7975
--- /dev/null
+++ b/src/lib/transaction/api/rejectProductApi.js
@@ -0,0 +1,9 @@
+import odooApi from '@/core/api/odooApi'
+
+const rejectProductApi = async ({ idSo, idProduct, reason }) => {
+ const dataCheckout = await odooApi('POST', `/api/v1/sale_order/${idSo}/reject/${idProduct}`, {
+ reason_reject: reason
+ });
+ return dataCheckout
+}
+export default rejectProductApi \ No newline at end of file
diff --git a/src/lib/transaction/components/Transaction.jsx b/src/lib/transaction/components/Transaction.jsx
index 8f4b2038..4d401037 100644
--- a/src/lib/transaction/components/Transaction.jsx
+++ b/src/lib/transaction/components/Transaction.jsx
@@ -1,8 +1,11 @@
import Spinner from '@/core/components/elements/Spinner/Spinner';
+import NextImage from 'next/image';
+import rejectImage from '../../../../public/images/reject.png';
import useTransaction from '../hooks/useTransaction';
import TransactionStatusBadge from './TransactionStatusBadge';
import Divider from '@/core/components/elements/Divider/Divider';
-import { useMemo, useRef, useState } from 'react';
+import { useEffect, useMemo, useRef, useState } from 'react';
+import ImageNext from 'next/image';
import {
downloadPurchaseOrder,
downloadQuotation,
@@ -33,12 +36,18 @@ import useAuth from '@/core/hooks/useAuth';
import StepApproval from './stepper';
import aprpoveApi from '../api/approveApi';
import rejectApi from '../api/rejectApi';
+import rejectProductApi from '../api/rejectProductApi';
+import { useRouter } from 'next/router';
const Transaction = ({ id }) => {
+ const router = useRouter();
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [selectedProduct, setSelectedProduct] = useState(null);
+ const [reason, setReason] = useState('');
const auth = useAuth();
const { transaction } = useTransaction({ id });
-
- const statusApprovalWeb = transaction.data?.approvalStep
+
+ const statusApprovalWeb = transaction.data?.approvalStep;
const { queryAirwayBill } = useAirwayBill({ orderId: id });
const [airwayBillPopup, setAirwayBillPopup] = useState(null);
@@ -49,6 +58,7 @@ const Transaction = ({ id }) => {
const [idAWB, setIdAWB] = useState(null);
const openUploadPo = () => setUploadPo(true);
const closeUploadPo = () => setUploadPo(false);
+
const submitUploadPo = async () => {
const file = poFile.current.files[0];
const name = poNumber.current.value;
@@ -78,7 +88,7 @@ const Transaction = ({ id }) => {
const closeCancelTransaction = () => setCancelTransaction(false);
const [rejectTransaction, setRejectTransaction] = useState(false);
-
+
const openRejectTransaction = () => setRejectTransaction(true);
const closeRejectTransaction = () => setRejectTransaction(false);
const submitCancelTransaction = async () => {
@@ -106,13 +116,13 @@ const Transaction = ({ id }) => {
await aprpoveApi({ id });
toast.success('Berhasil melanjutkan approval');
transaction.refetch();
- }
+ };
const handleReject = async () => {
- await rejectApi({ id });
- closeRejectTransaction()
- transaction.refetch();
- }
+ await rejectApi({ id });
+ closeRejectTransaction();
+ transaction.refetch();
+ };
const memoizeVariantGroupCard = useMemo(
() => (
@@ -139,6 +149,18 @@ const Transaction = ({ id }) => {
[transaction.data]
);
+ const memoizeVariantGroupCardReject = useMemo(
+ () => (
+ <div className='p-4 pt-0 flex flex-col gap-y-3'>
+ <VariantGroupCard
+ variants={transaction.data?.productsRejectLine}
+ buyMore
+ />
+ </div>
+ ),
+ [transaction.data]
+ );
+
if (transaction.isLoading) {
return (
<div className='flex justify-center my-6'>
@@ -151,6 +173,37 @@ const Transaction = ({ id }) => {
setIdAWB(null);
};
+ const openModal = (product) => {
+ setSelectedProduct(product);
+ setIsModalOpen(true);
+ };
+
+ const closeModal = () => {
+ setIsModalOpen(false);
+ setSelectedProduct(null);
+ setReason('');
+ };
+
+ const handleRejectProduct = async () => {
+ try {
+ if (!reason.trim()) {
+ toast.error('Masukkan alasan terlebih dahulu');
+ return;
+ } else {
+ let idSo = transaction?.data.id;
+ let idProduct = selectedProduct?.id;
+ await rejectProductApi({ idSo, idProduct, reason });
+ closeModal();
+ toast.success('Produk berhasil di reject');
+ setTimeout(() => {
+ window.location.reload();
+ }, 1500);
+ }
+ } catch (error) {
+ toast.error('Gagal reject produk. Silakan coba lagi.');
+ }
+ };
+
return (
transaction.data?.name && (
<>
@@ -238,7 +291,13 @@ const Transaction = ({ id }) => {
<MobileView>
<div className='p-4'>
- <StepApproval layer={2} status={'cancel'} className='ml-auto' />
+ {auth?.feature?.soApproval && (
+ <StepApproval
+ layer={statusApprovalWeb}
+ status={transaction?.data?.status}
+ className='ml-auto'
+ />
+ )}
</div>
<div className='flex flex-col gap-y-4 p-4'>
<DescriptionRow label='Status Transaksi'>
@@ -293,40 +352,6 @@ const Transaction = ({ id }) => {
<Divider />
- {!auth?.feature.soApproval && (
- <div className='p-4 flex flex-col gap-y-4'>
- <DescriptionRow label='Purchase Order'>
- {transaction.data?.purchaseOrderName || '-'}
- </DescriptionRow>
- <div className='flex items-center'>
- <p className='text-gray_r-11 leading-none'>Dokumen PO</p>
- <button
- type='button'
- className='btn-light py-1.5 px-3 ml-auto'
- onClick={
- transaction.data?.purchaseOrderFile
- ? () => downloadPurchaseOrder(transaction.data)
- : openUploadPo
- }
- >
- {transaction.data?.purchaseOrderFile ? 'Download' : 'Upload'}
- </button>
- </div>
- </div>
- )}
-
- <Divider />
-
- <div className='font-medium p-4'>Detail Produk</div>
-
- {memoizeVariantGroupCard}
-
- <Divider />
-
- <SectionAddress address={transaction.data?.address} />
-
- <Divider />
-
<div className='p-4'>
<p className='font-medium'>Invoice</p>
<div className='flex flex-col gap-y-3 mt-4'>
@@ -358,34 +383,96 @@ const Transaction = ({ id }) => {
<Divider />
- <div className='p-4 pt-0'>
- {transaction.data?.status == 'draft' && auth?.feature.soApproval && (
- <div className='flex gap-x-2'>
- <button
- className='btn-yellow w-full'
- onClick={checkout}
- disabled={
- transaction.data?.status === 'cancel' ? true : false || auth?.webRole === statusApprovalWeb ? true : false
- }
- >
- Approve
- </button>
+ {!auth?.feature.soApproval && (
+ <div className='p-4 flex flex-col gap-y-4'>
+ <DescriptionRow label='Purchase Order'>
+ {transaction.data?.purchaseOrderName || '-'}
+ </DescriptionRow>
+ <div className='flex items-center'>
+ <p className='text-gray_r-11 leading-none'>Dokumen PO</p>
<button
- className='btn-solid-red px-7 w-full'
- onClick={checkout}
- disabled={
- transaction.data?.status === 'cancel' ? true : false || auth?.webRole === statusApprovalWeb ? true : false
+ type='button'
+ className='inline-block text-danger-500'
+ onClick={
+ transaction.data?.purchaseOrderFile
+ ? () => downloadPurchaseOrder(transaction.data)
+ : transaction?.data.invoices.length < 1
+ ? openUploadPo
+ : ''
}
>
- Reject
+ {transaction?.data?.purchaseOrderFile
+ ? 'Download'
+ : transaction?.data.invoices.length < 1
+ ? 'Upload'
+ : '-'}
</button>
</div>
- )}
- {transaction.data?.status == 'draft' && !auth?.feature?.soApproval && (
- <button className='btn-yellow w-full mt-4' onClick={checkout}>
- Lanjutkan Transaksi
- </button>
- )}
+ </div>
+ )}
+
+ <Divider />
+
+ <div className='font-medium p-4'>Detail Produk</div>
+ {transaction?.data?.products.length > 0 ? (
+ <div>{memoizeVariantGroupCard}</div>
+ ) : (
+ <div className='badge-red text-sm px-2 ml-4'>
+ Semua produk telah di reject
+ </div>
+ )}
+
+ {transaction?.data?.productsRejectLine.length > 0 && (
+ <div>
+ <div className='font-medium p-4'>Detail Produk Reject</div>
+ {memoizeVariantGroupCardReject}
+ </div>
+ )}
+
+ <Divider />
+
+ <SectionAddress address={transaction.data?.address} />
+
+ <Divider />
+
+ <div className='p-4 pt-0'>
+ {transaction.data?.status == 'draft' &&
+ auth?.feature.soApproval && (
+ <div className='flex gap-x-2'>
+ <button
+ className='btn-yellow w-full'
+ onClick={checkout}
+ disabled={
+ transaction.data?.status === 'cancel'
+ ? true
+ : false || auth?.webRole === statusApprovalWeb
+ ? true
+ : false
+ }
+ >
+ Approve
+ </button>
+ <button
+ className='btn-solid-red px-7 w-full'
+ onClick={checkout}
+ disabled={
+ transaction.data?.status === 'cancel'
+ ? true
+ : false || auth?.webRole === statusApprovalWeb
+ ? true
+ : false
+ }
+ >
+ Reject
+ </button>
+ </div>
+ )}
+ {transaction.data?.status == 'draft' &&
+ !auth?.feature?.soApproval && (
+ <button className='btn-yellow w-full mt-4' onClick={checkout}>
+ Lanjutkan Transaksi
+ </button>
+ )}
<button
className='btn-light w-full mt-4'
disabled={transaction.data?.status != 'draft'}
@@ -439,13 +526,20 @@ const Transaction = ({ id }) => {
Download
</button>
{transaction.data?.status == 'draft' &&
- auth?.feature?.soApproval && auth?.webRole && (
+ auth?.feature?.soApproval &&
+ auth?.webRole && (
<div className='flex gap-x-2'>
<button
className='btn-yellow'
onClick={handleApproval}
disabled={
- transaction.data?.status === 'cancel' ? true : false || auth?.webRole === statusApprovalWeb ? true : false || statusApprovalWeb < 1 ? true : false
+ transaction.data?.status === 'cancel'
+ ? true
+ : false || auth?.webRole === statusApprovalWeb
+ ? true
+ : false || statusApprovalWeb < 1
+ ? true
+ : false
}
>
Approve
@@ -454,7 +548,13 @@ const Transaction = ({ id }) => {
className='btn-solid-red px-7'
onClick={openRejectTransaction}
disabled={
- transaction.data?.status === 'cancel' ? true : false || auth?.webRole === statusApprovalWeb ? true : false || statusApprovalWeb < 1 ? true : false
+ transaction.data?.status === 'cancel'
+ ? true
+ : false || auth?.webRole === statusApprovalWeb
+ ? true
+ : false || statusApprovalWeb < 1
+ ? true
+ : false
}
>
Reject
@@ -467,15 +567,16 @@ const Transaction = ({ id }) => {
Lanjutkan Transaksi
</button>
)}
- {transaction.data?.status != 'draft' && !auth?.feature.soApproval && (
- <button
- className='btn-light'
- disabled={transaction.data?.status != 'waiting'}
- onClick={openCancelTransaction}
- >
- Batalkan Transaksi
- </button>
- )}
+ {transaction.data?.status != 'draft' &&
+ !auth?.feature.soApproval && (
+ <button
+ className='btn-light'
+ disabled={transaction.data?.status != 'waiting'}
+ onClick={openCancelTransaction}
+ >
+ Batalkan Transaksi
+ </button>
+ )}
</div>
<div className='grid grid-cols-2 gap-x-6 mt-6'>
@@ -490,7 +591,7 @@ const Transaction = ({ id }) => {
<div>Ketentuan Pembayaran</div>
<div>: {transaction?.data?.paymentTerm}</div>
- {!auth?.feature?.soApproval && (
+ {!auth?.feature?.soApproval ? (
<>
<div>Purchase Order</div>
<div>
@@ -501,15 +602,24 @@ const Transaction = ({ id }) => {
onClick={
transaction.data?.purchaseOrderFile
? () => downloadPurchaseOrder(transaction.data)
- : openUploadPo
+ : transaction?.data.invoices.length < 1
+ ? openUploadPo
+ : ''
}
>
{transaction?.data?.purchaseOrderFile
? 'Download'
- : 'Upload'}
+ : transaction?.data.invoices.length < 1
+ ? 'Upload'
+ : '-'}
</button>
</div>
</>
+ ) : (
+ <>
+ <div>Site</div>
+ <div>: {transaction?.data?.sitePartner}</div>
+ </>
)}
</div>
</div>
@@ -525,157 +635,333 @@ const Transaction = ({ id }) => {
/>
</div>
</div>
-
- <div className='text-h-sm font-semibold mt-10 mb-4'>
- Pengiriman
- </div>
- <div className='grid grid-cols-3 gap-1'>
- {transaction?.data?.pickings?.map((airway) => (
- <button
- className='shadow rounded-md p-3 text-gray_r-12 font-normal flex justify-between items-center text-left'
- key={airway?.id}
- onClick={() => setIdAWB(airway?.id)}
- >
- <div>
- <span className='text-sm text-gray_r-11'>
- No Resi : {airway?.trackingNumber || '-'}{' '}
- </span>
- <p className='mt-1 font-medium'>{airway?.name}</p>
- </div>
- <div className='flex gap-x-2'>
- <div className='text-sm text-gray-600 badge-green leading-[1.5] mt-1'>
- {airway?.delivered ? 'Pesanan Tiba' : 'Sedang Dikirim'}
- </div>
- <ChevronRightIcon className='w-5 stroke-2' />
+ <div className='flex '>
+ <div className='w-1/2'>
+ <div className='text-h-sm font-semibold mt-10 mb-4'>
+ Pengiriman
+ </div>
+ {transaction?.data?.pickings.length == 0 && (
+ <div className='badge-red text-sm'>
+ Belum ada pengiriman
</div>
- </button>
- ))}
+ )}
+ <div className='grid grid-cols-1 gap-1 w-2/3'>
+ {transaction?.data?.pickings?.map((airway) => (
+ <button
+ className='shadow rounded-md p-3 text-gray_r-12 font-normal flex justify-between items-center text-left h-20'
+ key={airway?.id}
+ onClick={() => setIdAWB(airway?.id)}
+ >
+ <div>
+ <span className='text-sm text-gray_r-11'>
+ No Resi : {airway?.trackingNumber || '-'}{' '}
+ </span>
+ <p className='mt-1 font-medium'>{airway?.name}</p>
+ </div>
+ <div className='flex gap-x-2'>
+ <div className='text-sm text-gray-600 badge-green leading-[1.5] mt-1'>
+ {airway?.delivered
+ ? 'Pesanan Tiba'
+ : 'Sedang Dikirim'}
+ </div>
+ <ChevronRightIcon className='w-5 stroke-2' />
+ </div>
+ </button>
+ ))}
+ </div>
+ </div>
+ <div className='invoice w-1/2 '>
+ <div className='text-h-sm font-semibold mt-10 mb-4 '>
+ Invoice
+ </div>
+ {transaction.data?.invoices?.length === 0 && (
+ <div className='badge-red text-sm'>Belum ada invoice</div>
+ )}
+ <div className='grid grid-cols-1 gap-1 w-2/3 '>
+ {transaction.data?.invoices?.map((invoice, index) => (
+ <Link href={`/my/invoices/${invoice.id}`} key={index}>
+ <div className='shadow rounded-md p-4 text-gray_r-12 font-normal flex justify-between'>
+ <div>
+ <p className='mb-1'>{invoice?.name}</p>
+ <div className='flex items-center gap-x-1'>
+ {invoice.amountResidual > 0 ? (
+ <div className='badge-red'>Belum Lunas</div>
+ ) : (
+ <div className='badge-green'>Lunas</div>
+ )}
+ <p className='text-caption-2 text-gray_r-11'>
+ {currencyFormat(invoice.amountTotal)}
+ </p>
+ </div>
+ </div>
+ <ChevronRightIcon className='w-5 stroke-2' />
+ </div>
+ </Link>
+ ))}
+ </div>
+ </div>
</div>
- {transaction?.data?.pickings.length == 0 && (
- <div className='badge-red text-sm'>Belum ada pengiriman</div>
- )}
- <div className='text-h-sm font-semibold mt-10 mb-4'>
+ <div className='text-h-sm font-semibold mt-4 mb-4'>
Rincian Pembelian
</div>
- <table className='table-data'>
- <thead>
- <tr>
- <th>Nama Produk</th>
- <th>Diskon</th>
- <th>Jumlah</th>
- <th>Harga</th>
- <th>Subtotal</th>
- </tr>
- </thead>
- <tbody>
- {transaction?.data?.products?.map((product) => (
- <tr key={product.id}>
- <td className='flex'>
- <Link
- href={createSlug(
- '/shop/product/',
- product?.parent.name,
- product?.parent.id
- )}
- className='w-[20%] flex-shrink-0'
- >
- <Image
- src={product?.parent?.image}
- alt={product?.name}
- className='object-contain object-center border border-gray_r-6 h-32 w-full rounded-md'
- />
- </Link>
- <div className='px-2 text-left'>
+ {transaction?.data?.products?.length > 0 ? (
+ <table className='table-data'>
+ <thead>
+ <tr>
+ <th>Nama Produk</th>
+ {/* <th>Diskon</th> */}
+ <th>Jumlah</th>
+ <th>Harga</th>
+ <th>Subtotal</th>
+ <th></th>
+ </tr>
+ </thead>
+ <tbody>
+ {transaction?.data?.products?.map((product) => (
+ <tr key={product.id}>
+ <td className='flex'>
<Link
href={createSlug(
'/shop/product/',
product?.parent.name,
product?.parent.id
)}
- className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
+ className='w-[20%] flex-shrink-0'
>
- {product?.parent?.name}
+ <div className='relative'>
+ <Image
+ src={product?.parent?.image}
+ alt={product?.name}
+ className='object-contain object-center border border-gray_r-6 h-32 w-full rounded-md'
+ />
+ <div className='absolute top-0 right-4 flex mt-3'>
+ <div className='gambarB '>
+ {product.isSni && (
+ <ImageNext
+ src='/images/sni-logo.png'
+ alt='SNI Logo'
+ className='w-2 h-4 object-contain object-top sm:h-4'
+ width={50}
+ height={50}
+ />
+ )}
+ </div>
+ <div className='gambarC '>
+ {product.isTkdn && (
+ <ImageNext
+ src='/images/TKDN.png'
+ alt='TKDN'
+ className='w-5 h-4 object-contain object-top ml-1 sm:h-4'
+ width={50}
+ height={50}
+ />
+ )}
+ </div>
+ </div>
+ </div>
</Link>
- <div className='text-gray_r-11 mt-2'>
- {product?.code}{' '}
- {product?.attributes.length > 0
- ? `| ${product?.attributes.join(', ')}`
- : ''}
+ <div className='px-2 text-left'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
+ >
+ {product?.parent?.name}
+ </Link>
+ <div className='text-gray_r-11 mt-2'>
+ {product?.code}{' '}
+ {product?.attributes.length > 0
+ ? `| ${product?.attributes.join(', ')}`
+ : ''}
+ </div>
</div>
- </div>
- </td>
- <td>
- {product.price.discountPercentage > 0
- ? `${product.price.discountPercentage}%`
- : ''}
- </td>
- <td>{product.quantity}</td>
- <td>
- {product.price.discountPercentage > 0 && (
- <div className='line-through mb-1 text-caption-1 text-gray_r-12/70'>
- {currencyFormat(product.price.price)}
+ </td>
+ {/* <td>
+ {product.price.discountPercentage > 0
+ ? `${product.price.discountPercentage}%`
+ : ''}
+ </td> */}
+ <td>{product.quantity}</td>
+ <td>
+ {/* {product.price.discountPercentage > 0 && (
+ <div className='line-through mb-1 text-caption-1 text-gray_r-12/70'>
+ {currencyFormat(product.price.price)}
+ </div>
+ )} */}
+ <div>
+ {currencyFormat(product.price.priceDiscount)}
</div>
- )}
- <div>{currencyFormat(product.price.priceDiscount)}</div>
- </td>
- <td>{currencyFormat(product.price.subtotal)}</td>
- </tr>
- ))}
- </tbody>
- </table>
-
- <div className='flex justify-end mt-4'>
- <div className='w-1/4 grid grid-cols-2 gap-y-3 text-gray_r-12/80'>
- <div className='text-right'>Subtotal</div>
- <div className='text-right font-medium'>
- {currencyFormat(transaction.data?.amountUntaxed)}
- </div>
+ </td>
+ <td>{currencyFormat(product.price.subtotal)}</td>
+ {/* {auth?.feature.soApproval && (auth.webRole == 2 || auth.webRole == 3) && (transaction.data.isReaject == false) && ( */}
+ {auth?.feature.soApproval &&
+ (auth.webRole == 2 || auth.webRole == 3) &&
+ router.asPath.includes('/my/quotations/') &&
+ transaction.data?.status == 'draft' && (
+ <td>
+ <button
+ className='bg-red-500 text-white py-1 px-3 rounded'
+ onClick={() => openModal(product)}
+ >
+ Reject
+ </button>
+ </td>
+ )}
+ </tr>
+ ))}
+ </tbody>
+ </table>
+ ) : (
+ <div className='badge-red text-sm'>
+ Semua produk telah di reject
+ </div>
+ )}
- <div className='text-right'>PPN 11%</div>
- <div className='text-right font-medium'>
- {currencyFormat(transaction.data?.amountTax)}
+ {isModalOpen && (
+ <div className='fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center'>
+ <div
+ className='bg-white p-4 rounded w-96
+ ease-in-out opacity-100
+ transform transition-transform duration-300 scale-100'
+ >
+ <h2 className='text-lg mb-2'>Berikan Alasan</h2>
+ <textarea
+ value={reason}
+ onChange={(e) => setReason(e.target.value)}
+ className='w-full p-2 border rounded'
+ rows='4'
+ ></textarea>
+ <div className='mt-4 flex justify-end'>
+ <button
+ className='bg-gray-300 text-black py-1 px-3 rounded mr-2'
+ onClick={closeModal}
+ >
+ Batal
+ </button>
+ <button
+ className='bg-red-500 text-white py-1 px-3 rounded'
+ onClick={handleRejectProduct}
+ >
+ Reject
+ </button>
+ </div>
</div>
+ </div>
+ )}
- <div className='text-right whitespace-nowrap'>
- Biaya Pengiriman
- </div>
- <div className='text-right font-medium'>
- {currencyFormat(transaction.data?.deliveryAmount)}
- </div>
+ {transaction?.data?.products?.length > 0 && (
+ <div className='flex justify-end mt-4'>
+ <div className='w-1/4 grid grid-cols-2 gap-y-3 text-gray_r-12/80'>
+ <div className='text-right'>Subtotal</div>
+ <div className='text-right font-medium'>
+ {currencyFormat(transaction.data?.amountUntaxed)}
+ </div>
- <div className='text-right'>Grand Total</div>
- <div className='text-right font-medium text-gray_r-12'>
- {currencyFormat(transaction.data?.amountTotal)}
+ <div className='text-right'>PPN 11%</div>
+ <div className='text-right font-medium'>
+ {currencyFormat(transaction.data?.amountTax)}
+ </div>
+
+ <div className='text-right whitespace-nowrap'>
+ Biaya Pengiriman
+ </div>
+ <div className='text-right font-medium'>
+ {currencyFormat(transaction.data?.deliveryAmount)}
+ </div>
+
+ <div className='text-right'>Grand Total</div>
+ <div className='text-right font-medium text-gray_r-12'>
+ {currencyFormat(transaction.data?.amountTotal)}
+ </div>
</div>
</div>
- </div>
+ )}
- <div className='text-h-sm font-semibold mt-10 mb-4'>Invoice</div>
- <div className='grid grid-cols-3 gap-4'>
- {transaction.data?.invoices?.map((invoice, index) => (
- <Link href={`/my/invoices/${invoice.id}`} key={index}>
- <div className='shadow rounded-md p-4 text-gray_r-12 font-normal flex justify-between'>
- <div>
- <p className='mb-2'>{invoice?.name}</p>
- <div className='flex items-center gap-x-1'>
- {invoice.amountResidual > 0 ? (
- <div className='badge-red'>Belum Lunas</div>
- ) : (
- <div className='badge-green'>Lunas</div>
- )}
- <p className='text-caption-2 text-gray_r-11'>
- {currencyFormat(invoice.amountTotal)}
- </p>
- </div>
- </div>
- <ChevronRightIcon className='w-5 stroke-2' />
- </div>
- </Link>
- ))}
- </div>
- {transaction.data?.invoices?.length === 0 && (
- <div className='badge-red text-sm'>Belum ada invoice</div>
+ {transaction?.data?.productsRejectLine.length > 0 && (
+ <div className='text-h-sm font-semibold mt-10 mb-4'>
+ Rincian Produk Reject
+ </div>
+ )}
+ {transaction?.data?.productsRejectLine.length > 0 && (
+ <table className='table-data'>
+ <thead>
+ <tr>
+ <th>Nama Produk</th>
+ {/* <th>Diskon</th> */}
+ <th>Jumlah</th>
+ <th>Harga</th>
+ <th>Subtotal</th>
+ </tr>
+ </thead>
+ <tbody>
+ {transaction?.data?.productsRejectLine?.map((product) => (
+ <tr key={product.id}>
+ <td className='flex'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='w-[20%] flex-shrink-0'
+ >
+ <Image
+ src={product?.parent?.image}
+ alt={product?.name}
+ className='object-contain object-center border border-gray_r-6 h-32 w-full rounded-md'
+ />
+ </Link>
+ <div className='px-2 text-left'>
+ <Link
+ href={createSlug(
+ '/shop/product/',
+ product?.parent.name,
+ product?.parent.id
+ )}
+ className='line-clamp-2 leading-6 !text-gray_r-12 font-normal'
+ >
+ {product?.parent?.name}
+ </Link>
+ <div className='text-gray_r-11 mt-2'>
+ {product?.code}{' '}
+ {product?.attributes.length > 0
+ ? `| ${product?.attributes.join(', ')}`
+ : ''}
+ </div>
+ </div>
+ </td>
+ {/* <td>
+ {product.price.discountPercentage > 0
+ ? `${product.price.discountPercentage}%`
+ : ''}
+ </td> */}
+ <td>{product.quantity}</td>
+ <td>
+ {/* {product.price.discountPercentage > 0 && (
+ <div className='line-through mb-1 text-caption-1 text-gray_r-12/70'>
+ {currencyFormat(product.price.price)}
+ </div>
+ )} */}
+ <div>
+ {currencyFormat(product.price.priceDiscount)}
+ </div>
+ </td>
+ <td className='flex justify-center'>
+ <NextImage
+ src={rejectImage}
+ alt='Reject'
+ width={90}
+ height={30}
+ />
+ </td>
+ </tr>
+ ))}
+ </tbody>
+ </table>
)}
</div>
</div>
diff --git a/src/lib/transaction/components/Transactions.jsx b/src/lib/transaction/components/Transactions.jsx
index be63effd..92bdd276 100644
--- a/src/lib/transaction/components/Transactions.jsx
+++ b/src/lib/transaction/components/Transactions.jsx
@@ -1,63 +1,163 @@
-import { useRouter } from 'next/router'
-import { useState } from 'react'
-import { toast } from 'react-hot-toast'
-import { EllipsisVerticalIcon, MagnifyingGlassIcon } from '@heroicons/react/24/outline'
+import { useRouter } from 'next/router';
+import { useEffect, useState } from 'react';
+import { toast } from 'react-hot-toast';
+import {
+ EllipsisVerticalIcon,
+ MagnifyingGlassIcon,
+} from '@heroicons/react/24/outline';
+import useAuth from '@/core/hooks/useAuth';
-import { downloadPurchaseOrder, downloadQuotation } from '../utils/transactions'
-import useTransactions from '../hooks/useTransactions'
-import currencyFormat from '@/core/utils/currencyFormat'
-import cancelTransactionApi from '../api/cancelTransactionApi'
-import TransactionStatusBadge from './TransactionStatusBadge'
-import Spinner from '@/core/components/elements/Spinner/Spinner'
-import Link from '@/core/components/elements/Link/Link'
-import BottomPopup from '@/core/components/elements/Popup/BottomPopup'
-import Pagination from '@/core/components/elements/Pagination/Pagination'
-import { toQuery } from 'lodash-contrib'
-import _ from 'lodash'
-import Alert from '@/core/components/elements/Alert/Alert'
-import MobileView from '@/core/components/views/MobileView'
-import DesktopView from '@/core/components/views/DesktopView'
-import Menu from '@/lib/auth/components/Menu'
+import {
+ downloadPurchaseOrder,
+ downloadQuotation,
+} from '../utils/transactions';
+import useTransactions from '../hooks/useTransactions';
+import currencyFormat from '@/core/utils/currencyFormat';
+import cancelTransactionApi from '../api/cancelTransactionApi';
+import TransactionStatusBadge from './TransactionStatusBadge';
+import Spinner from '@/core/components/elements/Spinner/Spinner';
+import Link from '@/core/components/elements/Link/Link';
+import BottomPopup from '@/core/components/elements/Popup/BottomPopup';
+import Pagination from '@/core/components/elements/Pagination/Pagination';
+import { toQuery } from 'lodash-contrib';
+import _ from 'lodash';
+import Alert from '@/core/components/elements/Alert/Alert';
+import MobileView from '@/core/components/views/MobileView';
+import DesktopView from '@/core/components/views/DesktopView';
+import Menu from '@/lib/auth/components/Menu';
+import * as XLSX from 'xlsx';
+import getSite from '../api/listSiteApi';
+import transactionsApi from '../api/transactionsApi';
const Transactions = ({ context = '' }) => {
- const router = useRouter()
- const { q = '', page = 1 } = router.query
+ const auth = useAuth();
+ const router = useRouter();
+ const { q = '', page = 1, site = null } = router.query;
- const limit = 15
+ const limit = 15;
+
+ const [inputQuery, setInputQuery] = useState(q);
+ const [toOthers, setToOthers] = useState(null);
+ const [toCancel, setToCancel] = useState(null);
+ const [listSites, setListSites] = useState([]);
+
+ const [siteFilter, setSiteFilter] = useState(site);
const query = {
name: q,
offset: (page - 1) * limit,
context,
- limit
- }
- const { transactions } = useTransactions({ query })
+ limit,
+ site:
+ siteFilter || (auth?.webRole === null && auth?.site ? auth.site : null),
+ };
+
+ const { transactions } = useTransactions({ query });
- const [inputQuery, setInputQuery] = useState(q)
- const [toOthers, setToOthers] = useState(null)
- const [toCancel, setToCancel] = useState(null)
+ const fetchSite = async () => {
+ const site = await getSite();
+ setListSites(site.sites);
+ };
const submitCancelTransaction = async () => {
const isCancelled = await cancelTransactionApi({
- transaction: toCancel
- })
+ transaction: toCancel,
+ });
if (isCancelled) {
- toast.success('Berhasil batalkan transaksi')
- transactions.refetch()
+ toast.success('Berhasil batalkan transaksi');
+ transactions.refetch();
}
- setToCancel(null)
- }
+ setToCancel(null);
+ };
- const pageCount = Math.ceil(transactions?.data?.saleOrderTotal / limit)
- let pageQuery = _.omit(query, ['limit', 'offset', 'context'])
- pageQuery = _.pickBy(pageQuery, _.identity)
- pageQuery = toQuery(pageQuery)
+ const pageCount = Math.ceil(transactions?.data?.saleOrderTotal / limit);
+ let pageQuery = _.omit(query, ['limit', 'offset', 'context']);
+ pageQuery = _.pickBy(
+ pageQuery,
+ (value, key) => value !== '' && !(key === 'page' && value === '1')
+ );
+ pageQuery = toQuery(pageQuery);
const handleSubmit = (e) => {
- e.preventDefault()
- router.push(`${router.pathname}?q=${inputQuery}`)
- }
+ e.preventDefault();
+ const queryParams = {};
+ if (inputQuery) queryParams.q = inputQuery;
+ if (siteFilter) queryParams.site = siteFilter;
+ router.push({
+ pathname: router.pathname,
+ query: queryParams,
+ });
+ };
+
+ const handleSiteFilterChange = (e) => {
+ setSiteFilter(e.target.value);
+ const queryParams = {};
+ if (inputQuery) queryParams.q = inputQuery;
+ if (e.target.value) queryParams.site = e.target.value;
+ router.push({
+ pathname: router.pathname,
+ query: queryParams,
+ });
+ };
+
+ const exportToExcel = (data, siteFilter) => {
+ const fieldsToExport = [
+ 'No. Transaksi',
+ 'No. PO',
+ 'Tanggal',
+ 'Created By',
+ 'Salesperson',
+ 'Total',
+ 'Status',
+ ];
+ const rowsToExport = [];
+
+ data.forEach((saleOrder) => {
+ const row = {
+ 'No. Transaksi': saleOrder.name,
+ 'No. PO': saleOrder.purchaseOrderName || '-',
+ Tanggal: saleOrder.dateOrder || '-',
+ 'Created By': saleOrder.address.customer?.name || '-',
+ Salesperson: saleOrder.sales,
+ Total: currencyFormat(saleOrder.amountTotal),
+ Status: saleOrder.status,
+ };
+ if (siteFilter) {
+ row['Site'] = siteFilter;
+ }
+ rowsToExport.push(row);
+ });
+ const worksheet = XLSX.utils.json_to_sheet(rowsToExport, {
+ header: fieldsToExport,
+ });
+
+ const workbook = XLSX.utils.book_new();
+ XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
+ XLSX.writeFile(workbook, 'transactions.xlsx');
+ };
+
+ const getAllData = async () => {
+ const query = {
+ name: q,
+ context,
+ site:
+ siteFilter || (auth?.webRole === null && auth?.site ? auth.site : null),
+ };
+ const queryString = toQuery(query)
+ const data = await transactionsApi({ query: queryString });
+ return data;
+ };
+
+ const handleExportExcel = async () => {
+ const dataToExport = await getAllData();
+
+ exportToExcel(dataToExport?.saleOrders, siteFilter);
+ };
+
+ useEffect(() => {
+ fetchSite();
+ }, []);
return (
<>
<MobileView>
@@ -81,17 +181,23 @@ const Transactions = ({ context = '' }) => {
</div>
)}
- {!transactions.isLoading && transactions.data?.saleOrders?.length === 0 && (
- <Alert type='info' className='text-center'>
- Tidak ada transaksi
- </Alert>
- )}
+ {!transactions.isLoading &&
+ transactions.data?.saleOrders?.length === 0 && (
+ <Alert type='info' className='text-center'>
+ Tidak ada transaksi
+ </Alert>
+ )}
{transactions.data?.saleOrders?.map((saleOrder, index) => (
- <div className='p-4 shadow border border-gray_r-3 rounded-md' key={index}>
+ <div
+ className='p-4 shadow border border-gray_r-3 rounded-md'
+ key={index}
+ >
<div className='grid grid-cols-2'>
<Link href={`${router.pathname}/${saleOrder.id}`}>
- <span className='text-caption-2 text-gray_r-11'>No. Transaksi</span>
+ <span className='text-caption-2 text-gray_r-11'>
+ No. Transaksi
+ </span>
<h2 className='text-danger-500 mt-1'>{saleOrder.name}</h2>
</Link>
<div className='flex gap-x-1 justify-end'>
@@ -105,13 +211,17 @@ const Transactions = ({ context = '' }) => {
<Link href={`${router.pathname}/${saleOrder.id}`}>
<div className='grid grid-cols-2 mt-3'>
<div>
- <span className='text-caption-2 text-gray_r-11'>No. Purchase Order</span>
+ <span className='text-caption-2 text-gray_r-11'>
+ No. Purchase Order
+ </span>
<p className='mt-1 font-medium text-gray_r-12'>
{saleOrder.purchaseOrderName || '-'}
</p>
</div>
<div className='text-right'>
- <span className='text-caption-2 text-gray_r-11'>Total Invoice</span>
+ <span className='text-caption-2 text-gray_r-11'>
+ Total Invoice
+ </span>
<p className='mt-1 font-medium text-gray_r-12'>
{saleOrder.invoiceCount} Invoice
</p>
@@ -120,10 +230,14 @@ const Transactions = ({ context = '' }) => {
<div className='grid grid-cols-2 mt-3'>
<div>
<span className='text-caption-2 text-gray_r-11'>Sales</span>
- <p className='mt-1 font-medium text-gray_r-12'>{saleOrder.sales}</p>
+ <p className='mt-1 font-medium text-gray_r-12'>
+ {saleOrder.sales}
+ </p>
</div>
<div className='text-right'>
- <span className='text-caption-2 text-gray_r-11'>Total Harga</span>
+ <span className='text-caption-2 text-gray_r-11'>
+ Total Harga
+ </span>
<p className='mt-1 font-medium text-gray_r-12'>
{currencyFormat(saleOrder.amountTotal)}
</p>
@@ -140,14 +254,18 @@ const Transactions = ({ context = '' }) => {
className='mt-2 mb-2'
/>
- <BottomPopup title='Lainnya' active={toOthers} close={() => setToOthers(null)}>
+ <BottomPopup
+ title='Lainnya'
+ active={toOthers}
+ close={() => setToOthers(null)}
+ >
<div className='flex flex-col gap-y-4 mt-2'>
<button
className='text-left disabled:opacity-60'
disabled={!toOthers?.purchaseOrderFile}
onClick={() => {
- downloadPurchaseOrder(toOthers)
- setToOthers(null)
+ downloadPurchaseOrder(toOthers);
+ setToOthers(null);
}}
>
Download PO
@@ -156,8 +274,8 @@ const Transactions = ({ context = '' }) => {
className='text-left disabled:opacity-60'
disabled={toOthers?.status != 'draft'}
onClick={() => {
- downloadQuotation(toOthers)
- setToOthers(null)
+ downloadQuotation(toOthers);
+ setToOthers(null);
}}
>
Download Quotation
@@ -166,8 +284,8 @@ const Transactions = ({ context = '' }) => {
className='text-left disabled:opacity-60'
disabled={toOthers?.status != 'waiting'}
onClick={() => {
- setToCancel(toOthers)
- setToOthers(null)
+ setToCancel(toOthers);
+ setToOthers(null);
}}
>
Batalkan Transaksi
@@ -175,7 +293,11 @@ const Transactions = ({ context = '' }) => {
</div>
</BottomPopup>
- <BottomPopup active={toCancel} close={() => setToCancel(null)} title='Batalkan Transaksi'>
+ <BottomPopup
+ active={toCancel}
+ close={() => setToCancel(null)}
+ title='Batalkan Transaksi'
+ >
<div className='leading-7 text-gray_r-12/80'>
Apakah anda yakin membatalkan transaksi{' '}
<span className='underline'>{toCancel?.name}</span>?
@@ -188,7 +310,11 @@ const Transactions = ({ context = '' }) => {
>
Ya, Batalkan
</button>
- <button className='btn-light flex-1' type='button' onClick={() => setToCancel(null)}>
+ <button
+ className='btn-light flex-1'
+ type='button'
+ onClick={() => setToCancel(null)}
+ >
Batal
</button>
</div>
@@ -205,21 +331,50 @@ const Transactions = ({ context = '' }) => {
<div className='flex mb-6 items-center justify-between'>
<h1 className='text-title-sm font-semibold'>
Daftar Transaksi{' '}
- {transactions?.data?.saleOrders ? `(${transactions?.data?.saleOrders.length})` : ''}
+ {transactions?.data?.saleOrders
+ ? `(${transactions?.data?.saleOrders.length})`
+ : ''}
</h1>
- <form className='flex gap-x-2' onSubmit={handleSubmit}>
- <input
- type='text'
- className='form-input'
- placeholder='Cari Transaksi...'
- value={inputQuery}
- onChange={(e) => setInputQuery(e.target.value)}
- />
- <button className='btn-light bg-transparent px-3' type='submit'>
- <MagnifyingGlassIcon className='w-6' />
- </button>
- </form>
+ <div className='grid grid-cols-2 gap-2'>
+ {listSites?.length > 0 ? (
+ <select
+ value={siteFilter}
+ onChange={handleSiteFilterChange}
+ className='form-input'
+ >
+ <option value=''>Pilih Site</option>
+ {listSites.map((site) => (
+ <option value={site} key={site}>
+ {site}
+ </option>
+ ))}
+ </select>
+ ) : (<div></div>)}
+
+ <form className='flex gap-x-1' onSubmit={handleSubmit}>
+ <input
+ type='text'
+ className='form-input'
+ placeholder='Cari Transaksi...'
+ value={inputQuery}
+ onChange={(e) => setInputQuery(e.target.value)}
+ />
+ <button
+ className='btn-light bg-transparent px-3'
+ type='submit'
+ >
+ <MagnifyingGlassIcon className='w-6' />
+ </button>
+ </form>
+ </div>
</div>
+ <button
+ onClick={handleExportExcel}
+ type='button'
+ className='btn-solid-red px-3 py-2 mr-auto mb-2'
+ >
+ <span>Download</span>
+ </button>
<table className='table-data'>
<thead>
<tr>
@@ -227,6 +382,9 @@ const Transactions = ({ context = '' }) => {
<th>No. PO</th>
<th>Tanggal</th>
<th>Created By</th>
+ {auth?.feature?.soApproval && (
+ <th>Site</th>
+ )}
<th className='!text-left'>Salesperson</th>
<th className='!text-left'>Total</th>
<th>Status</th>
@@ -252,13 +410,23 @@ const Transactions = ({ context = '' }) => {
{transactions.data?.saleOrders?.map((saleOrder) => (
<tr key={saleOrder.id}>
<td>
- <Link className='whitespace-nowrap' href={`${router.pathname}/${saleOrder.id}`}>{saleOrder.name}</Link>
+ <Link
+ className='whitespace-nowrap'
+ href={`${router.pathname}/${saleOrder.id}`}
+ >
+ {saleOrder.name}
+ </Link>
</td>
<td>{saleOrder.purchaseOrderName || '-'}</td>
<td>{saleOrder.dateOrder || '-'}</td>
<td>{saleOrder.address.customer?.name || '-'}</td>
+ {auth?.feature?.soApproval && (
+ <td>{saleOrder.sitePartner || '-'}</td>
+ )}
<td className='!text-left'>{saleOrder.sales}</td>
- <td className='!text-left'>{currencyFormat(saleOrder.amountTotal)}</td>
+ <td className='!text-left'>
+ {currencyFormat(saleOrder.amountTotal)}
+ </td>
<td>
<div className='flex justify-center'>
<TransactionStatusBadge status={saleOrder.status} />
@@ -272,14 +440,14 @@ const Transactions = ({ context = '' }) => {
<Pagination
pageCount={pageCount}
currentPage={parseInt(page)}
- url={router.pathname + pageQuery}
+ url={router.pathname + (pageQuery ? `?${pageQuery}` : '')}
className='mt-2 mb-2'
/>
</div>
</div>
</DesktopView>
</>
- )
-}
+ );
+};
-export default Transactions
+export default Transactions;
diff --git a/src/lib/variant/components/VariantCard.jsx b/src/lib/variant/components/VariantCard.jsx
index 9f1b5733..68cdf54f 100644
--- a/src/lib/variant/components/VariantCard.jsx
+++ b/src/lib/variant/components/VariantCard.jsx
@@ -1,15 +1,28 @@
import { useRouter } from 'next/router'
import { toast } from 'react-hot-toast'
-
+import useAuth from '@/core/hooks/useAuth';
import Image from '@/core/components/elements/Image/Image'
import Link from '@/core/components/elements/Link/Link'
import { createSlug } from '@/core/utils/slug'
import currencyFormat from '@/core/utils/currencyFormat'
import { updateItemCart } from '@/core/utils/cart'
import whatsappUrl from '@/core/utils/whatsappUrl'
+import {useState } from 'react';
+import rejectProductApi from '../../../lib/transaction/api/rejectProductApi'
+// import {useTransaction} from 'C:\Users\Indoteknik\next-indoteknik\src\lib\transaction\hooks\useTransaction.js'
+import useTransaction from '../../../lib/transaction/hooks/useTransaction';
+import ImageNext from 'next/image';
const VariantCard = ({ product, openOnClick = true, buyMore = false }) => {
const router = useRouter()
+ const id = router.query.id
+ const auth = useAuth();
+ const { transaction } = useTransaction({id});
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [selectedProduct, setSelectedProduct] = useState(null);
+ const [reason, setReason] = useState('');
+
+
const addItemToCart = () => {
toast.success('Berhasil menambahkan ke keranjang', { duration: 1500 })
@@ -19,19 +32,78 @@ const VariantCard = ({ product, openOnClick = true, buyMore = false }) => {
})
return
}
-
+
const checkoutItem = () => {
router.push(`/shop/checkout?product_id=${product.id}&qty=${product.quantity}`)
}
-
+
+ const openModal = (product) => {
+ setSelectedProduct(product);
+ setIsModalOpen(true);
+ };
+
+ const closeModal = () => {
+ setIsModalOpen(false);
+ setSelectedProduct(null);
+ setReason('');
+ };
+
+ const handleRejectProduct = async () => {
+ try {
+ if (!reason.trim()) {
+ toast.error('Masukkan alasan terlebih dahulu');
+ return;
+ }else{
+ let idSo = transaction?.data.id
+ let idProduct = selectedProduct.id
+ await rejectProductApi({ idSo, idProduct, reason});
+ closeModal();
+ toast.success("Produk berhasil di reject")
+ setTimeout(() => {
+ window.location.reload();
+ }, 1500);
+ }
+ } catch (error) {
+ toast.error('Gagal reject produk. Silakan coba lagi.');
+ }
+ };
+
const Card = () => (
<div className='flex gap-x-3'>
<div className='w-4/12 flex items-center gap-x-2'>
- <Image
+
+ <div className="relative">
+ <Image
src={product.parent.image}
alt={product.parent.name}
className='object-contain object-center border border-gray_r-6 h-32 w-full rounded-md'
/>
+ <div className="absolute top-0 right-4 flex mt-3">
+ <div className="gambarB ">
+ {product.isSni && (
+ <ImageNext
+ src="/images/sni-logo.png"
+ alt="SNI Logo"
+ className="w-2 h-5 object-contain object-top sm:h-6"
+ width={50}
+ height={50}
+ />
+ )}
+ </div>
+ <div className="gambarC ">
+ {product.isTkdn && (
+ <ImageNext
+ src="/images/TKDN.png"
+ alt="TKDN"
+ className="w-5 h-6 object-contain object-top ml-1 mr-1 sm:h-6"
+ width={50}
+ height={50}
+ />
+ )}
+ </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>
@@ -82,7 +154,7 @@ const VariantCard = ({ product, openOnClick = true, buyMore = false }) => {
<Link href={createSlug('/shop/product/', product.parent.name, product.parent.id)}>
<Card />
</Link>
- {buyMore && (
+ {buyMore && (!transaction?.data?.productsRejectLine.some(pr => pr.id === product.id)) && (
<div className='flex justify-end gap-x-2 mb-2'>
<button
type='button'
@@ -91,14 +163,48 @@ const VariantCard = ({ product, openOnClick = true, buyMore = false }) => {
>
Tambah Keranjang
</button>
- <button
- type='button'
- onClick={checkoutItem}
- className='btn-solid-red py-2 px-3 text-caption-1'
- >
- Beli Lagi
- </button>
+ {/* {auth?.feature.soApproval && (auth.webRole == 2 || auth.webRole == 3) && (transaction.data.isReaject == false) && ( */}
+ {auth?.feature.soApproval && (auth.webRole == 2 || auth.webRole == 3) && (router.asPath.includes("/my/quotations/")) && (
+ !transaction?.data?.productsRejectLine.some(pr => pr.id === product.id) && (
+ <button
+ className="bg-red-500 text-white py-1 px-3 rounded"
+ onClick={() => openModal(product)}
+ >
+ Reject
+ </button>
+ )
+ )}
+ {isModalOpen && (
+ <div className='fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center'>
+ <div className='bg-white p-4 rounded w-96
+ ease-in-out opacity-100
+ transform transition-transform duration-300 scale-100'>
+ <h2 className='text-lg mb-2'>Berikan Alasan</h2>
+ <textarea
+ value={reason}
+ onChange={(e) => setReason(e.target.value)}
+ className='w-full p-2 border rounded'
+ rows='4'
+ ></textarea>
+ <div className='mt-4 flex justify-end'>
+ <button
+ className='bg-gray-300 text-black py-1 px-3 rounded mr-2'
+ onClick={closeModal}
+ >
+ Batal
+ </button>
+ <button
+ className='bg-red-500 text-white py-1 px-3 rounded'
+ onClick={handleRejectProduct}
+ >
+ Reject
+ </button>
+ </div>
+ </div>
+ </div>
+ )}
</div>
+
)}
</>
)