diff options
| author | it-fixcomart <it@fixcomart.co.id> | 2025-08-11 09:29:11 +0700 |
|---|---|---|
| committer | it-fixcomart <it@fixcomart.co.id> | 2025-08-11 09:29:11 +0700 |
| commit | 43f08a6b39cf6555b9c87d94ade2af5a5f7747bf (patch) | |
| tree | a7a6b64e7bd44d0265908e6e953564b3f0fa4f4d /src/lib/checkout/components/SectionQuotationExpedition.jsx | |
| parent | bb656eb35742fbb18fe9c700a3569c4765c65a1c (diff) | |
<hafid> fix biteship quotation
Diffstat (limited to 'src/lib/checkout/components/SectionQuotationExpedition.jsx')
| -rw-r--r-- | src/lib/checkout/components/SectionQuotationExpedition.jsx | 369 |
1 files changed, 369 insertions, 0 deletions
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> + ); +} |
