diff options
Diffstat (limited to 'src/lib')
| -rw-r--r-- | src/lib/address/components/Addresses.jsx | 140 | ||||
| -rw-r--r-- | src/lib/address/components/CreateAddress.jsx | 205 | ||||
| -rw-r--r-- | src/lib/address/components/EditAddress.jsx | 420 | ||||
| -rw-r--r-- | src/lib/auth/components/CompanyProfile.jsx | 260 | ||||
| -rw-r--r-- | src/lib/checkout/components/Checkout.jsx | 3 | ||||
| -rw-r--r-- | src/lib/home/components/PreferredBrand.jsx | 10 | ||||
| -rw-r--r-- | src/lib/home/components/PromotionProgram.jsx | 2 | ||||
| -rw-r--r-- | src/lib/product/api/productSimilarApi.js | 2 |
8 files changed, 694 insertions, 348 deletions
diff --git a/src/lib/address/components/Addresses.jsx b/src/lib/address/components/Addresses.jsx index a610d371..9ca617ae 100644 --- a/src/lib/address/components/Addresses.jsx +++ b/src/lib/address/components/Addresses.jsx @@ -1,34 +1,72 @@ -import Link from '@/core/components/elements/Link/Link' -import Spinner from '@/core/components/elements/Spinner/Spinner' -import useAuth from '@/core/hooks/useAuth' -import { getItemAddress, updateItemAddress } from '@/core/utils/address' -import { useRouter } from 'next/router' -import useAddresses from '../hooks/useAddresses' -import MobileView from '@/core/components/views/MobileView' -import DesktopView from '@/core/components/views/DesktopView' -import Menu from '@/lib/auth/components/Menu' +import { useState } from 'react'; +import Link from '@/core/components/elements/Link/Link'; +import Spinner from '@/core/components/elements/Spinner/Spinner'; +import useAuth from '@/core/hooks/useAuth'; +import { getItemAddress, updateItemAddress } from '@/core/utils/address'; +import { useRouter } from 'next/router'; +import useAddresses from '../hooks/useAddresses'; +import MobileView from '@/core/components/views/MobileView'; +import DesktopView from '@/core/components/views/DesktopView'; +import Menu from '@/lib/auth/components/Menu'; +import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; const Addresses = () => { - const router = useRouter() - const { select = null } = router.query - const { addresses } = useAddresses() - const selectedAddress = getItemAddress(select || '') + const router = useRouter(); + const { select = null } = router.query; + const { addresses } = useAddresses(); + const selectedAddress = getItemAddress(select || ''); + const [changeConfirmation, setChangeConfirmation] = useState(false); + const [selectedForChange, setSelectedForChange] = useState(null); // State baru untuk simpan alamat yang akan diubah + const changeSelectedAddress = (id) => { - if (!select) return - updateItemAddress(select, id) - router.back() - } + if (!select) return; + updateItemAddress(select, id); + router.back(); + }; + + const handleConfirmSubmit = () => { + setChangeConfirmation(false); + if (selectedForChange) { + router.push(`/my/address/${selectedForChange}/edit`); + } + }; if (addresses.isLoading) { return ( <div className='flex justify-center my-6'> <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' /> </div> - ) + ); } return ( <> + <BottomPopup + active={changeConfirmation} + close={() => setChangeConfirmation(false)} // Menutup popup + title='Ubah alamat Bisnis' + > + <div className='leading-7 text-gray_r-12/80'> + Anda akan mengubah alamat utama bisnis? + </div> + <div className='flex mt-6 gap-x-4 md:justify-end'> + <button + className='btn-solid-red flex-1 md:flex-none' + type='button' + onClick={handleConfirmSubmit} + > + Yakin + </button> + <button + className='btn-light flex-1 md:flex-none' + type='button' + onClick={() => setChangeConfirmation(false)} + > + Batal + </button> + </div> + </BottomPopup> + <MobileView> <div className='p-4'> <div className='text-right'> @@ -37,7 +75,10 @@ const Addresses = () => { <div className='grid gap-y-4 mt-4'> {addresses.data?.map((address, index) => { - const type = address.type.charAt(0).toUpperCase() + address.type.slice(1) + ' Address' + const type = + address.type.charAt(0).toUpperCase() + + address.type.slice(1) + + ' Address'; return ( <AddressCard key={index} @@ -45,9 +86,11 @@ const Addresses = () => { type={type} changeSelectedAddress={changeSelectedAddress} selectedAddress={selectedAddress} + setChangeConfirmation={setChangeConfirmation} // Memanggil popup + setSelectedForChange={setSelectedForChange} // Simpan id address yang akan diubah select={select} /> - ) + ); })} </div> </div> @@ -72,7 +115,9 @@ const Addresses = () => { <div className='grid grid-cols-2 gap-4'> {addresses.data?.map((address, index) => { const type = - address.type.charAt(0).toUpperCase() + address.type.slice(1) + ' Address' + address.type.charAt(0).toUpperCase() + + address.type.slice(1) + + ' Address'; return ( <AddressCard key={index} @@ -80,20 +125,31 @@ const Addresses = () => { type={type} changeSelectedAddress={changeSelectedAddress} selectedAddress={selectedAddress} + setChangeConfirmation={setChangeConfirmation} + setSelectedForChange={setSelectedForChange} select={select} /> - ) + ); })} </div> </div> </div> </DesktopView> </> - ) -} + ); +}; -const AddressCard = ({ address, selectedAddress, changeSelectedAddress, type, select }) => { - const auth = useAuth() +const AddressCard = ({ + address, + selectedAddress, + changeSelectedAddress, + type, + select, + setChangeConfirmation, + setSelectedForChange, +}) => { + const auth = useAuth(); + const router = useRouter(); return ( <div @@ -106,23 +162,37 @@ const AddressCard = ({ address, selectedAddress, changeSelectedAddress, type, se (select && 'cursor-pointer hover:bg-gray_r-4 transition') }`} > - <div onClick={() => changeSelectedAddress(address.id)} className={select && 'cursor-pointer'}> + <div + onClick={() => changeSelectedAddress(address.id)} + className={select && 'cursor-pointer'} + > <div className='flex gap-x-2'> <div className='badge-red'>{type}</div> - {auth?.partnerId == address.id && <div className='badge-green'>Utama</div>} + {auth?.partnerId == address.id && ( + <div className='badge-green'>Utama</div> + )} </div> <p className='font-medium mt-2'>{address.name}</p> - {address.mobile && <p className='mt-2 text-gray_r-11'>{address.mobile}</p>} + {address.mobile && ( + <p className='mt-2 text-gray_r-11'>{address.mobile}</p> + )} <p className='mt-1 leading-6 text-gray_r-11'>{address.street}</p> </div> - <Link - href={`/my/address/${address.id}/edit`} + <button + onClick={() => { + if (type == 'Contact Address' && auth.parentId) { + setSelectedForChange(address.id); // Set alamat yang dipilih + setChangeConfirmation(true); // Tampilkan popup konfirmasi + } else { + router.push(`/my/address/${address.id}/edit`); + } + }} className='btn-light bg-white mt-3 w-full !text-gray_r-11' > Ubah Alamat - </Link> + </button> </div> - ) -} + ); +}; -export default Addresses +export default Addresses; diff --git a/src/lib/address/components/CreateAddress.jsx b/src/lib/address/components/CreateAddress.jsx index 86519147..e315affe 100644 --- a/src/lib/address/components/CreateAddress.jsx +++ b/src/lib/address/components/CreateAddress.jsx @@ -1,76 +1,101 @@ -import HookFormSelect from '@/core/components/elements/Select/HookFormSelect' -import useAuth from '@/core/hooks/useAuth' -import { useRouter } from 'next/router' -import { Controller, useForm } from 'react-hook-form' -import * as Yup from 'yup' -import cityApi from '../api/cityApi' -import districtApi from '../api/districtApi' -import subDistrictApi from '../api/subDistrictApi' -import { useEffect, useState } from 'react' -import createAddressApi from '../api/createAddressApi' -import { toast } from 'react-hot-toast' -import { yupResolver } from '@hookform/resolvers/yup' -import Menu from '@/lib/auth/components/Menu' +import HookFormSelect from '@/core/components/elements/Select/HookFormSelect'; +import useAuth from '@/core/hooks/useAuth'; +import { useRouter } from 'next/router'; +import { Controller, useForm } from 'react-hook-form'; +import * as Yup from 'yup'; +import cityApi from '../api/cityApi'; +import districtApi from '../api/districtApi'; +import subDistrictApi from '../api/subDistrictApi'; +import { useEffect, useState } from 'react'; +import createAddressApi from '../api/createAddressApi'; +import { toast } from 'react-hot-toast'; +import { yupResolver } from '@hookform/resolvers/yup'; +import Menu from '@/lib/auth/components/Menu'; +import useAddresses from '../hooks/useAddresses'; const CreateAddress = () => { - const auth = useAuth() - const router = useRouter() + const auth = useAuth(); + const router = useRouter(); const { register, formState: { errors }, handleSubmit, watch, setValue, - control + control, } = useForm({ resolver: yupResolver(validationSchema), - defaultValues - }) - - const [cities, setCities] = useState([]) - const [districts, setDistricts] = useState([]) - const [subDistricts, setSubDistricts] = useState([]) + defaultValues, + }); + const { addresses = [] } = useAddresses(); // Ensure addresses is an array + const [cities, setCities] = useState([]); + const [districts, setDistricts] = useState([]); + const [subDistricts, setSubDistricts] = useState([]); + const [filteredTypes, setFilteredTypes] = useState(types); // State to manage filtered types useEffect(() => { const loadCities = async () => { - let dataCities = await cityApi() - dataCities = dataCities.map((city) => ({ value: city.id, label: city.name })) - setCities(dataCities) + let dataCities = await cityApi(); + dataCities = dataCities.map((city) => ({ + value: city.id, + label: city.name, + })); + setCities(dataCities); + }; + loadCities(); + }, []); + + useEffect(() => { + if (addresses) { + let hasContactAddress = false; + + for (let i = 0; i < addresses?.data?.length; i++) { + if (addresses.data[i].type === 'contact') { + hasContactAddress = true; + break; + } + } + if (hasContactAddress) { + setFilteredTypes(types.filter((type) => type.value !== 'contact')); + } else { + setFilteredTypes(types); + } } - loadCities() - }, []) + }, [auth]); - const watchCity = watch('city') + const watchCity = watch('city'); useEffect(() => { - setValue('district', '') + setValue('district', ''); if (watchCity) { const loadDistricts = async () => { - let dataDistricts = await districtApi({ cityId: watchCity }) + let dataDistricts = await districtApi({ cityId: watchCity }); dataDistricts = dataDistricts.map((district) => ({ value: district.id, - label: district.name - })) - setDistricts(dataDistricts) - } - loadDistricts() + label: district.name, + })); + setDistricts(dataDistricts); + }; + loadDistricts(); } - }, [watchCity, setValue]) + }, [watchCity, setValue]); - const watchDistrict = watch('district') + const watchDistrict = watch('district'); useEffect(() => { - setValue('subDistrict', '') + setValue('subDistrict', ''); if (watchDistrict) { const loadSubDistricts = async () => { - let dataSubDistricts = await subDistrictApi({ districtId: watchDistrict }) + let dataSubDistricts = await subDistrictApi({ + districtId: watchDistrict, + }); dataSubDistricts = dataSubDistricts.map((district) => ({ value: district.id, - label: district.name - })) - setSubDistricts(dataSubDistricts) - } - loadSubDistricts() + label: district.name, + })); + setSubDistricts(dataSubDistricts); + }; + loadSubDistricts(); } - }, [watchDistrict, setValue]) + }, [watchDistrict, setValue]); const onSubmitHandler = async (values) => { const data = { @@ -78,15 +103,15 @@ const CreateAddress = () => { city_id: values.city, district_id: values.district, sub_district_id: values.subDistrict, - parent_id: auth.partnerId - } + parent_id: auth.partnerId, + }; - const address = await createAddressApi({ data }) + const address = await createAddressApi({ data }); if (address?.id) { - toast.success('Berhasil menambahkan alamat') - router.back() + toast.success('Berhasil menambahkan alamat'); + router.back(); } - } + }; return ( <div className='max-w-none md:container mx-auto flex p-0 md:py-10'> @@ -102,10 +127,16 @@ const CreateAddress = () => { name='type' control={control} render={(props) => ( - <HookFormSelect {...props} isSearchable={false} options={types} /> + <HookFormSelect + {...props} + isSearchable={false} + options={filteredTypes} + /> )} /> - <div className='text-caption-2 text-danger-500 mt-1'>{errors.type?.message}</div> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.type?.message} + </div> </div> <div> @@ -116,7 +147,9 @@ const CreateAddress = () => { type='text' className='form-input' /> - <div className='text-caption-2 text-danger-500 mt-1'>{errors.name?.message}</div> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.name?.message} + </div> </div> <div> @@ -127,7 +160,9 @@ const CreateAddress = () => { type='email' className='form-input' /> - <div className='text-caption-2 text-danger-500 mt-1'>{errors.email?.message}</div> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.email?.message} + </div> </div> <div> @@ -138,7 +173,9 @@ const CreateAddress = () => { type='tel' className='form-input' /> - <div className='text-caption-2 text-danger-500 mt-1'>{errors.mobile?.message}</div> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.mobile?.message} + </div> </div> <div> @@ -149,7 +186,9 @@ const CreateAddress = () => { type='text' className='form-input' /> - <div className='text-caption-2 text-danger-500 mt-1'>{errors.street?.message}</div> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.street?.message} + </div> </div> <div> @@ -160,7 +199,9 @@ const CreateAddress = () => { type='number' className='form-input' /> - <div className='text-caption-2 text-danger-500 mt-1'>{errors.zip?.message}</div> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.zip?.message} + </div> </div> <div> @@ -168,9 +209,13 @@ const CreateAddress = () => { <Controller name='city' control={control} - render={(props) => <HookFormSelect {...props} options={cities} />} + render={(props) => ( + <HookFormSelect {...props} options={cities} /> + )} /> - <div className='text-caption-2 text-danger-500 mt-1'>{errors.city?.message}</div> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.city?.message} + </div> </div> <div> @@ -179,10 +224,16 @@ const CreateAddress = () => { name='district' control={control} render={(props) => ( - <HookFormSelect {...props} options={districts} disabled={!watchCity} /> + <HookFormSelect + {...props} + options={districts} + disabled={!watchCity} + /> )} /> - <div className='text-caption-2 text-danger-500 mt-1'>{errors.district?.message}</div> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.district?.message} + </div> </div> <div> @@ -191,31 +242,37 @@ const CreateAddress = () => { name='subDistrict' control={control} render={(props) => ( - <HookFormSelect {...props} options={subDistricts} disabled={!watchDistrict} /> + <HookFormSelect + {...props} + options={subDistricts} + disabled={!watchDistrict} + /> )} /> </div> </div> - <button type='submit' className='btn-yellow w-full md:w-fit mt-6 ml-0 md:ml-auto'> + <button + type='submit' + className='btn-yellow w-full md:w-fit mt-6 ml-0 md:ml-auto' + > Simpan </button> </form> </div> </div> - ) -} + ); +}; const validationSchema = Yup.object().shape({ type: Yup.string().required('Harus di-pilih'), name: Yup.string().min(3, 'Minimal 3 karakter').required('Harus di-isi'), - // email: Yup.string().email('Format harus seperti contoh@email.com').required('Harus di-isi'), mobile: Yup.string().required('Harus di-isi'), street: Yup.string().required('Harus di-isi'), zip: Yup.string().required('Harus di-isi'), city: Yup.string().required('Harus di-pilih'), - district: Yup.string().required('Harus di-pilih') -}) + district: Yup.string().required('Harus di-pilih'), +}); const defaultValues = { type: '', @@ -226,14 +283,14 @@ const defaultValues = { city: '', district: '', subDistrict: '', - zip: '' -} + zip: '', +}; const types = [ { value: 'contact', label: 'Contact Address' }, { value: 'invoice', label: 'Invoice Address' }, { value: 'delivery', label: 'Delivery Address' }, - { value: 'other', label: 'Other Address' } -] + { value: 'other', label: 'Other Address' }, +]; -export default CreateAddress +export default CreateAddress; diff --git a/src/lib/address/components/EditAddress.jsx b/src/lib/address/components/EditAddress.jsx index 520bba51..ff6b1f12 100644 --- a/src/lib/address/components/EditAddress.jsx +++ b/src/lib/address/components/EditAddress.jsx @@ -1,18 +1,22 @@ -import { yupResolver } from '@hookform/resolvers/yup' -import { useRouter } from 'next/router' -import { useEffect, useState } from 'react' -import * as Yup from 'yup' -import cityApi from '../api/cityApi' -import { Controller, useForm } from 'react-hook-form' -import districtApi from '../api/districtApi' -import subDistrictApi from '../api/subDistrictApi' -import editAddressApi from '../api/editAddressApi' -import HookFormSelect from '@/core/components/elements/Select/HookFormSelect' -import { toast } from 'react-hot-toast' -import Menu from '@/lib/auth/components/Menu' +import { yupResolver } from '@hookform/resolvers/yup'; +import { useRouter } from 'next/router'; +import { useEffect, useState } from 'react'; +import * as Yup from 'yup'; +import cityApi from '../api/cityApi'; +import { Controller, useForm } from 'react-hook-form'; +import districtApi from '../api/districtApi'; +import subDistrictApi from '../api/subDistrictApi'; +import addressApi from '@/lib/address/api/addressApi'; +import editAddressApi from '../api/editAddressApi'; +import HookFormSelect from '@/core/components/elements/Select/HookFormSelect'; +import { toast } from 'react-hot-toast'; +import Menu from '@/lib/auth/components/Menu'; +import useAuth from '@/core/hooks/useAuth'; +import odooApi from '@/core/api/odooApi'; const EditAddress = ({ id, defaultValues }) => { - const router = useRouter() + const auth = useAuth(); + const router = useRouter(); const { register, formState: { errors }, @@ -20,205 +24,283 @@ const EditAddress = ({ id, defaultValues }) => { watch, setValue, getValues, - control + control, } = useForm({ resolver: yupResolver(validationSchema), - defaultValues - }) + defaultValues, + }); + const [cities, setCities] = useState([]); + const [districts, setDistricts] = useState([]); + const [subDistricts, setSubDistricts] = useState([]); - const [cities, setCities] = useState([]) - const [districts, setDistricts] = useState([]) - const [subDistricts, setSubDistricts] = useState([]) + useEffect(() => { + const loadProfile = async () => { + const dataProfile = await addressApi({ id: auth.parentId }); + setValue('industry', dataProfile.industryId); + setValue('companyType', dataProfile.companyTypeId); + setValue('taxName', dataProfile.taxName); + setValue('npwp', dataProfile.npwp); + setValue('alamat_wajib_pajak', dataProfile.alamatWajibPajak); + setValue('alamat_bisnis', dataProfile.alamatBisnis); + setValue('business_name', dataProfile.name); + }; + if (auth) loadProfile(); + }, [auth, setValue]); useEffect(() => { const loadCities = async () => { - let dataCities = await cityApi() + let dataCities = await cityApi(); dataCities = dataCities.map((city) => ({ value: city.id, - label: city.name - })) - setCities(dataCities) - } - loadCities() - }, []) + label: city.name, + })); + setCities(dataCities); + }; + loadCities(); + }, []); - const watchCity = watch('city') + const watchCity = watch('city'); useEffect(() => { - setValue('district', '') + setValue('district', ''); if (watchCity) { const loadDistricts = async () => { - let dataDistricts = await districtApi({ cityId: watchCity }) + let dataDistricts = await districtApi({ cityId: watchCity }); dataDistricts = dataDistricts.map((district) => ({ value: district.id, - label: district.name - })) - setDistricts(dataDistricts) - let oldDistrict = getValues('oldDistrict') + label: district.name, + })); + setDistricts(dataDistricts); + let oldDistrict = getValues('oldDistrict'); if (oldDistrict) { - setValue('district', oldDistrict) - setValue('oldDistrict', '') + setValue('district', oldDistrict); + setValue('oldDistrict', ''); } - } - loadDistricts() + }; + loadDistricts(); } - }, [watchCity, setValue, getValues]) + }, [watchCity, setValue, getValues]); - const watchDistrict = watch('district') + const watchDistrict = watch('district'); useEffect(() => { - setValue('subDistrict', '') + setValue('subDistrict', ''); if (watchDistrict) { const loadSubDistricts = async () => { let dataSubDistricts = await subDistrictApi({ - districtId: watchDistrict - }) + districtId: watchDistrict, + }); dataSubDistricts = dataSubDistricts.map((district) => ({ value: district.id, - label: district.name - })) - setSubDistricts(dataSubDistricts) - let oldSubDistrict = getValues('oldSubDistrict') + label: district.name, + })); + setSubDistricts(dataSubDistricts); + let oldSubDistrict = getValues('oldSubDistrict'); if (oldSubDistrict) { - setValue('subDistrict', oldSubDistrict) - setValue('oldSubDistrict', '') + setValue('subDistrict', oldSubDistrict); + setValue('oldSubDistrict', ''); } - } - loadSubDistricts() + }; + loadSubDistricts(); } - }, [watchDistrict, setValue, getValues]) - + }, [watchDistrict, setValue, getValues]); const onSubmitHandler = async (values) => { const data = { ...values, + phone: values.mobile, city_id: values.city, district_id: values.district, - sub_district_id: values.subDistrict + sub_district_id: values.subDistrict, + }; + const address = await editAddressApi({ id, data }); + let dataAlamat; + let isUpdated = true; + if (auth?.partnerId == id) { + dataAlamat = { + id_user: auth.partnerId, + company_type_id: values.companyType, + industry_id: values.industry, + tax_name: values.taxName, + alamat_lengkap_text: values.alamat_wajib_pajak, + street: values.street, + business_name: values.business_name, + name: values.business_name, + npwp: values.npwp, + }; + isUpdated = await odooApi( + 'PUT', + `/api/v1/partner/${auth.parentId}`, + dataAlamat + ); } - const address = await editAddressApi({ id, data }) - if (address?.id) { - toast.success('Berhasil mengubah alamat') - router.back() + // if (isUpdated?.id) { + if (address?.id && isUpdated?.id) { + toast.success('Berhasil mengubah alamat'); + router.back(); + } else { + toast.error('Terjadi kesalahan internal'); + router.back(); } - } + }; return ( - <div className='max-w-none md:container mx-auto flex p-0 md:py-10'> - <div className='hidden md:block w-3/12 pr-4'> - <Menu /> - </div> - <div className='w-full md:w-9/12 p-4 bg-white border border-gray_r-6 rounded'> - <h1 className='text-title-sm font-semibold mb-6 hidden md:block'>Ubah Alamat</h1> - <form onSubmit={handleSubmit(onSubmitHandler)}> - <div className='grid grid-cols-1 md:grid-cols-2 gap-4'> - <div> - <label className='form-label mb-2'>Label Alamat</label> - <Controller - name='type' - control={control} - render={(props) => ( - <HookFormSelect {...props} isSearchable={false} options={types} /> - )} - /> - <div className='text-caption-2 text-danger-500 mt-1'>{errors.type?.message}</div> - </div> + <> + <div className='max-w-none md:container mx-auto flex p-0 md:py-10'> + <div className='hidden md:block w-3/12 pr-4'> + <Menu /> + </div> + <div className='w-full md:w-9/12 p-4 bg-white border border-gray_r-6 rounded'> + <div className='flex justify-start items-center mb-6'> + <h1 className='text-title-sm font-semibold hidden md:block mr-2'> + Ubah Alamat + </h1> + {auth?.partnerId == id && <div className='badge-green'>Utama</div>} + </div> + <form onSubmit={handleSubmit(onSubmitHandler)}> + <div className='grid grid-cols-1 md:grid-cols-2 gap-4'> + <div> + <label className='form-label mb-2'>Label Alamat</label> + <Controller + name='type' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + isSearchable={false} + options={types} + /> + )} + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.type?.message} + </div> + </div> - <div> - <label className='form-label mb-2'>Nama</label> - <input - {...register('name')} - placeholder='John Doe' - type='text' - className='form-input' - /> - <div className='text-caption-2 text-danger-500 mt-1'>{errors.name?.message}</div> - </div> + <div> + <label className='form-label mb-2'>Nama</label> + <input + {...register('name')} + placeholder='John Doe' + type='text' + className='form-input' + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.name?.message} + </div> + </div> - <div> - <label className='form-label mb-2'>Email</label> - <input - {...register('email')} - placeholder='johndoe@example.com' - type='email' - className='form-input' - /> - <div className='text-caption-2 text-danger-500 mt-1'>{errors.email?.message}</div> - </div> + <div> + <label className='form-label mb-2'>Email</label> + <input + {...register('email')} + placeholder='johndoe@example.com' + type='email' + className='form-input' + disabled={auth?.partnerId == id && true} + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.email?.message} + </div> + </div> - <div> - <label className='form-label mb-2'>Mobile</label> - <input - {...register('mobile')} - placeholder='08xxxxxxxx' - type='tel' - className='form-input' - /> - <div className='text-caption-2 text-danger-500 mt-1'>{errors.mobile?.message}</div> - </div> + <div> + <label className='form-label mb-2'>Mobile</label> + <input + {...register('mobile')} + placeholder='08xxxxxxxx' + type='tel' + className='form-input' + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.mobile?.message} + </div> + </div> - <div> - <label className='form-label mb-2'>Alamat</label> - <input - {...register('street')} - placeholder='Jl. Bandengan Utara 85A' - type='text' - className='form-input' - /> - <div className='text-caption-2 text-danger-500 mt-1'>{errors.street?.message}</div> - </div> + <div> + <label className='form-label mb-2'>Alamat</label> + <input + {...register('street')} + placeholder='Jl. Bandengan Utara 85A' + type='text' + className='form-input' + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.street?.message} + </div> + </div> - <div> - <label className='form-label mb-2'>Kode Pos</label> - <input - {...register('zip')} - placeholder='10100' - type='number' - className='form-input' - /> - <div className='text-caption-2 text-danger-500 mt-1'>{errors.zip?.message}</div> - </div> + <div> + <label className='form-label mb-2'>Kode Pos</label> + <input + {...register('zip')} + placeholder='10100' + type='number' + className='form-input' + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.zip?.message} + </div> + </div> - <div> - <label className='form-label mb-2'>Kota</label> - <Controller - name='city' - control={control} - render={(props) => <HookFormSelect {...props} options={cities} />} - /> - <div className='text-caption-2 text-danger-500 mt-1'>{errors.city?.message}</div> - </div> + <div> + <label className='form-label mb-2'>Kota</label> + <Controller + name='city' + control={control} + render={(props) => ( + <HookFormSelect {...props} options={cities} /> + )} + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.city?.message} + </div> + </div> - <div> - <label className='form-label mb-2'>Kecamatan</label> - <Controller - name='district' - control={control} - render={(props) => ( - <HookFormSelect {...props} options={districts} disabled={!watchCity} /> - )} - /> - <div className='text-caption-2 text-danger-500 mt-1'>{errors.district?.message}</div> - </div> + <div> + <label className='form-label mb-2'>Kecamatan</label> + <Controller + name='district' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={districts} + disabled={!watchCity} + /> + )} + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.district?.message} + </div> + </div> - <div> - <label className='form-label mb-2'>Kelurahan</label> - <Controller - name='subDistrict' - control={control} - render={(props) => ( - <HookFormSelect {...props} options={subDistricts} disabled={!watchDistrict} /> - )} - /> + <div> + <label className='form-label mb-2'>Kelurahan</label> + <Controller + name='subDistrict' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={subDistricts} + disabled={!watchDistrict} + /> + )} + /> + </div> </div> - </div> - <button type='submit' className='btn-yellow w-full md:w-fit mt-6 ml-0 md:ml-auto'> - Simpan - </button> - </form> + <button + type='submit' + className='btn-yellow w-full md:w-fit mt-6 ml-0 md:ml-auto' + > + Simpan + </button> + </form> + </div> </div> - </div> - ) -} + </> + ); +}; const validationSchema = Yup.object().shape({ type: Yup.string().required('Harus di-pilih'), @@ -228,14 +310,14 @@ const validationSchema = Yup.object().shape({ street: Yup.string().required('Harus di-isi'), zip: Yup.string().required('Harus di-isi'), city: Yup.string().required('Harus di-pilih'), - district: Yup.string().required('Harus di-pilih') -}) + district: Yup.string().required('Harus di-pilih'), +}); const types = [ { value: 'contact', label: 'Contact Address' }, { value: 'invoice', label: 'Invoice Address' }, { value: 'delivery', label: 'Delivery Address' }, - { value: 'other', label: 'Other Address' } -] + { value: 'other', label: 'Other Address' }, +]; -export default EditAddress +export default EditAddress; diff --git a/src/lib/auth/components/CompanyProfile.jsx b/src/lib/auth/components/CompanyProfile.jsx index 2faede9b..7bda992f 100644 --- a/src/lib/auth/components/CompanyProfile.jsx +++ b/src/lib/auth/components/CompanyProfile.jsx @@ -1,78 +1,136 @@ -import odooApi from '@/core/api/odooApi' -import HookFormSelect from '@/core/components/elements/Select/HookFormSelect' -import useAuth from '@/core/hooks/useAuth' -import addressApi from '@/lib/address/api/addressApi' -import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline' -import { useEffect, useState } from 'react' -import { Controller, useForm } from 'react-hook-form' -import { toast } from 'react-hot-toast' +import odooApi from '@/core/api/odooApi'; +import HookFormSelect from '@/core/components/elements/Select/HookFormSelect'; +import useAuth from '@/core/hooks/useAuth'; +import addressApi from '@/lib/address/api/addressApi'; +import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'; +import { useEffect, useState } from 'react'; +import { Controller, useForm } from 'react-hook-form'; +import { toast } from 'react-hot-toast'; +import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; +import { yupResolver } from '@hookform/resolvers/yup'; +import * as Yup from 'yup'; const CompanyProfile = () => { - const auth = useAuth() - const [isOpen, setIsOpen] = useState(false) - const toggle = () => setIsOpen(!isOpen) - const { register, setValue, control, handleSubmit } = useForm({ - defaultValues: { - industry: '', - companyType: '', - name: '', - taxName: '', - npwp: '' - } - }) + const [changeConfirmation, setChangeConfirmation] = useState(false); + const auth = useAuth(); + const [isOpen, setIsOpen] = useState(false); + const toggle = () => setIsOpen(!isOpen); + const { + register, + formState: { errors }, + setValue, + control, + handleSubmit, + } = useForm({ + resolver: yupResolver(validationSchema), + defaultValues, + }); - const [industries, setIndustries] = useState([]) + const [industries, setIndustries] = useState([]); useEffect(() => { const loadIndustries = async () => { - const dataIndustries = await odooApi('GET', '/api/v1/partner/industry') - setIndustries(dataIndustries?.map((o) => ({ value: o.id, label: o.name }))) - } - loadIndustries() - }, []) + const dataIndustries = await odooApi('GET', '/api/v1/partner/industry'); + setIndustries( + dataIndustries?.map((o) => ({ value: o.id, label: o.name })) + ); + }; + loadIndustries(); + }, []); - const [companyTypes, setCompanyTypes] = useState([]) + const [companyTypes, setCompanyTypes] = useState([]); useEffect(() => { const loadCompanyTypes = async () => { - const dataCompanyTypes = await odooApi('GET', '/api/v1/partner/company_type') - setCompanyTypes(dataCompanyTypes?.map((o) => ({ value: o.id, label: o.name }))) - } - loadCompanyTypes() - }, []) + const dataCompanyTypes = await odooApi( + 'GET', + '/api/v1/partner/company_type' + ); + setCompanyTypes( + dataCompanyTypes?.map((o) => ({ value: o.id, label: o.name })) + ); + }; + loadCompanyTypes(); + }, []); useEffect(() => { const loadProfile = async () => { - const dataProfile = await addressApi({ id: auth.parentId }) - setValue('name', dataProfile.name) - setValue('industry', dataProfile.industryId) - setValue('companyType', dataProfile.companyTypeId) - setValue('taxName', dataProfile.taxName) - setValue('npwp', dataProfile.npwp) - } - if (auth) loadProfile() - }, [auth, setValue]) + const dataProfile = await addressApi({ id: auth.parentId }); + setValue('name', dataProfile.name); + setValue('industry', dataProfile.industryId); + setValue('companyType', dataProfile.companyTypeId); + setValue('taxName', dataProfile.taxName); + setValue('npwp', dataProfile.npwp); + setValue('alamat_wajib_pajak', dataProfile.alamatWajibPajak); + setValue('alamat_bisnis', dataProfile.alamatBisnis); + }; + if (auth) loadProfile(); + }, [auth, setValue]); const onSubmitHandler = async (values) => { - const data = { - ...values, - company_type_id: values.companyType, - industry_id: values.industry, - tax_name: values.taxName + if (changeConfirmation) { + const data = { + ...values, + id_user: auth.partnerId, + company_type_id: values.companyType, + industry_id: values.industry, + tax_name: values.taxName, + alamat_lengkap_text: values.alamat_wajib_pajak, + street: values.alamat_bisnis, + }; + const isUpdated = await odooApi( + 'PUT', + `/api/v1/partner/${auth.parentId}`, + data + ); + if (isUpdated?.id) { + toast.success('Berhasil mengubah profil', { duration: 1500 }); + return; + } + toast.error('Terjadi kesalahan internal'); } - const isUpdated = await odooApi('PUT', `/api/v1/partner/${auth.parentId}`, data) - if (isUpdated?.id) { - toast.success('Berhasil mengubah profil', { duration: 1500 }) - return - } - toast.error('Terjadi kesalahan internal') - } + }; + + const handleConfirmSubmit = () => { + setChangeConfirmation(false); + handleSubmit(onSubmitHandler)(); + }; return ( <> - <button type='button' onClick={toggle} className='p-4 flex items-center text-left w-full'> + <BottomPopup + active={changeConfirmation} + close={() => setChangeConfirmation(true)} + title='Ubah profil Bisnis' + > + <div className='leading-7 text-gray_r-12/80'> + Apakah anda yakin mengubah data bisnis? + </div> + <div className='flex mt-6 gap-x-4 md:justify-end'> + <button + className='btn-solid-red flex-1 md:flex-none' + type='button' + onClick={handleConfirmSubmit} + > + Ya, Ubah + </button> + <button + className='btn-light flex-1 md:flex-none' + type='button' + onClick={() => setChangeConfirmation(false)} + > + Batal + </button> + </div> + </BottomPopup> + <button + type='button' + onClick={toggle} + className='p-4 flex items-center text-left w-full' + > <div> <div className='font-semibold mb-2'>Informasi Usaha</div> <div className='text-gray_r-11'> - Dibawah ini adalah data usaha yang anda masukkan, periksa kembali data usaha anda. + Dibawah ini adalah data usaha yang anda masukkan, periksa kembali + data usaha anda. </div> </div> <div className='ml-auto p-2 bg-gray_r-3 rounded'> @@ -82,15 +140,26 @@ const CompanyProfile = () => { </button> {isOpen && ( - <form className='p-4 border-t border-gray_r-6' onSubmit={handleSubmit(onSubmitHandler)}> + <form + className='p-4 border-t border-gray_r-6' + onSubmit={(e) => { + e.preventDefault(); + setChangeConfirmation(true); + }} + > <div className='grid grid-cols-1 sm:grid-cols-2 gap-4'> <div> <label className='block mb-3'>Klasifikasi Jenis Usaha</label> <Controller name='industry' control={control} - render={(props) => <HookFormSelect {...props} options={industries} />} + render={(props) => ( + <HookFormSelect {...props} options={industries} /> + )} /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.industry?.message} + </div> </div> <div className='flex flex-wrap'> <div className='w-full mb-3'>Badan Usaha</div> @@ -98,8 +167,13 @@ const CompanyProfile = () => { <Controller name='companyType' control={control} - render={(props) => <HookFormSelect {...props} options={companyTypes} />} + render={(props) => ( + <HookFormSelect {...props} options={companyTypes} /> + )} /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.companyType?.message} + </div> </div> <div className='w-9/12 pl-1'> <input @@ -108,15 +182,55 @@ const CompanyProfile = () => { className='form-input' placeholder='Cth: Indoteknik Dotcom Gemilang' /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.name?.message} + </div> </div> </div> <div> <label>Nama Wajib Pajak</label> - <input {...register('taxName')} type='text' className='form-input mt-3' /> + <input + {...register('taxName')} + type='text' + className='form-input mt-3' + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.taxName?.message} + </div> + </div> + <div> + <label>Alamat Wajib Pajak</label> + <input + {...register('alamat_wajib_pajak')} + type='text' + className='form-input mt-3' + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.alamat_wajib_pajak?.message} + </div> + </div> + <div> + <label>Alamat Bisnis</label> + <input + {...register('alamat_bisnis')} + type='text' + className='form-input mt-3' + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.alamat_bisnis?.message} + </div> </div> <div> <label>Nomor NPWP</label> - <input {...register('npwp')} type='text' className='form-input mt-3' /> + <input + {...register('npwp')} + type='text' + className='form-input mt-3' + maxLength={16} + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.npwp?.message} + </div> </div> </div> <button type='submit' className='btn-yellow w-full mt-6'> @@ -125,7 +239,27 @@ const CompanyProfile = () => { </form> )} </> - ) -} + ); +}; + +export default CompanyProfile; + +const validationSchema = Yup.object().shape({ + alamat_bisnis: Yup.string().required('Harus di-isi'), + alamat_wajib_pajak: Yup.string().required('Harus di-isi'), + taxName: Yup.string().required('Harus di-isi'), + npwp: Yup.string().required('Harus di-isi'), + name: Yup.string().required('Harus di-isi'), + industry: Yup.string().required('Harus di-pilih'), + companyType: Yup.string().required('Harus di-pilih'), +}); -export default CompanyProfile +const defaultValues = { + industry: '', + companyType: '', + name: '', + taxName: '', + npwp: '', + alamat_wajib_pajak: '', + alamat_bisnis: '', +}; diff --git a/src/lib/checkout/components/Checkout.jsx b/src/lib/checkout/components/Checkout.jsx index f63ef457..4c7e852f 100644 --- a/src/lib/checkout/components/Checkout.jsx +++ b/src/lib/checkout/components/Checkout.jsx @@ -77,6 +77,9 @@ const Checkout = () => { if (!addresses) return; const matchAddress = (key) => { + if (key === 'invoicing') { + key = 'invoice'; + } const addressToMatch = getItemAddress(key); const foundAddress = addresses.filter( (address) => address.id == addressToMatch diff --git a/src/lib/home/components/PreferredBrand.jsx b/src/lib/home/components/PreferredBrand.jsx index b30fa5c9..eefced60 100644 --- a/src/lib/home/components/PreferredBrand.jsx +++ b/src/lib/home/components/PreferredBrand.jsx @@ -9,7 +9,7 @@ import Link from '@/core/components/elements/Link/Link' import axios from 'axios' const PreferredBrand = () => { - let query = 'level_s' + let query = '' let params = 'prioritas' const [isLoading, setIsLoading] = useState(true) const [startWith, setStartWith] = useState(null) @@ -18,7 +18,7 @@ const PreferredBrand = () => { const loadBrand = useCallback(async () => { setIsLoading(true) const name = startWith ? `${startWith}*` : '' - const result = await axios(`${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/brands?params=${name}`) + const result = await axios(`${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/brands?rows=20`) setIsLoading(false) setManufactures((manufactures) => [...result.data]) @@ -35,9 +35,9 @@ const PreferredBrand = () => { useEffect(() => { loadBrand() - }, [loadBrand]) + }, []) - const { preferredBrands } = usePreferredBrand(query) + // const { preferredBrands } = usePreferredBrand(query) const { isMobile, isDesktop } = useDevice() const swiperBanner = { modules:[Navigation, Pagination, Autoplay], @@ -59,7 +59,7 @@ const PreferredBrand = () => { return ( <div className='px-4 sm:px-0'> <div className='flex justify-between items-center mb-4'> - <div className='font-semibold sm:text-h-lg'>Brand Pilihan</div> + <h1 className='font-semibold text-[14px] sm:text-h-lg'><Link href='/shop/brands' className='!text-black font-semibold'>Brand Pilihan</Link></h1> {isDesktop && ( <Link href='/shop/brands' className='!text-red-500 font-semibold'> Lihat Semua diff --git a/src/lib/home/components/PromotionProgram.jsx b/src/lib/home/components/PromotionProgram.jsx index c2f76069..ae8d5d6f 100644 --- a/src/lib/home/components/PromotionProgram.jsx +++ b/src/lib/home/components/PromotionProgram.jsx @@ -16,7 +16,7 @@ const BannerSection = () => { return ( <div className='px-4 sm:px-0'> <div className='flex justify-between items-center mb-4 '> - <div className='font-semibold sm:text-h-lg'>Promo Tersedia</div> + <h1 className='font-semibold text-[14px] sm:text-h-lg'> <Link href='/shop/promo' className='!text-black font-semibold' >Promo Tersedia</Link></h1> {isDesktop && ( <Link href='/shop/promo' className='!text-red-500 font-semibold'> Lihat Semua diff --git a/src/lib/product/api/productSimilarApi.js b/src/lib/product/api/productSimilarApi.js index ecd6724a..51cc17fa 100644 --- a/src/lib/product/api/productSimilarApi.js +++ b/src/lib/product/api/productSimilarApi.js @@ -19,7 +19,7 @@ const productSimilarApi = async ({ query, source }) => { } } const dataProductSimilar = await axios( - `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/search?q=${query}&page=1&orderBy=popular-weekly&operation=OR&priceFrom=1` + `${process.env.NEXT_PUBLIC_SELF_HOST}/api/shop/search?q=${query}&page=1&orderBy=popular-weekly&operation=OR&priceFrom=1&source=similar` ) if (dataflashSale) { dataProductSimilar.data.response.products = [ |
