diff options
| author | it-fixcomart <it@fixcomart.co.id> | 2024-06-21 11:01:35 +0700 |
|---|---|---|
| committer | it-fixcomart <it@fixcomart.co.id> | 2024-06-21 11:01:35 +0700 |
| commit | 220190db66bcc1c6db78180c593f21e9cf8f363c (patch) | |
| tree | 1517faa9636a6b3b2cc8d468a57b1fe476c229d7 /src/lib/quotation/components | |
| parent | 208b234320b6c42491a4e87a1c3db3abab9c1715 (diff) | |
| parent | 1cf754b4d8da1aa28700ffc3dad67081f6daf9a5 (diff) | |
Merge branch 'promotion-program' into feature/all-promotion
Diffstat (limited to 'src/lib/quotation/components')
| -rw-r--r-- | src/lib/quotation/components/Quotation.jsx | 494 |
1 files changed, 412 insertions, 82 deletions
diff --git a/src/lib/quotation/components/Quotation.jsx b/src/lib/quotation/components/Quotation.jsx index 8c379ead..09d55e92 100644 --- a/src/lib/quotation/components/Quotation.jsx +++ b/src/lib/quotation/components/Quotation.jsx @@ -1,102 +1,283 @@ -import Alert from '@/core/components/elements/Alert/Alert' -import Divider from '@/core/components/elements/Divider/Divider' -import Link from '@/core/components/elements/Link/Link' -import useAuth from '@/core/hooks/useAuth' -import CartApi from '@/lib/cart/api/CartApi' -import { ExclamationCircleIcon } from '@heroicons/react/24/outline' -import { useEffect, useState } from 'react' -import _ from 'lodash' -import { deleteItemCart, getCart, getItemCart } from '@/core/utils/cart' -import currencyFormat from '@/core/utils/currencyFormat' -import { toast } from 'react-hot-toast' +import Alert from '@/core/components/elements/Alert/Alert'; +import Divider from '@/core/components/elements/Divider/Divider'; +import Link from '@/core/components/elements/Link/Link'; +import useAuth from '@/core/hooks/useAuth'; +import CartApi from '@/lib/cart/api/CartApi'; +import { ExclamationCircleIcon } from '@heroicons/react/24/outline'; +import { useEffect, useRef, useState } from 'react'; +import _ from 'lodash'; +import { deleteItemCart, getCart, getItemCart } from '@/core/utils/cart'; +import currencyFormat from '@/core/utils/currencyFormat'; +import { toast } from 'react-hot-toast'; // import checkoutApi from '@/lib/checkout/api/checkoutApi' -import { useRouter } from 'next/router' -import VariantGroupCard from '@/lib/variant/components/VariantGroupCard' -import MobileView from '@/core/components/views/MobileView' -import DesktopView from '@/core/components/views/DesktopView' -import Image from '@/core/components/elements/Image/Image' -import { useQuery } from 'react-query' -import CardProdcuctsList from '@/core/components/elements/Product/cartProductsList' +import { useRouter } from 'next/router'; +import VariantGroupCard from '@/lib/variant/components/VariantGroupCard'; +import MobileView from '@/core/components/views/MobileView'; +import DesktopView from '@/core/components/views/DesktopView'; +import Image from '@/core/components/elements/Image/Image'; +import { useQuery } from 'react-query'; +import CardProdcuctsList from '@/core/components/elements/Product/cartProductsList'; +import { Skeleton } from '@chakra-ui/react'; +import { + PickupAddress, + SectionAddress, + SectionExpedisi, + SectionListService, + SectionValidation, + calculateEstimatedArrival, + splitDuration, +} from '../../checkout/components/CheckoutSection'; +import addressesApi from '@/lib/address/api/addressesApi'; +import { getItemAddress } from '@/core/utils/address'; +import ExpedisiList from '../../checkout/api/ExpedisiList'; +import axios from 'axios'; -const { checkoutApi } = require('@/lib/checkout/api/checkoutApi') -const { getProductsCheckout } = require('@/lib/checkout/api/checkoutApi') +const { checkoutApi } = require('@/lib/checkout/api/checkoutApi'); +const { getProductsCheckout } = require('@/lib/checkout/api/checkoutApi'); const Quotation = () => { - const router = useRouter() - const auth = useAuth() + const router = useRouter(); + const auth = useAuth(); - const { data: cartCheckout } = useQuery('cartCheckout', () => getProductsCheckout()) + const { data: cartCheckout } = useQuery('cartCheckout', () => + getProductsCheckout() + ); - const [products, setProducts] = useState(null) - const [totalAmount, setTotalAmount] = useState(0) - const [totalDiscountAmount, setTotalDiscountAmount] = useState(0) + const SELF_PICKUP_ID = 32; + + const [products, setProducts] = useState(null); + const [totalAmount, setTotalAmount] = useState(0); + const [totalDiscountAmount, setTotalDiscountAmount] = useState(0); + + //start set up address and carrier + const [selectedCarrierId, setselectedCarrierId] = useState(0); + const [listExpedisi, setExpedisi] = useState([]); + const [selectedExpedisi, setSelectedExpedisi] = useState(0); + const [checkWeigth, setCheckWeight] = useState(false); + const [checkoutValidation, setCheckoutValidation] = useState(false); + const [loadingRajaOngkir, setLoadingRajaOngkir] = useState(false); + + const [listserviceExpedisi, setListServiceExpedisi] = useState([]); + const [selectedServiceType, setSelectedServiceType] = useState(null); + + const [selectedCarrier, setselectedCarrier] = useState(0); + const [totalWeight, setTotalWeight] = useState(0); + + const [biayaKirim, setBiayaKirim] = useState(0); + const [selectedExpedisiService, setselectedExpedisiService] = useState(null); + const [etd, setEtd] = useState(null); + const [etdFix, setEtdFix] = useState(null); + + const expedisiValidation = useRef(null); + + const [selectedAddress, setSelectedAddress] = useState({ + shipping: null, + invoicing: null, + }); + + const [addresses, setAddresses] = useState(null); + + useEffect(() => { + if (!auth) return; + + const getAddresses = async () => { + const dataAddresses = await addressesApi(); + setAddresses(dataAddresses); + }; + + getAddresses(); + }, [auth]); + + useEffect(() => { + if (!addresses) return; + + const matchAddress = (key) => { + const addressToMatch = getItemAddress(key); + const foundAddress = addresses.filter( + (address) => address.id == addressToMatch + ); + if (foundAddress.length > 0) { + return foundAddress[0]; + } + return addresses[0]; + }; + + setSelectedAddress({ + shipping: matchAddress('shipping'), + invoicing: matchAddress('invoicing'), + }); + }, [addresses]); + + const loadExpedisi = async () => { + let dataExpedisi = await ExpedisiList(); + dataExpedisi = dataExpedisi.map((expedisi) => ({ + value: expedisi.id, + label: expedisi.name, + carrierId: expedisi.deliveryCarrierId, + })); + setExpedisi(dataExpedisi); + }; + + const loadServiceRajaOngkir = async () => { + setLoadingRajaOngkir(true); + const body = { + origin: 2127, + destination: selectedAddress.shipping.rajaongkirCityId, + weight: totalWeight, + courier: selectedCarrier, + originType: 'subdistrict', + destinationType: 'subdistrict', + }; + setBiayaKirim(0); + const dataService = await axios( + '/api/rajaongkir-service?body=' + JSON.stringify(body) + ); + setLoadingRajaOngkir(false); + setListServiceExpedisi(dataService.data[0].costs); + if (dataService.data[0].costs[0]) { + setBiayaKirim(dataService.data[0].costs[0]?.cost[0].value); + setselectedExpedisiService( + dataService.data[0].costs[0]?.description + + '-' + + dataService.data[0].costs[0]?.service + ); + setEtd(dataService.data[0].costs[0]?.cost[0].etd); + toast.success('Harap pilih tipe layanan pengiriman'); + } else { + toast.error('Maaf, layanan tidak tersedia. Mohon pilih expedisi lain.'); + } + }; + + useEffect(() => { + setCheckoutValidation(false); + + if (selectedCarrier != 0 && selectedCarrier != 1 && totalWeight > 0) { + loadServiceRajaOngkir(); + } else { + setListServiceExpedisi(); + setBiayaKirim(0); + setselectedExpedisiService(); + setEtd(); + } + }, [selectedCarrier, selectedAddress, totalWeight]); + + useEffect(() => { + if (selectedExpedisi) { + let serviceType = selectedExpedisi.split(','); + if (serviceType[0] === 0) return; + + setselectedCarrier(serviceType[0]); + setselectedCarrierId(serviceType[1]); + setListServiceExpedisi([]); + } + }, [selectedExpedisi]); + + useEffect(() => { + if (selectedServiceType) { + let serviceType = selectedServiceType.split(','); + setBiayaKirim(serviceType[0]); + setselectedExpedisiService(serviceType[1]); + setEtd(serviceType[2]); + } + }, [selectedServiceType]); + + useEffect(() => { + if (etd) setEtdFix(calculateEstimatedArrival(etd)); + }, [etd]); + + // end set up address and carrier useEffect(() => { const loadProducts = async () => { - const cart = getCart() + const cart = getCart(); const variantIds = _.filter(cart, (o) => o.selected == true) .map((o) => o.productId) - .join(',') - const dataProducts = await CartApi({ variantIds }) + .join(','); + const dataProducts = await CartApi({ variantIds }); const productsWithQuantity = dataProducts?.map((product) => { return { ...product, - quantity: getItemCart({ productId: product.id }).quantity - } - }) + quantity: getItemCart({ productId: product.id }).quantity, + }; + }); if (productsWithQuantity) { Promise.all(productsWithQuantity).then((resolvedProducts) => { - setProducts(resolvedProducts) - }) + setProducts(resolvedProducts); + }); } - } + }; + loadExpedisi(); // loadProducts() - }, []) + }, []); useEffect(() => { - setProducts(cartCheckout?.products) - }, [cartCheckout]) + setProducts(cartCheckout?.products); + setCheckWeight(cartCheckout?.hasProductWithoutWeight); + setTotalWeight(cartCheckout?.totalWeight.g); + }, [cartCheckout]); useEffect(() => { if (products) { - let calculateTotalAmount = 0 - let calculateTotalDiscountAmount = 0 + let calculateTotalAmount = 0; + let calculateTotalDiscountAmount = 0; products.forEach((product) => { - calculateTotalAmount += product.price.price * product.quantity + calculateTotalAmount += product.price.price * product.quantity; calculateTotalDiscountAmount += - (product.price.price - product.price.priceDiscount) * product.quantity - }) - setTotalAmount(calculateTotalAmount) - setTotalDiscountAmount(calculateTotalDiscountAmount) + (product.price.price - product.price.priceDiscount) * + product.quantity; + }); + setTotalAmount(calculateTotalAmount); + setTotalDiscountAmount(calculateTotalDiscountAmount); } - }, [products]) + }, [products]); - const [isLoading, setIsLoading] = useState(false) + const [isLoading, setIsLoading] = useState(false); const checkout = async () => { - if (!products || products.length == 0) return - setIsLoading(true) + // validation checkout + if (selectedExpedisi === 0) { + setCheckoutValidation(true); + if (expedisiValidation.current) { + const position = expedisiValidation.current.getBoundingClientRect(); + window.scrollTo({ + top: position.top - 300 + window.pageYOffset, + behavior: 'smooth', + }); + } + return; + } + if (selectedCarrier != 1 && biayaKirim == 0) { + toast.error('Maaf, layanan tidak tersedia. Mohon pilih expedisi lain.'); + return; + } + + if (!products || products.length == 0) return; + setIsLoading(true); const productOrder = products.map((product) => ({ product_id: product.id, - quantity: product.quantity - })) + quantity: product.quantity, + })); let data = { - partner_shipping_id: auth.partnerId, - partner_invoice_id: auth.partnerId, + partner_shipping_id: selectedAddress.shipping.id, + partner_invoice_id: selectedAddress.invoicing.id, user_id: auth.id, - order_line: JSON.stringify(productOrder) - } - const isSuccess = await checkoutApi({ data }) - setIsLoading(false) + order_line: JSON.stringify(productOrder), + delivery_amount: biayaKirim, + carrier_id: selectedCarrierId, + estimated_arrival_days: splitDuration(etd), + delivery_service_type: selectedExpedisiService, + }; + 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 + 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') - } + toast.error('Gagal melakukan transaksi, terjadi kesalahan internal'); + }; - const taxTotal = (totalAmount - totalDiscountAmount) * 0.11 + const taxTotal = (totalAmount - totalDiscountAmount) * 0.11; return ( <> @@ -107,16 +288,80 @@ const Quotation = () => { <ExclamationCircleIcon className='w-7 text-blue-700' /> </div> <span className='leading-5'> - Jika mengalami kesulitan dalam melakukan pembelian di website Indoteknik. Hubungi kami - disini + Jika mengalami kesulitan dalam melakukan pembelian di website + Indoteknik. Hubungi kami disini </span> </Alert> </div> <Divider /> + {selectedCarrierId == SELF_PICKUP_ID && ( + <div className='p-4'> + <div + class='flex items-center p-4 mb-4 text-sm border border-yellow-500 text-yellow-800 rounded-lg bg-yellow-50' + role='alert' + > + <svg + class='flex-shrink-0 inline w-4 h-4 mr-3' + aria-hidden='true' + fill='currentColor' + viewBox='0 0 20 20' + > + <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> + <span class='sr-only'>Info</span> + <div className='text-justify'> + Fitur Self Pickup, hanya berlaku untuk customer di area jakarta. + Apa bila memilih fitur ini, anda akan dihubungi setelah barang + siap diambil. + </div> + </div> + </div> + )} + + {selectedCarrierId == SELF_PICKUP_ID && ( + <PickupAddress label='Alamat Pickup' /> + )} + {selectedCarrierId != SELF_PICKUP_ID && ( + <Skeleton + isLoaded={!!selectedAddress.invoicing && !!selectedAddress.shipping} + minHeight={320} + > + <SectionAddress + address={selectedAddress.shipping} + label='Alamat Pengiriman' + url='/my/address?select=shipping' + /> + <Divider /> + <SectionAddress + address={selectedAddress.invoicing} + label='Alamat Penagihan' + url='/my/address?select=invoice' + /> + </Skeleton> + )} + <Divider /> + <SectionValidation address={selectedAddress.invoicing} /> + <SectionExpedisi + address={selectedAddress.shipping} + listExpedisi={listExpedisi} + setSelectedExpedisi={setSelectedExpedisi} + checkWeigth={checkWeigth} + checkoutValidation={checkoutValidation} + expedisiValidation={expedisiValidation} + loadingRajaOngkir={loadingRajaOngkir} + /> + <Divider /> + <SectionListService + listserviceExpedisi={listserviceExpedisi} + setSelectedServiceType={setSelectedServiceType} + /> + <div className='p-4 flex flex-col gap-y-4'> - {products && <VariantGroupCard openOnClick={false} variants={products} />} + {products && ( + <VariantGroupCard openOnClick={false} variants={products} /> + )} </div> <Divider /> @@ -124,7 +369,9 @@ const Quotation = () => { <div className='p-4'> <div className='flex justify-between items-center'> <div className='font-medium'>Ringkasan Penawaran</div> - <div className='text-gray_r-11 text-caption-1'>{products?.length} Barang</div> + <div className='text-gray_r-11 text-caption-1'> + {products?.length} Barang + </div> </div> <hr className='my-4 border-gray_r-6' /> <div className='flex flex-col gap-y-4'> @@ -134,7 +381,9 @@ const Quotation = () => { </div> <div className='flex gap-x-2 justify-between'> <div className='text-gray_r-11'>Diskon Produk</div> - <div className='text-danger-500'>- {currencyFormat(cartCheckout?.totalDiscount)}</div> + <div className='text-danger-500'> + - {currencyFormat(cartCheckout?.totalDiscount)} + </div> </div> <div className='flex gap-x-2 justify-between'> <div className='text-gray_r-11'>Subtotal</div> @@ -144,17 +393,33 @@ const Quotation = () => { <div className='text-gray_r-11'>PPN 11%</div> <div>{currencyFormat(cartCheckout?.tax)}</div> </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 + )} + </div> + </div> </div> <hr className='my-4 border-gray_r-6' /> <div className='flex gap-x-2 justify-between mb-4'> <div>Grand Total</div> <div className='font-semibold text-gray_r-12'> - {currencyFormat(cartCheckout?.grandTotal)} + {currencyFormat( + cartCheckout?.grandTotal + + Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000 + )} </div> </div> - <p className='text-caption-2 text-gray_r-10 mb-2'>*) Belum termasuk biaya pengiriman</p> + <p className='text-caption-2 text-gray_r-10 mb-2'> + *) Belum termasuk biaya pengiriman + </p> <p className='text-caption-2 text-gray_r-10 leading-5'> - Dengan melakukan pembelian melalui website Indoteknik, saya menyetujui{' '} + Dengan melakukan pembelian melalui website Indoteknik, saya + menyetujui{' '} <Link href='/syarat-ketentuan' className='inline font-normal'> Syarat & Ketentuan </Link>{' '} @@ -165,7 +430,11 @@ const Quotation = () => { <Divider /> <div className='flex gap-x-3 p-4'> - <button className='flex-1 btn-yellow' onClick={checkout} disabled={isLoading}> + <button + className='flex-1 btn-yellow' + onClick={checkout} + disabled={isLoading} + > {isLoading ? 'Loading...' : 'Quotation'} </button> </div> @@ -174,15 +443,62 @@ const Quotation = () => { <DesktopView> <div className='container mx-auto py-10 flex'> <div className='w-3/4 border border-gray_r-6 rounded bg-white p-4'> - <div className='font-medium'>Detail Barang</div> - <CardProdcuctsList isLoading={isLoading} products={products} source='checkout' /> + {selectedCarrierId == SELF_PICKUP_ID && ( + <PickupAddress label='Alamat Pickup' /> + )} + {selectedCarrierId != SELF_PICKUP_ID && ( + <Skeleton + isLoaded={ + !!selectedAddress.invoicing && !!selectedAddress.shipping + } + minHeight={290} + > + <SectionAddress + address={selectedAddress.shipping} + label='Alamat Pengiriman' + url='/my/address?select=shipping' + /> + <Divider /> + <SectionAddress + address={selectedAddress.invoicing} + label='Alamat Penagihan' + url='/my/address?select=invoice' + /> + </Skeleton> + )} + <Divider /> + <SectionValidation address={selectedAddress.invoicing} /> + <SectionExpedisi + address={selectedAddress.shipping} + listExpedisi={listExpedisi} + setSelectedExpedisi={setSelectedExpedisi} + checkWeigth={checkWeigth} + checkoutValidation={checkoutValidation} + expedisiValidation={expedisiValidation} + loadingRajaOngkir={loadingRajaOngkir} + /> + <Divider /> + <SectionListService + listserviceExpedisi={listserviceExpedisi} + setSelectedServiceType={setSelectedServiceType} + /> + {/* <div className='p-4'> */} + <div className='font-medium mb-6'>Detail Barang</div> + <CardProdcuctsList + isLoading={isLoading} + products={products} + source='checkout' + /> + {/* </div> */} </div> <div className='w-1/4 pl-4'> <div className='sticky top-48 border border-gray_r-6 bg-white rounded p-4'> <div className='flex justify-between items-center'> <div className='font-medium'>Ringkasan Pesanan</div> - <div className='text-gray_r-11 text-caption-1'>{products?.length} Barang</div> + <div className='text-gray_r-11 text-caption-1'> + {products?.length} Barang + </div> </div> <hr className='my-4 border-gray_r-6' /> @@ -205,6 +521,16 @@ const Quotation = () => { <div className='text-gray_r-11'>PPN 11%</div> <div>{currencyFormat(cartCheckout?.tax)}</div> </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 + )} + </div> + </div> </div> <hr className='my-4 border-gray_r-6' /> @@ -212,14 +538,18 @@ const Quotation = () => { <div className='flex gap-x-2 justify-between mb-4'> <div>Grand Total</div> <div className='font-semibold text-gray_r-12'> - {currencyFormat(cartCheckout?.grandTotal)} + {currencyFormat( + cartCheckout?.grandTotal + + Math.round(parseInt(biayaKirim * 1.1) / 1000) * 1000 + )} </div> </div> - <p className='text-caption-2 text-gray_r-11 mb-2'> + {/* <p className='text-caption-2 text-gray_r-11 mb-2'> *) Belum termasuk biaya pengiriman - </p> + </p> */} <p className='text-caption-2 text-gray_r-11 leading-5'> - Dengan melakukan pembelian melalui website Indoteknik, saya menyetujui{' '} + Dengan melakukan pembelian melalui website Indoteknik, saya + menyetujui{' '} <Link href='/syarat-ketentuan' className='inline font-normal'> Syarat & Ketentuan </Link>{' '} @@ -240,7 +570,7 @@ const Quotation = () => { </div> </DesktopView> </> - ) -} + ); +}; -export default Quotation +export default Quotation; |
