summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIndoteknik . <andrifebriyadiputra@gmail.com>2025-08-16 14:46:48 +0700
committerIndoteknik . <andrifebriyadiputra@gmail.com>2025-08-16 14:46:48 +0700
commitc6b75363821e5c1153d8a9e2c1a4326568ab6026 (patch)
treed5940768d254d3aa6862c32012002d5274467227
parentba157d5e0cd30ae2ed13edba051038c2c7bb1a1f (diff)
parente3bf34095ac7571d04ebddba6f04815d7a71ed13 (diff)
fix merge
-rw-r--r--src/lib/address/components/EditAddress.jsx222
-rw-r--r--src/lib/auth/api/checkParentStatusApi.js13
-rw-r--r--src/lib/auth/components/SwitchAccount.jsx10
-rw-r--r--src/lib/checkout/components/SectionExpedition.jsx4
-rw-r--r--src/lib/checkout/components/SectionQuotationExpedition.jsx369
-rw-r--r--src/lib/checkout/stores/stateQuotation.js30
-rw-r--r--src/lib/maps/components/PinPointMap.jsx71
-rw-r--r--src/lib/maps/stores/useMaps.js39
-rw-r--r--src/lib/pengajuan-tempo/component/PengajuanTempo.jsx251
-rw-r--r--src/lib/quotation/components/Quotation.jsx390
-rw-r--r--src/lib/transaction/components/Transaction.jsx452
-rw-r--r--src/lib/transaction/components/Transactions.jsx43
-rw-r--r--src/lib/treckingAwb/component/InformationSection.jsx4
-rw-r--r--src/lib/treckingAwb/component/Manifest.jsx25
-rw-r--r--src/lib/variant/components/VariantGroupCard.jsx7
-rw-r--r--src/pages/api/shop/search.js3
-rw-r--r--src/pages/my/profile.jsx11
17 files changed, 1200 insertions, 744 deletions
diff --git a/src/lib/address/components/EditAddress.jsx b/src/lib/address/components/EditAddress.jsx
index deaa8a3e..6599a764 100644
--- a/src/lib/address/components/EditAddress.jsx
+++ b/src/lib/address/components/EditAddress.jsx
@@ -1,6 +1,6 @@
import { yupResolver } from '@hookform/resolvers/yup';
import { useRouter } from 'next/router';
-import { useEffect, useState } from 'react';
+import { useEffect, useState, useMemo } from 'react';
import * as Yup from 'yup';
import cityApi from '../api/cityApi';
import { Controller, useForm } from 'react-hook-form';
@@ -44,35 +44,61 @@ const EditAddress = ({ id, defaultValues }) => {
const [districts, setDistricts] = useState([]);
const [subDistricts, setSubDistricts] = useState([]);
const [tempAddress, setTempAddress] = useState(getValues('addressMap'));
- const { addressMaps,
+
+ const {
+ addressMaps,
selectedPosition,
detailAddress,
pinedMaps,
- setPinedMaps } = useMaps();
-
-
+ setPinedMaps,
+ getDefaultCenter, // penting untuk deteksi default center
+ } = useMaps();
+
+ // Helper: cek apakah benar2 sudah PIN (bukan default center & ada addressMaps)
+ const isPinned = useMemo(() => {
+ if (
+ !selectedPosition ||
+ typeof selectedPosition.lat !== 'number' ||
+ typeof selectedPosition.lng !== 'number'
+ )
+ return false;
+ const dc =
+ typeof getDefaultCenter === 'function'
+ ? getDefaultCenter()
+ : { lat: -6.2, lng: 106.816666 };
+ const nearDefault =
+ Math.abs(selectedPosition.lat - dc.lat) < 1e-4 &&
+ Math.abs(selectedPosition.lng - dc.lng) < 1e-4;
+ return Boolean(addressMaps) && !nearDefault;
+ }, [selectedPosition, addressMaps, getDefaultCenter]);
+
+ // Hanya isi addressMap & lat/lng di form kalau SUDAH PIN
useEffect(() => {
- if (addressMaps) {
+ if (addressMaps && isPinned) {
setTempAddress(addressMaps);
setValue('addressMap', addressMaps);
+ }
+ if (isPinned && selectedPosition) {
setValue('longtitude', selectedPosition.lng);
setValue('latitude', selectedPosition.lat);
}
- }, [addressMaps, selectedPosition, setValue]);
-
+ }, [addressMaps, selectedPosition, isPinned, setValue]);
+
+ // Isi ZIP/Prov dari detailAddress (JANGAN isi street)
useEffect(() => {
- if (Object.keys(detailAddress).length > 0) {
+ if (Object.keys(detailAddress || {}).length > 0 && isPinned) {
setValue('zip', detailAddress.postalCode);
const selectedState = states.find(
(state) =>
- detailAddress?.province.includes(state.label) ||
- state.label.includes(detailAddress?.province)
+ detailAddress?.province?.includes(state.label) ||
+ state.label?.includes(detailAddress?.province)
);
- setValue('state', selectedState?.value);
- setValue('street', detailAddress?.street);
+ setValue('state', selectedState?.value);
+ // jangan override street:
+ // setValue('street', detailAddress?.street);
}
- }, [detailAddress, setValue]);
-
+ }, [detailAddress, states, isPinned, setValue]);
+
useEffect(() => {
const loadProfile = async () => {
const dataProfile = await addressApi({ id: auth.partnerId });
@@ -88,8 +114,8 @@ const EditAddress = ({ id, defaultValues }) => {
setValue('subDistrict', dataProfile.subDistrict?.id);
};
if (auth) loadProfile();
- }, [auth?.parentId]);
-
+ }, [auth?.parentId, setValue]);
+
useEffect(() => {
const loadStates = async () => {
let dataStates = await stateApi({ tempo: false });
@@ -101,7 +127,7 @@ const EditAddress = ({ id, defaultValues }) => {
};
loadStates();
}, []);
-
+
const watchState = watch('state');
useEffect(() => {
setValue('city', '');
@@ -122,63 +148,64 @@ const EditAddress = ({ id, defaultValues }) => {
loadCities();
}
}, [watchState, setValue, getValues]);
-
+
useEffect(() => {
- if (Object.keys(detailAddress).length > 0) {
- const selectedCities = cities.find(
- (city) =>
- city.label.toLowerCase() === detailAddress?.district.toLowerCase()
- ) || cities.find(
+ if (Object.keys(detailAddress || {}).length > 0 && isPinned) {
+ const selectedCities =
+ cities.find(
(city) =>
- detailAddress?.district.toLowerCase().includes(city.label.toLowerCase()) ||
- city.label.toLowerCase().includes(detailAddress?.district.toLowerCase())
+ city.label.toLowerCase() === detailAddress?.district?.toLowerCase()
+ ) ||
+ cities.find(
+ (city) =>
+ detailAddress?.district
+ ?.toLowerCase()
+ .includes(city.label.toLowerCase()) ||
+ city.label
+ .toLowerCase()
+ .includes(detailAddress?.district?.toLowerCase())
);
- setValue('city', selectedCities?.value);
- }
- }, [cities, detailAddress, setValue]);
-
- const watchCity = watch('city');
- useEffect(() => {
- if (watchCity) {
- // setValue('district', '');
- const loadDistricts = async () => {
- let dataDistricts = await districtApi({ cityId: watchCity });
- dataDistricts = dataDistricts.map((district) => ({
- value: district.id,
- label: district.name,
+ setValue('city', selectedCities?.value);
+ }
+ }, [cities, detailAddress, isPinned, setValue]);
+
+ const watchCity = watch('city');
+ useEffect(() => {
+ if (watchCity) {
+ const loadDistricts = async () => {
+ let dataDistricts = await districtApi({ cityId: watchCity });
+ dataDistricts = dataDistricts.map((district) => ({
+ value: district.id,
+ label: district.name,
}));
setDistricts(dataDistricts);
let oldDistrict = getValues('oldDistrict');
if (oldDistrict) {
- // setValue('district', oldDistrict);
setValue('oldDistrict', '');
}
};
loadDistricts();
}
}, [watchCity, setValue, getValues]);
-
+
useEffect(() => {
- if (Object.keys(detailAddress).length > 0) {
+ if (Object.keys(detailAddress || {}).length > 0 && isPinned) {
const selectedDistrict = districts.find(
(district) =>
- detailAddress.subDistrict
- .toLowerCase()
+ detailAddress?.subDistrict
+ ?.toLowerCase()
.includes(district.label.toLowerCase()) ||
district.label
.toLowerCase()
- .includes(detailAddress.subDistrict.toLowerCase())
+ .includes(detailAddress?.subDistrict?.toLowerCase())
);
setValue('district', selectedDistrict?.value);
}
- }, [districts, detailAddress, setValue]);
-
-
-
+ }, [districts, detailAddress, isPinned, setValue]);
+
const watchDistrict = watch('district');
useEffect(() => {
if (watchDistrict) {
- // setValue('subDistrict', '');
const loadSubDistricts = async () => {
let dataSubDistricts = await subDistrictApi({
districtId: watchDistrict,
@@ -199,29 +226,27 @@ const EditAddress = ({ id, defaultValues }) => {
}
}, [watchDistrict, setValue, getValues]);
-
useEffect(() => {
- if (Object.keys(detailAddress).length > 0) {
+ if (Object.keys(detailAddress || {}).length > 0 && isPinned) {
const selectedSubDistrict = subDistricts.find(
(district) =>
- detailAddress.village
- .toLowerCase()
+ detailAddress?.village
+ ?.toLowerCase()
.includes(district.label.toLowerCase()) ||
- district.label
+ district.label
.toLowerCase()
- .includes(detailAddress.village.toLowerCase())
- );
-
- setValue('subDistrict', selectedSubDistrict?.value);
+ .includes(detailAddress?.village?.toLowerCase())
+ );
+ setValue('subDistrict', selectedSubDistrict?.value);
}
- }, [subDistricts, detailAddress, setValue]);
+ }, [subDistricts, detailAddress, isPinned, setValue]);
useEffect(() => {
if (id) {
setValue('id', id);
}
}, [id, setValue]);
-
+
const onSubmitHandler = async (values) => {
const data = {
...values,
@@ -230,20 +255,32 @@ const EditAddress = ({ id, defaultValues }) => {
city_id: parseInt(values.city, 10),
district_id: parseInt(values.district, 10),
sub_district_id: parseInt(values.subDistrict, 10),
- longtitude: selectedPosition?.lng,
- latitude: selectedPosition?.lat,
- address_map: addressMaps,
};
+
+ // kirim koordinat + address_map + use_pin HANYA jika sudah PIN
+ if (isPinned) {
+ data.longtitude = selectedPosition?.lng;
+ data.latitude = selectedPosition?.lat;
+ data.address_map = addressMaps || values.addressMap;
+ data.use_pin = true;
+ } else {
+ data.use_pin = false;
+ // pastikan tidak ada nilai default center yang ikut terkirim
+ delete data.longtitude;
+ delete data.latitude;
+ delete data.address_map;
+ }
+
if (!auth.company) {
data.alamat_lengkap_text = values.street;
}
+
try {
const address = await editAddressApi({ id, data });
console.log('Response address:', address);
let isUpdated = null;
- // Jika company dan partnerId sama dengan id, maka update data alamat wajib pajak
const isCompanyEditingSelf = auth.company && auth.partnerId == id;
if (isCompanyEditingSelf) {
@@ -260,15 +297,13 @@ const EditAddress = ({ id, defaultValues }) => {
mobile: values.mobile,
};
- const isUpdated = await editPartnerApi({
+ const isUpdatedRes = await editPartnerApi({
id: auth.partnerId,
data: dataAlamat,
});
-
- console.log('Response isUpdated:', isUpdated);
+ console.log('Response isUpdated:', isUpdatedRes);
}
- // Validasi kondisi sukses
const isSuccess = !!address?.id;
if (isSuccess) {
@@ -286,19 +321,8 @@ const EditAddress = ({ id, defaultValues }) => {
toast.error(error?.message || 'Terjadi kesalahan tidak terduga.');
}
-
const dataProfile = await addressApi({ id: auth.partnerId });
console.log('ini adalah', dataProfile);
-
-
- // if (isUpdated?.id) {
- // if (address?.id && auth.company ? isUpdated?.id : true) {
- // toast.success('Berhasil mengubah alamat');
- // router.back();
- // } else {
- // toast.error('Terjadi kesalahan internal');
- // router.back();
- // }
};
return (
@@ -310,12 +334,11 @@ const EditAddress = ({ id, defaultValues }) => {
close={() => setPinedMaps(false)}
>
<div className='flex mt-4'>
- <PinPointMap
- initialLatitude={selectedPosition?.lat}
- initialLongitude={selectedPosition?.lng}
- initialAddress={tempAddress}
- />
-
+ <PinPointMap
+ initialLatitude={selectedPosition?.lat}
+ initialLongitude={selectedPosition?.lng}
+ initialAddress={tempAddress}
+ />
</div>
</BottomPopup>
<div className='max-w-none md:container mx-auto flex p-0 md:py-10'>
@@ -334,15 +357,28 @@ const EditAddress = ({ id, defaultValues }) => {
<label className='form-label mb-2'>Koordinat Alamat</label>
{tempAddress ? (
<div className='flex gap-x-2 items-center'>
- <button type='button' className="flex items-center justify-center me-3 p-2 badge-solid-red text-white rounded-full hover:bg-red-500 transition">
- <MapPinIcon class='h-6 w-6' onClick={() => setPinedMaps(true)} />{' '}
- </button>
+ <button
+ type='button'
+ className='flex items-center justify-center me-3 p-2 badge-solid-red text-white rounded-full hover:bg-red-500 transition'
+ >
+ <MapPinIcon
+ className='h-6 w-6'
+ onClick={() => setPinedMaps(true)}
+ />
+ </button>
<span> {tempAddress} </span>
</div>
) : (
- <Button variant='plain' style={{ padding: 0 }} onClick={() => setPinedMaps(true)}>
- <button type='button' className="flex items-center justify-center me-3 p-2 badge-solid-red text-white rounded-full hover:bg-red-500 transition">
- <MapPinIcon className="h-6 w-6" />
+ <Button
+ variant='plain'
+ style={{ padding: 0 }}
+ onClick={() => setPinedMaps(true)}
+ >
+ <button
+ type='button'
+ className='flex items-center justify-center me-3 p-2 badge-solid-red text-white rounded-full hover:bg-red-500 transition'
+ >
+ <MapPinIcon className='h-6 w-6' />
</button>
Pin Koordinat Alamat
</Button>
@@ -530,4 +566,4 @@ const types = [
{ value: 'other', label: 'Other Address' },
];
-export default EditAddress;
+export default EditAddress; \ No newline at end of file
diff --git a/src/lib/auth/api/checkParentStatusApi.js b/src/lib/auth/api/checkParentStatusApi.js
new file mode 100644
index 00000000..aa2eb1b6
--- /dev/null
+++ b/src/lib/auth/api/checkParentStatusApi.js
@@ -0,0 +1,13 @@
+import odooApi from '@/core/api/odooApi';
+import { getAuth } from '@/core/utils/auth';
+
+const checkParentStatusApi = async () => {
+ const auth = getAuth();
+ const checkParentStatus = await odooApi(
+ 'GET',
+ `/api/v1/user/${auth.partnerId}/parent_status`
+ );
+ return checkParentStatus;
+};
+
+export default checkParentStatusApi; \ No newline at end of file
diff --git a/src/lib/auth/components/SwitchAccount.jsx b/src/lib/auth/components/SwitchAccount.jsx
index fc2ac941..840758c9 100644
--- a/src/lib/auth/components/SwitchAccount.jsx
+++ b/src/lib/auth/components/SwitchAccount.jsx
@@ -16,7 +16,7 @@ import { isValid } from 'zod';
import Spinner from "@/core/components/elements/Spinner/LogoSpinner";
import useDevice from '@/core/hooks/useDevice';
import BottomPopup from '@/core/components/elements/Popup/BottomPopup';
-const SwitchAccount = ({ company_type }) => {
+const SwitchAccount = ({ company_type, setIsAprove, setUbahAkun }) => {
const { isDesktop, isMobile } = useDevice();
const auth = useAuth();
const [isOpen, setIsOpen] = useState(true);
@@ -152,21 +152,23 @@ const SwitchAccount = ({ company_type }) => {
try {
const isUpdated = await switchAccountApi({ data });
- if (isUpdated?.switch === 'Pending') {
+ if (isUpdated?.switch === 'pending') {
toast.success('Berhasil mengajukan ubah akun', { duration: 1500 });
if (typeof window !== 'undefined') {
localStorage.setItem('autoCheckProfile', 'true');
}
setTimeout(() => {
+ setIsAprove('pending');
+ setUbahAkun('pending');
window.location.reload();
}, 1500);
} else {
- toast.error('Gagal mengubah akun. Silakan coba lagi nanti. Jika kendala masih berlanjut, silakan hubungi admin.');
+ toast.error('Gagal mengubah akun. Silakan coba lagi nanti atau hubungi admin jika masalah tetap terjadi.');
setIsLoadingPopup(false);
}
} catch (error) {
console.error(error);
- toast.error('Terjadi kesalahan saat menghubungi server. Periksa koneksi internet Anda, dan jika kendala masih berlanjut, silakan hubungi admin.');
+ toast.error('Terjadi kesalahan saat menghubungi server, silahkan cek internet Anda atau hubungi admin Indoteknik.');
setIsLoadingPopup(false);
}
};
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>
+ );
+}
diff --git a/src/lib/checkout/stores/stateQuotation.js b/src/lib/checkout/stores/stateQuotation.js
new file mode 100644
index 00000000..da84997a
--- /dev/null
+++ b/src/lib/checkout/stores/stateQuotation.js
@@ -0,0 +1,30 @@
+import { create } from "zustand";
+
+export const useQuotation = create((set) => ({
+ products : null,
+ checkWeigth : false,
+ hasFlashSale : false,
+ checkoutValidation : false,
+ biayaKirim : 0,
+ etd : null,
+ unit : null,
+ selectedCourier : null,
+ selectedCourierId : null,
+ selectedService : null,
+ listExpedisi : [],
+ productSla : null,
+ setCheckWeight : (checkWeigth) => set({ checkWeigth }),
+ setHasFlashSale : (hasFlashSale) => set({ hasFlashSale }),
+ setCheckoutValidation : (checkoutValidation) => set({ checkoutValidation }),
+ setBiayaKirim : (biayaKirim) => set({ biayaKirim }),
+ setProducts : (products) => set({ products }),
+ setEtd : (etd) => set({ etd }),
+ setUnit : (unit) => set({ unit }),
+ setSelectedCourier : (selectedCourier) => set({ selectedCourier }),
+ setSelectedService : (selectedService) => set({ selectedService }),
+ setSelectedCourierId : (selectedCourierId) => set({ selectedCourierId }),
+ setExpedisi : (listExpedisi) => set({ listExpedisi }),
+ setProductSla : (productSla) => set({ productSla })
+
+
+})) \ No newline at end of file
diff --git a/src/lib/maps/components/PinPointMap.jsx b/src/lib/maps/components/PinPointMap.jsx
index c46d838a..75ab1d59 100644
--- a/src/lib/maps/components/PinPointMap.jsx
+++ b/src/lib/maps/components/PinPointMap.jsx
@@ -14,37 +14,35 @@ const containerStyle = {
height: '400px',
};
-const defaultCenter = {
- lat: -6.2,
- lng: 106.816666,
-};
-
-const PinpointLocation = ({ initialLatitude, initialLongitude, initialAddress }) => {
+const PinpointLocation = ({
+ initialLatitude,
+ initialLongitude,
+ initialAddress,
+}) => {
const { isLoaded } = useJsApiLoader({
googleMapsApiKey: process.env.NEXT_PUBLIC_GOOGLE_API_KEY,
libraries: ['places'],
});
const {
- addressMaps,
setAddressMaps,
selectedPosition,
setSelectedPosition,
setDetailAddress,
setPinedMaps,
+ getDefaultCenter, // ✅ ambil default center dari store
} = useMaps();
const [tempAddress, setTempAddress] = useState(initialAddress || '');
const [tempPosition, setTempPosition] = useState(
- initialLatitude && initialLongitude
+ initialLatitude && initialLongitude
? { lat: parseFloat(initialLatitude), lng: parseFloat(initialLongitude) }
- : selectedPosition.lat && selectedPosition.lng
- ? selectedPosition
- : defaultCenter
+ : selectedPosition?.lat && selectedPosition?.lng
+ ? selectedPosition
+ : getDefaultCenter() // ✅ fallback aman untuk view
);
const [markerIcon, setMarkerIcon] = useState(null);
-
const autocompleteRef = useRef(null);
useEffect(() => {
@@ -55,7 +53,7 @@ const PinpointLocation = ({ initialLatitude, initialLongitude, initialAddress })
});
}
- // If we have initial coordinates but no address, fetch the address
+ // Jika ada koordinat awal tapi belum ada address → reverse geocode
if (initialLatitude && initialLongitude && !initialAddress) {
getAddress(parseFloat(initialLatitude), parseFloat(initialLongitude));
}
@@ -66,6 +64,7 @@ const PinpointLocation = ({ initialLatitude, initialLongitude, initialAddress })
return component ? component.long_name : '';
};
+ // fill from pin point
const getAddress = async (lat, lng) => {
try {
const response = await fetch(
@@ -78,14 +77,26 @@ const PinpointLocation = ({ initialLatitude, initialLongitude, initialAddress })
const formattedAddress = data.results[0].formatted_address;
const details = {
- street:
- getAddressComponent(addressComponents, 'route') +
- ' ' +
- getAddressComponent(addressComponents, 'street_number'),
- province: getAddressComponent(addressComponents, 'administrative_area_level_1'),
- district: getAddressComponent(addressComponents, 'administrative_area_level_2'),
- subDistrict: getAddressComponent(addressComponents, 'administrative_area_level_3'),
- village: getAddressComponent(addressComponents, 'administrative_area_level_4'),
+ // street:
+ // getAddressComponent(addressComponents, 'route') +
+ // ' ' +
+ // getAddressComponent(addressComponents, 'street_number'),
+ province: getAddressComponent(
+ addressComponents,
+ 'administrative_area_level_1'
+ ),
+ district: getAddressComponent(
+ addressComponents,
+ 'administrative_area_level_2'
+ ),
+ subDistrict: getAddressComponent(
+ addressComponents,
+ 'administrative_area_level_3'
+ ),
+ village: getAddressComponent(
+ addressComponents,
+ 'administrative_area_level_4'
+ ),
postalCode: getAddressComponent(addressComponents, 'postal_code'),
};
@@ -136,8 +147,15 @@ const PinpointLocation = ({ initialLatitude, initialLongitude, initialAddress })
const handleSavePinpoint = (event) => {
event.preventDefault();
- if (tempAddress === '') {
- alert('Silahkan pilih lokasi terlebih dahulu');
+
+ // ✅ cegah save jika masih di default center (user belum benar2 pilih lokasi)
+ const dc = getDefaultCenter();
+ const isDefault =
+ Math.abs(tempPosition.lat - dc.lat) < 1e-6 &&
+ Math.abs(tempPosition.lng - dc.lng) < 1e-6;
+
+ if (!tempAddress || isDefault) {
+ alert('Silahkan pilih lokasi di peta atau autocomplete terlebih dahulu');
return;
}
@@ -173,13 +191,13 @@ const PinpointLocation = ({ initialLatitude, initialLongitude, initialAddress })
{isLoaded ? (
<GoogleMap
mapContainerStyle={containerStyle}
- center={tempPosition}
+ center={tempPosition || getDefaultCenter()} // ✅ aman jika null
zoom={15}
onClick={onMapClick}
>
{markerIcon && (
<Marker
- position={tempPosition}
+ position={tempPosition || getDefaultCenter()} // ✅ selalu ada posisi
draggable={true}
onDragEnd={(e) => {
const lat = e.latLng.lat();
@@ -199,7 +217,8 @@ const PinpointLocation = ({ initialLatitude, initialLongitude, initialAddress })
<div style={{ marginTop: '20px' }}>
<Button variant='solid' onClick={handleUseCurrentLocation}>
- <LocateFixed className='h-6 w-6 text-gray-500 mr-2' /> Gunakan Lokasi Saat Ini
+ <LocateFixed className='h-6 w-6 text-gray-500 mr-2' /> Gunakan Lokasi
+ Saat Ini
</Button>
</div>
diff --git a/src/lib/maps/stores/useMaps.js b/src/lib/maps/stores/useMaps.js
index c57a05ad..f7636c24 100644
--- a/src/lib/maps/stores/useMaps.js
+++ b/src/lib/maps/stores/useMaps.js
@@ -1,32 +1,47 @@
import { create } from "zustand";
-const center = {
- lat: -6.200000, // Default latitude (Jakarta)
- lng: 106.816666, // Default longitude (Jakarta)
-};
-
-export const useMaps = create((set) => ({
- // State existing
- selectedPosition: center,
+const DEFAULT_CENTER = { lat: -6.2, lng: 106.816666 };
+
+export const useMaps = create((set, get) => ({
+ // ==== STATE ====
+ selectedPosition: null,
addressMaps: '',
detailAddress: {},
pinedMaps: false,
- // State tambahan untuk penyimpanan posisi sementara
+ // posisi sementara (create/edit)
tempPositionCreate: null,
tempPositionEdit: null,
- // Setter existing
+ // ==== SETTERS ====
setSelectedPosition: (position) => set({ selectedPosition: position }),
setAddressMaps: (addressMaps) => set({ addressMaps }),
setDetailAddress: (detailAddress) => set({ detailAddress }),
setPinedMaps: (pinedMaps) => set({ pinedMaps }),
- // Setter tambahan untuk posisi sementara
setTempPositionCreate: (position) => set({ tempPositionCreate: position }),
setTempPositionEdit: (position) => set({ tempPositionEdit: position }),
- // Opsional: Reset jika ingin clear saat keluar halaman
resetTempPositionCreate: () => set({ tempPositionCreate: null }),
resetTempPositionEdit: () => set({ tempPositionEdit: null }),
+
+ getDefaultCenter: () => DEFAULT_CENTER,
+
+ isPinned: () => {
+ const p = get().selectedPosition;
+ if (!p || typeof p.lat !== 'number' || typeof p.lng !== 'number') return false;
+ const isDefault =
+ Math.abs(p.lat - DEFAULT_CENTER.lat) < 1e-6 &&
+ Math.abs(p.lng - DEFAULT_CENTER.lng) < 1e-6;
+ return !isDefault;
+ },
+
+ resetPin: () => set({
+ selectedPosition: null,
+ addressMaps: '',
+ detailAddress: {},
+ pinedMaps: false,
+ tempPositionCreate: null,
+ tempPositionEdit: null,
+ }),
}));
diff --git a/src/lib/pengajuan-tempo/component/PengajuanTempo.jsx b/src/lib/pengajuan-tempo/component/PengajuanTempo.jsx
index d59bfd75..096fe1ed 100644
--- a/src/lib/pengajuan-tempo/component/PengajuanTempo.jsx
+++ b/src/lib/pengajuan-tempo/component/PengajuanTempo.jsx
@@ -38,8 +38,9 @@ const PengajuanTempo = () => {
const [bigData, setBigData] = useState();
const [idTempo, setIdTempo] = useState(0);
const { form, errors, validate, updateForm } = usePengajuanTempoStore();
- const { control, watch, setValue } = useForm();
+ const { control, watch, setValue, setError } = useForm();
const auth = useAuth();
+ console.log('auth', auth);
const router = useRouter();
const [BannerTempo, setBannerTempo] = useState();
const { formDokumen, errorsDokumen, validateDokumen, updateFormDokumen } =
@@ -426,111 +427,185 @@ const PengajuanTempo = () => {
}
};
- const handleDaftarTempo = async () => {
- for (const error of stepDivsError) {
- if (error.length > 0) {
- return;
+const handleDaftarTempo = async () => {
+const phones = [
+ { key: 'mobile', label: 'No. HP Perusahaan', value: form.mobile?.trim() },
+ {
+ key: 'direkturMobile',
+ label: 'No. HP Direktur',
+ value: formKontakPerson.direkturMobile?.trim(),
+ },
+ {
+ key: 'purchasingMobile',
+ label: 'No. HP Purchasing',
+ value: formKontakPerson.purchasingMobile?.trim(),
+ },
+ {
+ key: 'financeMobile',
+ label: 'No. HP Finance',
+ value: formKontakPerson.financeMobile?.trim(),
+ },
+ {
+ key: 'PICBarangMobile',
+ label: 'No. HP PIC Barang',
+ value: formPengiriman.PICBarangMobile?.trim(),
+ },
+ {
+ key: 'invoicePicMobile',
+ label: 'No. HP PIC Invoice',
+ value: formPengiriman.invoicePicMobile?.trim(),
+ },
+].filter((p) => p.value);
+
+
+ const seen = new Map();
+ let firstErrorField = null;
+
+ // Reset error manual
+ phones.forEach((p) => setError(p.key, { type: 'manual', message: '' }));
+
+ for (const phone of phones) {
+ if (!seen.has(phone.value)) {
+ seen.set(phone.value, phone);
+ } else {
+ const first = seen.get(phone.value);
+
+ // Tampilkan toast
+ toast.error(`${phone.label} tidak boleh sama dengan ${first.label}`);
+
+ // Error merah di bawah input
+ setError(phone.key, {
+ type: 'manual',
+ message: `${phone.label} tidak boleh sama dengan ${first.label}`,
+ });
+
+ // Pasangan pertama yang duplikat
+ setError(first.key, {
+ type: 'manual',
+ message: `${first.label} tidak boleh sama dengan ${phone.label}`,
+ });
+
+ if (!firstErrorField) {
+ firstErrorField = phone.key;
}
}
+ }
+
+ if (firstErrorField) {
+ setTimeout(() => {
+ const el = document.querySelector(`[name="${firstErrorField}"]`);
+ if (el) {
+ el.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ el.focus();
+ }
+ }, 100);
+ return;
+ }
- // Filter hanya dokumen dengan `format` yang tidak undefined
- const formattedDokumen = Object.entries(formDokumen)
- .filter(([_, doc]) => doc.format !== undefined) // Hanya dokumen dengan `format` tidak undefined
- .map(([key, doc]) => ({
- documentName: key,
- details: {
- name: doc.name,
- format: doc.format,
- base64: doc.base64,
- },
- }));
+ for (const error of stepDivsError) {
+ if (error.length > 0) {
+ return;
+ }
+ }
+
+ const formattedDokumen = Object.entries(formDokumen)
+ .filter(([_, doc]) => doc.format !== undefined)
+ .map(([key, doc]) => ({
+ documentName: key,
+ details: {
+ name: doc.name,
+ format: doc.format,
+ base64: doc.base64,
+ },
+ }));
- const toastId = toast.loading('Mengirimkan formulir pengajuan tempo...');
- setIsLoading(true);
- try {
- let address4;
- let address3;
- const address = await createPengajuanTempoApi({
- id: 0,
+ const toastId = toast.loading('Mengirimkan formulir pengajuan tempo...');
+ setIsLoading(true);
+
+ try {
+ let address4;
+ let address3;
+ const address = await createPengajuanTempoApi({
+ id: 0,
+ partner_id: auth.partnerId,
+ user_id: auth.parentId ? auth.parentId : auth.partnerId,
+ tempo_request: false,
+ ...form,
+ });
+
+ if (address.id) {
+ const address2 = await createPengajuanTempoApi({
+ id: address.id,
partner_id: auth.partnerId,
- user_id: auth.parentId ? auth.parentId : auth.partnerId,
+ user_id: address.userId,
tempo_request: false,
- ...form,
+ ...formKontakPerson,
});
- if (address.id) {
- const address2 = await createPengajuanTempoApi({
- id: address.id,
+
+ if (address2.id) {
+ address3 = await createPengajuanTempoApi({
+ id: address2.id,
partner_id: auth.partnerId,
- user_id: address.userId,
+ user_id: address2.userId,
tempo_request: false,
- ...formKontakPerson,
- });
- if (address2.id) {
- address3 = await createPengajuanTempoApi({
- id: address2.id,
- partner_id: auth.partnerId,
- user_id: address2.userId,
- tempo_request: false,
- ...formPengiriman,
- formDokumenProsedur: formPengiriman.dokumenProsedur
+ ...formPengiriman,
+ formDokumenProsedur: formPengiriman.dokumenProsedur
? JSON.stringify(formPengiriman.dokumenProsedur)
: false,
+ });
+
+ if (address3.id && formattedDokumen.length > 0) {
+ address4 = await createPengajuanTempoApi({
+ id: address3.id,
+ partner_id: auth.partnerId,
+ user_id: address3.userId,
+ tempo_request: true,
+ formDocs: JSON.stringify(formattedDokumen),
+ });
+ } else {
+ address4 = await createPengajuanTempoApi({
+ id: address3.id,
+ partner_id: auth.partnerId,
+ user_id: address3.userId,
+ tempo_request: true,
});
- if (address3.id && formattedDokumen.length > 0) {
- // Kirim dokumen yang sudah difilter
- address4 = await createPengajuanTempoApi({
- id: address3.id,
- partner_id: auth.partnerId,
- user_id: address3.userId,
- tempo_request: true,
- formDocs: JSON.stringify(formattedDokumen),
- });
- } else {
- address4 = await createPengajuanTempoApi({
- id: address3.id,
- partner_id: auth.partnerId,
- user_id: address3.userId,
- tempo_request: true,
- });
- }
}
}
+ }
- if (address4?.id) {
- toast.success('Pengajuan tempo berhasil dilakukan');
- const toastId = toast.loading('Mengubah status akun...');
- const isUpdated = await editAuthTempo();
- if (isUpdated?.user) {
- const update = await setAuth(isUpdated.user);
- if (update) {
- toast.dismiss(toastId);
- setIsLoading(false);
- toast.success('Berhasil mengubah status akun', { duration: 1000 });
- router.push('/pengajuan-tempo/finish');
- } else {
- toast.dismiss(toastId);
- setIsLoading(false);
- toast.success('Pengajuan tempo berhasil dilakukan');
- toast.error('Gagal mengubah status akun', { duration: 1000 });
- router.push('/pengajuan-tempo');
- }
- removeFromLocalStorage();
- return;
+ if (address4?.id) {
+ toast.success('Pengajuan tempo berhasil dilakukan');
+ const toastId2 = toast.loading('Mengubah status akun...');
+ const isUpdated = await editAuthTempo();
+ if (isUpdated?.user) {
+ const update = await setAuth(isUpdated.user);
+ if (update) {
+ toast.dismiss(toastId2);
+ setIsLoading(false);
+ toast.success('Berhasil mengubah status akun', { duration: 1000 });
+ router.push('/pengajuan-tempo/finish');
+ } else {
+ toast.dismiss(toastId2);
+ setIsLoading(false);
+ toast.success('Pengajuan tempo berhasil dilakukan');
+ toast.error('Gagal mengubah status akun', { duration: 1000 });
+ router.push('/pengajuan-tempo');
}
- } else {
- toast.dismiss(toastId);
- setIsLoading(false);
-
- toast.error('Terjadi kesalahan dalam pengiriman formulir');
+ removeFromLocalStorage();
+ return;
}
- } catch (error) {
+ } else {
toast.dismiss(toastId);
setIsLoading(false);
-
toast.error('Terjadi kesalahan dalam pengiriman formulir');
- console.error(error);
}
- };
+ } catch (error) {
+ toast.dismiss(toastId);
+ setIsLoading(false);
+ toast.error('Terjadi kesalahan dalam pengiriman formulir');
+ console.error(error);
+ }
+};
const removeFromLocalStorage = () => {
for (const key of stepLabels) {
@@ -660,10 +735,8 @@ const PengajuanTempo = () => {
isDisabled={!isCheckedTNC || isLoading}
onClick={handleDaftarTempo}
>
- {isLoading
- ? 'Loading...'
- : 'Daftar Tempo'}
- {!isLoading && <ChevronRightIcon className='w-5' />}
+ {isLoading ? 'Loading...' : 'Daftar Tempo'}
+ {!isLoading && <ChevronRightIcon className='w-5' />}
</Button>
</div>
)}
diff --git a/src/lib/quotation/components/Quotation.jsx b/src/lib/quotation/components/Quotation.jsx
index 2f4d6c46..f0791512 100644
--- a/src/lib/quotation/components/Quotation.jsx
+++ b/src/lib/quotation/components/Quotation.jsx
@@ -10,7 +10,6 @@ import { deleteItemCart, getCart, getItemCart } from '@/core/utils/cart';
import currencyFormat from '@/core/utils/currencyFormat';
import { toast } from 'react-hot-toast';
import { useProductCartContext } from '@/contexts/ProductCartContext';
-// 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';
@@ -19,11 +18,11 @@ 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 { useAddress } from '@/lib/checkout/stores/useAdress';
+import { useCheckout } from '@/lib/checkout/stores/stateCheckout';
import {
PickupAddress,
SectionAddress,
- SectionExpedisi,
- SectionListService,
SectionValidation,
calculateEstimatedArrival,
splitDuration,
@@ -31,7 +30,8 @@ import {
import addressesApi from '@/lib/address/api/addressesApi';
import { getItemAddress } from '@/core/utils/address';
import ExpedisiList from '../../checkout/api/ExpedisiList';
-import axios from 'axios';
+import SectionQuotationExpedition from '@/lib/checkout/components/SectionQuotationExpedition';
+import { useQuotation } from '@/lib/checkout/stores/stateQuotation';
const { checkoutApi } = require('@/lib/checkout/api/checkoutApi');
const { getProductsCheckout } = require('@/lib/checkout/api/checkoutApi');
@@ -51,41 +51,48 @@ const Quotation = () => {
const { setRefreshCart } = useProductCartContext();
const SELF_PICKUP_ID = 32;
- const [products, setProducts] = useState(null);
- const [totalAmount, setTotalAmount] = useState(0);
+ 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 [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('');
+ const [isLoading, setIsLoading] = useState(false);
+ const [etdFix, setEtdFix] = useState(null);
+
+ const {
+ selectedAddress,
+ setSelectedAddress,
+ addresses,
+ setAddresses,
+ setAddressMaps,
+ setCoordinate,
+ setPostalCode,
+ } = useAddress();
+
+ const {
+ products,
+ setProducts,
+ checkWeigth,
+ setCheckWeight,
+ checkoutValidation,
+ setCheckoutValidation,
+ biayaKirim,
+ etd,
+ unit,
+ selectedCourier,
+ selectedCourierId,
+ selectedService,
+ listExpedisi,
+ setExpedisi,
+ productSla,
+ setProductSla,
+ setBiayaKirim,
+ setUnit,
+ setEtd,
+ setSelectedCourier,
+ setSelectedService,
+ setSelectedCourierId
+ } = useQuotation();
+
useEffect(() => {
if (!auth) return;
@@ -116,198 +123,145 @@ const Quotation = () => {
return addresses[0];
};
+ let ship = matchAddress('shipping');
+
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();
+ setPostalCode(ship?.zip);
+ if (ship?.addressMap) {
+ setAddressMaps(ship?.addressMap);
+ setCoordinate({
+ destination_latitude: ship?.latitude,
+ destination_longitude: ship?.longtitude,
+ });
}
- }, [selectedCarrier, selectedAddress, totalWeight]);
+ }, [addresses]);
useEffect(() => {
- if (selectedExpedisi) {
- let serviceType = selectedExpedisi.split(',');
- if (serviceType[0] === 0) return;
+ const loadExpedisi = async () => {
+ let dataExpedisi = await ExpedisiList();
+ dataExpedisi = dataExpedisi.map((expedisi) => ({
+ value: expedisi.id,
+ label: expedisi.name,
+ carrierId: expedisi.deliveryCarrierId,
+ logo: expedisi.image,
+ }));
+ setExpedisi(dataExpedisi);
+ };
+
+ loadExpedisi();
- setselectedCarrier(serviceType[0]);
- setselectedCarrierId(serviceType[1]);
- setListServiceExpedisi([]);
- }
- }, [selectedExpedisi]);
+ const handlePopState = () => {
+ router.push('/shop/cart');
+ };
- useEffect(() => {
- if (selectedServiceType) {
- let serviceType = selectedServiceType.split(',');
- setBiayaKirim(serviceType[0]);
- setselectedExpedisiService(serviceType[1]);
- setEtd(serviceType[2]);
- }
- }, [selectedServiceType]);
+ window.onpopstate = handlePopState;
- useEffect(() => {
- if (etd) setEtdFix(calculateEstimatedArrival(etd));
- }, [etd]);
+ return () => {
+ window.onpopstate = null;
+ };
+ }, []);
useEffect(() => {
if (isApproval) {
- setselectedCarrierId(1);
- setselectedExpedisiService('indoteknik');
+ setSelectedCourierId(1);
+ setSelectedCourier('indoteknik');
}
}, [isApproval]);
- // end set up address and carrier
-
- useEffect(() => {
- const loadProducts = async () => {
- const cart = getCart();
- const variantIds = _.filter(cart, (o) => o.selected == true)
- .map((o) => o.productId)
- .join(',');
- const dataProducts = await CartApi({ variantIds });
- const productsWithQuantity = dataProducts?.map((product) => {
- return {
- ...product,
- quantity: getItemCart({ productId: product.id }).quantity,
- };
- });
- if (productsWithQuantity) {
- Promise.all(productsWithQuantity).then((resolvedProducts) => {
- setProducts(resolvedProducts);
- });
- }
- };
- loadExpedisi();
- // loadProducts()
- }, []);
-
useEffect(() => {
- setProducts(cartCheckout?.products);
- setCheckWeight(cartCheckout?.hasProductWithoutWeight);
- setTotalWeight(cartCheckout?.totalWeight.g);
+ if (cartCheckout) {
+ setProducts(cartCheckout?.products);
+ setCheckWeight(cartCheckout?.hasProductWithoutWeight);
+ }
}, [cartCheckout]);
useEffect(() => {
if (products) {
- let calculateTotalAmount = 0;
- let calculateTotalDiscountAmount = 0;
- products.forEach((product) => {
- calculateTotalAmount += product.price.price * product.quantity;
- calculateTotalDiscountAmount +=
- (product.price.price - product.price.priceDiscount) *
- product.quantity;
- });
- setTotalAmount(calculateTotalAmount);
- setTotalDiscountAmount(calculateTotalDiscountAmount);
+ const calculateTotals = () => {
+ let calculateTotalAmount = 0;
+ let calculateTotalDiscountAmount = 0;
+
+ products.forEach((product) => {
+ calculateTotalAmount += product.price.price * product.quantity;
+ calculateTotalDiscountAmount +=
+ (product.price.price - product.price.priceDiscount) * product.quantity;
+ });
+
+ setTotalAmount(calculateTotalAmount);
+ setTotalDiscountAmount(calculateTotalDiscountAmount);
+ };
+
+ calculateTotals();
}
}, [products]);
- const [isLoading, setIsLoading] = useState(false);
+ useEffect(() => {
+ if (etd) {
+ setEtdFix(calculateEstimatedArrival(etd));
+ }
+ }, [etd]);
const checkout = async () => {
- // validation checkout
- if (selectedExpedisi === 0 && !isApproval) {
+ // Validation
+ if (!selectedCourierId && !isApproval) {
setCheckoutValidation(true);
- if (expedisiValidation.current) {
- const position = expedisiValidation.current.getBoundingClientRect();
- window.scrollTo({
- top: position.top - 300 + window.pageYOffset,
- behavior: 'smooth',
- });
- }
+ toast.error('Silahkan pilih ekspedisi');
return;
}
- if (selectedCarrier != 1 && biayaKirim == 0 && !isApproval) {
+
+ if (selectedCourierId !== SELF_PICKUP_ID && biayaKirim === 0 && !isApproval) {
toast.error('Maaf, layanan tidak tersedia. Mohon pilih expedisi lain.');
return;
}
- if (!products || products.length == 0) return;
+ if (!products || products.length === 0) return;
- if (isApproval && note_websiteText == '') {
+ if (isApproval && note_websiteText === '') {
toast.error('Maaf, Note wajib dimasukkan.');
return;
}
setIsLoading(true);
- const productOrder = products.map((product) => ({
- product_id: product.id,
- quantity: product.quantity,
- }));
- let data = {
- partner_shipping_id: selectedAddress.shipping.id,
- partner_invoice_id: selectedAddress.invoicing.id,
- user_id: auth.id,
- order_line: JSON.stringify(productOrder),
- delivery_amount: biayaKirim,
- 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}`);
- setRefreshCart(true);
- return;
+ try {
+ const productOrder = products.map((product) => ({
+ product_id: product.id,
+ quantity: product.quantity,
+ }));
+
+ const data = {
+ partner_shipping_id: selectedAddress.shipping.id,
+ partner_invoice_id: selectedAddress.invoicing.id,
+ user_id: auth.id,
+ order_line: JSON.stringify(productOrder),
+ delivery_amount: biayaKirim,
+ carrier_id: selectedCourierId,
+ estimated_arrival_days: splitDuration(etd),
+ delivery_service_type: selectedService?.service_type || selectedCourier,
+ note_website: note_websiteText,
+ };
+
+ const isSuccess = await checkoutApi({ data });
+
+ if (isSuccess?.id) {
+ for (const product of products) {
+ deleteItemCart({ productId: product.id });
+ }
+ router.push(`/shop/quotation/finish?id=${isSuccess.id}`);
+ setRefreshCart(true);
+ } else {
+ toast.error('Gagal melakukan transaksi, terjadi kesalahan internal');
+ }
+ } catch (error) {
+ toast.error('Terjadi kesalahan saat memproses quotation');
+ } finally {
+ setIsLoading(false);
}
-
- toast.error('Gagal melakukan transaksi, terjadi kesalahan internal');
};
+
const taxTotal = (totalAmount - totalDiscountAmount) * (PPN - 1);
return (
@@ -327,21 +281,21 @@ const Quotation = () => {
<Divider />
- {selectedCarrierId == SELF_PICKUP_ID && (
+ {selectedCourierId == 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'
+ className='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'
+ className='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>
+ <span className='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
@@ -351,10 +305,10 @@ const Quotation = () => {
</div>
)}
- {selectedCarrierId == SELF_PICKUP_ID && (
+ {selectedCourierId == SELF_PICKUP_ID && (
<PickupAddress label='Alamat Pickup' />
)}
- {selectedCarrierId != SELF_PICKUP_ID && (
+ {selectedCourierId != SELF_PICKUP_ID && (
<Skeleton
isLoaded={!!selectedAddress.invoicing && !!selectedAddress.shipping}
minHeight={320}
@@ -374,32 +328,14 @@ const Quotation = () => {
)}
<Divider />
<SectionValidation address={selectedAddress.invoicing} />
- {!isApproval && (
- <>
- <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} />
)}
</div>
+ <SectionQuotationExpedition products={products} />
<Divider />
<div className='p-4'>
@@ -497,10 +433,10 @@ 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'>
- {selectedCarrierId == SELF_PICKUP_ID && (
+ {selectedCourierId == SELF_PICKUP_ID && (
<PickupAddress label='Alamat Pickup' />
)}
- {selectedCarrierId != SELF_PICKUP_ID && (
+ {selectedCourierId != SELF_PICKUP_ID && (
<Skeleton
isLoaded={
!!selectedAddress.invoicing && !!selectedAddress.shipping
@@ -522,31 +458,16 @@ const Quotation = () => {
)}
<Divider />
<SectionValidation address={selectedAddress.invoicing} />
- {!isApproval && (
- <SectionExpedisi
- address={selectedAddress.shipping}
- listExpedisi={listExpedisi}
- setSelectedExpedisi={setSelectedExpedisi}
- checkWeigth={checkWeigth}
- checkoutValidation={checkoutValidation}
- expedisiValidation={expedisiValidation}
- loadingRajaOngkir={loadingRajaOngkir}
- />
- )}
-
+
+ <SectionQuotationExpedition products={products} />
<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'>
@@ -601,9 +522,6 @@ const Quotation = () => {
)}
</div>
</div>
- {/* <p className='text-caption-2 text-gray_r-11 mb-2'>
- *) Belum termasuk biaya pengiriman
- </p> */}
<p className='text-caption-2 text-gray_r-11 leading-5'>
Dengan melakukan pembelian melalui website Indoteknik, saya
menyetujui{' '}
@@ -655,4 +573,4 @@ const Quotation = () => {
);
};
-export default Quotation;
+export default Quotation; \ No newline at end of file
diff --git a/src/lib/transaction/components/Transaction.jsx b/src/lib/transaction/components/Transaction.jsx
index 842567f8..77e60dc1 100644
--- a/src/lib/transaction/components/Transaction.jsx
+++ b/src/lib/transaction/components/Transaction.jsx
@@ -45,9 +45,11 @@ import {
downloadInvoice,
downloadTaxInvoice,
} from '@/lib/invoice/utils/invoices';
+import { Download } from 'lucide-react';
import axios from 'axios';
import InformationSection from '../../treckingAwb/component/InformationSection';
import { Button } from '@chakra-ui/react';
+import { div } from 'lodash-contrib';
const Transaction = ({ id }) => {
const PPN = process.env.NEXT_PUBLIC_PPN;
const router = useRouter();
@@ -56,7 +58,6 @@ const Transaction = ({ id }) => {
const [reason, setReason] = useState('');
const auth = useAuth();
const { transaction } = useTransaction({ id });
- console.log('transaction', transaction);
const statusApprovalWeb = transaction.data?.approvalStep;
const [isLoading, setIsLoading] = useState(false);
const { queryAirwayBill } = useAirwayBill({ orderId: id });
@@ -87,8 +88,6 @@ const Transaction = ({ id }) => {
setTotalDiscountAmount(calculateTotalDiscountAmount);
}
}, [transaction.data, transaction.isLoading]);
- console.log('totalAmount', totalAmount);
- console.log('totalDiscountAmount', totalDiscountAmount);
const submitUploadPo = async () => {
const file = poFile.current.files[0];
const name = poNumber.current.value;
@@ -195,6 +194,7 @@ const Transaction = ({ id }) => {
}
toast.success('Berhasil melanjutkan pesanan');
transaction.refetch();
+ // console.log(transaction);
/* const midtrans = async () => {
for (const product of products) deleteItemCart({ productId: product.id });
@@ -336,7 +336,7 @@ const Transaction = ({ id }) => {
const [day, month, year] = dateString.split('/');
return `${day} ${months[parseInt(month, 10) - 1]} ${year}`;
};
-
+ // console.log(transaction);
return (
transaction.data?.name && (
<>
@@ -526,7 +526,7 @@ const Transaction = ({ id }) => {
<div className='flex flex-row justify-between items-center gap-2 px-4'>
<div className='flex flex-col justify-start items-start gap-2'>
- <div className='font-medium'>Status Transaksi</div>
+ <div className='font-semibold'>{transaction.data?.name}</div>
<TransactionStatusBadge status={transaction.data?.status} />
</div>
<div>
@@ -559,16 +559,7 @@ const Transaction = ({ id }) => {
<Divider />
<div className='flex flex-col gap-y-4 p-4'>
- <DescriptionRow label='Status Transaksi'>
- <div className='flex justify-end'>
- <TransactionStatusBadge status={transaction.data?.status} />
- </div>
- </DescriptionRow>
- <DescriptionRow label='Status Transaksi'>
- <div className='flex justify-end font-semibold text-red-500'>
- {transaction.data?.expectedReadyToShip}
- </div>
- </DescriptionRow>
+ <h4 className="font-semibold">Detail Order</h4>
<DescriptionRow label='No Transaksi'>
<p className='font-semibold'>{transaction.data?.name}</p>
</DescriptionRow>
@@ -580,9 +571,6 @@ const Transaction = ({ id }) => {
<DescriptionRow label='Purchase Order'>
{transaction.data?.purchaseOrderName || '-'}
</DescriptionRow>
- <DescriptionRow label='Ketentuan Pembayaran'>
- {transaction.data?.paymentTerm || '-'}
- </DescriptionRow>
<DescriptionRow label='Nama Sales'>
{transaction.data?.sales}
</DescriptionRow>
@@ -590,103 +578,80 @@ const Transaction = ({ id }) => {
<Divider />
+ <div className='flex flex-col gap-y-4 p-4'>
+ <h4 className="font-semibold">Alamat Pengiriman</h4>
+ <DescriptionRow label='Nama Penerima'>
+ <p className='font-semibold'>{transaction?.data?.address?.customer?.name}</p>
+ </DescriptionRow>
+ <DescriptionRow label='No. Telp'>
+ {transaction?.data?.address?.customer?.phone
+ ? transaction?.data?.address?.customer?.phone
+ : '-'}
+ </DescriptionRow>
+ <DescriptionRow label='Email'>
+ {transaction?.data?.address?.customer?.email
+ ? transaction?.data?.address?.customer?.email
+ : '-'}
+ </DescriptionRow>
+ <DescriptionRow label='Alamat Pengiriman'>
+ {transaction?.data?.address?.customer?.alamatBisnis}
+ </DescriptionRow>
+ </div>
+
+ <Divider />
<div className='p-4'>
- <div className='flex flex-row justify-between items-center'>
- <div className='font-medium'>Info Pengiriman</div>
- <span
- className='text-red-500'
- onClick={() => setIdAWB(transaction?.data?.pickings[0]?.id)}
+ <div className='font-medium mb-4'>Info Pengiriman</div>
+ {transaction?.data?.pickings.length == 0 && (
+ <div className='badge-red text-sm'>
+ Belum ada pengiriman
+ </div>
+ )}
+ {transaction?.data?.pickings?.map((airway) => (
+ <div
+ key={airway?.id}
+ className='border border-gray_r-6 rounded mb-3'
>
- Lihat Detail
- </span>
- </div>
- <hr className='mt-4 mb-4 border border-gray-100' />
- <div className='flex flex-col gap-y-4'>
- <DescriptionRow label='Dokumen Pengiriman'>
- <p className='text-red-500 font-semibold text-start'>
- {transaction.data?.pickings?.length == 0
- ? 'Belum ada pengiriman'
- : transaction?.data?.pickings[0].name}
- </p>
- </DescriptionRow>
- <DescriptionRow label='Kurir'>
- <p className='text-start'>
- {transaction?.data?.pickings[0]?.carrierName ? (
- <p className=' text-nowrap'>
- {transaction?.data?.pickings[0]?.carrierName}
- </p>
- ) : (
- '-'
- )}
- </p>
- </DescriptionRow>
- <DescriptionRow label='Jenis Service'>
- <p className='text-start'>
- {transaction?.data?.pickings[0]?.serviceType &&
- transaction?.data?.pickings[0]?.carrierName
- ? transaction?.data?.pickings[0]?.serviceType
- : '-'}
- </p>
- </DescriptionRow>
- <DescriptionRow label='Nomor Resi'>
- <div className='flex flex-row gap-1 text-start'>
- {transaction?.data?.pickings[0]?.trackingNumber || '-'}
- {transaction?.data?.pickings[0]?.trackingNumber && (
- <button
- className={`${
- copied ? 'text-gray-400' : 'text-red-600 '
- }`}
- onClick={() =>
- handleCopyClick(
- transaction?.data?.pickings[0]?.trackingNumber
- )
+ <InformationSection manifests={airway} />
+ <div className='p-4'>
+ <button
+ className='bg-transparent text-red-600 hover:underline p-0 font-semibold'
+ onClick={() => {
+ if (airway?.waybillNumber == '-') {
+ toast.error('Nomor Resi belum tersedia');
+ return;
}
- >
- <svg
- aria-hidden='true'
- fill='none'
- stroke='currentColor'
- stroke-width='1.5'
- viewBox='0 0 24 24'
- className='w-5 h-6'
- >
- <path
- d='M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75'
- stroke-linecap='round'
- stroke-linejoin='round'
- ></path>
- </svg>
- </button>
- )}
- </div>
- </DescriptionRow>
- <DescriptionRow label='Estimasi Tiba'>
- <p className='text-start'>
- {transaction?.data?.pickings[0]?.eta
- ? transaction?.data?.pickings[0]?.eta
- : '-'}
- </p>
- </DescriptionRow>
- <DescriptionRow label='Alamat Pengiriman'>
- <div className='flex flex-col justify-start items-start'>
- <div className='text-start text-nowrap truncate w-full'>
- {transaction?.data?.address?.customer?.name}
- </div>
- <div className='text-start'>
- {transaction?.data?.address?.customer?.phone
- ? transaction?.data?.address?.customer?.phone
- : '-'}
- </div>
- <div className='text-start'>
- {transaction?.data?.address?.customer?.alamatBisnis}
- </div>
+ setIdAWB(airway.id);
+ }}
+ >
+ Lacak Pengiriman
+ </button>
</div>
- </DescriptionRow>
- </div>
+ </div>
+ // <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>
- {/* <Divider />
+ <Divider />
<div className='p-4'>
<p className='font-medium'>Invoice</p>
@@ -715,17 +680,17 @@ const Transaction = ({ id }) => {
<div className='badge-red text-sm px-2'>Belum ada invoice</div>
)}
</div>
- </div> */}
+ </div>
<Divider />
- {/* {!auth?.feature.soApproval && (
+ {!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>
+ <div className='flex items-center justify-between'>
+ <p className='text-gray_r-11 leading-none'>Dokumen PO : </p>
<button
type='button'
className='inline-block text-danger-500'
@@ -747,26 +712,22 @@ const Transaction = ({ id }) => {
</div>
)}
- <Divider /> */}
+ <Divider />
<div className='font-medium p-4'>Detail Produk</div>
{transaction?.data?.products.length > 0 ? (
<div className='p-4 pt-0 flex flex-col gap-y-3'>
- <VariantGroupCard variants={transaction.data?.products} buyMore />
+ <VariantGroupCard variants={transaction.data?.products}/>
<div className='font-medium'>Rincian Pembayaran</div>
<div className='flex justify-between mt-1'>
<p className='text-gray_r-12/70'>Metode Pembayaran</p>
<p>
- {transaction.data?.paymentType
- ? transaction.data?.paymentType
- ?.replace(/_/g, ' ')
- .replace(/\b\w/g, (char) => char.toUpperCase())
- : '-'}
+ {transaction.data?.paymentTerm || '-'}
</p>
</div>
<div className='flex justify-between mt-1'>
<p className='text-gray_r-12/70'>Berat Barang</p>
- <p>{transaction.data?.pickings[0]?.weightTotal + ' Kg'}</p>
+ <p>{(transaction.data?.products?.reduce((total, item) => total + (item.weight || 0), 0)) + ' Kg'}</p>
</div>
<hr className='mt-1 border border-gray-100' />
<div className='flex justify-between mt-1'>
@@ -822,7 +783,7 @@ const Transaction = ({ id }) => {
{transaction.data?.status === 'draft' && (
<div className='p-4 pt-0'>
<button
- className='btn-yellow w-full mt-4'
+ className='btn-light w-full mt-4'
disabled={transaction.data?.status != 'draft'}
onClick={() => downloadQuotation(transaction.data)}
>
@@ -834,6 +795,15 @@ const Transaction = ({ id }) => {
>
Batalkan Transaksi
</button>
+ {transaction.data?.status == 'draft' &&
+ transaction?.data?.purchaseOrderFile && (
+ <button
+ className='btn-yellow w-full mt-4'
+ onClick={openContinueTransaction}
+ >
+ Lanjutkan Transaksi
+ </button>
+ )}
</div>
)}
</MobileView>
@@ -876,15 +846,44 @@ const Transaction = ({ id }) => {
{transaction?.data?.name}
</span>
<TransactionStatusBadge status={transaction?.data?.status} />
+ {transaction.data?.status === 'draft' && (
+ <div className='flex items-center justify-between w-full'>
+ <button
+ type='button'
+ className='btn-light px-3 py-2'
+ onClick={() => downloadQuotation(transaction.data)}
+ >
+ <Download size={12} />
+ </button>
+
+ <div className="flex gap-x-4">
+ <button
+ className='btn-solid-red'
+ onClick={openCancelTransaction}
+ >
+ Batalkan Transaksi
+ </button>
+ {transaction.data?.status == 'draft' &&
+ transaction?.data?.purchaseOrderFile && (
+ <button
+ className='btn-yellow'
+ onClick={openContinueTransaction}
+ >
+ Lanjutkan Transaksi
+ </button>
+ )}
+ </div>
+ </div>
+ )}
</div>
- {transaction.data?.status === 'draft' && (
+ {/* {transaction.data?.status === 'draft' && (
<div className='flex gap-x-4'>
<button
type='button'
- className='btn-yellow px-3 py-2 mr-auto'
+ className='btn-light px-3 py-2 mr-auto'
onClick={() => downloadQuotation(transaction.data)}
>
- Download
+ <Download size={12} />
</button>
<button
className='btn-solid-red'
@@ -892,8 +891,18 @@ const Transaction = ({ id }) => {
>
Batalkan Transaksi
</button>
+
+ {transaction.data?.status == 'draft' &&
+ transaction?.data?.purchaseOrderFile && (
+ <button
+ className='btn-yellow'
+ onClick={openContinueTransaction}
+ >
+ Lanjutkan Transaksi
+ </button>
+ )}
</div>
- )}
+ )} */}
<div className='grid grid-cols-2 gap-x-6 mt-4'>
<div className='grid grid-cols-[35%_65%] gap-y-4'>
@@ -1016,88 +1025,54 @@ const Transaction = ({ id }) => {
</div>
<div className='flex flex-col w-1/2 justify-start items-start'>
<span className='text-h-sm font-medium mb-2'>
- Info Pengiriman
+ Info Ekspedisi
</span>
<div className='grid grid-cols-[34%_2%_64%] gap-y-4 w-full'>
- <div>Nomor Resi</div>
- <div>: </div>
- <div className='flex flex-row gap-1 '>
- {transaction?.data?.pickings[0]?.trackingNumber || '-'}
- {transaction?.data?.pickings[0]?.trackingNumber && (
- <button
- className={`${
- copied ? 'text-gray-400' : 'text-red-600 '
- }`}
- onClick={() =>
- handleCopyClick(
- transaction?.data?.pickings[0]?.trackingNumber
- )
- }
- >
- <svg
- aria-hidden='true'
- fill='none'
- stroke='currentColor'
- stroke-width='1.5'
- viewBox='0 0 24 24'
- className='w-5 h-6'
- >
- <path
- d='M15.75 17.25v3.375c0 .621-.504 1.125-1.125 1.125h-9.75a1.125 1.125 0 01-1.125-1.125V7.875c0-.621.504-1.125 1.125-1.125H6.75a9.06 9.06 0 011.5.124m7.5 10.376h3.375c.621 0 1.125-.504 1.125-1.125V11.25c0-4.46-3.243-8.161-7.5-8.876a9.06 9.06 0 00-1.5-.124H9.375c-.621 0-1.125.504-1.125 1.125v3.5m7.5 10.375H9.375a1.125 1.125 0 01-1.125-1.125v-9.25m12 6.625v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5a3.375 3.375 0 00-3.375-3.375H9.75'
- stroke-linecap='round'
- stroke-linejoin='round'
- ></path>
- </svg>
- </button>
- )}
- </div>
-
<div>Kurir</div>
<div>: </div>
- {transaction?.data?.pickings[0]?.carrierName ? (
+ {transaction?.data?.carrierName ? (
<div className='flex flex-row w-full gap-1 items-center justify-start '>
<p className=' text-nowrap'>
- {transaction?.data?.pickings[0]?.carrierName}
+ {transaction?.data?.carrierName}
</p>
- <span
- className='text-red-500 text-sm font-semibold hover:cursor-pointer'
- onClick={() =>
- setIdAWB(transaction?.data?.pickings[0]?.id)
- }
- >
- Lacak Pengiriman
- </span>
</div>
) : (
'-'
)}
+ {transaction?.data?.carrierId !== 32 &&(
+ <>
+ <div>Jenis Service</div>
+ <div>: </div>
+ <div>
+ {' '}
+ {transaction?.data?.serviceType
+ ? transaction?.data?.serviceType
+ : '-'}
+ </div>
+ </>
+ )}
- <div>Jenis Service</div>
- <div>: </div>
- <div>
- {' '}
- {transaction?.data?.pickings[0]?.serviceType &&
- transaction?.data?.pickings[0]?.carrierName
- ? transaction?.data?.pickings[0]?.serviceType
- : '-'}
- </div>
-
- <div>Tanggal Kirim</div>
+ <div>Estimasi Tanggal Kirim</div>
<div>: </div>
<div>
- {transaction?.data?.pickings[0]?.date
- ? formatDate(transaction?.data?.pickings[0]?.date)
- : '-'}
- </div>
-
- <div>Estimasi Tiba</div>
- <div>: </div>
- <div className='text-red-500'>
- {transaction?.data?.pickings[0]?.eta
- ? transaction?.data?.pickings[0]?.eta
+ {transaction?.data?.expectedReadyToShip
+ ? transaction?.data?.expectedReadyToShip
: '-'}
</div>
- {transaction?.data?.pickings[0] && (
+ {transaction?.data?.carrierId !== 32 &&(
+ <>
+ <div>Estimasi Tiba</div>
+ <div>: </div>
+ <div className=''>
+ {transaction?.data?.etaDateStart && transaction?.data?.etaDateEnd ? (
+ `${transaction.data.etaDateStart} - ${transaction.data.etaDateEnd}`
+ ) : (
+ '-'
+ )}
+ </div>
+ </>
+ )}
+ {transaction?.data?.pickings[0] && transaction?.data?.carrierId !== 32 && (
<div className='w-full bagian-informasi col-span-3'>
<div
class='flex items-center w-fit py-2 px-3 mb-4 text-sm border border-yellow-500 text-yellow-800 rounded-lg bg-yellow-50'
@@ -1123,72 +1098,39 @@ const Transaction = ({ id }) => {
</div>
</div>
- <div className='flex gap-x-3'>
- <div className='w-1/2'>
- <div className='text-h-sm font-semibold mt-10 mb-4'>
- Informasi Pelanggan
- </div>
- <div className='border border-gray_r-6 rounded p-3'>
- <div className='font-medium mb-4'>Detail Pelanggan</div>
- <SectionContent
- address={transaction?.data?.address?.customer}
- />
- </div>
- </div>
- <div className='w-1/2'>
- <div className='text-h-sm font-semibold mt-10 mb-4'>
- Informasi Pengiriman
+ <div className='text-h-sm font-semibold mt-4 mb-4'>
+ Informasi Pengiriman
+ </div>
+ <div className='grid grid-cols-1 md:grid-cols-2 gap-3'>
+ {transaction?.data?.pickings.length == 0 && (
+ <div className='badge-red text-sm'>
+ Belum ada pengiriman
</div>
- {transaction?.data?.pickings.length == 0 && (
- <div className='badge-red text-sm'>
- Belum ada pengiriman
- </div>
- )}
- {/* <div className='grid grid-cols-1 gap-1 w-1/2'> */}
- {transaction?.data?.pickings?.map((airway) => (
- <div
- key={airway?.id}
- className='border border-gray_r-6 rounded p-3'
- >
- <InformationSection manifests={airway} />
- <div className='p-4'>
- <button
- className='bg-transparent text-red-600 hover:underline p-0 font-semibold'
- onClick={() => {
- if (airway?.waybillNumber == '-') {
- toast.error('Nomor Resi belum tersedia');
- return;
- }
- setIdAWB(airway.id);
- }}
- >
- Lacak Pengiriman
- </button>
- </div>
+ )}
+ {transaction?.data?.pickings?.map((airway) => (
+ <div
+ key={airway?.id}
+ className='border border-gray_r-6 rounded p-3'
+ >
+ <InformationSection manifests={airway} />
+ <div className='p-4'>
+ <button
+ className='bg-transparent text-red-600 hover:underline p-0 font-semibold'
+ onClick={() => {
+ if (airway?.waybillNumber == '-') {
+ toast.error('Nomor Resi belum tersedia');
+ return;
+ }
+ setIdAWB(airway.id);
+ }}
+ >
+ Lacak Pengiriman
+ </button>
</div>
- // <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>
+ ))}
+ {/* </div> */}
+
</div>
<div className='flex '>
diff --git a/src/lib/transaction/components/Transactions.jsx b/src/lib/transaction/components/Transactions.jsx
index de93d742..5e37be50 100644
--- a/src/lib/transaction/components/Transactions.jsx
+++ b/src/lib/transaction/components/Transactions.jsx
@@ -345,10 +345,9 @@ const Transactions = ({ context = '' }) => {
}, []);
const handleBuyBack = async (products) => {
- // if (status === 'success') return;
-
try {
// setStatus('loading');
+ console.log("Products to add:", products);
const results = await Promise.all(
products.map((product) =>
@@ -358,32 +357,38 @@ const Transactions = ({ context = '' }) => {
id: product.id,
qty: product.quantity,
selected: true,
- source: 'buy', // Tetap gunakan 'buy' agar bisa masuk ke halaman pembelian
- qtyAppend: false,
+ source: 'add_to_cart',
+ qtyAppend: true,
+ }).catch(error => {
+ return { error, product };
})
)
);
- // ✅ Panggil setRefreshCart(true) setiap kali satu produk berhasil ditambahkan
+ const failedOperations = results.filter(result => result && result.error);
+ // console.log(results);
+
+ if (failedOperations.length > 0) {
+ console.error('Some products failed to add to cart:', failedOperations);
+ toast.error(`${failedOperations.length} produk gagal ditambahkan ke keranjang`);
+
+ // You might want to proceed with the successful ones or handle differently
+ if (failedOperations.length < products.length) {
+ toast.success(`${products.length - failedOperations.length} produk berhasil ditambahkan ke keranjang`);
+ setRefreshCart(true);
+ router.push('/shop/cart');
+ }
+ return;
+ }
+ // All operations succeeded
setRefreshCart(true);
-
- // setStatus('idle');
toast.success('Semua produk berhasil ditambahkan ke keranjang belanja');
- // Tampilkan notifikasi
- // toast({
- // title: 'Tambah ke keranjang',
- // description: 'Semua produk berhasil ditambahkan ke keranjang belanja',
- // status: 'success',
- // duration: 3000,
- // isClosable: true,
- // position: 'top',
- // });
-
- // Redirect ke halaman checkout
- router.push('/shop/checkout?source=buy');
+ router.push('/shop/cart');
+
} catch (error) {
console.error('Gagal menambahkan produk ke keranjang:', error);
+ toast.error('Terjadi kesalahan saat menambahkan produk ke keranjang');
// setStatus('error');
}
};
diff --git a/src/lib/treckingAwb/component/InformationSection.jsx b/src/lib/treckingAwb/component/InformationSection.jsx
index a2297af3..4b3bd5fb 100644
--- a/src/lib/treckingAwb/component/InformationSection.jsx
+++ b/src/lib/treckingAwb/component/InformationSection.jsx
@@ -69,6 +69,10 @@ const InformationSection = ({ manifests }) => {
<span className='text-red-600 font-semibold'>{manifests?.eta}</span>
</span>
</div>
+ <div className='grid grid-cols-[150px_auto]'>
+ <span>Total Product</span>
+ <span className='font-semibold'> : {Array.isArray(manifests?.products) ? manifests.products.length : 0} Product</span>
+ </div>
</div>
</div>
);
diff --git a/src/lib/treckingAwb/component/Manifest.jsx b/src/lib/treckingAwb/component/Manifest.jsx
index acb86f57..6eb0b0ac 100644
--- a/src/lib/treckingAwb/component/Manifest.jsx
+++ b/src/lib/treckingAwb/component/Manifest.jsx
@@ -223,6 +223,31 @@ const Manifest = ({ idAWB, closePopup }) => {
)
}
</div>
+
+ {/* Barang */}
+ <div className='mt-1'>
+ {Array.isArray(manifests?.products) && manifests.products.length > 0 ? (
+ <div className='flex flex-col gap-4'>
+ {manifests.products.map((product, idx) => (
+ <div key={idx} className='flex gap-4 border-b pb-4'>
+ {/* Gambar Produk */}
+ <img
+ src={product.image}
+ alt={product.name}
+ className='w-16 h-16 object-contain border'
+ />
+ {/* Info Produk */}
+ <div className='flex flex-col flex-1'>
+ <span className='font-semibold'>{product.name}</span>
+ <span className='text-sm text-gray-500'>{product.code}</span>
+ </div>
+ </div>
+ ))}
+ </div>
+ ) : (
+ <span></span>
+ )}
+ </div>
</BottomPopup>
)}
</>
diff --git a/src/lib/variant/components/VariantGroupCard.jsx b/src/lib/variant/components/VariantGroupCard.jsx
index 1e921546..7db9703b 100644
--- a/src/lib/variant/components/VariantGroupCard.jsx
+++ b/src/lib/variant/components/VariantGroupCard.jsx
@@ -10,7 +10,10 @@ const VariantGroupCard = ({ variants, ...props }) => {
return (
<>
{variantsToShow?.map((variant, index) => (
- <>
+ <div
+ key={index}
+ className='shadow border border-gray rounded-md p-4 mb-1 shadow-sm bg-white'
+ >
<VariantCard key={index} product={variant} {...props} />
{variant.program &&
variant.program.items &&
@@ -48,7 +51,7 @@ const VariantGroupCard = ({ variants, ...props }) => {
</div>
</div>
))}
- </>
+ </div>
))}
{variants.length > 2 && (
<button
diff --git a/src/pages/api/shop/search.js b/src/pages/api/shop/search.js
index e14b0ca2..3d258a97 100644
--- a/src/pages/api/shop/search.js
+++ b/src/pages/api/shop/search.js
@@ -90,7 +90,8 @@ export default async function handler(req, res) {
];
if (orderBy === 'stock') {
- filterQueries.push('stock_total_f:{0 TO *}');
+ filterQueries.push('stock_total_f:{1 TO *}&sort=stock_total_f desc');
+ // filterQueries.push(`stock_total_f DESC`)
}
if (fq && source != 'similar' && typeof fq != 'string') {
diff --git a/src/pages/my/profile.jsx b/src/pages/my/profile.jsx
index 887489e0..98e95b4f 100644
--- a/src/pages/my/profile.jsx
+++ b/src/pages/my/profile.jsx
@@ -18,6 +18,7 @@ import { useRouter } from 'next/router';
export default function Profile() {
const auth = useAuth();
+ console.log('auth', auth);
const [isChecked, setIsChecked] = useState(false);
const [ubahAkun, setUbahAkun] = useState(false);
const [isAprove, setIsAprove] = useState();
@@ -73,7 +74,7 @@ export default function Profile() {
<>
<BottomPopup
active={changeConfirmation}
- close={() => setChangeConfirmation(false)} // Menutup popup
+ close={() => setChangeConfirmation(false)} //Menutup popup
title="Ubah type akun"
>
<div className="leading-7 text-gray_r-12/80">
@@ -111,9 +112,9 @@ export default function Profile() {
<p className="ml-2">Ubah ke akun bisnis</p>
</div>
))}
- {isChecked && (
+ {isChecked && ubahAkun !== 'pending' && (
<div>
- <SwitchAccount company_type="nonpkp" />
+ <SwitchAccount company_type="nonpkp" setIsAprove={setIsAprove} setUbahAkun={setUbahAkun}/>
<Divider />
</div>
)}
@@ -148,9 +149,9 @@ export default function Profile() {
<p className="ml-2">Ubah ke akun bisnis</p>
</div>
))}
- {isChecked && (
+ {isChecked && ubahAkun !== 'pending' && (
<div>
- <SwitchAccount company_type="nonpkp" />
+ <SwitchAccount company_type="nonpkp" setIsAprove={setIsAprove} setUbahAkun={setUbahAkun}/>
<Divider />
</div>
)}