summaryrefslogtreecommitdiff
path: root/src/lib/checkout/components
diff options
context:
space:
mode:
authorit-fixcomart <it@fixcomart.co.id>2025-08-11 09:29:11 +0700
committerit-fixcomart <it@fixcomart.co.id>2025-08-11 09:29:11 +0700
commit43f08a6b39cf6555b9c87d94ade2af5a5f7747bf (patch)
treea7a6b64e7bd44d0265908e6e953564b3f0fa4f4d /src/lib/checkout/components
parentbb656eb35742fbb18fe9c700a3569c4765c65a1c (diff)
<hafid> fix biteship quotation
Diffstat (limited to 'src/lib/checkout/components')
-rw-r--r--src/lib/checkout/components/SectionExpedition.jsx4
-rw-r--r--src/lib/checkout/components/SectionQuotationExpedition.jsx369
2 files changed, 371 insertions, 2 deletions
diff --git a/src/lib/checkout/components/SectionExpedition.jsx b/src/lib/checkout/components/SectionExpedition.jsx
index 7a02c6e9..2e92ffbc 100644
--- a/src/lib/checkout/components/SectionExpedition.jsx
+++ b/src/lib/checkout/components/SectionExpedition.jsx
@@ -341,7 +341,7 @@ export default function SectionExpedition({ products }) {
<div className='w-[350px] max'>
<div className='px-4 py-2'>
<div className='flex justify-between items-center'>
- <div className='w-[450px]'>
+ <div className='w-full'>
<div className='relative'>
{/* Custom Select Input Field */}
<div
@@ -407,7 +407,7 @@ export default function SectionExpedition({ products }) {
</div>
{checkoutValidation && (
<span className='text-sm text-red-500'>
- *silahkan pilih expedisi
+ *Silahkan pilih expedisi
</span>
)}
</div>
diff --git a/src/lib/checkout/components/SectionQuotationExpedition.jsx b/src/lib/checkout/components/SectionQuotationExpedition.jsx
new file mode 100644
index 00000000..b8ea04ef
--- /dev/null
+++ b/src/lib/checkout/components/SectionQuotationExpedition.jsx
@@ -0,0 +1,369 @@
+'use client';
+
+import { Skeleton } from '@chakra-ui/react';
+import axios from 'axios';
+import Image from 'next/image';
+import React, { useEffect, useState } from 'react';
+import { useQuery } from 'react-query';
+import toast from 'react-hot-toast';
+import { useAddress } from '../stores/useAdress';
+import { useQuotation } from '../stores/stateQuotation';
+
+import currencyFormat from '@/core/utils/currencyFormat';
+import { formatShipmentRange } from '../utils/functionCheckouit';
+import odooApi from '@/core/api/odooApi';
+
+function mappingItems(products) {
+ return products?.map((item) => ({
+ name: item?.name,
+ description: `${item.code} - ${item.name}`,
+ value: item.price.priceDiscount,
+ weight: item.weight * 1000,
+ quantity: item.quantity,
+ }));
+}
+
+function reverseMappingCourier(couriersOdoo, couriers, includeInstant = false) {
+ const courierMap = couriers.reduce((acc, item) => {
+ const { courier_name, courier_code, courier_service_code } = item;
+ const key = courier_code.toLowerCase();
+
+ if (
+ !includeInstant &&
+ (['hours'].includes(item.shipment_duration_unit.toLowerCase()) ||
+ item.service_type === 'same_day')
+ ) {
+ return acc;
+ }
+
+ if (!acc[key]) {
+ acc[key] = {
+ courier_name: item.courier_name,
+ courier_code: courier_code,
+ service_type: {},
+ };
+ }
+
+ acc[key].service_type[courier_service_code] = {
+ service_name: item.courier_service_name,
+ duration: item.duration,
+ shipment_range: item.shipment_duration_range,
+ shipment_unit: item.shipment_duration_unit,
+ price: item.price,
+ service_type: courier_service_code,
+ description: item.description,
+ };
+
+ return acc;
+ }, {});
+
+ return couriersOdoo.map((courierOdoo) => {
+ const courierNameKey = courierOdoo.label.toLowerCase();
+ const carrierId = courierOdoo.carrierId;
+
+ const mappedCourier = courierMap[courierNameKey] || false;
+
+ if (!mappedCourier) {
+ return {
+ ...courierOdoo,
+ courier: false,
+ };
+ }
+
+ return {
+ ...courierOdoo,
+ courier: {
+ ...mappedCourier,
+ courier_id_odoo: carrierId,
+ },
+ };
+ });
+}
+
+export default function SectionExpeditionQuotation({ products }) {
+ const { addressMaps, coordinate, postalCode } = useAddress();
+ const [serviceOptions, setServiceOptions] = useState([]);
+ const [isOpen, setIsOpen] = useState(false);
+ const [onFocusSelectedCourier, setOnFocuseSelectedCourier] = useState(false);
+ const [couriers, setCouriers] = useState(null);
+ const [slaProducts, setSlaProducts] = useState(null);
+ const [savedServiceOptions, setSavedServiceOptions] = useState([]);
+
+ const {
+ checkWeigth,
+ checkoutValidation,
+ setBiayaKirim,
+ setUnit,
+ setEtd,
+ selectedCourier,
+ setSelectedCourier,
+ selectedService,
+ setSelectedService,
+ listExpedisi,
+ productSla,
+ setProductSla,
+ setSelectedCourierId,
+ } = useQuotation();
+
+ let destination = {};
+ let items = mappingItems(products);
+
+ if (addressMaps) {
+ destination = {
+ origin_latitude: -6.3031123,
+ origin_longitude: 106.7794934999,
+ ...coordinate,
+ };
+ } else if (postalCode) {
+ destination = {
+ origin_postal_code: 14440,
+ destination_postal_code: postalCode,
+ };
+ }
+
+ const fetchSlaProducts = async () => {
+ try {
+ let productsMapped = products.map((item) => ({
+ id: item.id,
+ quantity: item.quantity,
+ }));
+
+ let data = {
+ products: JSON.stringify(productsMapped),
+ };
+ const res = await odooApi('POST', `/api/v1/product/variants/sla`, data);
+ setSlaProducts(res);
+ } catch (error) {
+ console.error('Failed to fetch SLA:', error);
+ }
+ };
+
+ useEffect(() => {
+ fetchSlaProducts();
+ }, []);
+
+ useEffect(() => {
+ if (slaProducts) {
+ let productSla = slaProducts?.slaTotal;
+ if (slaProducts.slaUnit === 'jam') {
+ productSla = 1;
+ }
+ setProductSla(productSla);
+ }
+ }, [slaProducts]);
+
+ const fetchExpedition = async () => {
+ const body = {
+ ...destination,
+ couriers:
+ 'gojek,grab,deliveree,lalamove,jne,tiki,ninja,lion,rara,sicepat,jnt,pos,idexpress,rpx,wahana,jdl,pos,anteraja,sap,paxel,borzo',
+ items,
+ };
+ const response = await axios.get(`/api/biteship-service`, {
+ params: { body: JSON.stringify(body) },
+ });
+ return response;
+ };
+
+ const { data, isLoading } = useQuery(
+ ['expedition', JSON.stringify(destination), JSON.stringify(items)],
+ fetchExpedition,
+ {
+ enabled:
+ Boolean(Object.keys(destination).length) &&
+ items?.length > 0 &&
+ !checkWeigth &&
+ onFocusSelectedCourier,
+ staleTime: Infinity,
+ cacheTime: Infinity,
+ }
+ );
+
+ useEffect(() => {
+ const instant = slaProducts?.includeInstant || false;
+ if (data) {
+ const couriers = reverseMappingCourier(
+ listExpedisi,
+ data?.data?.pricing,
+ instant
+ );
+ setCouriers(couriers);
+ }
+ }, [data, slaProducts]);
+
+ const onCourierChange = (courier) => {
+ setIsOpen(false);
+ setOnFocuseSelectedCourier(false);
+ setSelectedService(null);
+ setBiayaKirim(0);
+ if (courier !== 0 && courier !== 32) {
+ if (courier.courier) {
+ setSelectedCourier(courier.courier.courier_code);
+ setSelectedCourierId(courier.carrierId);
+ setServiceOptions(Object.values(courier.courier.service_type));
+ } else {
+ if (
+ (courier.label === 'GRAB' || courier.label === 'GOJEK') &&
+ !addressMaps
+ ) {
+ toast.error(
+ `Maaf, layanan kurir ${courier.label} tidak tersedia karena belum mengatur PinPoint.`
+ );
+ } else {
+ toast.error('Maaf, layanan tidak tersedia. Mohon pilih ekspedisi lain.');
+ }
+ setServiceOptions([]);
+ }
+ } else {
+ setSelectedCourier(courier === 32 ? 'SELF PICKUP' : null);
+ setSelectedCourierId(courier);
+ setServiceOptions([]);
+ }
+ };
+
+ const handleSelect = (service) => {
+ setSelectedService(service);
+ setBiayaKirim(service?.price);
+ setEtd(service?.shipment_range);
+ setUnit(service?.shipment_unit);
+ setIsOpen(false);
+ };
+
+ useEffect(() => {
+ if (serviceOptions.length > 0) {
+ setSavedServiceOptions(serviceOptions);
+ }
+ }, [serviceOptions]);
+
+ return (
+ <div className='px-4 py-2'>
+ <div className='flex justify-between items-center'>
+ <div className='font-medium'>Pilih Ekspedisi: </div>
+ <div className='w-[350px]'>
+ <div
+ className='w-full p-2 border rounded-lg bg-white cursor-pointer'
+ onClick={() => setOnFocuseSelectedCourier(!onFocusSelectedCourier)}
+ >
+ {selectedCourier ? (
+ <div className='flex justify-between'>
+ <span>{selectedCourier}</span>
+ </div>
+ ) : (
+ <span className='text-gray-500'>Pilih Expedisi</span>
+ )}
+ </div>
+ {onFocusSelectedCourier && (
+ <div className='absolute bg-white border rounded-lg mt-1 shadow-lg z-10 max-h-[200px] overflow-y-auto w-[350px]'>
+ {!isLoading ? (
+ <>
+ <div
+ key={32}
+ onClick={() => onCourierChange(32)}
+ className='p-2 hover:bg-gray-100 cursor-pointer'
+ >
+ <p className='font-semibold'>SELF PICKUP</p>
+ </div>
+ {couriers?.map((courier) => (
+ <div
+ key={courier?.courier?.courier_code}
+ onClick={() => onCourierChange(courier)}
+ className='flex justify-between p-2 items-center hover:bg-gray-100 cursor-pointer'
+ >
+ <p className='font-semibold'>{courier?.label}</p>
+ <Image
+ src={courier?.logo}
+ alt={courier?.courier?.courier_name}
+ width={50}
+ height={50}
+ />
+ </div>
+ ))}
+ </>
+ ) : (
+ <>
+ <Skeleton height={40} />
+ <Skeleton height={40} />
+ </>
+ )}
+ </div>
+ )}
+ {checkoutValidation && (
+ <span className='text-sm text-red-500'>
+ *Silahkan pilih ekspedisi
+ </span>
+ )}
+ </div>
+ </div>
+
+ {checkWeigth && (
+ <p className='mt-4 text-gray-600'>
+ Mohon maaf, pengiriman hanya tersedia untuk self pickup karena ada barang
+ yang belum memiliki berat. Silakan hubungi admin via{' '}
+ <a
+ className='text-blue-600 underline'
+ href='https://api.whatsapp.com/send?phone=6281717181922'
+ target='_blank'
+ rel='noopener noreferrer'
+ >
+ tautan ini
+ </a>
+ </p>
+ )}
+
+ {(serviceOptions.length > 0 || selectedService) &&
+ selectedCourier &&
+ selectedCourier !== 32 &&
+ selectedCourier !== 0 && (
+ <div className='mt-4 flex justify-between'>
+ <div className='font-medium mb-2'>Tipe Layanan Ekspedisi:</div>
+ <div className='relative w-[350px]'>
+ <div
+ className='p-2 border rounded-lg bg-white cursor-pointer'
+ onClick={() => setIsOpen(!isOpen)}
+ >
+ {selectedService ? (
+ <div className='flex justify-between'>
+ <span>{selectedService.service_name}</span>
+ <span>
+ {currencyFormat(
+ Math.round((selectedService?.price * 1.1) / 1000) * 1000
+ )}
+ </span>
+ </div>
+ ) : (
+ <span className='text-gray-500'>Pilih layanan pengiriman</span>
+ )}
+ </div>
+ {isOpen && (
+ <div className='absolute bg-white border rounded-lg mt-1 shadow-lg z-10 w-full'>
+ {serviceOptions.map((service) => (
+ <div
+ key={service.service_type}
+ onClick={() => handleSelect(service)}
+ className='flex justify-between p-2 items-center hover:bg-gray-100 cursor-pointer'
+ >
+ <div>
+ <p className='font-semibold'>{service.service_name}</p>
+ <p className='text-sm text-gray-600'>
+ {formatShipmentRange(
+ service.shipment_range,
+ service.shipment_unit,
+ productSla
+ )}
+ </p>
+ </div>
+ <span>
+ {currencyFormat(
+ Math.round((service?.price * 1.1) / 1000) * 1000
+ )}
+ </span>
+ </div>
+ ))}
+ </div>
+ )}
+ </div>
+ </div>
+ )}
+ </div>
+ );
+}