diff options
| author | it-fixcomart <it@fixcomart.co.id> | 2025-01-22 10:10:19 +0700 |
|---|---|---|
| committer | it-fixcomart <it@fixcomart.co.id> | 2025-01-22 10:10:19 +0700 |
| commit | 5cc3c938da3dfde636b918b8f2fdef33f3c61419 (patch) | |
| tree | 222177fe6dce70fc608698c700de17032105998a /src/lib | |
| parent | 238c675eecaf6f4f953d87c4b0a128bfa139cff4 (diff) | |
| parent | 6d9c1067b6e857eb95f12864cc88117350ae6cfb (diff) | |
Merge branch 'new-release' into CR/form-merchant
# Conflicts:
# src/lib/form/components/Merchant.jsx
Diffstat (limited to 'src/lib')
38 files changed, 11171 insertions, 378 deletions
diff --git a/src/lib/address/api/stateApi.js b/src/lib/address/api/stateApi.js index cea49e7e..6bfd36d2 100644 --- a/src/lib/address/api/stateApi.js +++ b/src/lib/address/api/stateApi.js @@ -1,8 +1,8 @@ -import odooApi from '@/core/api/odooApi' +import odooApi from '@/core/api/odooApi'; -const stateApi = async () => { - const dataState = await odooApi('GET', '/api/v1/state') - return dataState -} +const stateApi = async ({ tempo = false }) => { + const dataState = await odooApi('GET', `/api/v1/state?tempo=${tempo}`); + return dataState; +}; -export default stateApi
\ No newline at end of file +export default stateApi; diff --git a/src/lib/address/components/CreateAddress.jsx b/src/lib/address/components/CreateAddress.jsx index 9d70e8fc..97db7ed8 100644 --- a/src/lib/address/components/CreateAddress.jsx +++ b/src/lib/address/components/CreateAddress.jsx @@ -37,7 +37,7 @@ const CreateAddress = () => { useEffect(() => { const loadState = async () => { - let dataState = await stateApi(); + let dataState = await stateApi({ tempo: false }); dataState = dataState.map((state) => ({ value: state.id, label: state.name, @@ -52,7 +52,7 @@ const CreateAddress = () => { setValue('city', ''); if (watchState) { const loadCities = async () => { - let dataCities = await cityApi({stateId: watchState}); + let dataCities = await cityApi({ stateId: watchState }); dataCities = dataCities.map((city) => ({ value: city.id, label: city.name, @@ -243,7 +243,11 @@ const CreateAddress = () => { name='city' control={control} render={(props) => ( - <HookFormSelect {...props} options={cities} disabled={!watchState}/> + <HookFormSelect + {...props} + options={cities} + disabled={!watchState} + /> )} /> <div className='text-caption-2 text-danger-500 mt-1'> diff --git a/src/lib/address/components/EditAddress.jsx b/src/lib/address/components/EditAddress.jsx index 23cf72a9..62858465 100644 --- a/src/lib/address/components/EditAddress.jsx +++ b/src/lib/address/components/EditAddress.jsx @@ -34,7 +34,7 @@ const EditAddress = ({ id, defaultValues }) => { const [states, setStates] = useState([]); const [cities, setCities] = useState([]); const [districts, setDistricts] = useState([]); - const [subDistricts, setSubDistricts] = useState([]); + const [subDistricts, setSubDistricts] = useState([]); useEffect(() => { const loadProfile = async () => { @@ -46,13 +46,16 @@ const EditAddress = ({ id, defaultValues }) => { setValue('alamat_wajib_pajak', dataProfile.alamatWajibPajak); setValue('alamat_bisnis', dataProfile.alamatBisnis); setValue('business_name', dataProfile.name); + setValue('city', dataProfile.city?.id); + setValue('district', dataProfile.district?.id); + setValue('subDistrict', dataProfile.subDistrict?.id); }; if (auth) loadProfile(); }, [auth?.parentId]); useEffect(() => { const loadStates = async () => { - let dataStates = await stateApi(); + let dataStates = await stateApi({ tempo: false }); dataStates = dataStates.map((state) => ({ value: state.id, label: state.name, @@ -60,12 +63,12 @@ const EditAddress = ({ id, defaultValues }) => { setStates(dataStates); }; loadStates(); - },[]) + }, []); const watchState = watch('state'); useEffect(() => { - setValue('city', ''); - if(watchState) { + if (watchState) { + setValue('city', ''); const loadCities = async () => { let dataCities = await cityApi({ stateId: watchState }); dataCities = dataCities.map((city) => ({ @@ -81,13 +84,12 @@ const EditAddress = ({ id, defaultValues }) => { }; loadCities(); } - }, [watchState, setValue, getValues]); const watchCity = watch('city'); useEffect(() => { - setValue('district', ''); if (watchCity) { + // setValue('district', ''); const loadDistricts = async () => { let dataDistricts = await districtApi({ cityId: watchCity }); dataDistricts = dataDistricts.map((district) => ({ @@ -97,7 +99,7 @@ const EditAddress = ({ id, defaultValues }) => { setDistricts(dataDistricts); let oldDistrict = getValues('oldDistrict'); if (oldDistrict) { - setValue('district', oldDistrict); + // setValue('district', oldDistrict); setValue('oldDistrict', ''); } }; @@ -107,8 +109,8 @@ const EditAddress = ({ id, defaultValues }) => { const watchDistrict = watch('district'); useEffect(() => { - setValue('subDistrict', ''); if (watchDistrict) { + // setValue('subDistrict', ''); const loadSubDistricts = async () => { let dataSubDistricts = await subDistrictApi({ districtId: watchDistrict, @@ -287,7 +289,11 @@ const EditAddress = ({ id, defaultValues }) => { name='city' control={control} render={(props) => ( - <HookFormSelect {...props} options={cities} disabled={!watchState} /> + <HookFormSelect + {...props} + options={cities} + disabled={!watchState} + /> )} /> <div className='text-caption-2 text-danger-500 mt-1'> @@ -348,7 +354,7 @@ const validationSchema = Yup.object().shape({ mobile: Yup.string().required('Harus di-isi'), street: Yup.string().required('Harus di-isi'), zip: Yup.string().required('Harus di-isi'), - state : Yup.string().required('Harus di-pilih'), + state: Yup.string().required('Harus di-pilih'), city: Yup.string().required('Harus di-pilih'), district: Yup.string().required('Harus di-pilih'), }); diff --git a/src/lib/auth/api/switchAccountApi.js b/src/lib/auth/api/switchAccountApi.js new file mode 100644 index 00000000..79ca2553 --- /dev/null +++ b/src/lib/auth/api/switchAccountApi.js @@ -0,0 +1,14 @@ +import odooApi from '@/core/api/odooApi'; +import { getAuth } from '@/core/utils/auth'; + +const switchAccountApi = async ({ data }) => { + const auth = getAuth(); + const switchAccount = await odooApi( + 'PUT', + `/api/v1/user/${auth.partnerId}/switch`, + data + ); + return switchAccount; +}; + +export default switchAccountApi; diff --git a/src/lib/auth/api/switchAccountProgresApi.js b/src/lib/auth/api/switchAccountProgresApi.js new file mode 100644 index 00000000..6289a5dd --- /dev/null +++ b/src/lib/auth/api/switchAccountProgresApi.js @@ -0,0 +1,13 @@ +import odooApi from '@/core/api/odooApi'; +import { getAuth } from '@/core/utils/auth'; + +const switchAccountProgresApi = async () => { + const auth = getAuth(); + const switchAccount = await odooApi( + 'GET', + `/api/v1/user/${auth.partnerId}/switch_progres` + ); + return switchAccount; +}; + +export default switchAccountProgresApi; diff --git a/src/lib/auth/components/CompanyProfile.jsx b/src/lib/auth/components/CompanyProfile.jsx index 7bda992f..410d6a23 100644 --- a/src/lib/auth/components/CompanyProfile.jsx +++ b/src/lib/auth/components/CompanyProfile.jsx @@ -9,9 +9,14 @@ 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'; +import SwitchAccount from '@/lib/auth/components/SwitchAccount'; +import { Checkbox } from '@chakra-ui/react'; const CompanyProfile = () => { const [changeConfirmation, setChangeConfirmation] = useState(false); + const [changeType, setChangeType] = useState(false); + const [isChecked, setIsChecked] = useState(false); + const [company_type, setCompany_type] = useState('nonpkp'); const auth = useAuth(); const [isOpen, setIsOpen] = useState(false); const toggle = () => setIsOpen(!isOpen); @@ -53,14 +58,20 @@ const CompanyProfile = () => { 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); - setValue('alamat_wajib_pajak', dataProfile.alamatWajibPajak); - setValue('alamat_bisnis', dataProfile.alamatBisnis); + const dataProfile = await addressApi({ + id: auth.parentId ? auth.parentId : auth.parent_id, + }); + setCompany_type(dataProfile?.companyType); + 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); + setValue('company_type', dataProfile?.companyType); + setValue('email_bisnis', dataProfile.email); + setValue('mobile_bisnis', dataProfile.mobile); }; if (auth) loadProfile(); }, [auth, setValue]); @@ -75,6 +86,8 @@ const CompanyProfile = () => { tax_name: values.taxName, alamat_lengkap_text: values.alamat_wajib_pajak, street: values.alamat_bisnis, + email: values.email_bisnis, + mobile: values.mobile_bisnis, }; const isUpdated = await odooApi( 'PUT', @@ -93,10 +106,49 @@ const CompanyProfile = () => { setChangeConfirmation(false); handleSubmit(onSubmitHandler)(); }; + const handleConfirmSubmitType = () => { + setChangeType(false); + setIsChecked(true); + setIsOpen(!isOpen); + }; + const handleChange = async () => { + if (isChecked) { + setIsChecked(!isChecked); + setIsOpen(!isOpen); + } else { + setIsChecked(!isChecked); + setChangeType(true); + } + }; return ( <> <BottomPopup + active={changeType} + close={() => setChangeType(false)} // Menutup popup + title='Ubah type akun' + > + <div className='leading-7 text-gray_r-12/80'> + Anda akan mengubah type akun anda? + </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={handleConfirmSubmitType} + > + Yakin + </button> + <button + className='btn-light flex-1 md:flex-none' + type='button' + onClick={() => setChangeType(false)} + > + Batal + </button> + </div> + </BottomPopup> + <BottomPopup active={changeConfirmation} close={() => setChangeConfirmation(true)} title='Ubah profil Bisnis' @@ -121,25 +173,37 @@ const CompanyProfile = () => { </button> </div> </BottomPopup> - <button - type='button' - onClick={toggle} - className='p-4 flex items-center text-left w-full' - > + <div className='p-4 flex-row items-center text-left w-full'> + {company_type === 'nonpkp' && ( + <div className='text-sm mb-2 flex items-center'> + <Checkbox + borderColor='gray.600' + colorScheme='red' + size='lg' + isChecked={isChecked} + onChange={handleChange} + /> + <p className='ml-2'>Ubah ke akun PKP</p> + </div> + )} <div> - <div className='font-semibold mb-2'>Informasi Usaha</div> + <div className='font-semibold mb-2 flex flex-row gap-x-2'> + <h2>Informasi Usaha</h2> + <div className='badge-red'>{company_type.toUpperCase()}</div> + </div> <div className='text-gray_r-11'> 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'> - {!isOpen && <ChevronDownIcon className='w-6' />} - {isOpen && <ChevronUpIcon className='w-6' />} - </div> - </button> - - {isOpen && ( + {/* <button + onClick={toggle} + className='btn-yellow w-full sm:w-fit sm:ml-auto min-w-[92px]' + > + Ubah + </button> */} + </div> + {!isOpen && ( <form className='p-4 border-t border-gray_r-6' onSubmit={(e) => { @@ -199,6 +263,28 @@ const CompanyProfile = () => { </div> </div> <div> + <label>Email Bisnis</label> + <input + {...register('email_bisnis')} + type='email' + className='form-input mt-3' + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.email_bisnis?.message} + </div> + </div> + <div> + <label>No. Handphone Bisnis</label> + <input + {...register('mobile_bisnis')} + type='tel' + className='form-input mt-3' + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.mobile_bisnis?.message} + </div> + </div> + <div> <label>Alamat Wajib Pajak</label> <input {...register('alamat_wajib_pajak')} @@ -238,6 +324,7 @@ const CompanyProfile = () => { </button> </form> )} + {isOpen && <SwitchAccount company_type={company_type} />} </> ); }; @@ -246,18 +333,38 @@ 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'), + email_bisnis: Yup.string().required('Harus di-isi'), + mobile_bisnis: Yup.string().required('Harus di-isi'), industry: Yup.string().required('Harus di-pilih'), companyType: Yup.string().required('Harus di-pilih'), + taxName: Yup.string(), + npwp: Yup.string(), + alamat_wajib_pajak: Yup.string(), + company_type: Yup.string(), + taxName: Yup.string().when('company_type', { + is: (company_type) => company_type !== 'Non PKP', + then: Yup.string().required('Harus di-isi'), + otherwise: Yup.string().notRequired(), + }), + npwp: Yup.string().when('company_type', { + is: (company_type) => company_type !== 'Non PKP', + then: Yup.string().required('Harus di-isi'), + otherwise: Yup.string().notRequired(), + }), + alamat_wajib_pajak: Yup.string().when('company_type', { + is: (company_type) => company_type !== 'Non PKP', + then: Yup.string().required('Harus di-isi'), + otherwise: Yup.string().notRequired(), + }), }); const defaultValues = { industry: '', companyType: '', name: '', + email_bisnis: '', + mobile_bisnis: '', taxName: '', npwp: '', alamat_wajib_pajak: '', diff --git a/src/lib/auth/components/Menu.jsx b/src/lib/auth/components/Menu.jsx index 9cd10ab4..4682dbab 100644 --- a/src/lib/auth/components/Menu.jsx +++ b/src/lib/auth/components/Menu.jsx @@ -2,9 +2,24 @@ import Link from '@/core/components/elements/Link/Link'; import { useRouter } from 'next/router'; import ImageNext from 'next/image'; import whatsappUrl from '@/core/utils/whatsappUrl'; +import useAuth from '@/core/hooks/useAuth'; +import switchAccountProgresApi from '@/lib/auth/api/switchAccountProgresApi.js'; +import { useState, useEffect } from 'react'; +import { InfoIcon } from 'lucide-react'; import { deleteAuth } from '@/core/utils/auth'; const Menu = () => { const router = useRouter(); + const auth = useAuth(); + const [ubahAkun, setUbahAkun] = useState(); + useEffect(() => { + const loadProgres = async () => { + const progresSwitchAccount = await switchAccountProgresApi(); + if (progresSwitchAccount) { + setUbahAkun(progresSwitchAccount.status); + } + }; + loadProgres(); + }, []); const routeStartWith = (route) => router.pathname.startsWith(route); @@ -13,63 +28,134 @@ const Menu = () => { router.push('/login'); }); }; - return ( <div className='grid grid-cols-1 bg-white border border-gray_r-6 rounded py-2 px-4 sticky top-48'> - <div className='mt-4 mb-1 font-medium'>Menu</div> - <LinkItem href='/my/quotations' active={routeStartWith('/my/quotations')}> - <div className='flex gap-x-3 items-center'> - <ImageNext - src='/images/icon/icon_daftar_quotation.svg' - width={18} - height={20} - /> - <p>Daftar Quotation</p> - </div> - </LinkItem> - <LinkItem - href='/my/transactions' - active={routeStartWith('/my/transactions')} - > - <div className='flex gap-x-3 items-center'> - <ImageNext - src='/images/icon/icon_daftar_transaksi.svg' - width={18} - height={20} - /> - <p>Daftar Transaksi</p> - </div> - </LinkItem> - <LinkItem href='/my/shipments' active={routeStartWith('/my/shipments')}> - <div className='flex gap-x-3 items-center'> - <ImageNext - src='/images/icon/icon_pengiriman.svg' - width={18} - height={20} - /> - <p>Daftar Pengiriman</p> - </div> - </LinkItem> - <LinkItem href='/my/invoices' active={routeStartWith('/my/invoices')}> - <div className='flex gap-x-3 items-center'> - <ImageNext - src='/images/icon/icon_invoice.svg' - width={18} - height={20} - /> - <p>Invoice & Faktur Pajak</p> + <div className='flex justify-between py-4'> + <div className='font-semibold text-gray_r-12'>Akun Saya</div> + <div className='relative group'> + {auth?.company && !(ubahAkun === 'pending') && ( + <> + <Link + href='/my/profile' + className='badge-solid-red mt-1 p-2 flex flex-row items-center gap-x-2' + > + <p className='text-white'>Akun Bisnis</p>{' '} + <InfoIcon size={14} color='white' /> + </Link> + <div className='absolute bottom-full transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-700 text-white text-xs rounded py-1 px-2 w-72 text-justify left-36'> + Anda terdaftar sebagai akun bisnis, segala bentuk transaksi anda + untuk perusahaan yang sudah anda daftarkan di Indoteknik.com + </div> + </> + )} + {ubahAkun === 'pending' && ( + <> + <Link + href='/my/profile' + className='badge-yellow mt-1 p-2 flex flex-row items-center gap-x-2' + > + <p className='text-warning-900'>Review</p> + <InfoIcon size={14} className='text-warning-900' /> + </Link> + <div className='absolute bottom-full transform -translate-x-1/2 mb-2 hidden group-hover:block bg-gray-700 text-white text-xs rounded py-1 px-2 w-72 text-justify left-36'> + Proses perubahan akun anda sedang kami review, mohon menunggu + hingga 2x24 jam. + </div> + </> + )} + {!auth?.company && !(ubahAkun === 'pending') && ( + <> + <Link + href='/my/profile' + className='badge-gray mt-1 p-2 flex flex-row items-center gap-x-2' + > + <p className='text-gray_r-10'>Akun Individu</p> + <InfoIcon size={14} className='text-gray_r-10' /> + </Link> + <div className='absolute bottom-full left-36 transform -translate-x-1/2 mb-3 w-72 text-justify hidden group-hover:block bg-gray-700 text-white text-xs rounded py-1 px-2'> + <p className='whitespace-pre-wrap break-words'> + Anda terdaftar sebagai akun individu, Segala bentuk transaksi + anda untuk Pribadi/Individu. + </p> + </div> + </> + )} </div> - </LinkItem> - <LinkItem href='/my/wishlist' active={routeStartWith('/my/wishlist')}> - <div className='flex gap-x-3 items-center'> - <ImageNext - src='/images/icon/icon_wishlist.svg' - width={18} - height={20} - /> - <p>Wishlist</p> - </div> - </LinkItem> + </div> + <div className='mt-2 mb-1 font-medium'>Menu</div> + <div className='flex flex-col gap-y-2'> + <LinkItem + href='/my/quotations' + active={routeStartWith('/my/quotations')} + className='' + > + <div className='flex gap-x-3 items-center'> + <ImageNext + src='/images/icon/icon_daftar_quotation.svg' + width={18} + height={20} + /> + <p>Daftar Quotation</p> + </div> + </LinkItem> + <LinkItem + href='/my/transactions' + active={routeStartWith('/my/transactions')} + > + <div className='flex gap-x-3 items-center'> + <ImageNext + src='/images/icon/icon_daftar_transaksi.svg' + width={18} + height={20} + /> + <p>Daftar Transaksi</p> + </div> + </LinkItem> + <LinkItem href='/my/shipments' active={routeStartWith('/my/shipments')}> + <div className='flex gap-x-3 items-center'> + <ImageNext + src='/images/icon/icon_pengiriman.svg' + width={18} + height={20} + /> + <p>Daftar Pengiriman</p> + </div> + </LinkItem> + <LinkItem href='/my/invoices' active={routeStartWith('/my/invoices')}> + <div className='flex gap-x-3 items-center'> + <ImageNext + src='/images/icon/icon_invoice.svg' + width={18} + height={20} + /> + <p>Invoice & Faktur Pajak</p> + </div> + </LinkItem> + {auth && + auth.partnerTempo && + (auth.partnerTempo || auth.tempoProgres === 'review') && ( + <LinkItem href='/my/tempo' active={routeStartWith('/my/tempo')}> + <div className='flex gap-x-3 items-center'> + <ImageNext + src='/images/icon/icon_tempo.svg' + width={18} + height={20} + /> + <p>Pembayaran Tempo</p> + </div> + </LinkItem> + )} + <LinkItem href='/my/wishlist' active={routeStartWith('/my/wishlist')}> + <div className='flex gap-x-3 items-center'> + <ImageNext + src='/images/icon/icon_wishlist.svg' + width={18} + height={20} + /> + <p>Wishlist</p> + </div> + </LinkItem> + </div> <div className='mt-4 mb-1 font-medium'>Pusat Bantuan</div> <LinkItem @@ -87,40 +173,41 @@ const Menu = () => { </div> </LinkItem> <div className='mt-4 mb-1 font-medium'>Pengaturan Akun</div> - <LinkItem href='/my/address' active={routeStartWith('/my/address')}> - <div className='flex gap-x-3 items-center'> - <ImageNext - src='/images/icon/icon_daftar_alamat.svg' - width={18} - height={20} - /> - <p>Daftar Alamat</p> - </div> - </LinkItem> - <LinkItem href='/my/profile' active={routeStartWith('/my/profile')}> - <div className='flex gap-x-3 items-center'> - <ImageNext - src='/images/icon/icon_profile.svg' - width={18} - height={20} - /> - <p>Profil Saya</p> - </div> - </LinkItem> - <button - type='button' - onClick={logout} - className='text-gray_r-12/80 p-2 text-left' - > - <div className='flex gap-x-3 items-center'> - <ImageNext - src='/images/icon/icon_logout.svg' - width={18} - height={20} - /> - <p>Keluar Akun</p> - </div> - </button> + <div className='flex flex-col gap-y-2'> + <LinkItem href='/my/address' active={routeStartWith('/my/address')}> + <div className='flex gap-x-3 items-center'> + <ImageNext + src='/images/icon/icon_daftar_alamat.svg' + width={18} + height={20} + /> + <p>Daftar Alamat</p> + </div> + </LinkItem> + <LinkItem href='/my/profile' active={routeStartWith('/my/profile')}> + <div className='flex gap-x-3 items-center'> + <ImageNext + src='/images/icon/icon_profile.svg' + width={18} + height={20} + /> + <p>Profil Saya</p> + </div> + </LinkItem> + <button + type='button' + className='text-gray_r-12/80 p-2 text-left hover:bg-gray_r-5 ' + > + <div className='flex gap-x-3 items-center'> + <ImageNext + src='/images/icon/icon_logout.svg' + width={18} + height={20} + /> + <p>Keluar Akun</p> + </div> + </button> + </div> </div> ); }; @@ -128,8 +215,8 @@ const Menu = () => { const LinkItem = ({ children, ...props }) => ( <Link {...props} - className={`!text-gray_r-12/80 !font-normal p-2 rounded ${ - props.active == true ? 'bg-gray_r-3' : '' + className={`!text-gray_r-12/80 !font-normal p-2 rounded transition-colors duration-200 ${ + props.active ? 'bg-gray_r-3' : 'hover:bg-gray_r-5 ' }`} > {children} diff --git a/src/lib/auth/components/PersonalProfile.jsx b/src/lib/auth/components/PersonalProfile.jsx index b9fb3f5f..3053255d 100644 --- a/src/lib/auth/components/PersonalProfile.jsx +++ b/src/lib/auth/components/PersonalProfile.jsx @@ -1,96 +1,112 @@ -import useAuth from '@/core/hooks/useAuth' -import { setAuth } from '@/core/utils/auth' -import addressApi from '@/lib/address/api/addressApi' -import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline' -import { useEffect, useState } from 'react' -import { useForm } from 'react-hook-form' -import { toast } from 'react-hot-toast' -import editPersonalProfileApi from '../api/editPersonalProfileApi' +import useAuth from '@/core/hooks/useAuth'; +import { setAuth } from '@/core/utils/auth'; +import addressApi from '@/lib/address/api/addressApi'; +import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'; +import { useEffect, useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { toast } from 'react-hot-toast'; +import editPersonalProfileApi from '../api/editPersonalProfileApi'; const PersonalProfile = () => { - const auth = useAuth() - const [isOpen, setIsOpen] = useState(true) - const toggle = () => setIsOpen(!isOpen) + const auth = useAuth(); + const [isOpen, setIsOpen] = useState(true); + const toggle = () => setIsOpen(!isOpen); const { register, setValue, handleSubmit } = useForm({ defaultValues: { email: '', name: '', mobile: '', - password: '' - } - }) + password: '', + }, + }); useEffect(() => { const loadProfile = async () => { - const dataProfile = await addressApi({ id: auth.partnerId }) - setValue('email', dataProfile?.email) - setValue('name', dataProfile?.name) - setValue('mobile', dataProfile?.mobile) - } - if (auth) loadProfile() - }, [auth, setValue]) + const dataProfile = await addressApi({ + id: auth.partnerId ? auth.partnerId : auth.partner_id, + }); + setValue('email', dataProfile?.email); + setValue('name', dataProfile?.name); + setValue('mobile', dataProfile?.mobile); + }; + if (auth) loadProfile(); + }, [auth, setValue]); const onSubmitHandler = async (values) => { - let data = values - if (!values.password) delete data.password - const isUpdated = await editPersonalProfileApi({ data }) + let data = values; + if (!values.password) delete data.password; + const isUpdated = await editPersonalProfileApi({ data }); if (isUpdated?.user) { - setAuth(isUpdated.user) - setValue('password', '') - toast.success('Berhasil mengubah profil', { duration: 1500 }) - return + setAuth(isUpdated.user); + setValue('password', ''); + toast.success('Berhasil mengubah profil', { duration: 1500 }); + return; } - toast.error('Terjadi kesalahan internal') - } + toast.error('Terjadi kesalahan internal'); + }; return ( <> - <button type='button' onClick={toggle} className='p-4 flex items-center text-left w-full'> + <div type='button' className='p-4 flex items-center text-left w-full'> <div> <div className='font-semibold mb-2'>Informasi Akun</div> <div className='text-gray_r-11'> - Dibawah ini adalah data diri yang anda masukan, periksa kembali data diri anda + Dibawah ini adalah data diri yang anda masukan, periksa kembali data + diri anda </div> </div> - <div className='ml-auto p-2 bg-gray_r-3 rounded'> - {!isOpen && <ChevronDownIcon className='w-6' />} - {isOpen && <ChevronUpIcon className='w-6' />} - </div> - </button> + </div> - {isOpen && ( - <form className='p-4 border-t border-gray_r-6' onSubmit={handleSubmit(onSubmitHandler)}> - <div className='grid grid-cols-1 sm:grid-cols-2 gap-4'> - <div> - <label>Email</label> - <input {...register('email')} type='text' disabled className='form-input mt-3' /> - </div> - <div> - <label>Nama Lengkap</label> - <input {...register('name')} type='text' className='form-input mt-3' /> - </div> - <div> - <label>No. Handphone</label> - <input {...register('mobile')} type='tel' className='form-input mt-3' /> - </div> - <div> - <label>Kata Sandi</label> - <input - {...register('password')} - type='password' - className='form-input mt-3' - placeholder='Isi jika ingin mengubah kata sandi' - /> - </div> + <form + className='p-4 border-t border-gray_r-6' + onSubmit={handleSubmit(onSubmitHandler)} + > + <div className='grid grid-cols-1 sm:grid-cols-2 gap-4'> + <div> + <label>Email</label> + <input + {...register('email')} + type='text' + disabled + className='form-input mt-3' + /> + </div> + <div> + <label>Nama Lengkap</label> + <input + {...register('name')} + type='text' + className='form-input mt-3' + /> </div> - <button type='submit' className='btn-yellow w-full sm:w-fit sm:ml-auto mt-6'> - Simpan - </button> - </form> - )} + <div> + <label>No. Handphone</label> + <input + {...register('mobile')} + type='tel' + className='form-input mt-3' + /> + </div> + <div> + <label>Kata Sandi</label> + <input + {...register('password')} + type='password' + className='form-input mt-3' + placeholder='Isi jika ingin mengubah kata sandi' + /> + </div> + </div> + <button + type='submit' + className='btn-yellow w-full sm:w-fit sm:ml-auto mt-6 max-w-28' + > + Simpan + </button> + </form> </> - ) -} + ); +}; -export default PersonalProfile +export default PersonalProfile; diff --git a/src/lib/auth/components/StatusSwitchAccount.jsx b/src/lib/auth/components/StatusSwitchAccount.jsx new file mode 100644 index 00000000..73316616 --- /dev/null +++ b/src/lib/auth/components/StatusSwitchAccount.jsx @@ -0,0 +1,5 @@ +const StatusSwitchAccount = ({ status }) => { + return <>Perpindahan akun anda masih {status}</>; +}; + +export default StatusSwitchAccount; diff --git a/src/lib/auth/components/SwitchAccount.jsx b/src/lib/auth/components/SwitchAccount.jsx new file mode 100644 index 00000000..46e57348 --- /dev/null +++ b/src/lib/auth/components/SwitchAccount.jsx @@ -0,0 +1,301 @@ +import useAuth from '@/core/hooks/useAuth'; +import { setAuth } from '@/core/utils/auth'; +import addressApi from '@/lib/address/api/addressApi'; +import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'; +import { useEffect, useState, useMemo } from 'react'; +import { useForm } from 'react-hook-form'; +import { toast } from 'react-hot-toast'; +import switchAccountApi from '../api/switchAccountApi'; +import FormBisnis from '~/modules/register/components/FormBisnis.tsx'; +import RegistrasiBisnis from '~/modules/register/components/RegistrasiBisnis.tsx'; +import { Radio, RadioGroup, Stack, Divider, Button } from '@chakra-ui/react'; +import { useRegisterStore } from '~/modules/register/stores/useRegisterStore.ts'; +import { registerUser } from '~/services/auth'; +import { useMutation } from 'react-query'; +import { isValid } from 'zod'; +import useDevice from '@/core/hooks/useDevice'; +import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; +const SwitchAccount = ({ company_type }) => { + const { isDesktop, isMobile } = useDevice(); + const auth = useAuth(); + const [isOpen, setIsOpen] = useState(true); + const toggle = () => setIsOpen(!isOpen); + const [isPKP, setIsPKP] = useState(true); + const [isTerdaftar, setIsTerdaftar] = useState(false); + const [isChecked, setIsChecked] = useState(false); + const [selectedValueBisnis, setSelectedValueBisnis] = useState('false'); + const [selectedValue, setSelectedValue] = useState('PKP'); + const [buttonSubmitClick, setButtonSubmitClick] = useState(false); + const [changeConfirmation, setChangeConfirmation] = useState(false); + const { register, setValue, handleSubmit } = useForm({ + defaultValues: { + email: '', + name: '', + phone: '', + password: '', + }, + }); + const mutation = useMutation({ + mutationFn: (data) => registerUser(data), + }); + const [notValid, setNotValid] = useState(false); + const { + form, + isCheckedTNC, + isValidCaptcha, + errors, + validate, + updateForm, + resetForm, + } = useRegisterStore(); + const isFormValid = useMemo(() => Object.keys(errors).length === 0, [errors]); + useEffect(() => { + const loadProfile = async () => { + const dataProfile = await addressApi({ id: auth.partnerId }); + setValue('email', dataProfile?.email); + setValue('name', dataProfile?.name); + setValue('phone', dataProfile?.phone); + }; + if (auth) loadProfile(); + }, [auth, setValue]); + + useEffect(() => { + if (selectedValue === 'PKP') { + updateForm('is_pkp', 'true'); + validate(); + } else { + updateForm('is_pkp', 'false'); + validate(); + } + }, [selectedValue]); + + useEffect(() => { + if (isTerdaftar) { + updateForm('is_terdaftar', 'true'); + validate(); + } else { + updateForm('is_terdaftar', 'false'); + validate(); + } + }, [isTerdaftar]); + useEffect(() => { + const loadProfile = async () => { + const dataProfile = await addressApi({ + id: auth.parentId ? auth.parentId : auth.parent_id, + }); + if (dataProfile?.companyType === 'nonpkp') { + setSelectedValue('PKP'); + } + if (auth?.company) { + updateForm('email_partner', dataProfile?.email); + updateForm('business_name', dataProfile?.name); + updateForm('industry_id', `${dataProfile?.industryId}`); + updateForm('company_type_id', `${dataProfile?.companyTypeId}`); + updateForm('nama_wajib_pajak', dataProfile?.taxName); + updateForm('npwp', dataProfile?.npwp); + updateForm('sppkp', dataProfile?.sppkp); + updateForm('alamat_wajib_pajak', dataProfile?.alamatWajibPajak); + updateForm('alamat_bisnis', dataProfile?.alamatBisnis); + validate(); + } + }; + if (auth) loadProfile(); + }, [auth, setValue]); + useEffect(() => { + updateForm('name', '-'); + updateForm('email', 'example@mail.com'); + updateForm('password', 'example@mail.com'); + updateForm('phone', '081234567890'); + validate(); + }, [buttonSubmitClick, changeConfirmation]); + + const handleChangeBisnis = (value) => { + resetForm(); + setSelectedValueBisnis(value); + if (value === 'true') { + validate(); + setIsTerdaftar(true); + } else { + validate(); + setIsTerdaftar(false); + } + }; + const handleChange = (value) => { + setSelectedValue(value); + if (value === 'PKP') { + validate(); + setIsPKP(true); + } else { + validate(); + setIsPKP(false); + setIsPKP(false); + } + }; + const onSubmitHandler = async (values) => { + toast.loading('Mengubah status akun...'); + updateForm('parent_id', `${auth.parentId}`); + setChangeConfirmation(false); + // let data = { ...form, id: `${auth.partnerId}` }; + const data = form; + if (!isFormValid) { + setNotValid(true); + setButtonSubmitClick(!buttonSubmitClick); + return; + } else { + setButtonSubmitClick(!buttonSubmitClick); + setNotValid(false); + } + // if (!values.password) delete data.password; + const isUpdated = await switchAccountApi({ data }); + + if (isUpdated?.switch === 'Pending') { + // setAuth(isUpdated.user); + // setValue('password', ''); + toast.success('Berhasil mengubah akun', { duration: 1500 }); + setTimeout(() => { + window.location.reload(); + }, 1500); + return; + } + toast.error('Terjadi kesalahan internal'); + }; + + const onSubmitHandlerCancel = async (values) => { + window.location.reload(); + }; + + return ( + <> + <BottomPopup + active={changeConfirmation} + close={() => setChangeConfirmation(false)} + title='Ubah profil Bisnis' + > + <div className='leading-7 text-gray_r-12/80'> + Anda yakin akan merubah profil bisnis anda dari INDIVIDU menjadi{' '} + {selectedValue}? + </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={onSubmitHandler} + > + Ya, Ubah + </button> + <button + className='btn-light flex-1 md:flex-none' + type='button' + onClick={() => setChangeConfirmation(false)} + > + Batal + </button> + </div> + </BottomPopup> + {/* <div type='button' className='ml-4 flex items-center text-left w-full'> + <div + className={`flex ${ + isDesktop ? 'flex-row' : 'flex-col gap-y-2' + } items-start justify-start bg-slate-50`} + > + <div className='flex font-semibold mr-2'>Informasi Bisnis</div> + <div className='text-red-500 text-xs'> + *Perubahan akun tidak dapat diubah kembali + </div> + </div> + </div> */} + <div className='px-4 '> + <div + class='flex items-center p-4 mb-4 text-sm border border-red-500 text-red-800 rounded-lg bg-red-50' + role='alert' + > + <svg + class='flex-shrink-0 inline w-4 h-4 mr-3' + aria-hidden='true' + fill='currentColor' + viewBox='0 0 20 20' + > + <path d='M10 .5a9.5 9.5 0 1 0 9.5 9.5A9.51 9.51 0 0 0 10 .5ZM9.5 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3ZM12 15H8a1 1 0 0 1 0-2h1v-3H8a1 1 0 0 1 0-2h2a1 1 0 0 1 1 1v4h1a1 1 0 0 1 0 2Z' /> + </svg> + <span class='sr-only'>Info</span> + <div className='text-justify'> + Mohon diperhatikan bahwa perubahan data akun bisnis akan + mengakibatkan perubahan pada informasi yang tertera di faktur pajak + dan invoice. + </div> + </div> + </div> + + <div className='px-4 mb-4'> + {!auth?.company && company_type === 'nonpkp' && ( + <> + <div className='mb-4'> + <p className='text-black font-bold mb-2'> + Bisnis Terdaftar di Indoteknik? + </p> + <RadioGroup + onChange={handleChangeBisnis} + value={selectedValueBisnis} + > + <Stack direction='row'> + <Radio colorScheme='red' value='true'> + Sudah Terdaftar + </Radio> + <Radio colorScheme='red' value='false' className='ml-2'> + Belum Terdaftar + </Radio> + </Stack> + </RadioGroup> + </div> + {!isTerdaftar && ( + <div className=''> + <p className='text-black font-bold mb-2'>Tipe Bisnis</p> + <RadioGroup onChange={handleChange} value={selectedValue}> + <Stack direction='row' className='font-bold'> + <Radio colorScheme='red' value='PKP'> + PKP + </Radio> + {!auth?.company && company_type === 'nonpkp' && ( + <Radio colorScheme='red' value='Non-PKP' className='ml-4'> + Non-PKP + </Radio> + )} + </Stack> + </RadioGroup> + </div> + )} + </> + )} + <FormBisnis + type={isDesktop ? 'profil' : 'bisnis'} + required={isTerdaftar} + isPKP={isPKP} + chekValid={notValid} + buttonSubmitClick={buttonSubmitClick} + /> + <div className='flex flex-row justify-end mt-4 '> + <div className='mr-4'> + <button + type='submit' + onClick={() => setChangeConfirmation(true)} + className='btn-yellow w-full sm:w-fit sm:ml-auto mt-6 mr-8 md:mr-4' + > + {mutation.isLoading ? 'Loading...' : 'Simpan Perubahan'} + </button> + </div> + <div> + <button + type='submit' + onClick={onSubmitHandlerCancel} + className='btn-solid-red w-full sm:w-fit sm:ml-auto mt-6' + > + {mutation.isLoading ? 'Loading...' : 'Batal'} + </button> + </div> + </div> + </div> + </> + ); +}; + +export default SwitchAccount; diff --git a/src/lib/cart/components/Cart.jsx b/src/lib/cart/components/Cart.jsx index c6aaa596..1e25d959 100644 --- a/src/lib/cart/components/Cart.jsx +++ b/src/lib/cart/components/Cart.jsx @@ -27,6 +27,7 @@ import CardProdcuctsList from '@/core/components/elements/Product/cartProductsLi // import cardProdcuctsList from '@/core/components/elements/Product/cartProductsList' const Cart = () => { + const PPN = process.env.NEXT_PUBLIC_PPN const router = useRouter() const [products, setProducts] = useState(null) const [isLoading, setIsLoading] = useState(true) @@ -97,7 +98,7 @@ const Cart = () => { if (product.canBuy == false) { toggleSelected(product.id) } - let priceBeforeTax = product.price.price / 1.11 + let priceBeforeTax = product.price.price / PPN calculateTotalPriceBeforeTax += priceBeforeTax * product.quantity calculateTotalTaxAmount += (product.price.price - priceBeforeTax) * product.quantity calculateTotalDiscountAmount += diff --git a/src/lib/cart/components/CartOld.jsx b/src/lib/cart/components/CartOld.jsx index 718541af..358efa49 100644 --- a/src/lib/cart/components/CartOld.jsx +++ b/src/lib/cart/components/CartOld.jsx @@ -20,6 +20,7 @@ import whatsappUrl from '@/core/utils/whatsappUrl' import useAuth from '@/core/hooks/useAuth' const Cart = () => { + const PPN = process.env.NEXT_PUBLIC_PPN const router = useRouter() const [products, setProducts] = useState(null) const auth = useAuth() @@ -67,7 +68,7 @@ const Cart = () => { }) if (!product.selected) continue - let priceBeforeTax = product.price.price / 1.11 + let priceBeforeTax = product.price.price / PPN calculateTotalPriceBeforeTax += priceBeforeTax * product.quantity calculateTotalTaxAmount += (product.price.price - priceBeforeTax) * product.quantity calculateTotalDiscountAmount += diff --git a/src/lib/cart/components/Cartheader.jsx b/src/lib/cart/components/Cartheader.jsx index 1c30bb13..6d4e2679 100644 --- a/src/lib/cart/components/Cartheader.jsx +++ b/src/lib/cart/components/Cartheader.jsx @@ -14,6 +14,7 @@ const { ShoppingCartIcon, PhotoIcon } = require('@heroicons/react/24/outline'); const { default: Link } = require('next/link'); const Cardheader = (cartCount) => { + const PPN = process.env.NEXT_PUBLIC_PPN const router = useRouter(); const [subTotal, setSubTotal] = useState(null); const [buttonLoading, SetButtonTerapkan] = useState(false); @@ -66,7 +67,7 @@ const Cardheader = (cartCount) => { for (const product of products) { if (product.quantity == '') continue; - let priceBeforeTax = product.price.price / 1.11; + let priceBeforeTax = product.price.price / PPN; calculateTotalPriceBeforeTax += priceBeforeTax * product.quantity; calculateTotalTaxAmount += (product.price.price - priceBeforeTax) * product.quantity; diff --git a/src/lib/checkout/components/Checkout.jsx b/src/lib/checkout/components/Checkout.jsx index 6fb5cdb4..e3b586f1 100644 --- a/src/lib/checkout/components/Checkout.jsx +++ b/src/lib/checkout/components/Checkout.jsx @@ -50,6 +50,7 @@ function convertToInternational(number) { } const Checkout = () => { + const PPN = process.env.NEXT_PUBLIC_PPN ? parseFloat(process.env.NEXT_PUBLIC_PPN) : 0; const router = useRouter(); const query = router.query.source ?? null; const qVoucher = router.query.voucher ?? null; @@ -587,7 +588,6 @@ const Checkout = () => { setItemTnC(item); SetBottomPopupTnC(true); }; - // const taxTotal = (totalAmount - totalDiscountAmount - discountVoucher) * 0.11 const hasNoPrice = useMemo(() => { if (!products) return false; @@ -1210,7 +1210,7 @@ const Checkout = () => { <div>{currencyFormat(cartCheckout?.subtotal)}</div> </div> <div className='flex gap-x-2 justify-between'> - <div className='text-gray_r-11'>PPN 11%</div> + <div className='text-gray_r-11'>PPN {((PPN - 1) * 100).toFixed(0)}%</div> <div>{currencyFormat(cartCheckout?.tax)}</div> </div> <div className='flex gap-x-2 justify-between'> @@ -1514,7 +1514,7 @@ const Checkout = () => { <div>{currencyFormat(cartCheckout?.subtotal)}</div> </div> <div className='flex gap-x-2 justify-between'> - <div className='text-gray_r-11'>PPN 11%</div> + <div className='text-gray_r-11'>PPN {((PPN - 1) * 100).toFixed(0)}%</div> <div>{currencyFormat(cartCheckout?.tax)}</div> </div> <div className='flex gap-x-2 justify-between'> diff --git a/src/lib/checkout/components/CheckoutOld.jsx b/src/lib/checkout/components/CheckoutOld.jsx index 5b479a73..433c5672 100644 --- a/src/lib/checkout/components/CheckoutOld.jsx +++ b/src/lib/checkout/components/CheckoutOld.jsx @@ -239,7 +239,7 @@ const Checkout = () => { setIsLoading(false) window.location.href = payment.data.redirectUrl } - const taxTotal = (totalAmount - totalDiscountAmount) * 0.11 + const taxTotal = (totalAmount - totalDiscountAmount) * (PPN - 1) return ( <> diff --git a/src/lib/checkout/email/FinishCheckoutEmail.jsx b/src/lib/checkout/email/FinishCheckoutEmail.jsx index d19ba1ca..9a94587e 100644 --- a/src/lib/checkout/email/FinishCheckoutEmail.jsx +++ b/src/lib/checkout/email/FinishCheckoutEmail.jsx @@ -17,6 +17,7 @@ import { import FinishCheckout from '../components/FinishCheckout' const FinishCheckoutEmail = ({ transaction, payment, statusPayment }) => { + const PPN = process.env.NEXT_PUBLIC_PPN return ( <Html> @@ -206,9 +207,9 @@ const FinishCheckoutEmail = ({ transaction, payment, statusPayment }) => { </Column> </Row> <Row style={style.descriptionRow}> - <Column style={style.descriptionLCol}>PPN 11% (Incl.)</Column> + <Column style={style.descriptionLCol}>PPN {((PPN - 1) * 100).toFixed(0)}% (Incl.)</Column> <Column style={style.descriptionRCol}> - {currencyFormat(transaction.subtotal * 0.11)} + {currencyFormat(transaction.subtotal * (PPN - 1))} </Column> </Row> diff --git a/src/lib/flashSale/components/FlashSale.jsx b/src/lib/flashSale/components/FlashSale.jsx index f4be279e..9c0e713b 100644 --- a/src/lib/flashSale/components/FlashSale.jsx +++ b/src/lib/flashSale/components/FlashSale.jsx @@ -9,13 +9,23 @@ import { FlashSaleSkeleton } from '../skeleton/FlashSaleSkeleton'; const FlashSale = () => { const [flashSales, setFlashSales] = useState(null); const [isLoading, setIsLoading] = useState(true); + const [duration, setDuration] = useState(); + const calculateRemainingTime = (endDate) => { + const currentTime = new Date(); + const endTime = new Date(endDate); + const remainingTimeInSeconds = (endTime - currentTime) / 1000; + + return Math.max(Math.round(remainingTimeInSeconds), 0); + }; useEffect(() => { const loadFlashSales = async () => { const res = await fetch('/api/flashsale-header'); const { data } = await res.json(); if (data) { setFlashSales(data); + const remainingTime = calculateRemainingTime(data[0]?.endDate); + setDuration(remainingTime); } setIsLoading(false); }; @@ -37,7 +47,7 @@ const FlashSale = () => { <div className='font-medium sm:text-h-lg mt-1.5'> {flashSale.name} </div> - <CountDown initialTime={flashSale.duration} /> + <CountDown initialTime={duration} /> </div> <div className='relative'> @@ -59,7 +69,7 @@ const FlashSale = () => { /> <FlashSaleProduct flashSaleId={flashSale.pricelistId} - duration={flashSale.duration} + duration={duration} /> </div> </div> diff --git a/src/lib/form/components/KunjunganSales.jsx b/src/lib/form/components/KunjunganSales.jsx index 3779b836..0f63de03 100644 --- a/src/lib/form/components/KunjunganSales.jsx +++ b/src/lib/form/components/KunjunganSales.jsx @@ -42,7 +42,7 @@ const KunjunganSales = () => { useEffect(() => { const loadState = async () => { - let dataState = await stateApi(); + let dataState = await stateApi({ tempo: false }); dataState = dataState.map((state) => ({ value: state.id, label: state.name, diff --git a/src/lib/form/components/KunjunganService.jsx b/src/lib/form/components/KunjunganService.jsx index e3c83f78..a3871054 100644 --- a/src/lib/form/components/KunjunganService.jsx +++ b/src/lib/form/components/KunjunganService.jsx @@ -41,7 +41,7 @@ const CreateKunjunganService = () => { useEffect(() => { const loadState = async () => { - let dataState = await stateApi(); + let dataState = await stateApi({ tempo: false }); dataState = dataState.map((state) => ({ value: state.id, label: state.name, diff --git a/src/lib/form/components/Merchant.jsx b/src/lib/form/components/Merchant.jsx new file mode 100644 index 00000000..03b8fc84 --- /dev/null +++ b/src/lib/form/components/Merchant.jsx @@ -0,0 +1,349 @@ +import HookFormSelect from '@/core/components/elements/Select/HookFormSelect'; +import cityApi from '@/lib/address/api/cityApi'; +import stateApi from '@/lib/address/api/stateApi.js'; +import { yupResolver } from '@hookform/resolvers/yup'; +import React, { useEffect, useRef, useState } from 'react'; +import ReCAPTCHA from 'react-google-recaptcha'; +import { Controller, useForm } from 'react-hook-form'; +import { toast } from 'react-hot-toast'; +import * as Yup from 'yup'; +import createLeadApi from '../api/createLeadApi'; +import PageContent from '@/lib/content/components/PageContent'; +import { useRouter } from 'next/router'; +import useAuth from '@/core/hooks/useAuth'; + +const CreateMerchant = () => { + const { + register, + handleSubmit, + formState: { errors }, + control, + reset, + watch, + setValue, + } = useForm({ + resolver: yupResolver(validationSchema), + defaultValues, + }); + const list_unit = [ + { + value: 'Manufacturing', + label: 'Manufacturing', + }, + { + value: 'Hospitality', + label: 'Hospitality', + }, + { + value: 'Automotive', + label: 'Automotive', + }, + { + value: 'Retail', + label: 'Retail', + }, + { + value: 'Maining', + label: 'Maining', + }, + { + value: 'Lain - Lain', + label: 'Lain - Lain', + }, + ]; + const [cities, setCities] = useState([]); + const [state, setState] = useState([]); + const [company_unit, setCompany_unit] = useState(list_unit); + + const recaptchaRef = useRef(null); + const router = useRouter(); + + const auth = useAuth(); + if (auth == false) { + router.push(`/login?next=${encodeURIComponent('/daftar-merchant')}`); + } + + useEffect(() => { + const loadState = async () => { + let dataState = await stateApi({ tempo: false }); + dataState = dataState.map((state) => ({ + value: state.id, + label: state.name, + })); + setState(dataState); + }; + loadState(); + }, []); + + const watchState = watch('state'); + useEffect(() => { + if (auth == false) { + return; + } + const loadCities = async () => { + setValue('city', ''); + let dataCities = await cityApi({ stateId: watchState }); + dataCities = dataCities?.map((city) => ({ + value: city.id, + label: city.name, + })); + setCities(dataCities); + }; + loadCities(); + }, [auth, watchState, setValue]); + + const onSubmitHandler = async (values) => { + const recaptchaValue = recaptchaRef.current.getValue(); + if (!recaptchaValue) { + toast.error('Catcha harus diisi'); + return; + } + const data = { + ...values, + name: 'Form Merchant - ' + values.company, + contact_name: values.cp, + email_from: values.email, + phone: values.phone, + description: + 'Nama Perusahaan : ' + + values.company + + ' \r\n Alamat : ' + + values.address + + ' \r\n Kota : ' + + values.city + + ' \r\n Unit Perusahaan : ' + + values.company_unit + + ' \r\n Telepon: ' + + values.phone + + ' \r\n Email : ' + + values.email + + ' \r\n Website : ' + + values.website + + ' \r\n No Hp : ' + + values.mobile + + 'Keterangan : ' + + values.description, + }; + const create_leads = await createLeadApi({ data }); + if (create_leads) { + toast.success('Berhasil menambahkan data'); + reset(); + recaptchaRef.current.reset(); + } + }; + if (!auth) { + return; + } + return ( + <div className='container mx-auto p-4 md:p-0 my-0 md:my-10'> + <h1 className='text-h-sm md:text-title-sm font-semibold mb-6'> + Form Merchant + </h1> + <div className='w-full p-4 bg-white border border-gray_r-6 rounded'> + <div + className='flex items-center bg-blue-100 border border-blue-400 text-blue-500 font-bold px-4 py-3 mb-4' + role='alert' + > + <p> + Penjualan online adalah hal yang HARUS dilakukan mulai sekarang. + Perubahan dalam banyak industri dan pola pembelian. Gabung dengan + platform kami dan mulai penjualan lansung di ribuan perusahaan d + seluruh Indonesia.{' '} + </p> + </div> + <div className='w-full grid grid-cols-2 gap-x-2'> + <form onSubmit={handleSubmit(onSubmitHandler)}> + <div className=''> + <div> + <label className='form-label mb-2'>Nama Perusahan *</label> + <input + {...register('company')} + placeholder='PT.Indoteknik' + type='text' + className='form-input' + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.company?.message} + </div> + </div> + </div> + <div className=''> + <div> + <label className='form-label mb-2'>Alamat*</label> + <input + {...register('address')} + placeholder='jl. Bandengan no.31 ' + type='text' + className='form-input' + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.address?.message} + </div> + </div> + </div> + <div className=''> + <div> + <label className='form-label mb-2'>No. Telp *</label> + <input + {...register('phone')} + placeholder='021-XXXX' + type='text' + className='form-input' + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.phone?.message} + </div> + </div> + </div> + <div> + <label className='form-label mb-2'>Provinsi*</label> + <Controller + name='state' + control={control} + render={(props) => ( + <HookFormSelect {...props} options={state} /> + )} + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.state?.message} + </div> + </div> + <div className=''> + <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> + <div className=''> + <div> + <label className='form-label mb-2'>Unit Perusahaan*</label> + <Controller + name='company_unit' + control={control} + render={(props) => ( + <HookFormSelect {...props} options={company_unit} /> + )} + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.company_unit?.message} + </div> + </div> + </div> + <div className=''> + <div> + <label className='form-label mb-2'>Website *</label> + <input + {...register('website')} + placeholder='https://indoteknik.com' + type='text' + className='form-input' + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.website?.message} + </div> + </div> + </div> + <div className=''> + <div> + <label className='form-label mb-2'>Contact Person*</label> + <input + {...register('cp')} + placeholder='Jhone doe' + type='text' + className='form-input' + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.cp?.message} + </div> + </div> + </div> + <div className=''> + <div> + <label className='form-label mb-2'>No HP *</label> + <input + {...register('mobile')} + placeholder='628XXXXXXX' + type='text' + className='form-input' + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.mobile?.message} + </div> + </div> + </div> + <div className=''> + <div> + <label className='form-label mb-2'>Alamat Email *</label> + <input + {...register('email')} + placeholder='contoh@email.com' + type='email' + className='form-input' + /> + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.email?.message} + </div> + </div> + </div> + <div className=''> + <div> + <ReCAPTCHA + ref={recaptchaRef} + sitekey={process.env.NEXT_PUBLIC_RECAPTCHA_GOOGLE} + /> + </div> + </div> + <div className=''> + <div> + <button + type='submit' + className='btn-yellow w-full md:w-fit mt-6 ml-0 md:ml-auto' + > + Simpan + </button> + </div> + </div> + </form> + <PageContent path='/daftar-merchant' /> + </div> + </div> + </div> + ); +}; +const validationSchema = Yup.object().shape({ + company: Yup.string().required('Harus di-isi'), + email: Yup.string() + .email('Format harus seperti contoh@email.com') + .required('Harus di-isi'), + phone: Yup.string().required('Harus di-isi'), + cp: Yup.string().required('Harus di-isi'), + state: Yup.string().required('Harus dipilih'), + city: Yup.string().required('Harus di-isi'), + company_unit: Yup.string().required('Harus di-isi'), + address: Yup.string().required('Harus di-isi'), + website: Yup.string().required('Harus di-isi'), + mobile: Yup.string().required('Harus di-isi'), +}); +const defaultValues = { + company: '', + email: '', + phone: '', + state: '', + city: '', + company_unit: '', + cp: '', + address: '', + website: '', + mobile: '', +}; + +export default CreateMerchant; diff --git a/src/lib/form/components/RequestForQuotation.jsx b/src/lib/form/components/RequestForQuotation.jsx index 170a5c62..8861338f 100644 --- a/src/lib/form/components/RequestForQuotation.jsx +++ b/src/lib/form/components/RequestForQuotation.jsx @@ -42,7 +42,7 @@ const RequestForQuotation = () => { useEffect(() => { const loadState = async () => { - let dataState = await stateApi(); + let dataState = await stateApi({ tempo: false }); dataState = dataState.map((state) => ({ value: state.id, label: state.name, diff --git a/src/lib/invoice/components/Invoice.jsx b/src/lib/invoice/components/Invoice.jsx index 81202b1c..15bfa746 100644 --- a/src/lib/invoice/components/Invoice.jsx +++ b/src/lib/invoice/components/Invoice.jsx @@ -13,6 +13,7 @@ import { createSlug } from '@/core/utils/slug' import { useEffect, useState } from 'react' const Invoice = ({ id }) => { + const PPN = process.env.NEXT_PUBLIC_PPN const { invoice } = useInvoice({ id }) const [totalAmount, setTotalAmount] = useState(0) @@ -255,8 +256,8 @@ const Invoice = ({ id }) => { {currencyFormat(invoice.data?.amountTotal)} </div> - <div className='text-right'>PPN 11% (Incl.)</div> - <div className='text-right font-medium'>{currencyFormat(totalAmount * 0.11)}</div> + <div className='text-right'>PPN {((PPN - 1) * 100).toFixed(0)}% (Incl.)</div> + <div className='text-right font-medium'>{currencyFormat(invoice.data?.amountTotal - totalAmount)}</div> </div> </div> </div> diff --git a/src/lib/merchant/components/Merchant.jsx b/src/lib/merchant/components/Merchant.jsx index a92619b6..3c16e3ba 100644 --- a/src/lib/merchant/components/Merchant.jsx +++ b/src/lib/merchant/components/Merchant.jsx @@ -81,111 +81,13 @@ const Merchant = () => { <InformasiPerusahaan buttonSubmitClick={buttonSubmitClick} />; }, [buttonSubmitClick]); - // useEffect(() => { - // const loadBigData = async () => { - // const toCamelCase = (str) => - // str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); - - // const transformKeysToCamelCase = (data) => { - // if (Array.isArray(data)) { - // return data.map((item) => transformKeysToCamelCase(item)); - // } else if (data && typeof data === 'object') { - // return Object.keys(data).reduce((acc, key) => { - // const camelKey = toCamelCase(key); // Ubah kunci menjadi camelCase - // acc[camelKey] = transformKeysToCamelCase(data[key]); // Rekursif untuk nested object atau array - // return acc; - // }, {}); - // } - // return data; // Jika bukan object atau array, kembalikan nilai aslinya - // }; - - // try { - // const dataPaymentTerm = await odooApi( - // 'GET', - // `/api/v1/partner/detail-tempo/${auth.parentId}` - // ); - // const transformedData = transformKeysToCamelCase(dataPaymentTerm); - // setBigData(transformedData); - // } catch (error) { - // console.error('Error loading dataPaymentTerm:', error); - // } - // }; - - // loadBigData(); - // }, [auth]); - - // useEffect(() => { - // const cachedData = bigData; - - // const loadBigData = async () => { - // if (cachedData) { - // // Ambil kunci-kunci yang relevan berdasarkan currentStep dari stepDivsForm - // const formKeys = stepDivsForm[currentStep] - // ? Object.keys(stepDivsForm[currentStep]) - // : []; - // if (currentStep === 3) { - // stepDivsUpdateForm[currentStep](cachedData.supplierIds); - // } else if (currentStep === 4) { - // formKeys.forEach((key) => { - // if (cachedData[key]) { - // // Proses hanya kunci yang ada di cachedData - // const { name, format, base64 } = cachedData[key]; - // stepDivsUpdateForm[currentStep](key, name, format, base64); - // } - // }); - // } else { - // formKeys.forEach((key) => { - // if (bigData[key]) { - // // Ubah data menjadi string - // const stringData = - // typeof bigData[key] === 'object' - // ? JSON.stringify(bigData[key]) // Untuk objek atau array - // : String(bigData[key]); // Untuk tipe primitif - // // Kirim data yang sudah diubah ke string ke stepDivsUpdateForm - // stepDivsUpdateForm[currentStep](key, stringData); - // } - // }); - // } - // } - // }; - // loadBigData(); - // validate(); - // validateKontakPerson(); - // validatePengiriman(); - // validateDokumen(); - // validateSupplier(); - // updateHasSave(true); - // }, [currentStep, bigData, auth]); - const goToNextStep = () => { - // if (!isFormValid) { - // setNotValid(true); - // setButtonSubmitClick(!buttonSubmitClick); - // return; - // } else { - // // saveToLocalStorage(stepLabels[currentStep], stepDivsForm[currentStep]); - // if (currentStep == 3) { - // handleDaftarTempoSupplier(); - // } else if (currentStep == 4) { - // handleDaftarTempoDokumen(); - // } else { - // handleDaftarTempoPerPage( - // stepLabels[currentStep], - // stepDivsForm[currentStep] - // ); - // } - // setButtonSubmitClick(!buttonSubmitClick); - // setNotValid(false); - // } setCurrentStep((prev) => (prev === NUMBER_OF_STEPS - 1 ? prev : prev + 1)); }; const goPrevStep = () => { setCurrentStep((prev) => (prev === NUMBER_OF_STEPS - 1 ? prev : prev - 1)); }; - const handleDaftarMerchant = () => { - setButtonSubmitClick((prev) => !prev); - }; useEffect(() => { const getBanner = async () => { @@ -236,62 +138,7 @@ const Merchant = () => { ? 'flex-col justify-start items-start' : 'flex-col justify-end items-end' } mb-8 gap-2`} - > - {/* <span className='text-xs opacity-60'> - *Pastikan data yang anda masukan sudah benar dan sesuai - </span> */} - {/* <div - className={` flex flex-row ${ - currentStep > 0 && currentStep < 5 - ? 'justify-between' - : 'justify-end' - } items-center w-full ${isMobile ? 'gap-x-4 ' : ''}`} - > - {currentStep < 5 && currentStep > 0 && ( - <Button - colorScheme='yellow' - w={`${isMobile ? 'full' : 'fit'}`} - onClick={goPrevStep} - > - {<ChevronLeftIcon className='w-5' />} - <span className={`font-medium ${isMobile ? 'text-xs' : ''} `}> - Langkah Sebelumnya - </span> - </Button> - )} - {currentStep < 5 && ( - <> - <Button - colorScheme='red' - w={`${isMobile ? 'full' : 'fit'}`} - isDisabled={currentStep === NUMBER_OF_STEPS - 1} - onClick={goToNextStep} - > - <span className={`${isMobile ? 'text-xs' : ''} `}> - Langkah Selanjutnya - </span> - {<ChevronRightIcon className='w-5' />} - </Button> - </> - )} - </div> */} - {/* {currentStep == 0 && ( - <div - className={`flex flex-col ${ - isMobile ? 'items-start' : 'items-end' - } w-full justify-start gap-4`} - > - <Button - colorScheme='red' - w={`${isMobile ? 'full' : '36'}`} - // isDisabled={!isCheckedTNC} - onClick={handleDaftarMerchant} - > - Daftar Merchant {<ChevronRightIcon className='w-5' />} - </Button> - </div> - )} */} - </div> + ></div> </div> </> ); diff --git a/src/lib/pengajuan-tempo/api/createPengajuanTempoApi.js b/src/lib/pengajuan-tempo/api/createPengajuanTempoApi.js new file mode 100644 index 00000000..af1d6c3a --- /dev/null +++ b/src/lib/pengajuan-tempo/api/createPengajuanTempoApi.js @@ -0,0 +1,14 @@ +import odooApi from '@/core/api/odooApi'; +import { getAuth } from '@/core/utils/auth'; + +const createPengajuanTempoApi = async (data) => { + const auth = getAuth(); + const dataPengajuanTempo = await odooApi( + 'POST', + `/api/v1/partner/pengajuan_tempo`, + data + ); + return dataPengajuanTempo; +}; + +export default createPengajuanTempoApi; diff --git a/src/lib/pengajuan-tempo/api/editAuthTempo.js b/src/lib/pengajuan-tempo/api/editAuthTempo.js new file mode 100644 index 00000000..5fa3786b --- /dev/null +++ b/src/lib/pengajuan-tempo/api/editAuthTempo.js @@ -0,0 +1,13 @@ +import odooApi from '@/core/api/odooApi'; +import { getAuth } from '@/core/utils/auth'; + +const editAuthTempo = async () => { + const auth = getAuth(); + const dataProfile = await odooApi( + 'PUT', + `/api/v1/user/${auth.id}/after_request_tempo` + ); + return dataProfile; +}; + +export default editAuthTempo; diff --git a/src/lib/pengajuan-tempo/component/Dokumen.jsx b/src/lib/pengajuan-tempo/component/Dokumen.jsx new file mode 100644 index 00000000..f986d47a --- /dev/null +++ b/src/lib/pengajuan-tempo/component/Dokumen.jsx @@ -0,0 +1,1351 @@ +import React, { useState, useEffect, useMemo, useRef } from 'react'; +import { Controller, set, useForm } from 'react-hook-form'; +import { usePengajuanTempoStoreDokumen } from '../../../../src-migrate/modules/register/stores/usePengajuanTempoStore'; +import ProgressBar from '@ramonak/react-progress-bar'; +import { UseToastOptions } from '@chakra-ui/react'; +import { toast } from 'react-hot-toast'; +import getFileBase64 from '@/core/utils/getFileBase64'; +import useDevice from '@/core/hooks/useDevice'; +import imageCompression from 'browser-image-compression'; +const Dokumen = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { + const { control, watch } = useForm(); + const { + formDokumen, + errorsDokumen, + validateDokumen, + updateFormDokumen, + getJumlahDokumenDiisi, + } = usePengajuanTempoStoreDokumen(); + const { isDesktop, isMobile } = useDevice(); + // const handleInputChange = (event) => { + // const { name, value } = event.target; + // updateFormDokumen(name, value); + // validateDokumen(); + // }; + const handleInputChange = async (event) => { + let fileBase64 = ''; + const { name } = event.target; + const file = event.target.files?.[0]; + // Allowed file extensions + const allowedExtensions = ['pdf', 'png', 'jpg', 'jpeg']; + let fileExtension = ''; + if (file) { + fileExtension = file.name.split('.').pop()?.toLowerCase(); // Extract file extension + + // Check if the file extension is allowed + if (!fileExtension || !allowedExtensions.includes(fileExtension)) { + toast.error( + 'Format file yang diijinkan adalah .pdf, .png, .jpg, atau .jpeg', + { duration: 4000 } + ); + + event.target.value = ''; + return; + } + + // Check for file size + if (file.size > 2000000) { + // try { + // const toastId = toast.loading('mencoba mengompresi file...'); + // // Compress image file + // const options = { + // maxSizeMB: 0.5, // Target size in MB + // maxWidthOrHeight: 1920, // Adjust as needed + // useWebWorker: true, + // }; + // const compressedFile = await imageCompression(file, options); + // toast.success('berhasil mengompresi file', { duration: 4000 }); + // // Convert compressed file to Base64 + // fileBase64 = await getFileBase64(compressedFile); + // updateFormDokumen( + // name, + // compressedFile.name, + // fileExtension, + // fileBase64 + // ); + // } catch (error) { + // toast.error('Gagal mengompresi file', { duration: 4000 }); + // } + toast.error('Max File Upload 2MB', { duration: 4000 }); + } else { + // Convert file to Base64 + fileBase64 = await getFileBase64(file); + updateFormDokumen(name, file.name, fileExtension, fileBase64); + } + validateDokumen(); + } + }; + + const isFormValid = useMemo( + () => Object.keys(errorsDokumen).length === 0, + [errorsDokumen] + ); + const dokumenNibRef = useRef(null); + const dokumenNpwpRef = useRef(null); + const dokumenSppkpRef = useRef(null); + const dokumenAktaPerubahanRef = useRef(null); + const dokumenKtpDirutRef = useRef(null); + const dokumenAktaPendirianRef = useRef(null); + const dokumenLaporanKeuanganRef = useRef(null); + const dokumenFotoKantorRef = useRef(null); + const dokumenTempatBekerjaRef = useRef(null); + + useEffect(() => { + validateDokumen(); + // window.scrollTo({ + // top: 0, + // behavior: 'smooth', + // }); + const loadIndustries = async () => { + if (!isFormValid) { + const options = { + behavior: 'smooth', + block: 'center', + }; + if (errorsDokumen.dokumenNib && dokumenNibRef.current) { + dokumenNibRef.current.scrollIntoView(options); + return; + } + if (errorsDokumen.dokumenNpwp && dokumenNpwpRef.current) { + dokumenNpwpRef.current.scrollIntoView(options); + return; + } + if (errorsDokumen.dokumenSppkp && dokumenSppkpRef.current) { + dokumenSppkpRef.current.scrollIntoView(options); + return; + } + if ( + errorsDokumen.dokumenAktaPerubahan && + dokumenAktaPerubahanRef.current + ) { + dokumenAktaPerubahanRef.current.scrollIntoView(options); + return; + } + if (errorsDokumen.dokumenKtpDirut && dokumenKtpDirutRef.current) { + dokumenKtpDirutRef.current.scrollIntoView(options); + return; + } + if ( + errorsDokumen.dokumenAktaPendirian && + dokumenAktaPendirianRef.current + ) { + dokumenAktaPendirianRef.current.scrollIntoView(options); + return; + } + if ( + errorsDokumen.dokumenLaporanKeuangan && + dokumenLaporanKeuanganRef.current + ) { + dokumenLaporanKeuanganRef.current.scrollIntoView(options); + return; + } + if (errorsDokumen.dokumenFotoKantor && dokumenFotoKantorRef.current) { + dokumenFotoKantorRef.current.scrollIntoView(options); + return; + } + if ( + errorsDokumen.dokumenTempatBekerja && + dokumenTempatBekerjaRef.current + ) { + dokumenTempatBekerjaRef.current.scrollIntoView(options); + return; + } + } + }; + loadIndustries(); + }, [buttonSubmitClick, chekValid]); + + useEffect(() => { + validateDokumen(); + }, [buttonSubmitClick]); + return ( + <> + {isDesktop && ( + <div> + <h1 className={`font-bold ${isKonfirmasi ? 'text-xl' : ''}`}> + Dokumen + </h1> + <form className='flex mt-4 flex-col w-full '> + <div className='w-full grid grid-cols-[1fr_auto_1fr] gap-5'> + <div className='kolom-kiri w-full grid grid-rows-2 gap-7 '> + <div + className='w-full grid grid-cols-2 gap-5' + ref={dokumenNpwpRef} + > + <div> + <label className='form-label text-nowrap'> + NPWP Perusahaan + </label> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + </div> + <div className=''> + <div className='flex flex-col items-start'> + <label + htmlFor='dokumenNpwp' + className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' + > + {formDokumen?.dokumenNpwp?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenNpwp} + id='dokumenNpwp' + name='dokumenNpwp' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsDokumen.dokumenNpwp} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2'> + {formDokumen?.dokumenNpwp?.name} + </span> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenNpwp} + </div> + )} + </div> + </div> + + <div + className='w-full grid grid-cols-2 gap-5' + ref={dokumenSppkpRef} + > + <div> + <label className='form-label text-nowrap'>SPPKP</label> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + </div> + <div className=''> + <div className='flex flex-col items-start'> + <label + htmlFor='dokumenSppkp' + className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' + > + {formDokumen?.dokumenSppkp?.name.length > 0 + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenSppkp} + id='dokumenSppkp' + name='dokumenSppkp' + type='file' + className='hidden' + aria-invalid={errorsDokumen.dokumenSppkp} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2'> + {formDokumen?.dokumenSppkp?.name} + </span> + </div> + + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenSppkp} + </div> + )} + </div> + </div> + + <div + className='w-full grid grid-cols-2 gap-5' + ref={dokumenNibRef} + > + <div> + <label className='form-label text-nowrap'> + NIB + <span className='text-danger-500 text-xl'>*</span> + </label> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + </div> + <div className=''> + <div className='flex flex-col items-start'> + <label + htmlFor='dokumenNib' + className='cursor-pointer bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded min-w-40 text-center' + > + {formDokumen?.dokumenNib?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenNib' + name='dokumenNib' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsDokumen.dokumenNib} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2'> + {formDokumen?.dokumenNib?.name} + </span> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenNib} + </div> + )} + </div> + </div> + + <div className='w-full grid grid-cols-2 gap-5'> + <div> + <label className='form-label text-nowrap'> + SIUP <span className=' opacity-60'>(Opsional)</span>{' '} + </label> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + </div> + <div className=''> + <div className='flex flex-col items-start '> + <label + htmlFor='dokumenSiup' + className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' + > + {formDokumen?.dokumenSiup?.name.length > 0 + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenAktaPerubahan} + id='dokumenSiup' + name='dokumenSiup' + type='file' + className='hidden' + aria-invalid={errorsDokumen.dokumenAktaPerubahan} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2'> + {formDokumen?.dokumenSiup?.name} + </span> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenSiup} + </div> + )} + </div> + </div> + + <div className='w-full grid grid-cols-2 gap-5'> + <div> + <label className='form-label text-nowrap'> + TDP <span className=' opacity-60'>(Opsional)</span>{' '} + </label> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + </div> + <div className=''> + <div className='flex flex-col items-start'> + <label + htmlFor='dokumenTdp' + className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' + > + {formDokumen?.dokumenTdp?.name.length > 0 + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenAktaPerubahan} + id='dokumenTdp' + name='dokumenTdp' + type='file' + className='hidden' + aria-invalid={errorsDokumen.dokumenTdp} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2'> + {formDokumen?.dokumenTdp?.name} + </span> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenTdp} + </div> + )} + </div> + </div> + + <div className='w-full grid grid-cols-2 gap-5'> + <div> + <label className='form-label text-nowrap'> + SKDP <span className=' opacity-60'>(Opsional)</span>{' '} + </label> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + </div> + <div className=''> + <div className='flex flex-col items-start'> + <label + htmlFor='dokumenSkdp' + className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' + > + {formDokumen?.dokumenSkdp?.name.length > 0 + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenAktaPerubahan} + id='dokumenSkdp' + name='dokumenSkdp' + type='file' + className='hidden' + aria-invalid={errorsDokumen.dokumenSkdp} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2'> + {formDokumen?.dokumenSkdp?.name} + </span> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenSkdp} + </div> + )} + </div> + </div> + + <div className='w-full grid grid-cols-2 gap-5'> + <div> + <label className='form-label text-nowrap'> + SKT <span className=' opacity-60'>(Opsional)</span>{' '} + </label> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + </div> + <div className=''> + <div className='flex flex-col items-start'> + <label + htmlFor='dokumenSkt' + className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' + > + {formDokumen?.dokumenSkt?.name.length > 0 + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenAktaPerubahan} + id='dokumenSkt' + name='dokumenSkt' + type='file' + className='hidden' + aria-invalid={errorsDokumen.dokumenSkt} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2'> + {formDokumen?.dokumenSkt?.name} + </span> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenSkt} + </div> + )} + </div> + </div> + </div> + <div className='w-px bg-gray-300'></div> + <div + className='kolom kanan w-full grid grid-rows-2 gap-10 ' + ref={dokumenAktaPendirianRef} + > + <div className='w-full grid grid-cols-2 gap-5'> + <div> + <label className='form-label text-nowrap'> + Akta Pendirian{' '} + <span className=' opacity-60'>(Opsional)</span>{' '} + <span className='text-danger-500 text-xl'>*</span> + </label> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + </div> + <div className=''> + <div className='flex flex-col items-start'> + <label + htmlFor='dokumenAktaPendirian' + className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' + > + {formDokumen?.dokumenAktaPendirian?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenAktaPendirian} + id='dokumenAktaPendirian' + name='dokumenAktaPendirian' + type='file' + className='hidden' + aria-invalid={errorsDokumen.dokumenAktaPendirian} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2'> + {formDokumen?.dokumenAktaPendirian?.name} + </span> + </div> + + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenAktaPendirian} + </div> + )} + </div> + </div> + + <div + className='w-full grid grid-cols-2 gap-5' + ref={dokumenAktaPerubahanRef} + > + <div> + <label className='form-label text-nowrap'> + Akta Perubahan{' '} + <span className=' opacity-60'>(Opsional)</span>{' '} + </label> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + </div> + <div className=''> + <div className='flex flex-col items-start'> + <label + htmlFor='dokumenAktaPerubahan' + className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' + > + {formDokumen?.dokumenAktaPerubahan?.name.length > 0 + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenAktaPerubahan} + id='dokumenAktaPerubahan' + name='dokumenAktaPerubahan' + type='file' + className='hidden' + aria-invalid={errorsDokumen.dokumenAktaPerubahan} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2'> + {formDokumen?.dokumenAktaPerubahan?.name} + </span> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenAktaPerubahan} + </div> + )} + </div> + </div> + + <div + className='w-full grid grid-cols-2 gap-5' + ref={dokumenTempatBekerjaRef} + > + <div> + <label className='form-label text-nowrap'> + Foto tempat kerja + </label> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + </div> + <div className=''> + <div className='flex flex-col items-start'> + <label + htmlFor='dokumenTempatBekerja' + className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' + > + {formDokumen?.dokumenTempatBekerja?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenTempatBekerja} + id='dokumenTempatBekerja' + name='dokumenTempatBekerja' + type='file' + className='hidden' + aria-invalid={errorsDokumen.dokumenTempatBekerja} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2'> + {formDokumen?.dokumenTempatBekerja?.name} + </span> + </div> + + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenTempatBekerja} + </div> + )} + </div> + </div> + + <div + className='w-full grid grid-cols-2 gap-5' + ref={dokumenFotoKantorRef} + > + <div> + <label className='form-label text-nowrap'> + Foto Kantor (Tampak Depan) + </label> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + </div> + <div className=''> + <div className='flex flex-col items-start'> + <label + htmlFor='dokumenFotoKantor' + className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' + > + {formDokumen?.dokumenFotoKantor?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenFotoKantor} + id='dokumenFotoKantor' + name='dokumenFotoKantor' + type='file' + className='hidden' + aria-invalid={errorsDokumen.dokumenFotoKantor} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2'> + {formDokumen?.dokumenFotoKantor?.name} + </span> + </div> + + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenFotoKantor} + </div> + )} + </div> + </div> + + <div + className='w-full grid grid-cols-2 gap-5' + ref={dokumenKtpDirutRef} + > + <div> + <label className='form-label text-nowrap'> + KTP Dirut/Direktur{' '} + <span className=' opacity-60'>(Opsional)</span>{' '} + <span className='text-danger-500 text-xl'>*</span> + </label> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + </div> + <div className=''> + <div className='flex flex-col items-start'> + <label + htmlFor='dokumenKtpDirut' + className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' + > + {formDokumen?.dokumenKtpDirut?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenKtpDirut} + id='dokumenKtpDirut' + name='dokumenKtpDirut' + type='file' + className='hidden' + aria-invalid={errorsDokumen.dokumenKtpDirut} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2'> + {formDokumen?.dokumenKtpDirut?.name} + </span> + </div> + + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenKtpDirut} + </div> + )} + </div> + </div> + <div + className='w-full grid grid-cols-2 gap-5' + ref={dokumenLaporanKeuanganRef} + > + <div> + <label className='form-label text-nowrap'> + Laporan Keuangan + <span className=' opacity-60'>(Opsional)</span> + </label> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + </div> + <div className=''> + <div className='flex flex-col items-start'> + <label + htmlFor='dokumenLaporanKeuangan' + className='cursor-pointer min-w-40 text-center bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded' + > + {formDokumen?.dokumenLaporanKeuangan?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenLaporanKeuangan} + id='dokumenLaporanKeuangan' + name='dokumenLaporanKeuangan' + type='file' + className='hidden' + aria-invalid={errorsDokumen.dokumenLaporanKeuangan} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2'> + {formDokumen?.dokumenLaporanKeuangan?.name} + </span> + </div> + + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenLaporanKeuangan} + </div> + )} + </div> + </div> + + <div></div> + </div> + </div> + </form> + <div className='w-1/2 mt-8 flex gap-3 flex-col'> + <span className='opacity-75 text-danger-500 text-sm'> + *Jika anda ingin kredit limit lebih dari 25 Juta, harap lampirkan + dokumen yang diberi tanda bintang *) + </span> + <div className='flex justify-between'> + <p className='font-bold'>Upload Progress</p> + <p> + <span className='text-red-500 font-bold'> + {parseInt((getJumlahDokumenDiisi() / 13) * 100)} % + </span> + <span className='ml-2 opacity-60'> + {getJumlahDokumenDiisi() >= 5 + ? getJumlahDokumenDiisi() == 13 + ? 'Selesai' + : 'Sedikit Lagi' + : ''} + </span> + </p> + </div> + {/* 50 keatas baru muncul kata kata sedikit lagi */} + <ProgressBar + completed={getJumlahDokumenDiisi()} + bgColor='#ef4444' + labelAlignment='outside' + isLabelVisible={false} + baseBgColor='#E5E7EB' + labelColor='#e80909' + labelSize='0' + maxCompleted={13} + height='14px' + /> + <span className='opacity-75'> + Tingkatin sedikit lagi agar pengajuan tempo kamu dapat kami proses + dengan cepat + </span> + </div> + </div> + )} + {isMobile && ( + <div> + <h1 + className={`font-bold py-4 mt-8 ${ + isKonfirmasi ? 'text-xl' : 'text-xl' + }`} + > + Dokumen + </h1> + <form className='flex mt-4 flex-col w-full '> + <div className='w-full flex flex-col gap-4'> + <div className='w-full flex flex-col gap-2' ref={dokumenNpwpRef}> + <label className='form-label text-nowrap'> + NPWP Perusahaan + </label> + <div className='flex flex-row gap-2'> + <label + htmlFor='dokumenNpwp' + className='cursor-pointer bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded w-fit min-w-40 text-nowrap text-center flex items-center justify-center' + > + {formDokumen?.dokumenNpwp?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenNpwp} + id='dokumenNpwp' + name='dokumenNpwp' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsDokumen.dokumenNpwp} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2 truncate'> + {formDokumen?.dokumenNpwp?.name} + </span> + </div> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenNpwp} + </div> + )} + </div> + + <div className='w-full flex flex-col gap-2' ref={dokumenSppkpRef}> + <label className='form-label text-nowrap'>SPPKP</label> + <div className='flex flex-row gap-2'> + <label + htmlFor='dokumenSppkp' + className='cursor-pointer bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded w-fit min-w-40 text-nowrap text-center flex items-center justify-center' + > + {formDokumen?.dokumenSppkp?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenNpwp} + id='dokumenSppkp' + name='dokumenSppkp' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsDokumen.dokumenSppkp} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2 truncate'> + {formDokumen?.dokumenSppkp?.name} + </span> + </div> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenSppkp} + </div> + )} + </div> + <div className='w-full flex flex-col gap-2' ref={dokumenNibRef}> + <label className='form-label text-nowrap'> + NIB + <span className='text-danger-500 text-xl'>*</span> + </label> + <div className='flex flex-row gap-2'> + <label + htmlFor='dokumenNib' + className='cursor-pointer bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded w-fit min-w-40 text-nowrap text-center flex items-center justify-center' + > + {formDokumen?.dokumenNib?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenNib' + name='dokumenNib' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsDokumen.dokumenNib} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2 truncate'> + {formDokumen?.dokumenNib?.name} + </span> + </div> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenNib} + </div> + )} + </div> + + <div className='w-full flex flex-col gap-2'> + <label className='form-label text-nowrap'> + SIUP <span className=' opacity-60'>(Opsional)</span> + </label> + <div className='flex flex-row gap-2'> + <label + htmlFor='dokumenSiup' + className='cursor-pointer bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded w-fit min-w-40 text-nowrap text-center flex items-center justify-center' + > + {formDokumen?.dokumenSiup?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenNpwp} + id='dokumenSiup' + name='dokumenSiup' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsDokumen.dokumenSiup} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2 truncate'> + {formDokumen?.dokumenSiup?.name} + </span> + </div> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenSiup} + </div> + )} + </div> + + <div className='w-full flex flex-col gap-2'> + <label className='form-label text-nowrap'> + TDP <span className=' opacity-60'>(Opsional)</span> + </label> + <div className='flex flex-row gap-2'> + <label + htmlFor='dokumenTdp' + className='cursor-pointer bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded w-fit min-w-40 text-nowrap text-center flex items-center justify-center' + > + {formDokumen?.dokumenTdp?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenNpwp} + id='dokumenTdp' + name='dokumenTdp' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsDokumen.dokumenTdp} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2 truncate'> + {formDokumen?.dokumenTdp?.name} + </span> + </div> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenTdp} + </div> + )} + </div> + <div className='w-full flex flex-col gap-2'> + <label className='form-label text-nowrap'> + SKDP <span className=' opacity-60'>(Opsional)</span> + </label> + <div className='flex flex-row gap-2'> + <label + htmlFor='dokumenSkdp' + className='cursor-pointer bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded w-fit min-w-40 text-nowrap text-center flex items-center justify-center' + > + {formDokumen?.dokumenTdp?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenNpwp} + id='dokumenSkdp' + name='dokumenSkdp' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsDokumen.dokumenSkdp} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2 truncate'> + {formDokumen?.dokumenSkdp?.name} + </span> + </div> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenSkdp} + </div> + )} + </div> + <div className='w-full flex flex-col gap-2'> + <label className='form-label text-nowrap'> + SKT <span className=' opacity-60'>(Opsional)</span> + </label> + <div className='flex flex-row gap-2'> + <label + htmlFor='dokumenSkt' + className='cursor-pointer bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded w-fit min-w-40 text-nowrap text-center flex items-center justify-center' + > + {formDokumen?.dokumenSkt?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenNpwp} + id='dokumenSkt' + name='dokumenSkt' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsDokumen.dokumenSkt} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2 truncate'> + {formDokumen?.dokumenSkt?.name} + </span> + </div> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenSkdp} + </div> + )} + </div> + <div + className='w-full flex flex-col gap-2' + ref={dokumenAktaPendirianRef} + > + <label className='form-label text-nowrap'> + Akta Pendirian <span className=' opacity-60'>(Opsional)</span>{' '} + <span className='text-danger-500 text-xl'>*</span> + </label> + <div className='flex flex-row gap-2'> + <label + htmlFor='dokumenAktaPendirian' + className='cursor-pointer bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded w-fit min-w-40 text-nowrap text-center flex items-center justify-center' + > + {formDokumen?.dokumenAktaPendirian?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenNpwp} + id='dokumenAktaPendirian' + name='dokumenAktaPendirian' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsDokumen.dokumenAktaPendirian} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2 truncate'> + {formDokumen?.dokumenAktaPendirian?.name} + </span> + </div> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenAktaPendirian} + </div> + )} + </div> + <div + className='w-full flex flex-col gap-2' + ref={dokumenAktaPerubahanRef} + > + <label className='form-label text-nowrap'> + Akta Perubahan <span className=' opacity-60'>(Opsional)</span> + </label> + <div className='flex flex-row gap-2'> + <label + htmlFor='dokumenAktaPerubahan' + className='cursor-pointer bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded w-fit min-w-40 text-nowrap text-center flex items-center justify-center' + > + {formDokumen?.dokumenAktaPerubahan?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenNpwp} + id='dokumenAktaPerubahan' + name='dokumenAktaPerubahan' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsDokumen.dokumenAktaPerubahan} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2 truncate'> + {formDokumen?.dokumenAktaPerubahan?.name} + </span> + </div> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenAktaPerubahan} + </div> + )} + </div> + + <div + className='w-full flex flex-col gap-2' + ref={dokumenTempatBekerjaRef} + > + <label className='form-label text-nowrap'> + Foto tempat kerja + </label> + <div className='flex flex-row gap-2'> + <label + htmlFor='dokumenTempatBekerja' + className='cursor-pointer bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded w-fit min-w-40 text-nowrap text-center flex items-center justify-center' + > + {formDokumen?.dokumenTempatBekerja?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenNpwp} + id='dokumenTempatBekerja' + name='dokumenTempatBekerja' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsDokumen.dokumenTempatBekerja} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2 truncate'> + {formDokumen?.dokumenTempatBekerja?.name} + </span> + </div> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenTempatBekerja} + </div> + )} + </div> + <div + className='w-full flex flex-col gap-2' + ref={dokumenFotoKantorRef} + > + <label className='form-label text-nowrap'> + Foto Kantor (Tampak Depan) + </label> + <div className='flex flex-row gap-2'> + <label + htmlFor='dokumenFotoKantor' + className='cursor-pointer bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded w-fit min-w-40 text-nowrap text-center flex items-center justify-center' + > + {formDokumen?.dokumenFotoKantor?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenNpwp} + id='dokumenFotoKantor' + name='dokumenFotoKantor' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsDokumen.dokumenFotoKantor} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2 truncate'> + {formDokumen?.dokumenFotoKantor?.name} + </span> + </div> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenFotoKantor} + </div> + )} + </div> + <div + className='w-full flex flex-col gap-2' + ref={dokumenKtpDirutRef} + > + <label className='form-label text-nowrap'> + KTP Dirut/Direktur{' '} + <span className=' opacity-60'>(Opsional)</span>{' '} + <span className='text-danger-500 text-xl'>*</span> + </label> + <div className='flex flex-row gap-2'> + <label + htmlFor='dokumenKtpDirut' + className='cursor-pointer bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded w-fit min-w-40 text-nowrap text-center flex items-center justify-center' + > + {formDokumen?.dokumenKtpDirut?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenNpwp} + id='dokumenKtpDirut' + name='dokumenKtpDirut' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsDokumen.dokumenKtpDirut} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 line-clamp-2 truncate'> + {formDokumen?.dokumenKtpDirut?.name} + </span> + </div> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenKtpDirut} + </div> + )} + </div> + + <div + className='w-full flex flex-col gap-2' + ref={dokumenLaporanKeuanganRef} + > + <label className='form-label text-nowrap'> + Laporan Keuangan{' '} + <span className=' opacity-60'>(Opsional)</span> + </label> + <div className='flex flex-row gap-2'> + <label + htmlFor='dokumenLaporanKeuangan' + className='cursor-pointer bg-gray-200 hover:bg-gray-300 text-gray-700 py-2 px-4 rounded w-fit min-w-40 text-nowrap text-center flex items-center justify-center' + > + {formDokumen?.dokumenLaporanKeuangan?.name + ? 'Ubah Dokumen' + : 'Upload Dokumen'} + </label> + <input + // value={formDokumen.dokumenNpwp} + id='dokumenLaporanKeuangan' + name='dokumenLaporanKeuangan' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsDokumen.dokumenLaporanKeuangan} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <span className='mt-2 text-gray-600 truncate'> + {formDokumen?.dokumenLaporanKeuangan?.name} + </span> + </div> + <span className='text-xs opacity-60 text-red-500'> + Format: pdf, jpeg, jpg, png. max file size 2MB + </span> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenLaporanKeuangan} + </div> + )} + </div> + </div> + </form> + <div className='w-full mt-8 flex gap-3 flex-col'> + <span className='opacity-75 text-danger-500 text-xs'> + *Jika anda ingin kredit limit lebih dari 25 Juta, harap lampirkan + dokumen yang diberi tanda bintang *) + </span> + <div className='flex justify-between'> + <p className='font-bold'>Upload Progress</p> + <p> + <span className='text-red-500 font-bold'> + {parseInt((getJumlahDokumenDiisi() / 13) * 100)} % + </span> + <span className='ml-2 opacity-60'> + {getJumlahDokumenDiisi() >= 5 + ? getJumlahDokumenDiisi() == 13 + ? 'Selesai' + : 'Sedikit Lagi' + : ''} + </span> + </p> + </div> + {/* 50 keatas baru muncul kata kata sedikit lagi */} + <ProgressBar + completed={getJumlahDokumenDiisi()} + bgColor='#ef4444' + labelAlignment='outside' + isLabelVisible={false} + baseBgColor='#E5E7EB' + labelColor='#e80909' + labelSize='0' + maxCompleted={13} + height='10px' + /> + <span className='opacity-75 text-xs text-red-500'> + Tingkatin sedikit lagi agar pengajuan tempo kamu dapat kami proses + dengan cepat + </span> + </div> + </div> + )} + </> + ); +}; + +export default Dokumen; diff --git a/src/lib/pengajuan-tempo/component/FinishTempo.jsx b/src/lib/pengajuan-tempo/component/FinishTempo.jsx new file mode 100644 index 00000000..bfcd0909 --- /dev/null +++ b/src/lib/pengajuan-tempo/component/FinishTempo.jsx @@ -0,0 +1,112 @@ +import Link from 'next/link'; +import Image from '~/components/ui/image'; +import whatsappUrl from '@/core/utils/whatsappUrl'; +import { useEffect, useState } from 'react'; +import odooApi from '@/core/api/odooApi'; +import useDevice from '@/core/hooks/useDevice'; +import useAuth from '@/core/hooks/useAuth'; +import axios from 'axios'; +import { toast } from 'react-hot-toast'; +import { ChevronRightIcon, ChevronLeftIcon } from '@heroicons/react/24/outline'; + +const FinishTempo = ({ query }) => { + const [data, setData] = useState(); + const [transactionData, setTransactionData] = useState(); + const { isDesktop, isMobile } = useDevice(); + const auth = useAuth(); + const so_order = query?.order_id?.replaceAll('-', '/'); + useEffect(() => { + const fetchData = async () => { + const fetchedData = await odooApi( + 'GET', + `/api/v1/sale_order_number?sale_number=${so_order}` + ); + setData(fetchedData[0]); + }; + fetchData(); + }, [query]); + + return ( + <div className='container flex flex-col items-center gap-4'> + <div + className={`flex ${ + isMobile ? 'w-full' : 'w-2/3' + } justify-center items-center`} + > + <h1 + className={`text-red-500 text-center py-4 font-semibold ${ + isMobile ? 'text-lg' : 'text-3xl' + }`} + > + {query?.status == 'finish' && + 'Form Pengajuan Tempo kamu Telah Berhasil Didaftarkan Mohon menunggu hingga Proses Verifikasi Selesai Dilakukan'} + {query?.status == 'switch-account' && + 'Form Pengajuan Tempo Kamu Gagal Dilakukan'} + {query?.status == 'review' && + 'Pengajuan Tempo dalam Proses Verifikasi'} + {query?.status == 'approve' && 'Pengajuan Tempo Berhasil'} + </h1> + </div> + {query?.status == 'finish' && ( + <Image + src='/images/REGISTRASI-TEMPO.svg' + alt='Registrasi Tempo' + width={isMobile ? 300 : 550} + height={isMobile ? 300 : 550} + /> + )} + {query?.status == 'switch-account' && ( + <Image + src='/images/ICON-TEMPO.png' + alt='Registrasi Tempo' + width={isMobile ? 300 : 550} + height={isMobile ? 300 : 550} + /> + )} + {query?.status == 'review' && ( + <Image + src='/images/ICON-DOKUMEN-VERIFIKASI.png' + alt='Registrasi Tempo' + width={isMobile ? 300 : 550} + height={isMobile ? 300 : 550} + /> + )} + {query?.status == 'approve' && ( + <Image + src='/images/ICON-WEBSITE-TELAH-MENDAFTAR-AKUN-TEMPO.svg' + alt='Registrasi Tempo' + width={isMobile ? 300 : 600} + height={isMobile ? 300 : 550} + /> + )} + + <div + className={`mt-2 text-center opacity-75 leading-6 p-4 md:p-0 ${ + isMobile ? 'w-full text-sm' : 'w-4/5 text-base' + }`} + > + {query?.status == 'switch-account' && + 'Terima kasih atas minat anda untuk mendaftar Tempo, namun sayangnya akun anda bukan merupakan akun bisnis. Segera ubah akun anda menjadi Bisnis untuk menggunakan fitur ini'} + {query?.status == 'finish' && + 'Mohon menunggu untuk verifikasi dokumen dan kelengkapan data yang telah anda berikan. Proses approval pembayaran tempo kamu berhasil atau tidak akan diinfokan melalui email perusahaan / email yang mendaftar'} + {query?.status == 'review' && + 'Proses pengajuan tempo anda saat ini sedang dalam tahapan proses verifikasi oleh tim indoteknik, mohon menunggu'} + {query?.status == 'approve' && + 'Proses pengajuan tempo anda sudah berhasil terdaftar di indoteknik.com. Nikmati pembelian anda di website indoteknik dengan menggunakan pembayaran tempo'} + </div> + <Link + href={query?.status == 'switch-account' ? `/my/profile` : `/my/tempo/`} + className='btn-solid-red rounded-md text-base flex flex-row items-center justify-center' + > + {query?.status == 'switch-account' + ? 'Ubah Akun' + : query?.status == 'approve' + ? 'Lihat Detail Tempo' + : 'Lihat Status Pendaftaran'} + <ChevronRightIcon className='w-5' /> + </Link> + </div> + ); +}; + +export default FinishTempo; diff --git a/src/lib/pengajuan-tempo/component/Konfirmasi.jsx b/src/lib/pengajuan-tempo/component/Konfirmasi.jsx new file mode 100644 index 00000000..0e593ac4 --- /dev/null +++ b/src/lib/pengajuan-tempo/component/Konfirmasi.jsx @@ -0,0 +1,297 @@ +import React, { useState, useEffect, useMemo, useRef } from 'react'; +import { Controller, set, useForm } from 'react-hook-form'; +import HookFormSelect from '@/core/components/elements/Select/HookFormSelect'; +import { + usePengajuanTempoStoreDokumen, + usePengajuanTempoStore, +} from '../../../../src-migrate/modules/register/stores/usePengajuanTempoStore'; +import KontakPerusahaan from './KontakPerusahaan'; +import ProgressBar from '@ramonak/react-progress-bar'; +import { UseToastOptions } from '@chakra-ui/react'; +import odooApi from '~/libs/odooApi'; +import { toast } from 'react-hot-toast'; +import getFileBase64 from '@/core/utils/getFileBase64'; +import { CheckCircleIcon } from '@heroicons/react/24/outline'; +import InformasiPerusahaan from './informasiPerusahaan'; +import Pengiriman from './Pengiriman'; +import KonfirmasiDokumen from './KonfirmasiDokumen'; +import useDevice from '@/core/hooks/useDevice'; +import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'; +const Konfirmasi = ({ chekValid, buttonSubmitClick }) => { + const { control, watch, setValue, getValues } = useForm(); + const { isDesktop, isMobile } = useDevice(); + const [isOpenInformasi, setIsOpenInformasi] = useState(true); + const [isOpenKontak, setIsOpenKontak] = useState(false); + const [isOpenPengiriman, setIsOpenPengiriman] = useState(false); + const [isOpenKonfirmasi, setIsOpenKonfirmasi] = useState(false); + const [industries, setIndustries] = useState([]); + const { + formDokumen, + errorsDokumen, + validateDokumen, + updateFormDokumen, + getJumlahDokumenDiisi, + } = usePengajuanTempoStoreDokumen(); + const { form, errors, validate, updateForm } = usePengajuanTempoStore(); + const handleInputChange = async (event) => { + let fileBase64 = ''; + const { name } = event.target; + const file = event.target.files?.[0]; + // Allowed file extensions + const allowedExtensions = ['pdf', 'png', 'jpg', 'jpeg']; + let fileExtension = ''; + if (file) { + fileExtension = file.name.split('.').pop()?.toLowerCase(); + + if (!fileExtension || !allowedExtensions.includes(fileExtension)) { + toast.error( + 'Format file yang diijinkan adalah .pdf, .png, .jpg, atau .jpeg', + { duration: 4000 } + ); + + event.target.value = ''; + return; + } + if (file.size > 2000000) { + toast.error('Maksimal ukuran file adalah 2MB', { duration: 4000 }); + + event.target.value = ''; + return; + } + + fileBase64 = await getFileBase64(file); + updateFormDokumen(name, file.name, fileExtension, fileBase64); + validateDokumen(); + } + }; + + const isFormValid = useMemo( + () => Object.keys(errorsDokumen).length === 0, + [errorsDokumen] + ); + const dokumenNibRef = useRef(null); + const dokumenNpwpRef = useRef(null); + const dokumenSppkpRef = useRef(null); + const dokumenAktaPerubahanRef = useRef(null); + const dokumenKtpDirutRef = useRef(null); + const dokumenAktaPendirianRef = useRef(null); + const dokumenLaporanKeuanganRef = useRef(null); + const dokumenFotoKantorRef = useRef(null); + const dokumenTempatBekerjaRef = useRef(null); + + useEffect(() => { + const loadIndustries = async () => { + if (!isFormValid) { + const options = { + behavior: 'smooth', + block: 'center', + }; + if (errorsDokumen.dokumenNib && dokumenNibRef.current) { + dokumenNibRef.current.scrollIntoView(options); + return; + } + if (errorsDokumen.dokumenNpwp && dokumenNpwpRef.current) { + dokumenNpwpRef.current.scrollIntoView(options); + return; + } + if (errorsDokumen.dokumenSppkp && dokumenSppkpRef.current) { + dokumenSppkpRef.current.scrollIntoView(options); + return; + } + if ( + errorsDokumen.dokumenAktaPerubahan && + dokumenAktaPerubahanRef.current + ) { + dokumenAktaPerubahanRef.current.scrollIntoView(options); + return; + } + if (errorsDokumen.dokumenKtpDirut && dokumenKtpDirutRef.current) { + dokumenKtpDirutRef.current.scrollIntoView(options); + return; + } + if ( + errorsDokumen.dokumenAktaPendirian && + dokumenAktaPendirianRef.current + ) { + dokumenAktaPendirianRef.current.scrollIntoView(options); + return; + } + if ( + errorsDokumen.dokumenLaporanKeuangan && + dokumenLaporanKeuanganRef.current + ) { + dokumenLaporanKeuanganRef.current.scrollIntoView(options); + return; + } + if (errorsDokumen.dokumenFotoKantor && dokumenFotoKantorRef.current) { + dokumenFotoKantorRef.current.scrollIntoView(options); + return; + } + if ( + errorsDokumen.dokumenTempatBekerja && + dokumenTempatBekerjaRef.current + ) { + dokumenTempatBekerjaRef.current.scrollIntoView(options); + return; + } + } + }; + loadIndustries(); + }, [buttonSubmitClick, chekValid]); + + useEffect(() => { + validateDokumen(); + }, [buttonSubmitClick]); + + useEffect(() => { + const loadIndustries = async () => { + const dataIndustries = await odooApi('GET', '/api/v1/partner/industry'); + setIndustries( + dataIndustries?.map((o) => ({ + value: o.id, + label: o.name, + category: o.category, + })) + ); + }; + loadIndustries(); + }, []); + + useEffect(() => { + const selectedIndustryType = industries.find( + (industry) => industry.value === watch('industry_id') + ); + if (selectedIndustryType) { + updateForm('industry_id', `${selectedIndustryType?.value}`); + validate(); + } + }, [watch('industry_id'), industries]); + + useEffect(() => { + if (form.industry_id) { + setValue('industry_id', parseInt(form.industry_id)); + } + }, [form]); + + return ( + <> + {isDesktop && ( + <form className='flex mt-4 flex-col w-full '> + <div className='w-full grid grid-cols-[1fr_auto_1fr] gap-5'> + <div className='w-full flex flex-col gap-5 '> + <div className=''> + <InformasiPerusahaan isKonfirmasi={true} /> + </div> + <div className='h-px bg-gray-300'></div> + <div className=''> + <KontakPerusahaan isKonfirmasi={true} /> + </div> + </div> + + <div className='w-px bg-gray-300'></div> + <div className='w-full grid grid-rows-[1fc_auto_1fc] gap-5'> + <div> + <Pengiriman isKonfirmasi={true} /> + </div> + <div className='h-px bg-gray-300'></div> + <div> + <KonfirmasiDokumen isKonfirmasi={true} /> + </div> + </div> + </div> + </form> + )} + {isMobile && ( + <form className='flex mt-8 py-4 flex-col w-full gap-4'> + <div className='flex flex-col gap-4'> + <div className='flex flex-row justify-between items-center'> + <div className='flex flex-col justify-center items-start'> + <p className='font-semibold text-lg'>Informasi Perusahaan</p> + <span className='text-xs opacity-70'> + Pastikan informasi usaha yang anda masukkan sudah sesuai + dengan data perusahaan anda + </span> + </div> + <div className='p-2 bg-gray-200'> + {isOpenInformasi ? ( + <ChevronUpIcon + className='w-4' + onClick={() => setIsOpenInformasi(!isOpenInformasi)} + /> + ) : ( + <ChevronDownIcon + className='w-4' + onClick={() => setIsOpenInformasi(!isOpenInformasi)} + /> + )} + </div> + </div> + {isOpenInformasi && <InformasiPerusahaan isKonfirmasi={true} />} + <div className='h-[2px] bg-gray-300 w-[120%] inset-0 mt-4 mb-4 relative transform -translate-x-5'></div> + </div> + <div className='flex flex-col gap-4'> + <div className='flex flex-row justify-between'> + <p className='font-semibold text-lg'>Kontak Person</p> + <div className='p-2 bg-gray-200'> + {isOpenKontak ? ( + <ChevronUpIcon + className='w-4' + onClick={() => setIsOpenKontak(!isOpenKontak)} + /> + ) : ( + <ChevronDownIcon + className='w-4' + onClick={() => setIsOpenKontak(!isOpenKontak)} + /> + )} + </div> + </div> + {isOpenKontak && <KontakPerusahaan isKonfirmasi={true} />} + <div className='h-[2px] bg-gray-300 w-[120%] inset-0 mt-4 mb-4 relative transform -translate-x-5'></div> + </div> + <div className='flex flex-col gap-4'> + <div className='flex flex-row justify-between'> + <p className='font-semibold text-lg'>Pengiriman</p> + <div className='p-2 bg-gray-200'> + {isOpenPengiriman ? ( + <ChevronUpIcon + className='w-4' + onClick={() => setIsOpenPengiriman(!isOpenPengiriman)} + /> + ) : ( + <ChevronDownIcon + className='w-4' + onClick={() => setIsOpenPengiriman(!isOpenPengiriman)} + /> + )} + </div> + </div> + {isOpenPengiriman && <Pengiriman isKonfirmasi={true} />} + <div className='h-[2px] bg-gray-300 w-[120%] inset-0 mt-4 mb-4 relative transform -translate-x-5'></div> + </div> + <div className='flex flex-col gap-4'> + <div className='flex flex-row justify-between'> + <p className='font-semibold text-lg'>Dokumen</p> + <div className='p-2 bg-gray-200'> + {isOpenKonfirmasi ? ( + <ChevronUpIcon + className='w-4' + onClick={() => setIsOpenKonfirmasi(!isOpenKonfirmasi)} + /> + ) : ( + <ChevronDownIcon + className='w-4' + onClick={() => setIsOpenKonfirmasi(!isOpenKonfirmasi)} + /> + )} + </div> + </div> + {isOpenKonfirmasi && <KonfirmasiDokumen isKonfirmasi={true} />} + </div> + </form> + )} + </> + ); +}; + +export default Konfirmasi; diff --git a/src/lib/pengajuan-tempo/component/KonfirmasiDokumen.jsx b/src/lib/pengajuan-tempo/component/KonfirmasiDokumen.jsx new file mode 100644 index 00000000..66d33b43 --- /dev/null +++ b/src/lib/pengajuan-tempo/component/KonfirmasiDokumen.jsx @@ -0,0 +1,1677 @@ +import React, { useState, useEffect, useMemo, useRef } from 'react'; +import { Controller, set, useForm } from 'react-hook-form'; +import { usePengajuanTempoStoreDokumen } from '../../../../src-migrate/modules/register/stores/usePengajuanTempoStore'; +import ProgressBar from '@ramonak/react-progress-bar'; +import { UseToastOptions } from '@chakra-ui/react'; +import { toast } from 'react-hot-toast'; +import getFileBase64 from '@/core/utils/getFileBase64'; +import Image from 'next/image'; +import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; +import { EyeIcon } from '@heroicons/react/24/outline'; +import useDevice from '@/core/hooks/useDevice'; +import imageCompression from 'browser-image-compression'; +const KonfirmasiDokumen = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { + const { control, watch } = useForm(); + const [isExample, setIsExample] = useState(false); + const { isDesktop, isMobile } = useDevice(); + const [base64, setBase64] = useState(); + const [pdfData, setPdfData] = useState(); + const [format, setFormat] = useState(); + const [url, setUrl] = useState(); + const [popUpSource, setSource] = useState(); + const { + formDokumen, + errorsDokumen, + validateDokumen, + updateFormDokumen, + getJumlahDokumenDiisi, + } = usePengajuanTempoStoreDokumen(); + + const handleConfirmSubmit = (format, base64) => { + if (format == 'pdf') { + setFormat(`application/${format}`); + } else if (format == 'png' || format == 'jpg' || format == 'jpeg') { + setFormat(`image/${format}`); + } else { + setFormat(format); + } + setBase64( + base64.trim().replace(/^"+/, '').replace(/"+$/, '').replaceAll('\\', '') + ); + setIsExample(!isExample); + }; + const handleInputChange = async (event) => { + let fileBase64 = ''; + const { name } = event.target; + const file = event.target.files?.[0]; + // Allowed file extensions + const allowedExtensions = ['pdf', 'png', 'jpg', 'jpeg']; + let fileExtension = ''; + if (file) { + fileExtension = file.name.split('.').pop()?.toLowerCase(); // Extract file extension + + // Check if the file extension is allowed + if (!fileExtension || !allowedExtensions.includes(fileExtension)) { + toast.error( + 'Format file yang diijinkan adalah .pdf, .png, .jpg, atau .jpeg', + { duration: 4000 } + ); + + event.target.value = ''; + return; + } + + // Check for file size + if (file.size > 500000) { + try { + const toastId = toast.loading('mencoba mengompresi file...'); + // Compress image file + const options = { + maxSizeMB: 0.5, // Target size in MB + maxWidthOrHeight: 1920, // Adjust as needed + useWebWorker: true, + }; + const compressedFile = await imageCompression(file, options); + toast.success('berhasil mengompresi file', { duration: 4000 }); + // Convert compressed file to Base64 + fileBase64 = await getFileBase64(compressedFile); + updateFormDokumen( + name, + compressedFile.name, + fileExtension, + fileBase64 + ); + } catch (error) { + toast.error('Gagal mengompresi file', { duration: 4000 }); + } + } else { + // Convert file to Base64 + fileBase64 = await getFileBase64(file); + updateFormDokumen(name, file.name, fileExtension, fileBase64); + } + validateDokumen(); + } + }; + + const isFormValid = useMemo( + () => Object.keys(errorsDokumen).length === 0, + [errorsDokumen] + ); + const dokumenNibRef = useRef(null); + const dokumenNpwpRef = useRef(null); + const dokumenSppkpRef = useRef(null); + const dokumenAktaPerubahanRef = useRef(null); + const dokumenKtpDirutRef = useRef(null); + const dokumenAktaPendirianRef = useRef(null); + const dokumenLaporanKeuanganRef = useRef(null); + const dokumenFotoKantorRef = useRef(null); + const dokumenTempatBekerjaRef = useRef(null); + + useEffect(() => { + const loadIndustries = async () => { + if (!isFormValid) { + const options = { + behavior: 'smooth', + block: 'center', + }; + if (errorsDokumen.dokumenNib && dokumenNibRef.current) { + dokumenNibRef.current.scrollIntoView(options); + return; + } + if (errorsDokumen.dokumenNpwp && dokumenNpwpRef.current) { + dokumenNpwpRef.current.scrollIntoView(options); + return; + } + if (errorsDokumen.dokumenSppkp && dokumenSppkpRef.current) { + dokumenSppkpRef.current.scrollIntoView(options); + return; + } + if ( + errorsDokumen.dokumenAktaPerubahan && + dokumenAktaPerubahanRef.current + ) { + dokumenAktaPerubahanRef.current.scrollIntoView(options); + return; + } + if (errorsDokumen.dokumenKtpDirut && dokumenKtpDirutRef.current) { + dokumenKtpDirutRef.current.scrollIntoView(options); + return; + } + if ( + errorsDokumen.dokumenAktaPendirian && + dokumenAktaPendirianRef.current + ) { + dokumenAktaPendirianRef.current.scrollIntoView(options); + return; + } + if ( + errorsDokumen.dokumenLaporanKeuangan && + dokumenLaporanKeuanganRef.current + ) { + dokumenLaporanKeuanganRef.current.scrollIntoView(options); + return; + } + if (errorsDokumen.dokumenFotoKantor && dokumenFotoKantorRef.current) { + dokumenFotoKantorRef.current.scrollIntoView(options); + return; + } + if ( + errorsDokumen.dokumenTempatBekerja && + dokumenTempatBekerjaRef.current + ) { + dokumenTempatBekerjaRef.current.scrollIntoView(options); + return; + } + } + }; + loadIndustries(); + }, [buttonSubmitClick, chekValid]); + + useEffect(() => { + validateDokumen(); + }, [buttonSubmitClick]); + return ( + <> + {isDesktop && ( + <div> + <h1 className={`font-bold ${isKonfirmasi ? 'text-xl' : ''}`}> + Dokumen + </h1> + <form className='flex flex-col w-full gap-5 '> + <div className='w-full flex flex-row items-center '> + <div className='w-2/5 flex'> + <label className='form-label text-nowrap'> + NPWP Perusahaan + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-3/5 text-gray-600 truncate max-w-48'> + {formDokumen?.dokumenNpwp?.name} + </span> + <div className='w-2/5 flex flex-row gap-2'> + <label + htmlFor='dokumenNpwp' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenNpwp?.name ? 'Ubah' : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenNpwp' + name='dokumenNpwp' + type='file' + title=' ' + ref={dokumenNpwpRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenNpwp} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenNpwp.format, + formDokumen.dokumenNpwp.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenNpwp} + </div> + )} + </div> + </div> + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-nowrap'>SPPKP</label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-3/5 text-gray-600 truncate max-w-48'> + {formDokumen?.dokumenSppkp?.name} + </span> + <div className='w-2/5 flex flex-row gap-2'> + <label + htmlFor='dokumenSppkp' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenSppkp?.name.length > 0 + ? 'Ubah' + : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenSppkp' + name='dokumenSppkp' + type='file' + title=' ' + ref={dokumenSppkpRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenSppkp} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenSppkp.format, + formDokumen.dokumenSppkp.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenSppkp} + </div> + )} + </div> + </div> + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-nowrap'>NIB</label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-3/5 text-gray-600 truncate max-w-48'> + {formDokumen?.dokumenNib?.name} + </span> + <div className='w-2/5 flex flex-row gap-2'> + <label + htmlFor='dokumenNib' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenNib?.name ? 'Ubah' : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenNib' + name='dokumenNib' + type='file' + title=' ' + ref={dokumenNibRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenNib} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenNib.format, + formDokumen.dokumenNib.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenNib} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + SIUP <span className=' opacity-60'>(Opsional)</span> + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-3/5 text-gray-600 truncate max-w-48'> + {formDokumen?.dokumenSiup?.name} + </span> + <div className='w-2/5 flex flex-row gap-2'> + <label + htmlFor='dokumenSiup' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen.dokumenSiup.name ? 'Ubah' : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenSiup' + name='dokumenSiup' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsDokumen.dokumenSiup} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenSiup.format, + formDokumen.dokumenSiup.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenSiup} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + TDP <span className=' opacity-60'>(Opsional)</span> + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-3/5 text-gray-600 truncate max-w-48'> + {formDokumen?.dokumenTdp?.name} + </span> + <div className='w-2/5 flex flex-row gap-2'> + <label + htmlFor='dokumenTdp' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen.dokumenTdp.name ? 'Ubah' : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenTdp' + name='dokumenTdp' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsDokumen.dokumenTdp} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenTdp.format, + formDokumen.dokumenTdp.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenTdp} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + SKDP <span className=' opacity-60'>(Opsional)</span> + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-3/5 text-gray-600 truncate max-w-48'> + {formDokumen?.dokumenSkdp?.name} + </span> + <div className='w-2/5 flex flex-row gap-2'> + <label + htmlFor='dokumenSkdp' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen.dokumenSkdp.name ? 'Ubah' : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenSkdp' + name='dokumenSkdp' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsDokumen.dokumenSkdp} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenSkdp.format, + formDokumen.dokumenSkdp.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenSkdp} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + SKT <span className=' opacity-60'>(Opsional)</span> + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-3/5 text-gray-600 truncate max-w-48'> + {formDokumen?.dokumenSkt?.name} + </span> + <div className='w-2/5 flex flex-row gap-2'> + <label + htmlFor='dokumenSkt' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen.dokumenSkt.name ? 'Ubah' : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenSkt' + name='dokumenSkt' + type='file' + title=' ' + className='hidden' + aria-invalid={errorsDokumen.dokumenSkt} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenSkt.format, + formDokumen.dokumenSkt.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenSkt} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Akta Pendirian <span className=' opacity-60'>(Opsional)</span> + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-3/5 text-gray-600 truncate max-w-48'> + {formDokumen?.dokumenAktaPendirian?.name} + </span> + <div className='w-2/5 flex flex-row gap-2'> + <label + htmlFor='dokumenAktaPendirian' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenAktaPendirian?.name + ? 'Ubah' + : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenAktaPendirian' + name='dokumenAktaPendirian' + type='file' + title=' ' + ref={dokumenAktaPendirianRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenAktaPendirian} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenAktaPendirian.format, + formDokumen.dokumenAktaPendirian.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenAktaPendirian} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + {' '} + Akta Perubahan <span className=' opacity-60'>(Opsional)</span> + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-3/5 text-gray-600 truncate max-w-48'> + {formDokumen?.dokumenAktaPerubahan?.name} + </span> + <div className='w-2/5 flex flex-row gap-2'> + <label + htmlFor='dokumenAktaPerubahan' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen.dokumenAktaPerubahan.name + ? 'Ubah' + : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenAktaPerubahan' + name='dokumenAktaPerubahan' + type='file' + title=' ' + ref={dokumenAktaPerubahanRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenAktaPerubahan} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenAktaPerubahan.format, + formDokumen.dokumenAktaPerubahan.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenAktaPerubahan} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Foto tempat kerja + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-3/5 text-gray-600 truncate max-w-48'> + {formDokumen?.dokumenTempatBekerja?.name} + </span> + <div className='w-2/5 flex flex-row gap-2'> + <label + htmlFor='dokumenTempatBekerja' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenTempatBekerja?.name + ? 'Ubah' + : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenTempatBekerja' + name='dokumenTempatBekerja' + type='file' + title=' ' + ref={dokumenTempatBekerjaRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenTempatBekerja} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenTempatBekerja.format, + formDokumen.dokumenTempatBekerja.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenTempatBekerja} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Foto Kantor (Tampak Depan) + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-3/5 text-gray-600 truncate max-w-48'> + {formDokumen?.dokumenFotoKantor?.name} + </span> + <div className='w-2/5 flex flex-row gap-2'> + <label + htmlFor='dokumenFotoKantor' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenFotoKantor?.name ? 'Ubah' : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenFotoKantor' + name='dokumenFotoKantor' + type='file' + title=' ' + ref={dokumenFotoKantorRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenFotoKantor} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenFotoKantor.format, + formDokumen.dokumenFotoKantor.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenFotoKantor} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + KTP Dirut/Direktur{' '} + <span className=' opacity-60'>(Opsional)</span> + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-3/5 text-gray-600 truncate max-w-48'> + {formDokumen?.dokumenKtpDirut?.name} + </span> + <div className='w-2/5 flex flex-row gap-2'> + <label + htmlFor='dokumenKtpDirut' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenKtpDirut?.name ? 'Ubah' : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenKtpDirut' + name='dokumenKtpDirut' + type='file' + title=' ' + ref={dokumenKtpDirutRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenKtpDirut} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenKtpDirut.format, + formDokumen.dokumenKtpDirut.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenKtpDirut} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Laporan Keuangan + <span className=' opacity-60'>(Opsional)</span> + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-3/5 text-gray-600 truncate max-w-48'> + {formDokumen?.dokumenLaporanKeuangan?.name} + </span> + <div className='w-2/5 flex flex-row gap-2'> + <label + htmlFor='dokumenLaporanKeuangan' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenLaporanKeuangan?.name + ? 'Ubah' + : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenLaporanKeuangan' + name='dokumenLaporanKeuangan' + type='file' + title=' ' + ref={dokumenLaporanKeuanganRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenLaporanKeuangan} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenLaporanKeuangan.format, + formDokumen.dokumenLaporanKeuangan.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenLaporanKeuangan} + </div> + )} + </div> + </div> + </form> + </div> + )} + {isMobile && ( + <div className=''> + <h1 className={`font-bold ${isKonfirmasi ? 'hidden' : 'text-xl'}`}> + Dokumen + </h1> + <form className='flex flex-col w-full gap-5 text-sm'> + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-wrap'>NPWP Perusahaan</label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-2/5 text-gray-600 truncate '> + {formDokumen?.dokumenNpwp?.name} + </span> + <div className='w-3/5 flex flex-row gap-2'> + <label + htmlFor='dokumenNpwp' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenNpwp?.name ? 'Sudah' : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenNpwp' + name='dokumenNpwp' + type='file' + title=' ' + ref={dokumenNpwpRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenNpwp} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenNpwp.format, + formDokumen.dokumenNpwp.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenNpwp} + </div> + )} + </div> + </div> + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-wrap'>SPPKP</label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-2/5 text-gray-600 truncate '> + {formDokumen?.dokumenSppkp?.name} + </span> + <div className='w-3/5 flex flex-row gap-2'> + <label + htmlFor='dokumenSppkp' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenSppkp?.name ? 'Ubah' : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenSppkp' + name='dokumenSppkp' + type='file' + title=' ' + ref={dokumenSppkpRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenSppkp} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenSppkp.format, + formDokumen.dokumenSppkp.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenSppkp} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-wrap'>NIB</label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-center justify-start'> + <span className='w-2/5 text-gray-600 truncate '> + {formDokumen?.dokumenNib?.name} + </span> + <div className='w-3/5 flex flex-row gap-2'> + <label + htmlFor='dokumenNib' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenNib?.name ? 'Ubah' : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenNib' + name='dokumenNib' + type='file' + title=' ' + ref={dokumenNibRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenNib} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenNib.format, + formDokumen.dokumenNib.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenNib} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-wrap'> + SIUP <span className=' opacity-60'>(Opsional)</span> + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-2/5 text-gray-600 truncate '> + {formDokumen?.dokumenSiup?.name} + </span> + <div className='w-3/5 flex flex-row gap-2'> + <label + htmlFor='dokumenSiup' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenSiup?.name ? 'Ubah' : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenSiup' + name='dokumenSiup' + type='file' + title=' ' + ref={dokumenAktaPerubahanRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenSiup} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenSiup.format, + formDokumen.dokumenSiup.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenSiup} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-wrap'> + TDP <span className=' opacity-60'>(Opsional)</span> + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-2/5 text-gray-600 truncate '> + {formDokumen?.dokumenTdp?.name} + </span> + <div className='w-3/5 flex flex-row gap-2'> + <label + htmlFor='dokumenTdp' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenTdp?.name ? 'Ubah' : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenTdp' + name='dokumenTdp' + type='file' + title=' ' + ref={dokumenAktaPerubahanRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenTdp} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenTdp.format, + formDokumen.dokumenTdp.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenTdp} + </div> + )} + </div> + </div> + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-wrap'> + SKDP <span className=' opacity-60'>(Opsional)</span> + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-2/5 text-gray-600 truncate '> + {formDokumen?.dokumenSkdp?.name} + </span> + <div className='w-3/5 flex flex-row gap-2'> + <label + htmlFor='dokumenSkdp' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenSkdp?.name ? 'Ubah' : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenSkdp' + name='dokumenSkdp' + type='file' + title=' ' + ref={dokumenAktaPerubahanRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenSkdp} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenSkdp.format, + formDokumen.dokumenSkdp.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenSkdp} + </div> + )} + </div> + </div> + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-wrap'> + SKT <span className=' opacity-60'>(Opsional)</span> + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-2/5 text-gray-600 truncate '> + {formDokumen?.dokumenSkt?.name} + </span> + <div className='w-3/5 flex flex-row gap-2'> + <label + htmlFor='dokumenSkt' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenSkt?.name ? 'Ubah' : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenSkt' + name='dokumenSkt' + type='file' + title=' ' + ref={dokumenAktaPerubahanRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenSkt} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenSkt.format, + formDokumen.dokumenSkt.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenSkt} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-wrap'> + {' '} + Akta Perubahan <span className=' opacity-60'>(Opsional)</span> + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-2/5 text-gray-600 truncate '> + {formDokumen?.dokumenAktaPerubahan?.name} + </span> + <div className='w-3/5 flex flex-row gap-2'> + <label + htmlFor='dokumenAktaPerubahan' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenAktaPerubahan?.name + ? 'Ubah' + : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenAktaPerubahan' + name='dokumenAktaPerubahan' + type='file' + title=' ' + ref={dokumenAktaPerubahanRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenAktaPerubahan} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenAktaPerubahan.format, + formDokumen.dokumenAktaPerubahan.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenAktaPerubahan} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-wrap'> + KTP Dirut/Direktur{' '} + <span className=' opacity-60'>(Opsional)</span> + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-2/5 text-gray-600 truncate '> + {formDokumen?.dokumenKtpDirut?.name} + </span> + <div className='w-3/5 flex flex-row gap-2'> + <label + htmlFor='dokumenKtpDirut' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenKtpDirut?.name ? 'Ubah' : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenKtpDirut' + name='dokumenKtpDirut' + type='file' + title=' ' + ref={dokumenKtpDirutRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenKtpDirut} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenKtpDirut.format, + formDokumen.dokumenKtpDirut.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenKtpDirut} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-wrap'> + Akta Pendirian <span className=' opacity-60'>(Opsional)</span> + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-2/5 text-gray-600 truncate '> + {formDokumen?.dokumenAktaPendirian?.name} + </span> + <div className='w-3/5 flex flex-row gap-2'> + <label + htmlFor='dokumenAktaPendirian' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenAktaPendirian?.name + ? 'Ubah' + : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenAktaPendirian' + name='dokumenAktaPendirian' + type='file' + title=' ' + ref={dokumenAktaPendirianRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenAktaPendirian} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenAktaPendirian.format, + formDokumen.dokumenAktaPendirian.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenAktaPendirian} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-wrap'> + Laporan Keuangan + <span className=' opacity-60'>(Opsional)</span> + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-2/5 text-gray-600 truncate '> + {formDokumen?.dokumenLaporanKeuangan?.name} + </span> + <div className='w-3/5 flex flex-row gap-2'> + <label + htmlFor='dokumenLaporanKeuangan' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenLaporanKeuangan?.name + ? 'Ubah' + : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenLaporanKeuangan' + name='dokumenLaporanKeuangan' + type='file' + title=' ' + ref={dokumenLaporanKeuanganRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenLaporanKeuangan} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenLaporanKeuangan.format, + formDokumen.dokumenLaporanKeuangan.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenLaporanKeuangan} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-wrap'> + Foto Kantor (Tampak Depan) + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-2/5 text-gray-600 truncate '> + {formDokumen?.dokumenFotoKantor?.name} + </span> + <div className='w-3/5 flex flex-row gap-2'> + <label + htmlFor='dokumenFotoKantor' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20 text-center' + > + {formDokumen?.dokumenFotoKantor?.name ? 'Ubah' : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenFotoKantor' + name='dokumenFotoKantor' + type='file' + title=' ' + ref={dokumenFotoKantorRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenFotoKantor} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenFotoKantor.format, + formDokumen.dokumenFotoKantor.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenFotoKantor} + </div> + )} + </div> + </div> + + <div className='w-full flex flex-row items-center '> + <div className='w-2/5'> + <label className='form-label text-wrap'> + Foto tempat kerja + </label> + </div> + <div className='w-3/5'> + <div className='flex flex-row items-start justify-between'> + <span className='w-2/5 text-gray-600 truncate'> + {formDokumen?.dokumenTempatBekerja?.name} + </span> + <div className='w-3/5 flex flex-row gap-2'> + <label + htmlFor='dokumenTempatBekerja' + className='cursor-pointer bg-red-500 hover:bg-red-600 text-white py-1 px-3 text-sm rounded min-w-20' + > + {formDokumen?.dokumenTempatBekerja?.name + ? 'Ubah' + : 'Upload'} + </label> + <input + // value={formDokumen?.dokumenNib?.name} + id='dokumenTempatBekerja' + name='dokumenTempatBekerja' + type='file' + title=' ' + ref={dokumenTempatBekerjaRef} + className='hidden' + aria-invalid={errorsDokumen.dokumenTempatBekerja} + onChange={handleInputChange} + accept='.pdf,.png,.jpg,.jpeg' + /> + <button + className='rounded text-white p-2 flex flex-row bg-red-500 hover:cursor-pointer hover:bg-red-400' + type='button' + onClick={() => + handleConfirmSubmit( + formDokumen.dokumenTempatBekerja.format, + formDokumen.dokumenTempatBekerja.base64 + ) + } + > + <EyeIcon className={`w-4 ${isDesktop && ''}`} /> + </button> + </div> + {/* <Image + src={`data:image/png;base64, ${formDokumen.dokumenNib.base64}`} + alt='Contoh SPPKP' + className='w-full h-full ' + width={800} + height={800} + quality={85} + /> */} + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsDokumen.dokumenTempatBekerja} + </div> + )} + </div> + </div> + </form> + </div> + )} + {isDesktop && ( + <BottomPopup + className='w-full h-full' + title='Dokumen' + active={isExample} + close={() => setIsExample(false)} + > + <div className='flex justify-center items-center p-2'> + <iframe + // src={`${ + // format == null ? url : `data:${format};base64,${base64}` + // }`} + // src={`http://192.168.23.244:8069` + `${format}`} + src={`data:${format};base64,${base64}`} + width='800px' + height='600px' + title='Document' + ></iframe> + {/* <embed + src={`data:${format};base64,${base64}`} + width='100%' + height='600px' + /> */} + </div> + </BottomPopup> + )} + {isMobile && ( + <BottomPopup + className='!container' + title='Dokumen' + active={isExample} + close={() => setIsExample(false)} + > + <div className='flex justify-center items-center p-2'> + {/* <iframe + // src={`${ + // format == null ? url : `data:${format};base64,${base64}` + // }`} + src={`http://192.168.23.244:8069` + `${format}`} + // src={format} + width='800px' + height='600px' + title='Document' + ></iframe> */} + <embed + src={`data:${format};base64,${base64}`} + width='100%' + height='600px' + /> + </div> + </BottomPopup> + )} + </> + ); +}; + +export default KonfirmasiDokumen; + +{ + /* <iframe + src={`data:application/octet-stream;base64, ${base64}`} + // src={`data:application/${format};base64, ${base64}`} + width='800px' + height='600px' + title='NPWP File' + frameBorder='0' + sandbox + ></iframe> */ +} +{ + /* <Image + src={`data:image/${format};base64, ${base64}`} + alt='Contoh SPPKP' + className='w-full h-full ' + width={1200} + height={1200} + quality={85} + /> */ +} + +// <div className='flex justify-center items-center p-2'> +// {!format.includes('undifined') ? ( +// <object +// data={`data:${format};base64,${base64}`} +// type={format} +// width='100%' +// height='100%' +// onError={(e) => { +// // Jika <object> gagal menampilkan PDF, unduh otomatis +// const link = document.createElement('a'); +// link.href = `data:${format};base64,${base64}`; +// link.download = 'document.pdf'; +// link.click(); +// }} +// > +// PDF tidak dapat ditampilkan. +// <a +// href={`data:${format};base64,${base64}`} +// download='document.pdf' +// > +// {' '} +// Klik <span className='text-red-500'>di sini</span> untuk +// mengunduh PDF +// </a> +// </object> +// ) : ( +// <embed +// src={ +// '/web/image/2540181?unique=7050288dd34d7318bf059e7f362540f6ad5533f9' +// } +// width='100%' +// height='600px' +// /> +// )} +// {/* <embed +// src={ +// '/web/image/2540181?unique=7050288dd34d7318bf059e7f362540f6ad5533f9' +// } +// width='100%' +// height='600px' +// /> */} +// </div> diff --git a/src/lib/pengajuan-tempo/component/KontakPerusahaan.jsx b/src/lib/pengajuan-tempo/component/KontakPerusahaan.jsx new file mode 100644 index 00000000..b8ca3202 --- /dev/null +++ b/src/lib/pengajuan-tempo/component/KontakPerusahaan.jsx @@ -0,0 +1,684 @@ +import React, { useEffect, useMemo, useRef } from 'react'; +import { usePengajuanTempoStoreKontakPerson } from '../../../../src-migrate/modules/register/stores/usePengajuanTempoStore'; +import useDevice from '@/core/hooks/useDevice'; +const KontakPerusahaan = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { + const { + formKontakPerson, + errorsKontakPerson, + validateKontakPerson, + updateFormKontakPerson, + } = usePengajuanTempoStoreKontakPerson(); + const { isDesktop, isMobile } = useDevice(); + const handleInputChange = (event) => { + const { name, value } = event.target; + updateFormKontakPerson(name, value); + validateKontakPerson(); + }; + + const isFormValid = useMemo( + () => Object.keys(errorsKontakPerson).length === 0, + [errorsKontakPerson] + ); + + const direkturNameRef = useRef(null); + const direkturMobileRef = useRef(null); + const direkturEmailRef = useRef(null); + const purchasingNameRef = useRef(null); + const purchasingEmailRef = useRef(null); + const purchasingMobileRef = useRef(null); + const financeNameRef = useRef(null); + const financeMobileRef = useRef(null); + const financeEmailRef = useRef(null); + + useEffect(() => { + const loadIndustries = async () => { + if (!isFormValid) { + const options = { + behavior: 'smooth', + block: 'center', + }; + if (errorsKontakPerson.direkturName && direkturNameRef.current) { + direkturNameRef.current.scrollIntoView(options); + return; + } + if (errorsKontakPerson.direkturMobile && direkturMobileRef.current) { + direkturMobileRef.current.scrollIntoView(options); + return; + } + if (errorsKontakPerson.direkturEmail && direkturEmailRef.current) { + direkturEmailRef.current.scrollIntoView(options); + return; + } + if (errorsKontakPerson.purchasingName && purchasingNameRef.current) { + purchasingNameRef.current.scrollIntoView(options); + return; + } + if ( + errorsKontakPerson.purchasingMobile && + purchasingMobileRef.current + ) { + purchasingMobileRef.current.scrollIntoView(options); + return; + } + if (errorsKontakPerson.purchasingEmail && purchasingEmailRef.current) { + purchasingEmailRef.current.scrollIntoView(options); + return; + } + if (errorsKontakPerson.financeName && financeNameRef.current) { + financeNameRef.current.scrollIntoView(options); + return; + } + if (errorsKontakPerson.financeMobile && financeMobileRef.current) { + financeMobileRef.current.scrollIntoView(options); + return; + } + if (errorsKontakPerson.financeEmail && financeEmailRef.current) { + financeEmailRef.current.scrollIntoView(options); + return; + } + } + }; + loadIndustries(); + }, [buttonSubmitClick, chekValid]); + + useEffect(() => { + validateKontakPerson(); + }, [buttonSubmitClick]); + return ( + <> + {isDesktop && ( + <div className=''> + <h1 className={`font-bold ${isKonfirmasi ? 'text-xl' : ''}`}> + Kontak Person + </h1> + <form className='flex flex-col w-full '> + <div className='w-full grid grid-row-2 gap-5'> + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Nama Lengkap Direktur + </label> + </div> + <div className='w-3/5'> + <div className='flex items-center border border-gray-300 rounded-md'> + {/* Dropdown untuk memilih "Bpk" atau "Ibu" */} + <select + value={formKontakPerson.direkturTittle || ''} + id='direkturTittle' + name='direkturTittle' + className=' p-2 rounded-l-md' + onChange={handleInputChange} + > + <option value='' disabled> + Pilih + </option> + <option value='Bpk'>Bpk</option> + <option value='Ibu'>Ibu</option> + </select> + + {/* Input untuk nama */} + <input + value={formKontakPerson.direkturName} + id='direkturName' + name='direkturName' + placeholder='Masukkan nama direktur anda' + type='text' + className='form-input border-l border-r-0 border-t-0 border-b-0 border-gray-300 p-2 ml-2 flex-grow rounded-none' + aria-invalid={errorsKontakPerson.direkturName} + ref={direkturNameRef} + onChange={handleInputChange} + /> + </div> + {chekValid && ( + <> + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsKontakPerson.direkturName}{' '} + {errorsKontakPerson.direkturTittle} + </div> + </> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-wrap'> + No. HP Direktur{' '} + <span className=' opacity-60'>(Opsional)</span> + </label> + </div> + <div className='w-3/5'> + <input + id='direkturMobile' + name='direkturMobile' + ref={direkturMobileRef} + placeholder='Masukkan nomor direktur anda' + type='tel' + value={formKontakPerson.direkturMobile} + className='form-input' + aria-invalid={errorsKontakPerson.direkturMobile} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsKontakPerson.direkturMobile} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Email Direktur + </label> + </div> + <div className='w-3/5'> + <input + id='direkturEmail' + name='direkturEmail' + ref={direkturEmailRef} + placeholder='contoh@email.com' + type='email' + value={formKontakPerson.direkturEmail} + className='form-input' + aria-invalid={errorsKontakPerson.direkturEmail} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsKontakPerson.direkturEmail} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Nama Purchasing + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + isi nama purchasing yang bertanggung jawab di perusahaan + anda + </span> + )} + </div> + <div className='w-3/5'> + <div className='flex items-center border border-gray-300 rounded-md'> + <select + value={formKontakPerson.purchasingTittle || ''} + id='purchasingTittle' + name='purchasingTittle' + className=' p-2 rounded-l-md' + onChange={handleInputChange} + > + <option value='' disabled> + Pilih + </option> + <option value='Bpk'>Bpk</option> + <option value='Ibu'>Ibu</option> + </select> + <input + id='purchasingName' + name='purchasingName' + ref={purchasingNameRef} + placeholder='Masukkan nama purchasing anda' + value={formKontakPerson.purchasingName} + type='text' + className='form-input border-l border-r-0 border-t-0 border-b-0 border-gray-300 p-2 ml-2 flex-grow rounded-none' + aria-invalid={errorsKontakPerson.purchasingName} + onChange={handleInputChange} + /> + </div> + {chekValid && ( + <> + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsKontakPerson.purchasingName}{' '} + {errorsKontakPerson.purchasingTittle} + </div> + </> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + No. HP Purchasing + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + isi nomor purchasing yang bertanggung jawab diperusahaan + anda + </span> + )} + </div> + <div className='w-3/5'> + <input + id='purchasingMobile' + name='purchasingMobile' + ref={financeMobileRef} + placeholder='Masukkan nomor purchasing anda' + value={formKontakPerson.purchasingMobile} + type='tel' + className='form-input' + aria-invalid={errorsKontakPerson.purchasingMobile} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsKontakPerson.purchasingMobile} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Email Purchasing + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + isi email purchasing yang bertanggung jawab perusahaan + anda + </span> + )} + </div> + <div className='w-3/5'> + <input + id='purchasingEmail' + name='purchasingEmail' + ref={purchasingEmailRef} + placeholder='contoh@email.com' + value={formKontakPerson.purchasingEmail} + type='email' + className='form-input' + aria-invalid={errorsKontakPerson.purchasingEmail} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsKontakPerson.purchasingEmail} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Nama Finance + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + isi nama finance yang bertanggung jawab di perusahaan anda + </span> + )} + </div> + <div className='w-3/5'> + <div className='flex items-center border border-gray-300 rounded-md'> + <select + value={formKontakPerson.financeTittle || ''} + id='financeTittle' + name='financeTittle' + className=' p-2 rounded-l-md' + onChange={handleInputChange} + > + <option value='' disabled> + Pilih + </option> + <option value='Bpk'>Bpk</option> + <option value='Ibu'>Ibu</option> + </select> + <input + id='financeName' + name='financeName' + ref={financeNameRef} + placeholder='Masukkan nama finance' + value={formKontakPerson.financeName} + type='text' + className='form-input border-l border-r-0 border-t-0 border-b-0 border-gray-300 p-2 ml-2 flex-grow rounded-none' + aria-invalid={errorsKontakPerson.financeName} + onChange={handleInputChange} + /> + </div> + {chekValid && ( + <> + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsKontakPerson.financeName}{' '} + {errorsKontakPerson.financeTittle} + </div> + </> + )} + </div> + </div> + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + No. HP Finance + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + isi nomor finance yang bertanggung jawab di perusahaan + anda + </span> + )} + </div> + <div className='w-3/5'> + <input + id='financeMobile' + name='financeMobile' + ref={financeMobileRef} + placeholder='Masukkan nomor finance' + value={formKontakPerson.financeMobile} + type='tel' + className='form-input' + aria-invalid={errorsKontakPerson.financeMobile} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsKontakPerson.financeMobile} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Email Finance + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + isi email finance yang bertanggung jawab diperusahaan anda + </span> + )} + </div> + <div className='w-3/5'> + <input + id='financeEmail' + name='financeEmail' + ref={financeEmailRef} + placeholder='contoh@email.com' + type='email' + value={formKontakPerson.financeEmail} + className='form-input' + aria-invalid={errorsKontakPerson.financeEmail} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsKontakPerson.financeEmail} + </div> + )} + </div> + </div> + </div> + </form> + </div> + )} + {isMobile && ( + <div className='text-sm'> + <h1 + className={`font-bold py-4 mt-8 ${ + isKonfirmasi ? 'hidden' : 'text-xl' + }`} + > + Kontak Person + </h1> + <form className='flex flex-col w-full '> + <div className='w-full grid grid-row-2 gap-4'> + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-nowrap'> + Nama Lengkap Direktur + </label> + <div className='flex items-center border border-gray-300 rounded-md w-full'> + <select + value={formKontakPerson.direkturTittle || ''} + id='direkturTittle' + name='direkturTittle' + className=' p-2 rounded-l-md' + onChange={handleInputChange} + > + <option value='' disabled> + Pilih + </option> + <option value='Bpk'>Bpk</option> + <option value='Ibu'>Ibu</option> + </select> + <input + value={formKontakPerson.direkturName} + id='direkturName' + name='direkturName' + placeholder='Masukkan nama direktur anda' + type='text' + className='form-input border-l border-r-0 border-t-0 border-b-0 border-gray-300 p-2 ml-2 flex-grow rounded-none' + aria-invalid={errorsKontakPerson.direkturName} + ref={direkturNameRef} + onChange={handleInputChange} + /> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsKontakPerson.direkturName}{' '} + {errorsKontakPerson.direkturTittle} + </div> + )} + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-nowrap'> + No. HP Direktur{' '} + <span className=' opacity-60'>(Opsional)</span> + </label> + <input + id='direkturMobile' + name='direkturMobile' + ref={direkturMobileRef} + placeholder='Masukkan nomor direktur anda' + type='tel' + value={formKontakPerson.direkturMobile} + className='form-input' + aria-invalid={errorsKontakPerson.direkturMobile} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsKontakPerson.direkturMobile} + </div> + )} + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-nowrap'> + Email Direktur + </label> + <input + id='direkturEmail' + name='direkturEmail' + ref={direkturEmailRef} + placeholder='contoh@email.com' + type='email' + value={formKontakPerson.direkturEmail} + className='form-input' + aria-invalid={errorsKontakPerson.direkturEmail} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsKontakPerson.direkturEmail} + </div> + )} + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-nowrap'> + Nama Purchasing + </label> + <div className='flex items-center border border-gray-300 rounded-md w-full'> + <select + value={formKontakPerson.purchasingTittle || ''} + id='purchasingTittle' + name='purchasingTittle' + className=' p-2 rounded-l-md' + onChange={handleInputChange} + > + <option value='' disabled> + Pilih + </option> + <option value='Bpk'>Bpk</option> + <option value='Ibu'>Ibu</option> + </select> + <input + id='purchasingName' + name='purchasingName' + ref={purchasingNameRef} + placeholder='Masukkan nama purchasing anda' + value={formKontakPerson.purchasingName} + type='text' + className='form-input border-l border-r-0 border-t-0 border-b-0 border-gray-300 p-2 ml-2 flex-grow rounded-none' + aria-invalid={errorsKontakPerson.purchasingName} + onChange={handleInputChange} + /> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsKontakPerson.purchasingName}{' '} + {errorsKontakPerson.purchasingTittle} + </div> + )} + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-nowrap'> + No. HP Purchasing + </label> + <input + id='purchasingMobile' + name='purchasingMobile' + ref={financeMobileRef} + placeholder='Masukkan nomor purchasing anda' + value={formKontakPerson.purchasingMobile} + type='tel' + className='form-input' + aria-invalid={errorsKontakPerson.purchasingMobile} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsKontakPerson.purchasingMobile} + </div> + )} + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-nowrap'> + Email Purchasing + </label> + <input + id='purchasingEmail' + name='purchasingEmail' + ref={purchasingEmailRef} + placeholder='contoh@email.com' + value={formKontakPerson.purchasingEmail} + type='email' + className='form-input' + aria-invalid={errorsKontakPerson.purchasingEmail} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsKontakPerson.purchasingEmail} + </div> + )} + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-nowrap'>Nama Finance</label> + <div className='flex items-center border border-gray-300 rounded-md w-full'> + <select + value={formKontakPerson.financeTittle || ''} + id='financeTittle' + name='financeTittle' + className=' p-2 rounded-l-md' + onChange={handleInputChange} + > + <option value='' disabled> + Pilih + </option> + <option value='Bpk'>Bpk</option> + <option value='Ibu'>Ibu</option> + </select> + <input + id='financeName' + name='financeName' + ref={financeNameRef} + placeholder='Masukkan nama finance' + value={formKontakPerson.financeName} + type='text' + className='form-input border-l border-r-0 border-t-0 border-b-0 border-gray-300 p-2 ml-2 flex-grow rounded-none' + aria-invalid={errorsKontakPerson.financeName} + onChange={handleInputChange} + /> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsKontakPerson.financeName}{' '} + {errorsKontakPerson.financeTittle} + </div> + )} + </div> + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-nowrap'> + No. HP Finance + </label> + <input + id='financeMobile' + name='financeMobile' + ref={financeMobileRef} + placeholder='Masukkan nomor finance' + value={formKontakPerson.financeMobile} + type='tel' + className='form-input' + aria-invalid={errorsKontakPerson.financeMobile} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsKontakPerson.financeMobile} + </div> + )} + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-nowrap'>Email Finance</label> + <input + id='financeEmail' + name='financeEmail' + ref={financeEmailRef} + placeholder='contoh@email.com' + type='email' + value={formKontakPerson.financeEmail} + className='form-input' + aria-invalid={errorsKontakPerson.financeEmail} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsKontakPerson.financeEmail} + </div> + )} + </div> + </div> + </form> + </div> + )} + </> + ); +}; + +export default KontakPerusahaan; diff --git a/src/lib/pengajuan-tempo/component/PengajuanTempo.jsx b/src/lib/pengajuan-tempo/component/PengajuanTempo.jsx new file mode 100644 index 00000000..5bef5134 --- /dev/null +++ b/src/lib/pengajuan-tempo/component/PengajuanTempo.jsx @@ -0,0 +1,716 @@ +import React from 'react'; +import { useMemo, useState, useEffect, useRef } from 'react'; +import Image from '~/components/ui/image'; +import Stepper from './Stepper'; +import InformasiPerusahaan from './informasiPerusahaan'; +import KontakPerusahaan from './KontakPerusahaan'; +import Pengiriman from './Pengiriman'; +import Referensi from './Referensi'; +import Dokumen from './Dokumen'; +import Konfirmasi from './Konfirmasi'; +import { getAuth } from '~/libs/auth'; +import { setAuth } from '@/core/utils/auth'; +import useAuth from '@/core/hooks/useAuth'; +import { useRouter } from 'next/router'; +import { Controller, useForm } from 'react-hook-form'; +import { + usePengajuanTempoStore, + usePengajuanTempoStoreKontakPerson, + usePengajuanTempoStorePengiriman, + usePengajuanTempoStoreSupplier, + usePengajuanTempoStoreDokumen, +} from '../../../../src-migrate/modules/register/stores/usePengajuanTempoStore'; +import { ChevronRightIcon, ChevronLeftIcon } from '@heroicons/react/24/outline'; +import createPengajuanTempoApi from '../api/createPengajuanTempoApi'; +import { Button, Checkbox, Spinner, Tooltip } from '@chakra-ui/react'; +import clsxm from '~/libs/clsxm'; +import { toast } from 'react-hot-toast'; +import useDevice from '@/core/hooks/useDevice'; +import odooApi from '~/libs/odooApi'; +import editAuthTempo from '../api/editAuthTempo'; +import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; +import PageContent from '@/lib/content/components/PageContent'; +const PengajuanTempo = () => { + const { isDesktop, isMobile } = useDevice(); + const [currentStep, setCurrentStep] = React.useState(0); + const NUMBER_OF_STEPS = 6; + const [isLoading, setIsLoading] = useState(false); + const [bigData, setBigData] = useState(); + const [idTempo, setIdTempo] = useState(0); + const { form, errors, validate, updateForm } = usePengajuanTempoStore(); + const { control, watch, setValue } = useForm(); + const auth = useAuth(); + const router = useRouter(); + const [BannerTempo, setBannerTempo] = useState(); + const { formDokumen, errorsDokumen, validateDokumen, updateFormDokumen } = + usePengajuanTempoStoreDokumen(); + const { + formKontakPerson, + errorsKontakPerson, + validateKontakPerson, + updateFormKontakPerson, + } = usePengajuanTempoStoreKontakPerson(); + const { + formSupplier, + errorsSupplier, + validateSupplier, + updateFormSupplier, + hasSavedata, + updateHasSave, + } = usePengajuanTempoStoreSupplier(); + const { + formPengiriman, + errorsPengiriman, + validatePengiriman, + updateFormPengiriman, + } = usePengajuanTempoStorePengiriman(); + const [notValid, setNotValid] = useState(false); + const [buttonSubmitClick, setButtonSubmitClick] = useState(false); + const stepDivs = [ + <InformasiPerusahaan + key='informasi-perusahaan' + chekValid={notValid} + buttonSubmitClick={buttonSubmitClick} + />, + <KontakPerusahaan + key='kontak-perusahaan' + chekValid={notValid} + buttonSubmitClick={buttonSubmitClick} + />, + <Pengiriman + key='pengiriman' + chekValid={notValid} + buttonSubmitClick={buttonSubmitClick} + />, + <Referensi + key='referensi' + chekValid={notValid} + buttonSubmitClick={buttonSubmitClick} + data={bigData?.supplierIds} + />, + <Dokumen + key='dokumen' + chekValid={notValid} + buttonSubmitClick={buttonSubmitClick} + />, + <Konfirmasi + key='konfirmasi' + chekValid={notValid} + buttonSubmitClick={buttonSubmitClick} + />, + ]; + + const stepDivsError = [ + errors, + errorsKontakPerson, + errorsPengiriman, + errorsSupplier, + errorsDokumen, + <div key='konfirmasi'>Konfirmasi</div>, + ]; + const stepDivsForm = [ + form, + formKontakPerson, + formPengiriman, + formSupplier, + formDokumen, + '', + ]; + const stepDivsUpdateForm = [ + updateForm, + updateFormKontakPerson, + updateFormPengiriman, + updateFormSupplier, + updateFormDokumen, + <div key='konfirmasi'>Konfirmasi</div>, + ]; + const stepLabels = [ + 'informasi_perusahaan', + 'kontak_person', + 'Pengiriman', + 'Referensi', + 'Dokumen', + 'Konfirmasi', + ]; + const isFormValid = useMemo( + () => Object.keys(stepDivsError[currentStep]).length === 0, + [stepDivsError[currentStep]] + ); + useEffect(() => { + validate(); + validateKontakPerson(); + validatePengiriman(); + validateDokumen(); + updateHasSave(true); + if (isFormValid) { + window.scrollTo({ + top: 0, + behavior: 'smooth', + }); + } + }, [currentStep, buttonSubmitClick]); + useEffect(() => { + window.scrollTo({ + top: 0, + behavior: 'smooth', + }); + }, [currentStep]); + + useEffect(() => { + const loadBigData = async () => { + const toCamelCase = (str) => + str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase()); + + const transformKeysToCamelCase = (data) => { + if (Array.isArray(data)) { + return data.map((item) => transformKeysToCamelCase(item)); + } else if (data && typeof data === 'object') { + return Object.keys(data).reduce((acc, key) => { + const camelKey = toCamelCase(key); // Ubah kunci menjadi camelCase + acc[camelKey] = transformKeysToCamelCase(data[key]); // Rekursif untuk nested object atau array + return acc; + }, {}); + } + return data; // Jika bukan object atau array, kembalikan nilai aslinya + }; + + try { + const dataPaymentTerm = await odooApi( + 'GET', + `/api/v1/partner/detail-tempo/${auth.parentId}` + ); + const transformedData = transformKeysToCamelCase(dataPaymentTerm); + setBigData(transformedData); + } catch (error) { + console.error('Error loading dataPaymentTerm:', error); + } + }; + + loadBigData(); + }, [auth]); + + useEffect(() => { + const cachedData = bigData; + + const loadBigData = async () => { + if (cachedData) { + // Ambil kunci-kunci yang relevan berdasarkan currentStep dari stepDivsForm + const formKeys = stepDivsForm[currentStep] + ? Object.keys(stepDivsForm[currentStep]) + : []; + if (currentStep === 3) { + stepDivsUpdateForm[currentStep](cachedData.supplierIds); + } else if (currentStep === 4) { + formKeys.forEach((key) => { + if (cachedData[key]) { + // Proses hanya kunci yang ada di cachedData + const { name, format, base64 } = cachedData[key]; + stepDivsUpdateForm[currentStep](key, name, format, base64); + } + }); + } else { + formKeys.forEach((key) => { + if (bigData[key]) { + // Ubah data menjadi string + const stringData = + typeof bigData[key] === 'object' + ? JSON.stringify(bigData[key]) // Untuk objek atau array + : String(bigData[key]); // Untuk tipe primitif + // Kirim data yang sudah diubah ke string ke stepDivsUpdateForm + stepDivsUpdateForm[currentStep](key, stringData); + } + }); + } + } + }; + loadBigData(); + validate(); + validateKontakPerson(); + validatePengiriman(); + validateDokumen(); + validateSupplier(); + updateHasSave(true); + }, [currentStep, bigData, auth]); + + const goToNextStep = () => { + if (!isFormValid) { + setNotValid(true); + setButtonSubmitClick(!buttonSubmitClick); + return; + } else { + // saveToLocalStorage(stepLabels[currentStep], stepDivsForm[currentStep]); + if (currentStep == 3) { + handleDaftarTempoSupplier(); + } else if (currentStep == 4) { + handleDaftarTempoDokumen(); + } else { + handleDaftarTempoPerPage( + stepLabels[currentStep], + stepDivsForm[currentStep] + ); + } + setButtonSubmitClick(!buttonSubmitClick); + setNotValid(false); + } + setCurrentStep((prev) => (prev === NUMBER_OF_STEPS - 1 ? prev : prev + 1)); + }; + + const goPrevStep = () => { + // if (!isFormValid) { + // setNotValid(true); + // setButtonSubmitClick(!buttonSubmitClick); + // return; + // } else { + // // saveToLocalStorage(stepLabels[currentStep], stepDivsForm[currentStep]); + // if (currentStep == 3) { + // handleDaftarTempoSupplier(); + // } else if (currentStep == 4) { + // handleDaftarTempoDokumen(); + // } else { + // handleDaftarTempoPerPage( + // stepLabels[currentStep], + // stepDivsForm[currentStep] + // ); + // } + // setButtonSubmitClick(!buttonSubmitClick); + // setNotValid(false); + // } + setCurrentStep((prev) => (prev === NUMBER_OF_STEPS - 1 ? prev : prev - 1)); + }; + + const handleDaftarTempoPerPage = async ( + label, + formData, + tempoRequest = false + ) => { + for (const error of stepDivsError) { + if (error.length > 0) { + return; + } + } + const toastId = toast.loading('Mengirimkan formulir pengajuan tempo...'); + setIsLoading(true); + try { + const address = await createPengajuanTempoApi({ + id: idTempo, + user_id: auth.parentId, + partner_id: auth.partnerId, + section: label, + tempo_request: tempoRequest, + ...formData, + }); + if (address.id) { + setIdTempo(address.id); + } + toast.dismiss(toastId); + setIsLoading(false); + } catch (error) { + toast.dismiss(toastId); + setIsLoading(false); + + toast.error('Terjadi kesalahan dalam pengiriman formulir'); + console.error(error); + } + }; + const handleDaftarTempoDokumen = async () => { + for (const error of stepDivsError) { + if (error.length > 0) { + 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, + }, + })); + + const toastId = toast.loading('Mengirimkan formulir pengajuan tempo...'); + if (formattedDokumen.length === 0) { + // toast.error('Tidak ada dokumen valid untuk dikirim.'); + return; + } + + setIsLoading(true); + try { + const address = await createPengajuanTempoApi({ + id: idTempo, + user_id: auth.parentId, + partner_id: auth.partnerId, + formDocs: JSON.stringify(formattedDokumen), + }); + if (address.id) { + setIdTempo(address.id); + } + toast.dismiss(toastId); + setIsLoading(false); + } catch (error) { + toast.dismiss(toastId); + setIsLoading(false); + + toast.error('Terjadi kesalahan dalam pengiriman formulir'); + console.error(error); + } + }; + + const isSupplierDataChanged = (formSupplier, supplierIds) => { + if (formSupplier.length !== supplierIds.length) { + return true; + } + return formSupplier.some((supplier, index) => { + const original = supplierIds[index]; + return ( + supplier.supplier !== original.supplier || + supplier.pic !== original.pic || + supplier.telepon !== original.telepon || + supplier.durasiTempo !== original.durasiTempo || + supplier.creditLimit !== original.creditLimit + ); + }); + }; + + const handleDaftarTempoSupplier = async () => { + for (const error of stepDivsError) { + if (error.length > 0) { + return; + } + } + + const productOrder = formSupplier.map((product) => ({ + supplier: product.supplier, + pic: product.pic, + telepon: product.telepon, + durasiTempo: product.durasiTempo, + creditLimit: product.creditLimit, + })); + + // Periksa perubahan + const toastId = toast.loading('Mengirimkan formulir pengajuan tempo...'); + if (bigData?.supplierIds) { + if (!isSupplierDataChanged(productOrder, bigData.supplierIds)) { + return; + } + } + + setIsLoading(true); + try { + const address = await createPengajuanTempoApi({ + id: idTempo, + user_id: auth.parentId, + partner_id: auth.partnerId, + formSupplier: JSON.stringify(productOrder), + }); + if (address.id) { + setIdTempo(address.id); + } + toast.dismiss(toastId); + setIsLoading(false); + } catch (error) { + toast.dismiss(toastId); + setIsLoading(false); + + toast.error('Terjadi kesalahan dalam pengiriman formulir'); + console.error(error); + } + }; + + const handleDaftarTempo = async () => { + for (const error of stepDivsError) { + if (error.length > 0) { + 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, + }, + })); + + 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, + tempo_request: false, + ...form, + }); + if (address.id) { + const address2 = await createPengajuanTempoApi({ + id: address.id, + partner_id: auth.partnerId, + user_id: address.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, + }); + 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; + } + } else { + toast.dismiss(toastId); + setIsLoading(false); + + toast.error('Terjadi kesalahan dalam pengiriman formulir'); + } + } catch (error) { + toast.dismiss(toastId); + setIsLoading(false); + + toast.error('Terjadi kesalahan dalam pengiriman formulir'); + console.error(error); + } + }; + + const removeFromLocalStorage = () => { + for (const key of stepLabels) { + localStorage.removeItem(key); + } + }; + + const [isCheckedTNC, setIsCheckedTNC] = useState(false); + + const handleCheckChange = (checked) => { + setIsCheckedTNC(checked); + }; + + useEffect(() => { + const getBanner = async () => { + const get = await odooApi('GET', '/api/v1/banner?type=banner-form-tempo'); + setBannerTempo(get[0].image); + // setBannerTempo( + // 'https://erp.indoteknik.com/api/image/x_banner.banner/x_banner_image/431' + // ); + }; + getBanner(); + }, []); + return ( + <> + <div className='container flex flex-col items-center '> + {BannerTempo && ( + <Image + src={BannerTempo} + alt='FORM Tempo' + width={500} + height={160} + className='w-full mt-6' + /> + )} + <h1 className=' font-semibold text-center mb-6'> + Form Pengajuan Tempo + </h1> + <p + className={`text-center ${ + isMobile ? 'w-full text-sm' : 'w-3/4 mb-4' + }`} + > + Pembayaran tempo adalah layanan pembayaran berjangka yang difasilitasi + indoteknik.com untuk konsumen akun bisnis yang terdaftar dengan waktu + pembayaran mulai dari 7, 14, 21 hingga 30 Hari. + </p> + </div> + <div + className={`h-[2px] w-full ${isMobile ? 'mt-4' : 'mb-20'} bg-gray_r-3`} + /> + + <div className={`container ${isMobile ? 'mt-4' : 'mt-10'} flex flex-col`}> + <div className='flex items-center justify-center'> + <Stepper currentStep={currentStep} numberOfSteps={NUMBER_OF_STEPS} /> + </div> + <div>{stepDivs[currentStep]}</div> + {isDesktop && <section className='flex gap-10 mt-10'></section>} + {isMobile && ( + <div className='h-[2px] bg-gray-300 w-[120%] inset-0 mt-4 mb-4 relative transform -translate-x-5'></div> + )} + <div + className={`flex ${ + isMobile + ? 'flex-col justify-start items-start' + : 'flex-col justify-end items-end' + } mb-8 gap-2`} + > + <span className='text-xs opacity-60'> + *Pastikan data yang anda masukan sudah benar dan sesuai + </span> + <div + className={` flex flex-row ${ + currentStep > 0 && currentStep < 5 + ? 'justify-between' + : 'justify-end' + } items-center w-full ${isMobile ? 'gap-x-4 ' : ''}`} + > + {currentStep < 5 && currentStep > 0 && ( + <Button + colorScheme='yellow' + w={`${isMobile ? 'full' : 'fit'}`} + onClick={goPrevStep} + > + {<ChevronLeftIcon className='w-5' />} + <span className={`font-medium ${isMobile ? 'text-xs' : ''} `}> + Langkah Sebelumnya + </span> + </Button> + )} + {currentStep < 5 && ( + <> + <Tooltip + label={clsxm({ + 'Klik simpan data terlebih dahulu': + currentStep === 3 && !hasSavedata, + })} + > + <Button + colorScheme='red' + w={`${isMobile ? 'full' : 'fit'}`} + isDisabled={ + (currentStep === 3 && !hasSavedata) || + currentStep === NUMBER_OF_STEPS - 1 + } + onClick={goToNextStep} + > + <span className={`${isMobile ? 'text-xs' : ''} `}> + Langkah Selanjutnya + </span> + {<ChevronRightIcon className='w-5' />} + </Button> + </Tooltip> + </> + )} + </div> + {currentStep == 5 && ( + <div + className={`flex flex-col ${ + isMobile ? 'items-start' : 'items-end' + } w-full justify-start gap-4`} + > + <TempoTermCondition onCheckChange={handleCheckChange} /> + <Button + colorScheme='red' + w={`${isMobile ? 'full' : '36'}`} + isDisabled={!isCheckedTNC} + onClick={handleDaftarTempo} + > + Daftar Tempo {<ChevronRightIcon className='w-5' />} + </Button> + </div> + )} + </div> + </div> + </> + ); +}; +const TempoTermCondition = ({ onCheckChange }) => { + const [isCheckedTNC, SetIsCheckedTNC] = useState(false); + const [openTNC, SetOpenTNC] = useState(false); + const { isDesktop, isMobile } = useDevice(); + + const openTNCHandle = () => { + SetOpenTNC(!openTNC); + }; + + const toggleCheckTNC = () => { + const newValue = !isCheckedTNC; + SetIsCheckedTNC(newValue); + if (onCheckChange) { + onCheckChange(newValue); + } + }; + return ( + <> + <div className='flex items-center gap-x-2'> + <Checkbox + id='tnc' + name='tnc' + colorScheme='red' + isChecked={isCheckedTNC} + onChange={toggleCheckTNC} + /> + <div className={`${isMobile ? 'text-xs' : ''}`}> + <label htmlFor='tnc' className='cursor-pointer'> + Dengan ini saya menyetujui + </label>{' '} + <span + className='font-medium text-danger-500 cursor-pointer' + onClick={openTNCHandle} + > + syarat dan ketentuan + </span> + <label htmlFor='tnc' className='ml-2 cursor-pointer'> + yang berlaku + </label> + </div> + </div> + + <BottomPopup + active={openTNC} + close={() => SetOpenTNC(false)} + title='Syarat & Ketentuan Pendaftaran Tempo' + > + <PageContent path='/tempoTnd' /> + </BottomPopup> + </> + ); +}; + +export default PengajuanTempo; diff --git a/src/lib/pengajuan-tempo/component/Pengiriman.jsx b/src/lib/pengajuan-tempo/component/Pengiriman.jsx new file mode 100644 index 00000000..755cf45d --- /dev/null +++ b/src/lib/pengajuan-tempo/component/Pengiriman.jsx @@ -0,0 +1,2078 @@ +import React, { useState, useEffect, useMemo, useRef } from 'react'; +import { Controller, set, useForm } from 'react-hook-form'; +import HookFormSelect from '@/core/components/elements/Select/HookFormSelect'; +import odooApi from '~/libs/odooApi'; +import stateApi from '@/lib/address/api/stateApi.js'; +import cityApi from '@/lib/address/api/cityApi'; +import districtApi from '@/lib/address/api/districtApi'; +import subDistrictApi from '@/lib/address/api/subDistrictApi'; +import { Radio, RadioGroup, Stack, Checkbox } from '@chakra-ui/react'; +import { + usePengajuanTempoStorePengiriman, + usePengajuanTempoStore, +} from '../../../../src-migrate/modules/register/stores/usePengajuanTempoStore'; +import { toast } from 'react-hot-toast'; +import useDevice from '@/core/hooks/useDevice'; +const Pengiriman = ({ chekValid, buttonSubmitClick, isKonfirmasi }) => { + const { control, watch, setValue } = useForm(); + const { isDesktop, isMobile } = useDevice(); + const { + formPengiriman, + errorsPengiriman, + validatePengiriman, + updateFormPengiriman, + } = usePengajuanTempoStorePengiriman(); + const { form } = usePengajuanTempoStore(); + const [states, setState] = useState([]); + const [cities, setCities] = useState([]); + const [districts, setDistricts] = useState([]); + const [subDistricts, setSubDistricts] = useState([]); + const [zips, setZips] = useState([]); + const [citiesInvoice, setCitiesInvoice] = useState([]); + const [districtsInvoice, setDistrictsInvoice] = useState([]); + const [subDistrictsInvoice, setSubDistrictsInvoice] = useState([]); + const [zipsInvoice, setZipsInvoice] = useState([]); + const [sameAddress, setSameAddress] = useState(false); + const [sameAddressStreet, setSameAddressStreet] = useState(false); + + const [selectedIds, setSelectedIds] = useState( + formPengiriman.dokumenPengiriman + ? formPengiriman.dokumenPengiriman.split(',').map(Number) + : [] + ); + const [selectedIdsDokumenInvoice, setSelectedIdsselectedIdsDokumenInvoice] = + useState( + formPengiriman.dokumenPengirimanInvoice + ? formPengiriman.dokumenPengirimanInvoice.split(',').map(Number) + : [] + ); + + const handleCheckboxChange = (id) => { + const updatedSelected = selectedIds.includes(id) + ? selectedIds.filter((selectedId) => selectedId !== id) + : [...selectedIds, id]; + + setSelectedIds(updatedSelected); + + // Jika checkbox 'Lainnya' dipilih, aktifkan input custom + + updateFormPengiriman('dokumenPengiriman', updatedSelected.join(',')); + validatePengiriman(); + }; + const handleCheckboxChangeDokumenPengirimanInvoice = (id) => { + const updatedSelected = selectedIdsDokumenInvoice.includes(id) + ? selectedIdsDokumenInvoice.filter((selectedId) => selectedId !== id) + : [...selectedIdsDokumenInvoice, id]; + + setSelectedIdsselectedIdsDokumenInvoice(updatedSelected); + + // Jika checkbox 'Lainnya' dipilih, aktifkan input custom + + updateFormPengiriman('dokumenPengirimanInvoice', updatedSelected.join(',')); + validatePengiriman(); + }; + + useEffect(() => { + if (formPengiriman.dokumenPengiriman) { + setSelectedIds(formPengiriman.dokumenPengiriman.split(',').map(Number)); // Parse string menjadi array angka + } + }, [formPengiriman.dokumenPengiriman]); + useEffect(() => { + if (formPengiriman.dokumenPengirimanInvoice) { + setSelectedIdsselectedIdsDokumenInvoice( + formPengiriman.dokumenPengirimanInvoice.split(',').map(Number) + ); // Parse string menjadi array angka + } + }, [formPengiriman.dokumenPengirimanInvoice]); + + // Fungsi untuk memeriksa apakah item sudah dipilih + const isChecked = (id) => selectedIds.includes(id); + const isCheckedInvoice = (id) => selectedIdsDokumenInvoice.includes(id); + + useEffect(() => { + const loadState = async () => { + let dataState = await stateApi({ tempo: true }); + dataState = dataState.map((state) => ({ + value: state.id, + label: state.name, + })); + setState(dataState); + }; + loadState(); + }, []); + + const watchState = watch('statePengiriman'); + useEffect(() => { + // updateFormPengiriman('cityPengiriman', ''); + // setValue('cityPengiriman', ''); + // setValue('districtPengiriman', ''); + // setValue('subDistrictPengiriman', ''); + // setValue('zipPengiriman', ''); + if (watchState) { + updateFormPengiriman('statePengiriman', `${watchState}`); + validatePengiriman(); + const loadCities = async () => { + let dataCities = await cityApi({ stateId: watchState }); + dataCities = dataCities.map((city) => ({ + value: city.id, + label: city.name, + })); + setCities(dataCities); + }; + loadCities(); + } + }, [watchState]); + + const watchCity = watch('cityPengiriman'); + + useEffect(() => { + if (watchCity) { + updateFormPengiriman('cityPengiriman', `${watchCity}`); + validatePengiriman(); + const loadDistricts = async () => { + let dataDistricts = await districtApi({ cityId: watchCity }); + dataDistricts = dataDistricts.map((district) => ({ + value: district.id, + label: district.name, + })); + setDistricts(dataDistricts); + }; + loadDistricts(); + } + }, [watchCity, setValue]); + + const watchDistrict = watch('districtPengiriman'); + useEffect(() => { + // setValue('subDistrictPengiriman', ''); + if (watchDistrict) { + updateFormPengiriman('districtPengiriman', `${watchDistrict}`); + validatePengiriman(); + const loadSubDistricts = async () => { + let dataSubDistricts = await subDistrictApi({ + districtId: watchDistrict, + }); + dataSubDistricts = dataSubDistricts.map((district) => ({ + value: district.id, + label: district.name, + })); + setSubDistricts(dataSubDistricts); + }; + loadSubDistricts(); + } + }, [watchDistrict, setValue]); + + const watchsubDistrict = watch('subDistrictPengiriman'); + + useEffect(() => { + let kelurahan = ''; + + if (watchsubDistrict) { + updateFormPengiriman('subDistrictPengiriman', `${watchsubDistrict}`); + validatePengiriman(); + for (const data in subDistricts) { + if (subDistricts[data].value == watchsubDistrict) { + kelurahan = subDistricts[data].label.toLowerCase(); + } + } + const loadZip = async () => { + const response = await fetch( + `https://alamat.thecloudalert.com/api/cari/index/?keyword=${kelurahan}` + ); + + const result = await response.json(); + const dataZips = result.result.map((zip) => ({ + value: parseInt(zip.kodepos), + label: zip.kodepos, + })); + setZips(dataZips); + }; + loadZip(); + } + }, [watchsubDistrict, setValue, subDistricts]); + + const watchZip = watch('zipPengiriman'); + useEffect(() => { + if (watchZip) { + updateFormPengiriman('zipPengiriman', `${watchZip}`); + validatePengiriman(); + } + }, [watchZip, setValue]); + + const watchStateInvoice = watch('stateInvoice'); + useEffect(() => { + // setValue('cityInvoice', ''); + // setValue('districtInvoice', ''); + // setValue('subDistrictInvoice', ''); + // setValue('zipInvoice', ''); + if (watchStateInvoice) { + updateFormPengiriman('stateInvoice', `${watchStateInvoice}`); + validatePengiriman(); + const loadCities = async () => { + let dataCities = await cityApi({ stateId: watchStateInvoice }); + dataCities = dataCities.map((city) => ({ + value: city.id, + label: city.name, + })); + setCitiesInvoice(dataCities); + }; + loadCities(); + } + }, [watchStateInvoice]); + + const watchCityInvoice = watch('cityInvoice'); + useEffect(() => { + if (watchCityInvoice) { + updateFormPengiriman('cityInvoice', `${watchCityInvoice}`); + validatePengiriman(); + const loadDistricts = async () => { + let dataDistricts = await districtApi({ cityId: watchCityInvoice }); + dataDistricts = dataDistricts.map((district) => ({ + value: district.id, + label: district.name, + })); + setDistrictsInvoice(dataDistricts); + }; + loadDistricts(); + } + }, [watchCityInvoice]); + + const watchDistrictInvoice = watch('districtInvoice'); + useEffect(() => { + // setValue('subDistrictInvoice', ''); + if (watchDistrictInvoice) { + updateFormPengiriman('districtInvoice', `${watchDistrictInvoice}`); + validatePengiriman(); + const loadSubDistricts = async () => { + let dataSubDistricts = await subDistrictApi({ + districtId: watchDistrictInvoice, + }); + dataSubDistricts = dataSubDistricts.map((district) => ({ + value: district.id, + label: district.name, + })); + setSubDistrictsInvoice(dataSubDistricts); + }; + loadSubDistricts(); + } + }, [watchDistrictInvoice, setValue]); + + const watchsubDistrictInvoice = watch('subDistrictInvoice'); + + useEffect(() => { + let kelurahan = ''; + + if (watchsubDistrictInvoice) { + updateFormPengiriman('subDistrictInvoice', `${watchsubDistrictInvoice}`); + validatePengiriman(); + for (const data in subDistrictsInvoice) { + if (subDistrictsInvoice[data].value == watchsubDistrictInvoice) { + kelurahan = subDistrictsInvoice[data].label.toLowerCase(); + } + } + const loadZip = async () => { + const response = await fetch( + `https://alamat.thecloudalert.com/api/cari/index/?keyword=${kelurahan}` + ); + + const result = await response.json(); + const dataZips = result.result.map((zip) => ({ + value: parseInt(zip.kodepos), + label: zip.kodepos, + })); + setZipsInvoice(dataZips); + }; + loadZip(); + } + }, [watchsubDistrictInvoice, setValue, subDistrictsInvoice]); + + const watchZipInvoice = watch('zipInvoice'); + useEffect(() => { + if (watchZipInvoice) { + updateFormPengiriman('zipInvoice', `${watchZipInvoice}`); + validatePengiriman(); + } + }, [watchZipInvoice, setValue]); + + const handleInputChange = (event) => { + const { name, value } = event.target; + updateFormPengiriman(name, value); + validatePengiriman(); + }; + + const isFormValid = useMemo( + () => Object.keys(errorsPengiriman).length === 0, + [errorsPengiriman] + ); + + const PICNameRef = useRef(null); + const streetPengirimanRef = useRef(null); + const statePengirimanRef = useRef(null); + const cityPengirimanRef = useRef(null); + const districtPengirimanRef = useRef(null); + const subDistrictPengirimanRef = useRef(null); + const zipRef = useRef(null); + const invoicePicRef = useRef(null); + const streetInvoiceRef = useRef(null); + const stateInvoiceRef = useRef(null); + const cityInvoiceRef = useRef(null); + const districtInvoiceRef = useRef(null); + const subDistrictInvoiceRef = useRef(null); + const zipInvoiceRef = useRef(null); + const everyWeekdayInputRef = useRef(null); + const everyWeekInputRef = useRef(null); + const dokumenPengirimanRef = useRef(null); + const dokumenPengirimanInputRef = useRef(null); + const dokumenPengirimanInvoiceRef = useRef(null); + const dokumenPengirimanInvoiceInputRef = useRef(null); + + useEffect(() => { + const loadIndustries = async () => { + if (!isFormValid) { + const options = { + behavior: 'smooth', + block: 'center', + }; + if (errorsPengiriman.PICName && PICNameRef.current) { + PICNameRef.current.scrollIntoView(options); + return; + } + if (errorsPengiriman.streetPengiriman && streetPengirimanRef.current) { + streetPengirimanRef.current.scrollIntoView(options); + return; + } + if (errorsPengiriman.statePengiriman && statePengirimanRef.current) { + statePengirimanRef.current.scrollIntoView(options); + return; + } + if (errorsPengiriman.cityPengiriman && cityPengirimanRef.current) { + cityPengirimanRef.current.scrollIntoView(options); + return; + } + if ( + errorsPengiriman.districtsPengiriman && + districtPengirimanRef.current + ) { + districtPengirimanRef.current.scrollIntoView(options); + return; + } + if ( + errorsPengiriman.subDistrictsPengiriman && + subDistrictPengirimanRef.current + ) { + subDistrictPengirimanRef.current.scrollIntoView(options); + return; + } + if (errorsPengiriman.zip && zipRef.current) { + zipRef.current.scrollIntoView(options); + return; + } + if (errorsPengiriman.invoicePic && invoicePicRef.current) { + invoicePicRef.current.scrollIntoView(options); + return; + } + if (errorsPengiriman.streetInvoice && streetInvoiceRef.current) { + streetInvoiceRef.current.scrollIntoView(options); + return; + } + if (errorsPengiriman.stateInvoice && stateInvoiceRef.current) { + stateInvoiceRef.current.scrollIntoView(options); + return; + } + if (errorsPengiriman.cityInvoice && cityInvoiceRef.current) { + cityInvoiceRef.current.scrollIntoView(options); + return; + } + if (errorsPengiriman.districtsInvoice && districtInvoiceRef.current) { + districtInvoiceRef.current.scrollIntoView(options); + return; + } + if ( + errorsPengiriman.subDistrictsInvoice && + subDistrictInvoiceRef.current + ) { + subDistrictInvoiceRef.current.scrollIntoView(options); + return; + } + if (errorsPengiriman.zipInvoice && zipInvoiceRef.current) { + zipInvoiceRef.current.scrollIntoView(options); + return; + } + if ( + errorsPengiriman.everyWeekdayInput && + everyWeekdayInputRef.current + ) { + everyWeekdayInputRef.current.scrollIntoView(options); + return; + } + if (errorsPengiriman.everyWeekInput && everyWeekInputRef.current) { + everyWeekInputRef.current.scrollIntoView(options); + return; + } + if ( + errorsPengiriman.dokumenPengiriman && + dokumenPengirimanRef.current + ) { + dokumenPengirimanRef.current.scrollIntoView(options); + return; + } + if ( + errorsPengiriman.dokumenInvoice && + dokumenPengirimanInvoiceRef.current + ) { + dokumenPengirimanInvoiceRef.current.scrollIntoView(options); + return; + } + if ( + selectedIds && + formPengiriman.dokumenPengirimanInput === '' && + dokumenPengirimanInputRef.current + ) { + dokumenPengirimanInputRef.current.scrollIntoView(options); + return; + } + if ( + selectedIdsDokumenInvoice && + formPengiriman.dokumenPengirimanInvoiceInput === '' && + dokumenPengirimanInvoiceInputRef.current + ) { + dokumenPengirimanInvoiceInputRef.current.scrollIntoView(options); + return; + } + } + }; + loadIndustries(); + }, [buttonSubmitClick, chekValid]); + + useEffect(() => { + if (formPengiriman.isSameAddreesStreet == 'true') { + setSameAddressStreet(true); + } else { + setSameAddressStreet(false); + } + }, [formPengiriman.isSameAddreesStreet]); + useEffect(() => { + if (formPengiriman.isSameAddrees == 'true') { + setSameAddress(true); + } else { + setSameAddress(false); + } + }, [formPengiriman.isSameAddrees]); + + // useEffect(() => { + // validatePengiriman(); + // if (formPengiriman.isSameAddrees) { + // const isSame = formPengiriman.isSameAddrees; + // if (isSame == 'true') { + // setSameAddress(true); + // } else { + // setSameAddress(false); + // } + // } + // if (formPengiriman.isSameAddreesStreet) { + // const isSame = formPengiriman.isSameAddreesStreet; + // if (isSame == 'true') { + // setSameAddressStreet(true); + // } else { + // setSameAddressStreet(false); + // } + // } + // validatePengiriman(); + // }, [buttonSubmitClick]); + // useEffect(() => { + // if (formPengiriman.isSameAddrees) { + // const isSame = formPengiriman.isSameAddrees; + // if (isSame == 'true') { + // setSameAddress(true); + // } else { + // setSameAddress(false); + // } + // } + // if (formPengiriman.isSameAddreesStreet) { + // const isSame = formPengiriman.isSameAddreesStreet; + // if (isSame == 'true') { + // setSameAddressStreet(true); + // } else { + // setSameAddressStreet(false); + // } + // } + // validatePengiriman(); + // }, [formPengiriman.isSameAddrees, formPengiriman.isSameAddreesStreet]); + + useEffect(() => { + if (sameAddressStreet) { + updateFormPengiriman('streetPengiriman', form.street); + updateFormPengiriman('statePengiriman', form.state); + setValue('statePengiriman', parseInt(form.state)); + updateFormPengiriman('cityPengiriman', form.city); + setValue('cityPengiriman', parseInt(form.city)); + updateFormPengiriman('districtPengiriman', form.district); + setValue('districtPengiriman', parseInt(form.district)); + updateFormPengiriman('subDistrictPengiriman', form.subDistrict); + setValue('subDistrictPengiriman', parseInt(form.subDistrict)); + updateFormPengiriman('zipPengiriman', form.zip); + setValue('zipPengiriman', parseInt(form.zip)); + } + // else { + // updateFormPengiriman('streetPengiriman', ''); + // updateFormPengiriman('statePengiriman', ''); + // updateFormPengiriman('cityPengiriman', ''); + // updateFormPengiriman('zipPengiriman', ''); + // setValue('streetPengiriman', ''); + // setValue('statePengiriman', ''); + // setValue('cityPengiriman', ''); + // } + validatePengiriman(); + }, [ + sameAddressStreet, + form.state, + form.city, + form.district, + form.subDistrict, + form.zip, + ]); + + useEffect(() => { + if (sameAddress) { + updateFormPengiriman('streetInvoice', form.street); + updateFormPengiriman('stateInvoice', form.state); + setValue('stateInvoice', parseInt(form.state)); + updateFormPengiriman('cityInvoice', form.city); + setValue('cityInvoice', parseInt(form.city)); + updateFormPengiriman('districtInvoice', form.district); + setValue('districtInvoice', parseInt(form.district)); + updateFormPengiriman('subDistrictInvoice', form.subDistrict); + setValue('subDistrictInvoice', parseInt(form.subDistrict)); + updateFormPengiriman('zipInvoice', form.zip); + setValue('zipInvoice', parseInt(form.zip)); + } else { + // updateFormPengiriman('streetInvoice', ''); + // updateFormPengiriman('stateInvoice', ''); + // updateFormPengiriman('cityInvoice', ''); + // setValue('streetInvoice', ''); + // setValue('stateInvoice', ''); + // setValue('cityInvoice', ''); + } + validatePengiriman(); + }, [ + sameAddress, + form.street, + form.state, + form.city, + form.district, + form.subDistrict, + ]); + + // useEffect(() => { + // if (sameAddress) { + // updateFormPengiriman('streetInvoice', formPengiriman.streetPengiriman); + // updateFormPengiriman('stateInvoice', formPengiriman.statePengiriman); + // setValue('stateInvoice', parseInt(formPengiriman.statePengiriman)); + // updateFormPengiriman('cityInvoice', formPengiriman.cityPengiriman); + // setValue('cityInvoice', parseInt(formPengiriman.cityPengiriman)); + + // updateFormPengiriman('isSameAddrees', `${sameAddress}`); + // } else { + // // updateFormPengiriman('stateInvoice', formPengiriman.stateInvoice); + // // setValue('stateInvoice', parseInt(formPengiriman.stateInvoice)); + // // updateFormPengiriman('cityInvoice', formPengiriman.cityInvoice); + // // setValue('cityInvoice', parseInt(formPengiriman.cityInvoice)); + // } + // validatePengiriman(); + // }, [ + // sameAddress, + // formPengiriman.streetPengiriman, + // formPengiriman.statePengiriman, + // formPengiriman.cityPengiriman, + // ]); + + // useEffect(() => { + // if (formPengiriman.sameAddressStreet?.toLowerCase().includes('true')) { + // setSameAddressStreet(true); + // } else { + // setSameAddressStreet(false); + // } + // }, [formPengiriman.sameAddressStreet]); + + useEffect(() => { + if (formPengiriman.statePengiriman) { + setValue('statePengiriman', parseInt(formPengiriman.statePengiriman)); + } + }, [formPengiriman.statePengiriman]); + useEffect(() => { + if (formPengiriman.cityPengiriman) { + setValue('cityPengiriman', parseInt(formPengiriman.cityPengiriman)); + } + }, [formPengiriman.cityPengiriman]); + useEffect(() => { + if (formPengiriman.districtPengiriman) { + setValue( + 'districtPengiriman', + parseInt(formPengiriman.districtPengiriman) + ); + } + }, [formPengiriman.districtPengiriman]); + useEffect(() => { + if (formPengiriman.subDistrictPengiriman) { + setValue( + 'subDistrictPengiriman', + parseInt(formPengiriman.subDistrictPengiriman) + ); + } + }, [formPengiriman.subDistrictPengiriman]); + useEffect(() => { + if (formPengiriman.zipPengiriman) { + setValue('zipPengiriman', parseInt(formPengiriman.zipPengiriman)); + } + }, [formPengiriman.zipPengiriman]); + useEffect(() => { + if (formPengiriman.stateInvoice) { + setValue('stateInvoice', parseInt(formPengiriman.stateInvoice)); + } + }, [formPengiriman.stateInvoice]); + useEffect(() => { + if (formPengiriman.cityInvoice) { + setValue('cityInvoice', parseInt(formPengiriman.cityInvoice)); + } + }, [formPengiriman.cityInvoice]); + useEffect(() => { + if (formPengiriman.districtInvoice) { + setValue('districtInvoice', parseInt(formPengiriman.districtInvoice)); + } + }, [formPengiriman.districtInvoice]); + useEffect(() => { + if (formPengiriman.subDistrictInvoice) { + setValue( + 'subDistrictInvoice', + parseInt(formPengiriman.subDistrictInvoice) + ); + } + }, [formPengiriman.subDistrictInvoice]); + useEffect(() => { + if (formPengiriman.zipInvoice) { + setValue('zipInvoice', parseInt(formPengiriman.zipInvoice)); + } + }, [formPengiriman.zipInvoice]); + + const getFromLocalStorage = (key) => { + const itemStr = localStorage.getItem(key); + if (!itemStr) return null; + + const item = JSON.parse(itemStr); + return item; + }; + + // const cachedData = getFromLocalStorage('Pengiriman'); + // useEffect(() => { + // if (cachedData) { + // setValue('cityPengiriman', parseInt(cachedData?.cityPengiriman)); + // updateFormPengiriman('cityPengiriman', `${cachedData?.cityPengiriman}`); + // } + // if (cachedData?.statePengiriman) { + // setValue('statePengiriman', parseInt(cachedData?.statePengiriman)); + // } + // if (cachedData?.stateInvoice) { + // setValue('stateInvoice', parseInt(cachedData?.stateInvoice)); + // } + // if (cachedData?.cityInvoice) { + // setValue('cityInvoice', parseInt(cachedData?.cityInvoice)); + // } + // if (cachedData?.isSameAddrees) { + // updateFormPengiriman('isSameAddrees', `${cachedData?.isSameAddrees}`); + // } + // validatePengiriman(); + // }, [cachedData?.cityPengiriman]); + // useEffect(() => { + // if (cachedData?.everyWeek) { + // updateFormPengiriman('everyWeek', cachedData?.everyWeek); + // setEveryWeek(cachedData?.everyWeek); + // } + // if (cachedData?.everyWeekday) { + // updateFormPengiriman('everyWeekday', cachedData?.everyWeekday); + // setEveryWeekday(cachedData?.everyWeekday); + // } + // if (cachedData?.tukarInvoice) { + // updateFormPengiriman('tukarInvoice', cachedData?.tukarInvoice); + // setTukarInvoice(cachedData?.tukarInvoice); + // } + // if (cachedData?.everyWeekPembayaran) { + // updateFormPengiriman( + // 'everyWeekPembayaran', + // cachedData?.everyWeekPembayaran + // ); + // setEveryWeekPembayaran(cachedData?.everyWeekPembayaran); + // } + // if (cachedData?.everyWeekdayPembayaran) { + // updateFormPengiriman( + // 'everyWeekdayPembayaran', + // cachedData?.everyWeekdayPembayaran + // ); + // setEveryWeekdayPembayaran(cachedData?.everyWeekdayPembayaran); + // } + // if (cachedData?.tukarInvoicePembayaran) { + // updateFormPengiriman( + // 'tukarInvoicePembayaran', + // cachedData?.tukarInvoicePembayaran + // ); + // setTukarInvoicePembayaran(cachedData?.tukarInvoicePembayaran); + // } + // validatePengiriman(); + // }, [ + // cachedData?.everyWeek, + // cachedData?.everyWeekday, + // cachedData?.tukarInvoice, + // cachedData?.everyWeekdayPembayaran, + // cachedData?.everyWeekPembayaran, + // cachedData?.tukarInvoicePembayaran, + // ]); + const handleChangeSameAddress = () => { + // setSameAddress(!sameAddress); + // if (sameAddress) { + // updateFormPengiriman('sameAddress', `true`); + // } else { + // } + updateFormPengiriman('isSameAddrees', `${!sameAddress}`); + validatePengiriman(); + }; + const handleChangeSameAddressStreet = () => { + // updateFormPengiriman('sameAddressStreet', `${!sameAddressStreet}`); + // setSameAddressStreet(!sameAddressStreet); + // if (sameAddressStreet == false) { + // updateFormPengiriman('streetPengiriman', ''); + // updateFormPengiriman('statePengiriman', ''); + // updateFormPengiriman('cityPengiriman', ''); + // updateFormPengiriman('zipPengiriman', ''); + // setValue('streetPengiriman', ''); + // setValue('statePengiriman', ''); + // setValue('cityPengiriman', ''); + // } + updateFormPengiriman('isSameAddreesStreet', `${!sameAddressStreet}`); + validatePengiriman(); + }; + + return ( + <> + {isDesktop && ( + <div> + <h1 className={`font-bold ${isKonfirmasi ? 'text-xl' : ''}`}> + Pengiriman + </h1> + <form className='flex flex-col w-full '> + <div className='w-full grid grid-row-2 gap-5'> + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Nama PIC Penerimaan Barang + </label> + </div> + <div className='w-3/5'> + <div className='flex items-center border border-gray-300 rounded-md'> + <select + value={formPengiriman.PICTittle || ''} + id='PICTittle' + name='PICTittle' + className=' p-2 rounded-l-md' + onChange={handleInputChange} + > + <option value='' disabled> + Pilih + </option> + <option value='Bpk'>Bpk</option> + <option value='Ibu'>Ibu</option> + </select> + <input + value={formPengiriman.PICName} + id='PICName' + name='PICName' + placeholder='Masukkan nama pic penerimaan barang disini' + type='text' + className='form-input border-l border-r-0 border-t-0 border-b-0 border-gray-300 p-2 ml-2 flex-grow rounded-none' + aria-invalid={errorsPengiriman.PICName} + ref={PICNameRef} + aria-label='PICName' + onChange={handleInputChange} + /> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.PICName} {errorsPengiriman.PICTittle} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Alamat Pengiriman Barang + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + pastikan alamat yang anda isi sesuai dengan alamat kirim + barang + </span> + )} + </div> + <div className='w-3/5 flex gap-3 flex-col'> + <Checkbox + colorScheme='red' + isChecked={sameAddressStreet} + onChange={handleChangeSameAddressStreet} + > + Alamat Pengiriman sama dengan alamat perusahaan + </Checkbox> + + <> + <div> + <input + id='streetPengiriman' + name='streetPengiriman' + ref={streetPengirimanRef} + aria-label='streetPengiriman' + placeholder='Masukkan alamat lengkap pengiriman barang' + type='text' + value={formPengiriman.streetPengiriman} + className='form-input' + onChange={handleInputChange} + disabled={sameAddressStreet} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.streetPengiriman} + </div> + )} + </div> + <div + className={` sub-alamat flex ${ + isKonfirmasi ? 'flex-col' : 'flex-row' + } w-full gap-3`} + > + <div + className={`flex ${ + isKonfirmasi + ? ' flex-row gap-3 w-full' + : 'flex-row gap-3 w-2/5' + }`} + > + <div + className='w-full' + ref={statePengirimanRef} + aria-label='statePengiriman' + > + <Controller + name='statePengiriman' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + disabled={sameAddressStreet} + options={states} + placeholder='Provinsi' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.statePengiriman} + </div> + )} + </div> + <div className='w-full' ref={cityPengirimanRef}> + <Controller + name='cityPengiriman' + aria-label='cityPengiriman' + disabled={sameAddressStreet} + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={cities} + disabled={!watchState || sameAddressStreet} + placeholder='Kota' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.cityPengiriman} + </div> + )} + </div> + </div> + <div + className={`flex-row flex gap-2 justify-between ${ + isKonfirmasi ? 'w-full' : 'w-3/5' + }`} + > + <div className='w-full' ref={districtPengirimanRef}> + <Controller + name='districtPengiriman' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={districts} + disabled={ + !watchState || !watchCity || sameAddressStreet + } + placeholder='Kelurahan' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.subDistrictPengiriman} + </div> + )} + </div> + <div className='w-full' ref={subDistrictPengirimanRef}> + <Controller + name='subDistrictPengiriman' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={subDistricts} + disabled={!watchDistrict || sameAddressStreet} + placeholder='Kelurahan' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.subDistrictPengiriman} + </div> + )} + </div> + <div className='w-full' ref={zipRef}> + <Controller + name='zipPengiriman' + control={control} + render={(props) => ( + <> + {/* Jika zips tidak kosong, tampilkan dropdown */} + {zips.length > 0 ? ( + <HookFormSelect + {...props} + options={zips} + disabled={ + !watchsubDistrict || sameAddressStreet + } + placeholder='Zip' + /> + ) : ( + // Jika zips kosong, tampilkan input manual + <input + id='zipPengiriman' + name='zipPengiriman' + ref={zipRef} + placeholder='Kode Pos' + type='number' + disabled={ + !watchsubDistrict || sameAddressStreet + } + value={formPengiriman.zipPengiriman} + className='form-input' + onChange={handleInputChange} + /> + )} + </> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.zipPengiriman} + </div> + )} + </div> + </div> + </div> + </> + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Nama PIC Penerimaan Invoice + </label> + </div> + <div className='w-3/5'> + <div className='flex items-center border border-gray-300 rounded-md'> + <select + value={formPengiriman.invoicePicTittle || ''} + id='invoicePicTittle' + name='invoicePicTittle' + className=' p-2 rounded-l-md' + onChange={handleInputChange} + > + <option value='' disabled> + Pilih + </option> + <option value='Bpk'>Bpk</option> + <option value='Ibu'>Ibu</option> + </select> + <input + value={formPengiriman.invoicePic} + id='invoicePic' + name='invoicePic' + aria-label='invoicePic' + placeholder='Masukkan nama pic invoice disini' + type='text' + className='form-input border-l border-r-0 border-t-0 border-b-0 border-gray-300 p-2 ml-2 flex-grow rounded-none' + aria-invalid={errorsPengiriman.invoicePic} + ref={invoicePicRef} + onChange={handleInputChange} + /> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.invoicePic}{' '} + {errorsPengiriman.invoicePicTittle} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Alamat Pengiriman Invoice + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + Pastikan alamat yang anda isi sesuai dengan alamat kirim + invoice + </span> + )} + </div> + <div className='w-3/5 flex gap-3 flex-col'> + <div> + <Checkbox + colorScheme='red' + isChecked={sameAddress} + onChange={handleChangeSameAddress} + > + Alamat invoice sama dengan alamat perusahaan + </Checkbox> + </div> + + <> + <div> + <input + id='streetInvoice' + name='streetInvoice' + aria-label='streetInvoice' + ref={streetInvoiceRef} + disabled={sameAddress} + placeholder='Masukkan alamat lengkap pengiriman invoice' + type='text' + value={formPengiriman.streetInvoice} + className='form-input' + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.streetInvoice} + </div> + )} + </div> + <div + className={` sub-alamat flex ${ + isKonfirmasi ? 'flex-col' : 'flex-row' + } w-full gap-3`} + > + <div + className={`flex ${ + isKonfirmasi + ? ' flex-row gap-3 w-full' + : 'flex-row gap-3 w-2/5' + }`} + > + <div + className='w-full' + ref={stateInvoiceRef} + aria-label='stateInvoice' + > + <Controller + name='stateInvoice' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={states} + disabled={sameAddress} + placeholder='Provinsi' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.stateInvoice} + </div> + )} + </div> + <div + className='w-full' + ref={cityInvoiceRef} + aria-label='cityInvoice' + > + <Controller + name='cityInvoice' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={citiesInvoice} + disabled={!watchStateInvoice || sameAddress} + placeholder='Kota' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.cityInvoice} + </div> + )} + </div> + </div> + <div + className={`flex-row flex gap-2 justify-between ${ + isKonfirmasi ? 'w-full' : 'w-3/5' + }`} + > + <div className='w-full' ref={districtInvoiceRef}> + <Controller + name='districtInvoice' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={districtsInvoice} + disabled={ + !watchStateInvoice || + !watchCityInvoice || + sameAddress + } + placeholder='Kecamatan' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.districtInvoice} + </div> + )} + </div> + <div className='w-full' ref={subDistrictInvoiceRef}> + <Controller + name='subDistrictInvoice' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={subDistrictsInvoice} + disabled={!watchDistrictInvoice || sameAddress} + placeholder='Kelurahan' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.subDistrictsInvoice} + </div> + )} + </div> + <div className='w-full' ref={zipInvoiceRef}> + <Controller + name='zipInvoice' + control={control} + render={(props) => ( + <> + {zipsInvoice.length > 0 ? ( + <HookFormSelect + {...props} + options={zipsInvoice} + disabled={ + !watchsubDistrictInvoice || sameAddress + } + placeholder='Zip' + /> + ) : ( + <input + id='zipInvoice' + name='zipInvoice' + ref={zipInvoiceRef} + placeholder='Kode Pos' + type='number' + disabled={ + !watchsubDistrictInvoice || sameAddress + } + value={formPengiriman.zipInvoice} + className='form-input' + onChange={handleInputChange} + /> + )} + </> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.zipInvoice} + </div> + )} + </div> + </div> + </div> + </> + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-wrap'> + Jadwal Penukaran Invoice{' '} + <span className=' opacity-60'>(Opsional)</span> + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + isi jika perusahaan anda memiliki jadwal penukaran invoice + </span> + )} + </div> + <div className='w-3/5 flex gap-3 flex-col'> + <textarea + id='tukarInvoiceInput' + name='tukarInvoiceInput' + aria-label='tukarInvoiceInput' + placeholder='Masukkan jadwal penukaran invoice' + value={formPengiriman.tukarInvoiceInput} + className='form-input' + rows={4} + cols={40} + onChange={handleInputChange} + /> + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Jadwal Pembayaran{' '} + <span className=' opacity-60'>(Opsional)</span> + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + isi jika perusahaan anda memiliki jadwal pembayaran + </span> + )} + </div> + <div className='w-3/5 flex gap-3 flex-col'> + <textarea + id='tukarInvoiceInputPembayaran' + name='tukarInvoiceInputPembayaran' + placeholder='Masukkan jadwal pembayaran' + value={formPengiriman.tukarInvoiceInputPembayaran} + className='form-input' + rows={4} + cols={40} + onChange={handleInputChange} + /> + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-wrap'> + Apakah ada dokumen tanda terima yang diberikan pada saat + pengiriman barang?{' '} + <span className=' opacity-60'>(Opsional)</span> + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + Pilih dokumen tanda terima saat pengiriman barang jika ada + </span> + )} + </div> + <div + className='w-3/5 flex gap-3 flex-col' + ref={dokumenPengirimanRef} + aria-label='dokumenPengirimanInput' + > + <Checkbox + colorScheme='red' + key='0' + isChecked={isChecked(0)} + onChange={() => handleCheckboxChange(0)} + > + Surat Tanda Terima Barang (STTB) + </Checkbox> + <Checkbox + colorScheme='red' + key='1' + isChecked={isChecked(1)} + onChange={() => handleCheckboxChange(1)} + > + Good Receipt (GR) + </Checkbox> + <Checkbox + colorScheme='red' + key='2' + isChecked={isChecked(2)} + onChange={() => handleCheckboxChange(2)} + > + Surat Terima Barang (STB) + </Checkbox> + <Checkbox + colorScheme='red' + key='3' + isChecked={isChecked(3)} + onChange={() => handleCheckboxChange(3)} + > + Lembar Penerimaan Barang (LPB) + </Checkbox> + + <div className='flex gap-3 flex-col'> + <Checkbox + colorScheme='red' + key='4' + isChecked={ + isChecked(4) || formPengiriman.dokumenKirimInput + } + onChange={() => handleCheckboxChange(4)} + > + Lainnya + </Checkbox> + <textarea + id='dokumenKirimInput' + name='dokumenKirimInput' + aria-label='dokumenKirimInput' + placeholder='isi manual dokumen yang anda mau' + type='textarea' + ref={dokumenPengirimanInputRef} + value={formPengiriman.dokumenKirimInput} + className='form-input' + rows={4} + cols={40} + onChange={handleInputChange} + /> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.dokumenPengiriman} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-wrap'> + Dokumen yang dibawa saat pengiriman barang + <span className=' opacity-60'>(Opsional)</span> + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + Dokumen lampiran yang dibawa saat pengiriman barang + </span> + )} + </div> + <div className='flex gap-3 flex-col w-3/5 '> + <textarea + id='dokumenPengirimanInput' + name='dokumenPengirimanInput' + aria-label='dokumenPengirimanInput' + placeholder='isi manual dokumen yang anda mau' + type='textarea' + ref={dokumenPengirimanInputRef} + value={formPengiriman.dokumenPengirimanInput} + className='form-input' + rows={4} + cols={40} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.dokumenPengiriman} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-wrap'> + Dokumen yang dilampirkan saat Pengiriman Invoice{' '} + <span className=' opacity-60'>(Opsional)</span> + </label> + <span className='text-xs opacity-60'> + Dokumen lampiran saat pengiriman invoice + </span> + </div> + <div className='flex gap-3 flex-col w-3/5'> + <textarea + id='dokumenPengirimanInvoice' + aria-label='dokumenPengirimanInvoice' + name='dokumenPengirimanInvoice' + placeholder='isi manual dokumen yang anda mau' + type='textarea' + ref={dokumenPengirimanInvoiceRef} + value={formPengiriman.dokumenPengirimanInvoice} + className='form-input' + rows={4} + cols={40} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.dokumenPengirimanInvoice} + </div> + )} + </div> + </div> + </div> + </form> + </div> + )} + {isMobile && ( + <div className='text-sm'> + <h1 + className={`font-bold py-4 mt-8 ${ + isKonfirmasi ? 'hidden' : 'text-xl' + }`} + > + Pengiriman + </h1> + <form className='flex flex-col w-full '> + <div className='w-full grid grid-row-2 gap-2'> + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-nowrap'> + Nama PIC Penerimaan Barang + </label> + <div className='flex items-center border border-gray-300 rounded-md w-full'> + <select + value={formPengiriman.PICTittle || ''} + id='PICTittle' + name='PICTittle' + className=' p-2 rounded-l-md' + onChange={handleInputChange} + > + <option value='' disabled> + Pilih + </option> + <option value='Bpk'>Bpk</option> + <option value='Ibu'>Ibu</option> + </select> + <input + value={formPengiriman.PICName} + id='PICName' + name='PICName' + aria-label='PICName' + placeholder='Masukkan nama pic penerima barang disini' + type='text' + className='form-input border-l border-r-0 border-t-0 border-b-0 border-gray-300 p-2 ml-2 flex-grow rounded-none' + aria-invalid={errorsPengiriman.PICName} + ref={PICNameRef} + onChange={handleInputChange} + /> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.PICName} {errorsPengiriman.PICTittle} + </div> + )} + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-nowrap'> + Alamat Pengiriman Barang + </label> + <Checkbox + colorScheme='red' + isChecked={sameAddressStreet} + onChange={handleChangeSameAddressStreet} + size='sm' + > + Alamat Pengiriman sama dengan alamat perusahaan + </Checkbox> + <div className='w-full'> + <input + id='streetPengiriman' + name='streetPengiriman' + aria-label='streetPengiriman' + ref={streetPengirimanRef} + disabled={sameAddressStreet} + placeholder='Masukkan alamat lengkap pengiriman barang' + type='text' + value={formPengiriman.streetPengiriman} + className='form-input' + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.streetPengiriman} + </div> + )} + </div> + <div className='sub-alamat flex flex-row w-full gap-2'> + <div + className='w-2/5' + ref={statePengirimanRef} + aria-label='statePengiriman' + > + <Controller + name='statePengiriman' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + disabled={sameAddressStreet} + options={states} + placeholder='Provinsi' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.statePengiriman} + </div> + )} + </div> + <div + className='w-3/5' + ref={cityPengirimanRef} + aria-label='cityPengiriman' + > + <Controller + name='cityPengiriman' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={cities} + disabled={!watchState || sameAddressStreet} + placeholder='Kota' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.cityPengiriman} + </div> + )} + </div> + {/* <div className='w-1/3'> + <input + id='zipPengiriman' + aria-label='zipPengiriman' + name='zipPengiriman' + ref={zipRef} + placeholder='Kode Pos' + disabled={sameAddressStreet} + type='number' + value={formPengiriman.zipPengiriman} + className='form-input' + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.zipPengiriman} + </div> + )} + </div> */} + </div> + <div className='flex flex-row w-full gap-2'> + <div className='w-1/3' ref={districtPengirimanRef}> + <Controller + name='districtPengiriman' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={districts} + disabled={ + !watchState || !watchCity || sameAddressStreet + } + placeholder='Kelurahan' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.subDistrictPengiriman} + </div> + )} + </div> + <div className='w-1/3' ref={subDistrictPengirimanRef}> + <Controller + name='subDistrictPengiriman' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={subDistricts} + disabled={!watchDistrict || sameAddressStreet} + placeholder='Kelurahan' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.subDistrictPengiriman} + </div> + )} + </div> + <div className='w-1/3' ref={zipRef}> + <Controller + name='zipPengiriman' + control={control} + render={(props) => ( + <> + {/* Jika zips tidak kosong, tampilkan dropdown */} + {zips.length > 0 ? ( + <HookFormSelect + {...props} + options={zips} + disabled={!watchsubDistrict || sameAddressStreet} + placeholder='Zip' + /> + ) : ( + // Jika zips kosong, tampilkan input manual + <input + id='zipPengiriman' + name='zipPengiriman' + ref={zipRef} + placeholder='Kode Pos' + type='number' + disabled={!watchsubDistrict || sameAddressStreet} + value={formPengiriman.zipPengiriman} + className='form-input' + onChange={handleInputChange} + /> + )} + </> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.zipPengiriman} + </div> + )} + </div> + </div> + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-nowrap'> + Nama PIC Penerimaan Invoice + </label> + <div className='flex items-center border border-gray-300 rounded-md w-full'> + <select + value={formPengiriman.invoicePicTittle || ''} + id='invoicePicTittle' + name='invoicePicTittle' + className=' p-2 rounded-l-md' + onChange={handleInputChange} + > + <option value='' disabled> + Pilih + </option> + <option value='Bpk'>Bpk</option> + <option value='Ibu'>Ibu</option> + </select> + <input + value={formPengiriman.invoicePic} + id='invoicePic' + aria-label='invoicePic' + name='invoicePic' + placeholder='Masukkan nama pic invoice disini' + type='text' + className='form-input border-l border-r-0 border-t-0 border-b-0 border-gray-300 p-2 ml-2 flex-grow rounded-none' + aria-invalid={errorsPengiriman.invoicePic} + ref={invoicePicRef} + onChange={handleInputChange} + /> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.invoicePic}{' '} + {errorsPengiriman.invoicePicTittle} + </div> + )} + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-nowrap'> + Alamat Pengiriman Invoice + </label> + <div> + <Checkbox + colorScheme='red' + isChecked={sameAddress} + onChange={handleChangeSameAddress} + size='sm' + > + Alamat invoice sama dengan alamat pengiriman + </Checkbox> + </div> + + <> + <div className='w-full'> + <input + id='streetInvoice' + aria-label='streetInvoice' + name='streetInvoice' + ref={streetInvoiceRef} + disabled={sameAddress} + placeholder='Masukkan alamat lengkap pengiriman invoice' + type='text' + value={formPengiriman.streetInvoice} + className='form-input' + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.streetInvoice} + </div> + )} + </div> + <div className='sub-alamat flex flex-row w-full gap-3'> + <div + className='w-3/5' + ref={stateInvoiceRef} + aria-label='stateInvoice' + > + <Controller + name='stateInvoice' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={states} + disabled={sameAddress} + placeholder='Provinsi' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.stateInvoice} + </div> + )} + </div> + <div + className='w-2/5' + ref={cityInvoiceRef} + aria-label='cityInvoice' + > + <Controller + name='cityInvoice' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={cities} + disabled={!watchState || sameAddress} + placeholder='Kota' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.cityInvoice} + </div> + )} + </div> + </div> + <div className='sub-alamat flex flex-row w-full gap-3'> + <div className='w-1/3' ref={districtInvoiceRef}> + <Controller + name='districtInvoice' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={districtsInvoice} + disabled={ + !watchStateInvoice || + !watchCityInvoice || + sameAddress + } + placeholder='Kecamatan' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.districtInvoice} + </div> + )} + </div> + <div className='w-1/3' ref={subDistrictInvoiceRef}> + <Controller + name='subDistrictInvoice' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={subDistrictsInvoice} + disabled={!watchDistrictInvoice || sameAddress} + placeholder='Kelurahan' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.subDistrictsInvoice} + </div> + )} + </div> + <div className='w-1/3' ref={zipInvoiceRef}> + <Controller + name='zipInvoice' + control={control} + render={(props) => ( + <> + {zipsInvoice.length > 0 ? ( + <HookFormSelect + {...props} + options={zipsInvoice} + disabled={ + !watchsubDistrictInvoice || sameAddress + } + placeholder='Zip' + /> + ) : ( + <input + id='zipInvoice' + name='zipInvoice' + ref={zipInvoiceRef} + placeholder='Kode Pos' + type='number' + disabled={ + !watchsubDistrictInvoice || sameAddress + } + value={formPengiriman.zipInvoice} + className='form-input' + onChange={handleInputChange} + /> + )} + </> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.zipInvoice} + </div> + )} + </div> + </div> + </> + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-wrap'> + Jadwal Penukaran Invoice + <span className=' opacity-60'>(Opsional)</span> + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + isi jika perusahaan anda memiliki jadwal penukaran invoice + </span> + )} + <div className='w-full flex gap-2 flex-col'> + <textarea + id='tukarInvoiceInput' + name='tukarInvoiceInput' + placeholder='Masukkan jadwal penukaran invoice' + type='textarea' + value={formPengiriman.tukarInvoiceInput} + className='form-input' + rows={4} + cols={40} + onChange={handleInputChange} + /> + </div> + <div className='w-2/5'></div> + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-nowrap'> + Jadwal Pembayaran + <span className=' opacity-60'>(Opsional)</span> + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + isi jika perusahaan anda memiliki jadwal pembayaran + </span> + )} + <div className='w-full flex gap-2 flex-col'> + <textarea + id='tukarInvoiceInputPembayaran' + name='tukarInvoiceInputPembayaran' + placeholder='Masukkan jadwal pembayaran' + type='textarea' + value={formPengiriman.tukarInvoiceInputPembayaran} + className='form-input' + rows={4} + cols={40} + onChange={handleInputChange} + /> + </div> + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-wrap'> + Apakah ada dokumen tanda terima yang diberikan pada saat + pengiriman barang?{' '} + <span className=' opacity-60'>(Opsional)</span> + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + Pilih dokumen lampiran saat pengiriman barang + </span> + )} + <div + className='w-full flex gap-2 flex-col' + ref={dokumenPengirimanRef} + aria-label='dokumenPengirimanInput' + > + <Checkbox + size='sm' + colorScheme='red' + key='0' + isChecked={isChecked(0)} + onChange={() => handleCheckboxChange(0)} + > + Surat Tanda Terima Barang (STTB) + </Checkbox> + <Checkbox + size='sm' + colorScheme='red' + key='1' + isChecked={isChecked(1)} + onChange={() => handleCheckboxChange(1)} + > + Good Receipt (GR) + </Checkbox> + <Checkbox + size='sm' + colorScheme='red' + key='2' + isChecked={isChecked(2)} + onChange={() => handleCheckboxChange(2)} + > + Surat Terima Barang (STB) + </Checkbox> + <Checkbox + size='sm' + colorScheme='red' + key='3' + isChecked={isChecked(3)} + onChange={() => handleCheckboxChange(3)} + > + Lembar Penerimaan Barang (LPB) + </Checkbox> + <div className='flex gap-3 flex-col'> + <Checkbox + colorScheme='red' + key='4' + isChecked={ + isChecked(4) || formPengiriman.dokumenKirimInput + } + onChange={() => handleCheckboxChange(4)} + > + Lainnya + </Checkbox> + <textarea + id='dokumenKirimInput' + name='dokumenKirimInput' + aria-label='dokumenKirimInput' + placeholder='isi manual dokumen yang anda mau' + type='textarea' + ref={dokumenPengirimanInputRef} + value={formPengiriman.dokumenKirimInput} + className='form-input' + rows={4} + cols={40} + onChange={handleInputChange} + /> + </div> + + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.dokumenPengiriman} + </div> + )} + </div> + </div> + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-wrap'> + Dokumen yang dibawa saat pengiriman barang + <span className=' opacity-60'>(Opsional)</span> + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + Dokumen lampiran yang dibawa saat pengiriman barang + </span> + )} + <div className='flex gap-3 flex-col w-full'> + <textarea + id='dokumenPengirimanInput' + aria-label='dokumenPengirimanInput' + name='dokumenPengirimanInput' + placeholder='isi manual dokumen yang anda mau' + type='textarea' + ref={dokumenPengirimanInputRef} + value={formPengiriman.dokumenPengirimanInput} + className='form-input' + rows={4} + cols={40} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.dokumenPengiriman} + </div> + )} + </div> + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-wrap'> + Dokumen yang dilampirkan saat Pengiriman Invoice + <span className=' opacity-60'>(Opsional)</span> + </label> + <span className='text-xs opacity-60'> + Dokumen lampiran saat pengiriman invoice + </span> + <div className='flex gap-3 flex-col w-full'> + <textarea + id='dokumenPengirimanInvoice' + aria-label='dokumenPengirimanInvoice' + name='dokumenPengirimanInvoice' + placeholder='isi manual dokumen yang anda mau' + type='textarea' + ref={dokumenPengirimanInvoiceRef} + value={formPengiriman.dokumenPengirimanInvoice} + className='form-input' + rows={4} + cols={40} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.dokumenInvoicePengiriman} + </div> + )} + </div> + <div className='w-2/5'></div> + </div> + </div> + </form> + </div> + )} + </> + ); +}; + +export default Pengiriman; diff --git a/src/lib/pengajuan-tempo/component/Referensi.jsx b/src/lib/pengajuan-tempo/component/Referensi.jsx new file mode 100644 index 00000000..8db321d1 --- /dev/null +++ b/src/lib/pengajuan-tempo/component/Referensi.jsx @@ -0,0 +1,636 @@ +import React, { useState, useEffect, useMemo, useRef } from 'react'; +import { useForm } from 'react-hook-form'; +import { + usePengajuanTempoStoreSupplier, + usePengajuanTempoStore, +} from '../../../../src-migrate/modules/register/stores/usePengajuanTempoStore'; +import * as Yup from 'yup'; +import { yupResolver } from '@hookform/resolvers/yup'; +import { PlusCircleIcon } from '@heroicons/react/24/outline'; +import useDevice from '@/core/hooks/useDevice'; +import { Trash2Icon } from 'lucide-react'; +import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/24/outline'; +import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; +const initialData = []; +const Referensi = ({ chekValid, buttonSubmitClick, data }) => { + const [changeConfirmation, setChangeConfirmation] = useState(false); + const [selectedIndex, setSelectedIndex] = useState(null); + + const { isDesktop, isMobile } = useDevice(); + const [openIndexes, setOpenIndexes] = useState([]); + + const { + register, + formState: { errors }, + handleSubmit, + watch, + setValue, + control, + } = useForm({ + resolver: yupResolver(validationSchema), + defaultValues, + }); + const { formSupplier, updateFormSupplier, updateHasSave, hasSavedata } = + usePengajuanTempoStoreSupplier(); + const { form } = usePengajuanTempoStore(); + const [formData, setFormData] = useState([ + { + supplier: '', + pic: '', + telepon: '', + durasiTempo: '', + creditLimit: '', + }, + ]); + + const [buttonSubmit, setButtonSubmit] = useState(false); + const [supplierData, setSupplierData] = useState(initialData); + const [newSupplier, setNewSupplier] = useState({ + supplier: '', + pic: '', + telepon: '', + durasiTempo: '', + creditLimit: '', + }); + const onChangeInput = (e, index) => { + updateHasSave(false); + const { name, value } = e.target; + + let formattedValue = value; + + if (name === 'durasiTempo') { + formattedValue = value.replace(/\s*Hari\s*/g, ''); + } else if (name === 'creditLimit') { + formattedValue = value.replace(/^Rp\s*/, ''); + } + + const editData = supplierData.map((item, i) => + i === index ? { ...item, [name]: formattedValue } : item + ); + setSupplierData(editData); + if (value == '' && supplierData.supplier) { + updateHasSave(true); + } + }; + + const handleNewSupplierChange = (e) => { + updateHasSave(false); + const { name, value } = e.target; + + let formattedValue = value; + + if (name === 'durasiTempo') { + formattedValue = value.replace(/\s*Hari\s*/g, ''); + } else if (name === 'creditLimit') { + formattedValue = value.replace(/^Rp\s*/, ''); + } + + const updatedSupplier = { ...newSupplier, [name]: formattedValue }; + setNewSupplier(updatedSupplier); + if (value == '') { + updateHasSave(true); + } + }; + + useEffect(() => { + const isAllFieldsEmpty = Object.values(newSupplier).every( + (value) => value === '' + ); + if (isAllFieldsEmpty) { + updateHasSave(true); + } else { + updateHasSave(false); + } + }, [newSupplier]); + + const handleDeleteSupplier = () => { + if (selectedIndex !== null) { + // Logika untuk menghapus supplier + const updatedSuppliers = supplierData.filter( + (_, idx) => idx !== selectedIndex + ); + setSupplierData(updatedSuppliers); + setChangeConfirmation(false); + updateHasSave(false); + } + }; + + const handleAddNewSupplier = () => { + // updateHasSave(false); + if (Object.values(newSupplier).every((val) => val.trim() !== '')) { + setSupplierData((prevData) => { + const newData = [...prevData, newSupplier]; + return newData; + }); + + setNewSupplier({ + supplier: '', + pic: '', + telepon: '', + durasiTempo: '', + creditLimit: '', + }); + } + }; + + useEffect(() => { + handleAddNewSupplier(); + updateFormSupplier(supplierData); + setButtonSubmit(!buttonSubmit); + }, [buttonSubmitClick]); + const simpanData = () => { + setButtonSubmit(!buttonSubmit); + if (Object.values(newSupplier).every((val) => val.trim() !== '')) { + setSupplierData((prevData) => { + const newData = [...prevData, newSupplier]; + return newData; + }); + + setNewSupplier({ + supplier: '', + pic: '', + telepon: '', + durasiTempo: '', + creditLimit: '', + }); + } + updateHasSave(true); + }; + const formatRupiah = (value) => { + if (!value) return ''; + const numberString = value.replace(/[^0-9]/g, ''); + return numberString + ? 'Rp ' + new Intl.NumberFormat('id-ID').format(numberString) + : ''; + }; + const formatHari = (value) => { + if (!value) return ''; + + const numberString = value.replace(/[^0-9]/g, ''); + + return numberString ? numberString.replace(/Hari/g, '') + ' Hari' : ''; + }; + + useEffect(() => { + updateFormSupplier(supplierData); + }, [buttonSubmit]); + const getFromLocalStorage = (key) => { + const itemStr = localStorage.getItem(key); + if (!itemStr) return null; + + const item = JSON.parse(itemStr); + return item; + }; + useEffect(() => { + // const cachedData = getFromLocalStorage('Referensi'); + if (data) { + setSupplierData(data); + updateFormSupplier(data); + } + }, [buttonSubmitClick]); + + useEffect(() => { + setOpenIndexes(supplierData.map((_, index) => index)); + }, [supplierData]); + + const toggleOpen = (index) => { + setOpenIndexes((prev) => + prev.includes(index) ? prev.filter((i) => i !== index) : [...prev, index] + ); + }; + + const handleOpenConfirmation = (index) => { + setSelectedIndex(index); + setChangeConfirmation(true); + }; + return ( + <> + <BottomPopup + active={changeConfirmation} + close={() => setChangeConfirmation(false)} + title='Konfirmasi Hapus Data' + > + <div className='leading-7 text-gray_r-12/80'> + Apakah anda yakin menghapus data? + </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={handleDeleteSupplier} + > + Ya, Hapus + </button> + <button + className='btn-light flex-1 md:flex-none' + type='button' + onClick={() => setChangeConfirmation(false)} + > + Batal + </button> + </div> + </BottomPopup> + {isDesktop && ( + <div className='py-4'> + <div className='flex flex-col justify-start'> + <h1 className='font-bold text-2xl'> + Referensi Supplier / Rekanan Bisnis Perusahaan{' '} + <span className=' opacity-60 text-xl'>(Opsional)</span> + </h1> + <p className='opacity-60'> + Data yang anda berikan hanya untuk bahan referensi internal kami + untuk memberikan anda credit limit dan durasi tempo + </p> + </div> + <form className='flex mt-4 flex-col w-full '> + <table className='border' border='1' cellPadding='10'> + <thead> + <tr className='border '> + <th className='text-left px-5 py-2'> + Nama Supplier / Rekanan + </th> + <th className='text-left px-5 py-2'>PIC</th> + <th className='text-left px-5 py-2'>Telepon</th> + <th className='text-left px-5 py-2'>Durasi Tempo</th> + <th className='text-left px-5 py-2'>Credit Limit</th> + </tr> + </thead> + <tbody> + {supplierData.map((supplier, index) => ( + <tr key={index}> + <td> + <input + name='supplier' + value={supplier.supplier} + type='text' + onChange={(e) => onChangeInput(e, index)} + className='form-input border px-4 py-2' + placeholder='Type Supplier' + /> + </td> + <td> + <input + name='pic' + value={supplier.pic} + type='text' + className='form-input border px-4 py-2' + onChange={(e) => onChangeInput(e, index)} + placeholder='Type PIC' + /> + </td> + <td> + <input + name='telepon' + type='text' + className='form-input border px-4 py-2' + value={supplier.telepon} + onChange={(e) => onChangeInput(e, index)} + placeholder='Type Telepon' + /> + </td> + <td> + <input + name='durasiTempo' + type='text' + className='form-input border px-4 py-2' + value={formatHari(supplier.durasiTempo)} + onChange={(e) => onChangeInput(e, index)} + placeholder='Type Durasi Tempo' + /> + </td> + <td className='flex flex-row gap-2 justify-center items-center'> + <input + name='creditLimit' + type='text' + value={formatRupiah(supplier.creditLimit)} + className='form-input border px-4 py-2' + onChange={(e) => onChangeInput(e, index)} + placeholder='Type Credit Limit' + /> + <Trash2Icon + size={18} + onClick={() => handleOpenConfirmation(index)} + className='cursor-pointer' + /> + </td> + </tr> + ))} + <tr> + <td> + <input + name='supplier' + value={newSupplier.supplier} + type='text' + className='form-input border px-4 py-2' + onChange={handleNewSupplierChange} + placeholder='Isi nama supplier anda' + /> + </td> + <td> + <input + name='pic' + value={newSupplier.pic} + type='text' + className='form-input border px-4 py-2' + onChange={handleNewSupplierChange} + placeholder='Isi PIC supplier anda' + /> + </td> + <td> + <input + name='telepon' + value={newSupplier.telepon} + type='text' + onChange={handleNewSupplierChange} + placeholder='Isi telepon supplier anda' + className='form-input border px-4 py-2' + /> + </td> + <td> + <input + name='durasiTempo' + value={formatHari(newSupplier.durasiTempo)} + type='text' + onChange={handleNewSupplierChange} + className='form-input border px-4 py-2' + placeholder='Durasi jatuh tempo' + /> + </td> + <td className='flex flex-row items-center gap-2 '> + <input + name='creditLimit' + value={formatRupiah(newSupplier.creditLimit)} + type='text' + className='form-input border px-4 py-2' + onChange={handleNewSupplierChange} + placeholder='limit kredit' + /> + {/* <Trash2Icon + size={18} + onClick={() => handleDeleteSupplier(index)} + /> */} + </td> + </tr> + </tbody> + </table> + </form> + <div className='flex items-center gap-4 mt-8'> + {/* <button + onClick={handleAddNewSupplier} + className='bg-gray-200 border border-gray-500 rounded-md text-sm text-gray-500 p-2 h-11 mb-1 content-center flex flex-row justify-center items-center' + > + {<PlusCircleIcon className='w-5 mr-2' />} + {''} Tambah Data Baru + </button> */} + {!hasSavedata && ( + <> + <button + onClick={simpanData} + className={`bg-red-500 border border-red-500 rounded-md text-sm text-white p-2 h-11 mb-1 content-center flex flex-row justify-center items-center `} + > + Simpan Data + </button> + <span className='text-sm opacity-60 text-red-500'> + *Klik simpan sebelum lanjut ke tahap selanjutnya + </span> + </> + )} + </div> + </div> + )} + {isMobile && ( + <div className='text-sm'> + <div className='flex flex-col py-4 mt-8 justify-start'> + <h1 className='font-bold text-xl'> + Referensi Supplier / Rekanan Bisnis Perusahaan{' '} + <span className=' opacity-60 text-xl'>(Opsional)</span> + </h1> + <p className='opacity-60'> + Data yang anda berikan hanya untuk bahan referensi internal kami + untuk memberikan anda credit limit dan durasi tempo + </p> + </div> + <div className='flex gap-4 flex-col'> + <div className='h-[2px] bg-gray-300 w-[120%] inset-0 relative transform -translate-x-5'></div> + <h2 className='py-2 font-semibold text-base'> + Daftar Nama Supplier + </h2> + {/* <div className='h-[2px] bg-gray-300 w-[120%] inset-0 relative transform -translate-x-5'></div> */} + <div className=''> + {supplierData.map((supplier, index) => ( + <div key={index}> + <div + className='flex flex-row justify-center items-center py-4' + onClick={() => toggleOpen(index)} + > + <p className='font-semibold text-base w-4/5'> + {supplier.supplier} + </p> + <div className='w-1/5 flex justify-end items-center gap-2'> + <Trash2Icon + size={16} + color='red' + onClick={() => handleOpenConfirmation(index)} + className='cursor-pointer' + /> + {openIndexes.includes(index) ? ( + <ChevronUpIcon className='w-4' /> + ) : ( + <ChevronDownIcon className='w-4' /> + )} + </div> + </div> + {openIndexes.includes(index) && ( + <form className='flex flex-col w-full'> + <div className='w-full grid grid-row-2 gap-4'> + <div className='flex flex-row justify-start items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Nama Supplier + </label> + </div> + <div className='w-3/5 opacity-70'> + {supplier.supplier} + </div> + </div> + + <div className='flex flex-row justify-start items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + PIC + </label> + </div> + <div className='w-3/5 opacity-70'>{supplier.pic}</div> + </div> + + <div className='flex flex-row justify-start items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Telepon + </label> + </div> + <div className='w-3/5 opacity-70'> + {supplier.telepon} + </div> + </div> + + <div className='flex flex-row justify-start items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Durasi Tempo + </label> + </div> + <div className='w-3/5 opacity-70'> + {formatHari(supplier.durasiTempo)} + </div> + </div> + + <div className='flex flex-row justify-start items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Kredit Limit + </label> + </div> + <div className='w-3/5 opacity-70'> + {formatRupiah(supplier.creditLimit)} + </div> + </div> + </div> + </form> + )} + </div> + ))} + </div> + {/* <div className='h-[2px] bg-gray-300 w-[120%] inset-0 relative transform -translate-x-5'></div> */} + {/* <h2 className='py-2 font-semibold text-base text-red-500 flex flex-row'> + <PlusCircleIcon className='w-5 mr-2' /> + Tambah Data Baru + </h2> */} + <div className='h-[2px] bg-gray-300 w-[120%] inset-0 relative transform -translate-x-5'></div> + <form className='flex flex-col w-full'> + <div className='w-full grid grid-row-2 gap-2'> + <div className='flex flex-row justify-start items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Nama Supplier + </label> + </div> + <div className='w-3/5 opacity-70'> + <input + name='supplier' + value={newSupplier.supplier} + type='text' + className='form-input border px-4 py-2' + onChange={handleNewSupplierChange} + placeholder='Format: PT. ABC TESTING INDONESIA' + /> + </div> + </div> + + <div className='flex flex-row justify-start items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'>PIC</label> + </div> + <div className='w-3/5 opacity-70'> + <input + name='pic' + value={newSupplier.pic} + type='text' + className='form-input border px-4 py-2' + onChange={handleNewSupplierChange} + placeholder='John Doe' + /> + </div> + </div> + + <div className='flex flex-row justify-start items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'>Telepon</label> + </div> + <div className='w-3/5 opacity-70'> + <input + name='telepon' + value={newSupplier.telepon} + type='text' + onChange={handleNewSupplierChange} + placeholder='Format: 08123456789' + className='form-input border px-4 py-2' + /> + </div> + </div> + + <div className='flex flex-row justify-start items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Durasi Tempo + </label> + </div> + <div className='w-3/5 opacity-70'> + <input + name='durasiTempo' + value={formatHari(newSupplier.durasiTempo)} + type='text' + onChange={handleNewSupplierChange} + className='form-input border px-4 py-2' + placeholder='Isi durasi tempo supplier anda (hari)' + /> + </div> + </div> + + <div className='flex flex-row justify-start items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Kredit Limit + </label> + </div> + <div className='w-3/5 opacity-70'> + <input + name='creditLimit' + value={formatRupiah(newSupplier.creditLimit)} + type='text' + className='form-input border px-4 py-2' + onChange={handleNewSupplierChange} + placeholder='Rp 999.999.999' + /> + </div> + </div> + </div> + </form> + </div> + <div className='flex flex-col justify-start items-start gap-4 mt-8'> + {!hasSavedata && ( + <> + <span className='text-xs opacity-60 text-red-500'> + *Klik simpan sebelum lanjut ke tahap selanjutnya + </span> + <button + onClick={simpanData} + className={`bg-red-500 border border-red-500 rounded-md w-full text-sm text-white p-2 h-11 mb-1 content-center flex flex-row justify-center items-center`} + > + Simpan Data + </button> + </> + )} + </div> + </div> + )} + </> + ); +}; + +const validationSchema = Yup.object().shape({ + supplier: Yup.string().required('Harus di-isi'), + pic: Yup.string().required('Harus di-isi'), + telepon: Yup.string().required('Harus di-isi'), + durasiTempo: Yup.string().required('Harus di-isi'), + creditLimit: Yup.string().required('Harus di-isi'), +}); + +const defaultValues = { + supplier: '', + pic: '', + telepon: '', + durasiTempo: '', + creditLimit: '', +}; +export default Referensi; diff --git a/src/lib/pengajuan-tempo/component/Stepper.jsx b/src/lib/pengajuan-tempo/component/Stepper.jsx new file mode 100644 index 00000000..c5efa5bc --- /dev/null +++ b/src/lib/pengajuan-tempo/component/Stepper.jsx @@ -0,0 +1,65 @@ +import React from 'react'; +import useDevice from '@/core/hooks/useDevice'; +const Stepper = ({ currentStep, numberOfSteps }) => { + const { isDesktop, isMobile } = useDevice(); + const stepLabels = [ + 'Informasi Perusahaan', + 'Kontak Person', + 'Pengiriman', + 'Referensi', + 'Dokumen', + 'Konfirmasi', + ]; + const activeColor = (index) => + currentStep >= index ? 'bg-red-500' : 'bg-gray-300'; + const activeColorBullet = (index) => + currentStep >= index ? 'bg-red-500 ' : 'bg-white border-gray-300 border'; + const isFinalStep = (index) => index === numberOfSteps - 1; + const isFirstStep = (index) => index === 0; + return ( + <div className='flex items-center'> + {Array.from({ length: numberOfSteps }).map((_, index) => ( + <React.Fragment key={index}> + {isFirstStep(index) ? null : ( + <div + className={`${isMobile ? 'w-12' : 'w-48'} h-[1px] ${activeColor( + index + )}`} + ></div> + )} + <div + className={`w-6 h-6 ${ + currentStep == index + ? 'border-red-500 border' + : `${activeColorBullet(index)} ` + } rounded-full flex justify-center items-center text-nowrap`} + > + <div className='relative text-xs'> + <div + className={`absolute z-10 ${ + isMobile + ? `w-12 h-full top-4 ${ + isFinalStep(index) ? '-left-16' : '-left-4' + }` + : 'w-48 h-full -top-14 -left-24' + } `} + > + <div + className={`relative w-full max-w-md p-2 text-center ${ + currentStep == index + ? 'text-red-500' + : `${isMobile ? 'hidden' : ''}` + } text-nowrap`} + > + {stepLabels[index]} + </div> + </div> + </div> + </div> + </React.Fragment> + ))} + </div> + ); +}; + +export default Stepper; diff --git a/src/lib/pengajuan-tempo/component/informasiPerusahaan.jsx b/src/lib/pengajuan-tempo/component/informasiPerusahaan.jsx new file mode 100644 index 00000000..25a3a7ee --- /dev/null +++ b/src/lib/pengajuan-tempo/component/informasiPerusahaan.jsx @@ -0,0 +1,1630 @@ +import React, { useState, useEffect, useMemo, useRef } from 'react'; +import { Controller, set, useForm } from 'react-hook-form'; +import HookFormSelect from '@/core/components/elements/Select/HookFormSelect'; +import odooApi from '~/libs/odooApi'; +import stateApi from '@/lib/address/api/stateApi.js'; +import cityApi from '@/lib/address/api/cityApi'; +import districtApi from '@/lib/address/api/districtApi'; +import subDistrictApi from '@/lib/address/api/subDistrictApi'; +import { Radio, RadioGroup, Stack, Checkbox } from '@chakra-ui/react'; +import { usePengajuanTempoStore } from '../../../../src-migrate/modules/register/stores/usePengajuanTempoStore'; +import useDevice from '@/core/hooks/useDevice'; +import Divider from '@/core/components/elements/Divider/Divider'; +import { getAuth } from '~/libs/auth'; +import addressApi from '@/lib/address/api/addressApi'; +import { toast } from 'react-hot-toast'; +import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; +import { useRouter } from 'next/router'; +const InformasiPerusahaan = ({ + chekValid, + buttonSubmitClick, + isKonfirmasi, +}) => { + const auth = getAuth(); + const router = useRouter(); + const { isDesktop, isMobile } = useDevice(); + const { control, watch, setValue, getValues } = useForm(); + const { form, errors, validate, updateForm } = usePengajuanTempoStore(); + const [states, setState] = useState([]); + const [cities, setCities] = useState([]); + const [districts, setDistricts] = useState([]); + const [subDistricts, setSubDistricts] = useState([]); + const [zips, setZips] = useState([]); + const [industries, setIndustries] = useState([]); + const [paymentTerm, setPaymentTerm] = useState([]); + const [selectedCategory, setSelectedCategory] = useState(''); + const [bersedia, setBersedia] = useState(null); + const [changeConfirmation, setChangeConfirmation] = useState(false); + const category_produk = [ + { id: 2040, name: 'Pengaman, Kesehatan & Keamanan' }, + { id: 2097, name: 'Perkakas Tangan, Listrik & Pneumatic' }, + { id: 2161, name: 'Mesin Industrial' }, + { id: 2222, name: 'Mesin Pertanian & Perkebunan' }, + { id: 2246, name: 'Mesin Pembersih & Janitorial' }, + { id: 2273, name: 'Cairan Berbahan Kimia' }, + { id: 2315, name: 'Perlengkapan Pengukuran & Pengujian' }, + { id: 2354, name: 'Peralatan Listrik & Elektronik' }, + { id: 2394, name: 'Perlengkapan Logistik & Gudang' }, + { id: 2420, name: 'Peralatan Kantor & Stationery' }, + { id: 2445, name: 'Komponen & Aksesoris' }, + { id: 2477, name: 'Peralatan Horeca & Food Service' }, + ]; + const radioOptions = [ + { label: '5.000.000', value: '5000000' }, + { label: '10.000.000', value: '10000000' }, + { label: '15.000.000', value: '15000000' }, + { label: '20.000.000', value: '20000000' }, + { label: '25.000.000', value: '25000000' }, + { label: '30.000.000', value: '30000000' }, + { label: '35.000.000', value: '35000000' }, + ]; + + useEffect(() => { + const loadState = async () => { + let dataState = await stateApi({ tempo: true }); + dataState = dataState.map((state) => ({ + value: state.id, + label: state.name, + })); + setState(dataState); + }; + loadState(); + }, []); + + const watchState = watch('state'); + useEffect(() => { + if (watchState) { + updateForm('state', `${watchState}`); + validate(); + const loadCities = async () => { + let dataCities = await cityApi({ stateId: watchState }); + dataCities = dataCities.map((city) => ({ + value: city.id, + label: city.name, + })); + setCities(dataCities); + }; + loadCities(); + } + }, [watchState]); + + const watchCity = watch('city'); + + // Untuk memperbarui form.city + useEffect(() => { + if (watchCity && form.city !== `${watchCity}`) { + updateForm('city', `${watchCity}`); + validate(); + } + }, [watchCity]); + + // Untuk memuat distrik + useEffect(() => { + if (watchCity) { + const loadDistricts = async () => { + let dataDistricts = await districtApi({ cityId: watchCity }); + dataDistricts = dataDistricts.map((district) => ({ + value: district.id, + label: district.name, + })); + setDistricts(dataDistricts); + }; + loadDistricts(); + } + }, [watchCity]); + + const watchDistrict = watch('district'); + useEffect(() => { + setValue('subDistrict', ''); + if (watchDistrict) { + updateForm('district', `${watchDistrict}`); + validate(); + const loadSubDistricts = async () => { + let dataSubDistricts = await subDistrictApi({ + districtId: watchDistrict, + }); + dataSubDistricts = dataSubDistricts.map((district) => ({ + value: district.id, + label: district.name, + })); + setSubDistricts(dataSubDistricts); + }; + loadSubDistricts(); + } + }, [watchDistrict, setValue]); + + const watchsubDistrict = watch('subDistrict'); + + useEffect(() => { + let kelurahan = ''; + let kecamatan = ''; + + if (watchDistrict) { + for (const data in districts) { + if (districts[data].value == watchDistrict) { + kecamatan = districts[data].label.toLowerCase(); + } + } + } + + if (watchsubDistrict) { + updateForm('subDistrict', `${watchsubDistrict}`); + validate(); + for (const data in subDistricts) { + if (subDistricts[data].value == watchsubDistrict) { + kelurahan = subDistricts[data].label.toLowerCase(); + } + } + const loadZip = async () => { + const response = await fetch( + `https://alamat.thecloudalert.com/api/cari/index/?keyword=${kelurahan}` + ); + + let dataMasuk = []; // Array untuk menyimpan kode pos yang sudah diproses + + const result = await response.json(); + + // Filter dan map data + const dataZips = result.result + .filter((zip) => zip.kecamatan.toLowerCase() === kecamatan) // Filter berdasarkan kecamatan + .filter((zip) => { + // Pastikan zip.kodepos belum ada di dataMasuk + if (dataMasuk.includes(zip.kodepos)) { + return false; // Jika sudah ada, maka skip (tidak akan ditambahkan) + } else { + dataMasuk.push(zip.kodepos); // Tambahkan ke dataMasuk + return true; // Tambahkan zip ke hasil + } + }) + .map((zip) => ({ + value: parseInt(zip.kodepos), + label: zip.kodepos, + })); + + setZips(dataZips); // Set hasil ke state + }; + + loadZip(); + } + }, [watchsubDistrict, setValue, subDistricts]); + + const watchZip = watch('zip'); + useEffect(() => { + if (watchZip) { + updateForm('zip', `${watchZip}`); + validate(); + } + }, [watchZip]); + + useEffect(() => { + const loadIndustries = async () => { + const dataIndustries = await odooApi('GET', '/api/v1/partner/industry'); + setIndustries( + dataIndustries?.map((o) => ({ + value: o.id, + label: o.name, + category: o.category, + })) + ); + }; + loadIndustries(); + }, []); + useEffect(() => { + const selectedIndustryType = industries.find( + (industry) => industry.value === watch('industryId') + ); + if (selectedIndustryType) { + updateForm('industryId', `${selectedIndustryType?.value}`); + validate(); + setSelectedCategory(selectedIndustryType.category); + } + }, [watch('industryId'), industries]); + + useEffect(() => { + const loadPaymentTerm = async () => { + const dataPaymentTerm = [ + { id: 29, name: 'Tempo 7 Hari' }, + { id: 24, name: 'Tempo 14 Hari' }, + { id: 32, name: 'Tempo 21 Hari' }, + { id: 25, name: 'Tempo 30 Hari' }, + ]; + setPaymentTerm( + dataPaymentTerm?.map((o) => ({ + value: o.id, + label: o.name, + })) + ); + }; + loadPaymentTerm(); + }, []); + useEffect(() => { + const selectedPaymentTerm = paymentTerm.find( + (industry) => industry.value === watch('tempoDuration') + ); + if (selectedPaymentTerm) { + updateForm('tempoDuration', `${selectedPaymentTerm?.value}`); + validate(); + } + }, [watch('tempoDuration'), paymentTerm]); + + const estimasiValue = watch('estimasi'); + const tempoLimitValue = watch('tempoLimit'); + + // Memformat angka menjadi format rupiah + const formatRupiah = (value) => { + if (!value) return ''; + const numberString = value.replace(/[^0-9]/g, ''); // Menghapus karakter non-digit + return numberString + ? 'Rp ' + new Intl.NumberFormat('id-ID').format(numberString) + : ''; + }; + + const handleChange = (e) => { + const value = e.target.value; + const formattedValue = formatRupiah(value); + updateForm('estimasi', formattedValue.replace(/^Rp\s*/, '')); + validate(); + }; + + const [isCustom, setIsCustom] = React.useState(false); + const [tempoLimitValueEx, setTempoLimitValueEx] = React.useState(''); + const handleCheckboxBersediaChange = (value) => { + // if (value === 'bersedia') { + // setBersedia(true); + // } else if (value === 'tidakBersedia') { + // setBersedia(false); + // } + // updateForm('bersedia', `${value === 'bersedia'}`); + updateForm('bersedia', `${value}`); + validate(); + }; + const handleCheckboxPortalChange = (value) => { + // if (value === 'bersedia') { + // setBersedia(true); + // } else if (value === 'tidakBersedia') { + // setBersedia(false); + // } + // updateForm('bersedia', `${value === 'bersedia'}`); + updateForm('portal', `${value}`); + validate(); + }; + const [selectedIds, setSelectedIds] = useState( + form.categoryProduk ? form.categoryProduk.split(',').map(Number) : [] // Parse string menjadi array angka + ); + + const handleCheckboxChange = (id) => { + const updatedSelected = selectedIds.includes(id) + ? selectedIds.filter((selectedId) => selectedId !== id) + : [...selectedIds, id]; + + setSelectedIds(updatedSelected); + + // Mengubah array kembali menjadi string yang dipisahkan oleh koma + updateForm('categoryProduk', updatedSelected.join(',')); + validate(); + }; + + useEffect(() => { + if (form.categoryProduk) { + setSelectedIds(form.categoryProduk.split(',').map(Number)); // Parse string menjadi array angka + } + }, [form.categoryProduk]); + const isChecked = (id) => selectedIds.includes(id); + + const handleInputChange = (event) => { + const { name, value } = event.target; + updateForm(name, value); + validate(); + }; + + const midIndex = Math.ceil(category_produk.length / 2); + const firstColumn = category_produk.slice(0, midIndex); + const secondColumn = category_produk.slice(midIndex); + const isFormValid = useMemo(() => Object.keys(errors).length === 0, [errors]); + + const nameRef = useRef(null); + const industry_idRef = useRef(null); + const streetRef = useRef(null); + const stateRef = useRef(null); + const cityRef = useRef(null); + const districtRef = useRef(null); + const subDistrictRef = useRef(null); + const zipRef = useRef(null); + const mobileRef = useRef(null); + const bankNameRef = useRef(null); + const accountNameRef = useRef(null); + const accountNumberRef = useRef(null); + const estimasiRef = useRef(null); + const tempoDurationRef = useRef(null); + const bersediaRef = useRef(null); + const portalRef = useRef(null); + const categoryProdukRef = useRef(null); + const tempoLimitRef = useRef(null); + + useEffect(() => { + const options = { + behavior: 'smooth', + block: 'center', + }; + const loadIndustries = async () => { + if (!isFormValid) { + if (errors.name && nameRef.current) { + nameRef.current.scrollIntoView(options); + return; + } + if (errors.industryId && industry_idRef.current) { + industry_idRef.current.scrollIntoView(options); + return; + } + if (errors.street && streetRef.current) { + streetRef.current.scrollIntoView(options); + return; + } + if (errors.state && stateRef.current) { + stateRef.current.scrollIntoView(options); + return; + } + if (errors.city && cityRef.current) { + cityRef.current.scrollIntoView(options); + return; + } + if (errors.district && districtRef.current) { + districtRef.current.scrollIntoView(options); + return; + } + if (errors.subDistrict && subDistrictRef.current) { + subDistrictRef.current.scrollIntoView(options); + return; + } + if (errors.zip && zipRef.current) { + zipRef.current.scrollIntoView(options); + return; + } + if (errors.mobile && mobileRef.current) { + mobileRef.current.scrollIntoView(options); + return; + } + if (errors.bankName && bankNameRef.current) { + bankNameRef.current.scrollIntoView(options); + return; + } + if (errors.accountName && accountNameRef.current) { + accountNameRef.current.scrollIntoView(options); + return; + } + if (errors.accountNumber && accountNumberRef.current) { + accountNumberRef.current.scrollIntoView(options); + return; + } + if (errors.estimasi && estimasiRef.current) { + estimasiRef.current.scrollIntoView(options); + return; + } + if (errors.tempoDuration && tempoDurationRef.current) { + tempoDurationRef.current.scrollIntoView(options); + return; + } + if (errors.bersedia && bersediaRef.current) { + bersediaRef.current.scrollIntoView(options); + return; + } + if (errors.portal && portalRef.current) { + portalRef.current.scrollIntoView(options); + return; + } + if (errors.categoryProduk && categoryProdukRef.current) { + categoryProdukRef.current.scrollIntoView(options); + return; + } + if (errors.tempoLimit && tempoLimitRef.current) { + tempoLimitRef.current.scrollIntoView(options); + return; + } + } + }; + loadIndustries(); + }, [buttonSubmitClick, chekValid]); + useEffect(() => { + if (form.industryId) { + setValue('industryId', parseInt(form.industryId)); + } + if (form.state) { + setValue('state', parseInt(form.state)); + } + + if (form.district) { + setValue('district', parseInt(form.district)); + } + if (form.subDistrict) { + setValue('subDistrict', parseInt(form.subDistrict)); + } + if (form.zip) { + setValue('zip', parseInt(form.zip)); + } + if (form.tempoDuration) { + setValue('tempoDuration', parseInt(form.tempoDuration)); + } + if (form.tempoLimit) { + setValue('tempoLimit', form.tempoLimit); + } + if (form.tempoLimit) { + const isValueInOptions = radioOptions.some( + (option) => option.value === form.tempoLimit + ); + + if (isValueInOptions) { + setValue('tempoLimit', form.tempoLimit); // Set value dari radio options + setIsCustom(false); // Pastikan custom tidak aktif + } else { + setValue('tempoLimit', 'custom'); // Set value ke custom jika tidak termasuk dalam options + setIsCustom(true); // Aktifkan custom input + setTempoLimitValueEx(form.tempoLimit); // Set nilai input custom ke form.tempoLimit + } + } + }, [form]); + useEffect(() => { + if (form.city) { + setValue('city', parseInt(form.city)); + } + }, [form.city]); + useEffect(() => { + const loadProfile = async () => { + try { + const dataProfile = await addressApi({ id: auth.parentId }); + if (dataProfile.name) { + updateForm('name', dataProfile.name); + } + if (dataProfile.street) { + updateForm('street', dataProfile.street); + } + if (dataProfile.stateId.id) { + updateForm('state', `${dataProfile.stateId.id}`); + } + if (dataProfile.city.id) { + updateForm('city', `${dataProfile.city.id}`); + } + if (dataProfile.district.id) { + updateForm('district', `${dataProfile.district.id}`); + } + if (dataProfile.subDistrict.id) { + updateForm('subDistrict', `${dataProfile.subDistrict.id}`); + } + if (dataProfile.zip) { + updateForm('zip', dataProfile.zip); + } + if (dataProfile.mobile) { + updateForm('mobile', dataProfile.mobile.replace(/\D/g, '')); + } else { + setChangeConfirmation(true); + } + if (!dataProfile.email) { + setChangeConfirmation(true); + } + } catch (error) { + console.error('Error loading profile:', error); + } finally { + validate(); + } + }; + + if (auth?.parentId) { + loadProfile(); + } + }, [auth?.parentId]); + useEffect(() => { + const loadProfile = async () => { + try { + const dataProfile = await addressApi({ id: auth.parentId }); + setValue('industryId', parseInt(dataProfile.industryId)); + setValue('state', parseInt(dataProfile.stateId.id)); + setValue('city', parseInt(dataProfile.city.id)); + setValue('district', parseInt(dataProfile.district.id)); + setValue('subDistrict', parseInt(dataProfile.subDistrict.id)); + } catch (error) { + console.error('Error loading profile:', error); + } + }; + + if (auth?.parentId) { + loadProfile(); + } + }, [auth?.parentId, setValue]); + + const handleLengkapiData = () => { + router.push('/my/profile'); + }; + return ( + <> + <BottomPopup + active={changeConfirmation} + close={() => { + toast.error( + 'Mohon lengkapi data perusahaan sebelum melakukan pengajuan tempo' + ); + }} + title='Data Perusahaan Anda Belum Lengkap' + > + <div className='leading-7 text-gray_r-12/80'> + Mohon lengkapi data perusahaan sebelum melakukan pengajuan tempo + </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={handleLengkapiData} + > + Lengkapi Data + </button> + <button + className='btn-light flex-1 md:flex-none' + type='button' + onClick={() => { + toast.error( + 'Mohon lengkapi data perusahaan sebelum melakukan pengajuan tempo' + ); + }} + > + Batal + </button> + </div> + </BottomPopup> + {isDesktop && ( + <div className=''> + <h1 className={`font-bold ${isKonfirmasi ? 'text-xl' : ''}`}> + Informasi Perusahaan + </h1> + <form className='flex flex-col w-full '> + <div className='w-full grid grid-row-2 gap-5'> + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-nowrap'> + Nama Perusahaan + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + isi detail perusahaan sesuai dengan nama yang terdaftar{' '} + </span> + )} + </div> + <div className='w-3/5'> + <input + id='name' + name='name' + placeholder='Masukkan nama perusahaan' + type='text' + className='form-input' + disabled={true} + aria-invalid={errors.name} + value={form.name} + ref={nameRef} + onChange={handleInputChange} + /> + {/* {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + Format: PT. INDOTEKNIK DOTCOM GEMILANG + </span> + )} */} + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.name} + </div> + )} + </div> + </div> + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5' ref={industry_idRef}> + <label className='form-label text-nowrap'>Industri</label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + Pilih jenis industri perusahaan anda + </span> + )} + </div> + <div className='w-3/5'> + <Controller + name='industryId' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={industries} + placeholder={'Pilih industri perusahaan anda'} + /> + )} + /> + {!isKonfirmasi && selectedCategory && ( + <span className='text-gray_r-11 text-xs opacity-60'> + Kategori : {selectedCategory} + </span> + )} + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.industryId} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5 text-nowrap'> + <label className='form-label '>Alamat Perusahaan</label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + alamat sesuai dengan alamat kantor pusat + </span> + )} + </div> + <div className='w-3/5 flex gap-3 flex-col'> + <div> + <input + id='street' + name='street' + ref={streetRef} + placeholder='Masukkan alamat lengkap perusahaan' + type='text' + value={form.street} + className='form-input' + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.street} + </div> + )} + </div> + <div + className={` sub-alamat flex ${ + isKonfirmasi ? 'flex-col' : 'flex-row' + } w-full gap-3`} + > + <div + className={`flex ${ + isKonfirmasi + ? ' flex-row gap-3 w-full' + : 'flex-row gap-3 w-2/5' + }`} + > + <div + className={`${isKonfirmasi ? 'w-full' : 'w-full'}`} + ref={stateRef} + > + <Controller + name='state' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={states} + placeholder='Provinsi' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.state} + </div> + )} + </div> + <div + className={`${isKonfirmasi ? 'w-full' : 'w-full'}`} + ref={cityRef} + > + <Controller + name='city' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={cities} + disabled={!watchState} + placeholder='Kota' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.city} + </div> + )} + </div> + </div> + <div + className={`flex-row flex gap-2 justify-between ${ + isKonfirmasi ? 'w-full' : 'w-3/5' + }`} + > + <div className='w-full' ref={districtRef}> + <Controller + name='district' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={districts} + disabled={!watchState || !watchCity} + placeholder='Kecamatan' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.district} + </div> + )} + </div> + <div className='w-full' ref={subDistrictRef}> + <Controller + name='subDistrict' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={subDistricts} + disabled={!watchDistrict} + placeholder='Kelurahan' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.district} + </div> + )} + </div> + <div className='w-full' ref={zipRef}> + <Controller + name='zip' + control={control} + render={(props) => ( + <> + {/* Jika zips tidak kosong, tampilkan dropdown */} + {zips.length > 0 ? ( + <HookFormSelect + {...props} + options={zips} + disabled={!watchsubDistrict} + placeholder='Zip' + /> + ) : ( + // Jika zips kosong, tampilkan input manual + <input + id='zip' + name='zip' + ref={zipRef} + placeholder='Kode Pos' + type='number' + disabled={!watchsubDistrict} + value={form.zip} + className='form-input' + onChange={handleInputChange} + /> + )} + </> + )} + /> + + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.zip} + </div> + )} + </div> + </div> + {/* <div className='w-1/3'> + <input + id='zip' + name='zip' + ref={zipRef} + placeholder='Kode Pos' + type='number' + disabled={!watchsubDistrict} + value={form.zip} + className='form-input' + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.zip} + </div> + )} + </div> */} + </div> + </div> + </div> + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5 text-nowrap'> + <label className='form-label '>No. HP Perusahaan</label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + isi no telfon perusahaan yang sesuai + </span> + )} + </div> + <div className='w-3/5'> + <input + id='mobile' + name='mobile' + ref={mobileRef} + placeholder='Masukkan nomor telfon perusahaan' + type='tel' + value={form.mobile} + className='form-input' + aria-invalid={errors.mobile} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.mobile} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className=' w-2/5 text-nowrap'> + <label className='form-label'>Data Bank</label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + Isi detail data bank perusahaan anda + </span> + )} + </div> + <div className='w-3/5 flex gap-3 flex-row'> + <div> + <input + id='bankName' + name='bankName' + ref={bankNameRef} + placeholder='Nama bank' + type='text' + value={form.bankName} + className='form-input' + onChange={handleInputChange} + /> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + Format: BCA, Mandiri, CIMB, BNI dll + </span> + )} + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.bankName} + </div> + )} + </div> + <div> + <input + id='accountName' + name='accountName' + ref={accountNameRef} + placeholder='Nama Rekening' + type='text' + value={form.accountName} + className='form-input' + onChange={handleInputChange} + /> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + Format: John Doe + </span> + )} + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.accountName} + </div> + )} + </div> + <div> + <input + id='accountNumber' + name='accountNumber' + ref={accountNumberRef} + placeholder='Nomor Rekening Bank' + type='text' + value={form.accountNumber} + className='form-input' + onChange={handleInputChange} + /> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + Format: 01234567896 + </span> + )} + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.accountNumber} + </div> + )} + </div> + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5 text-nowrap'> + <label className='form-label '> + Website <span className=' opacity-60'>(Opsional)</span> + </label> + </div> + <div className='w-3/5'> + <input + id='website' + name='website' + placeholder='www.indoteknik.com' + type='text' + value={form.website} + className='form-input' + onChange={handleInputChange} + /> + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5 text-nowrap'> + <label className='form-label text-wrap'> + Estimasi Pembelian pertahun{' '} + <span className=' opacity-60 '>(Opsional)</span> + </label> + </div> + <div className='w-3/5'> + <div className='relative'> + <input + id='estimasi' + name='estimasi' + ref={estimasiRef} + // {...register('estimasi', { + // setValueAs: (value) => value.replace(/^Rp\s*/, ''), // Menyimpan hanya angka + // })} + placeholder='Isi estimasi pembelian produk pertahun' + type='text' + className='form-input' // padding untuk memberi ruang untuk "RP" + value={formatRupiah(form.estimasi)} + onChange={handleChange} // Mengatur perubahan input + /> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.estimasi} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-center'> + <div className='w-2/5'> + <label className='form-label text-nowrap'>Durasi Tempo</label> + <span className='text-xs opacity-60'> + Pilih durasi tempo yang anda inginkan + </span> + </div> + <div className='w-3/5 flex flex-col '> + <div className='flex flex-row items-center gap-3'> + <div + className={`${isKonfirmasi ? 'w-[75%]' : 'w-[25%]'}`} + ref={tempoDurationRef} + > + {/* <RadioGroup + onChange={onChangeTempoDuration} + value={form.tempoDuration} + > + <Stack direction='column' className=''> + <Radio colorScheme='red' value='7'> + 7 Hari + </Radio> + <Radio colorScheme='red' value='14' className=''> + 14 Hari + </Radio> + <Radio colorScheme='red' value='30' className=''> + 30 Hari + </Radio> + </Stack> + </RadioGroup> */} + <Controller + name='tempoDuration' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={paymentTerm} + placeholder={'Pilih Durasi Tempo'} + /> + )} + /> + + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.tempoDuration} + </div> + )} + </div> + </div> + <div className='text-red-500 text-xs mt-2'> + **Durasi tempo dapat berbeda sesuai dengan verifikasi oleh + tim Indoteknik.com + </div> + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-wrap '> + Apakah terdapat vendor portal pada perusahaan anda? + </label> + </div> + <div className='w-3/5 flex flex-col justify-start'> + <div className='flex gap-x-4' ref={portalRef}> + <RadioGroup + onChange={handleCheckboxPortalChange} + value={form.portal} + > + <Stack direction='row'> + <Radio colorScheme='red' value='ada'> + Ya, ada + </Radio> + <Radio colorScheme='red' value='tidak'> + Tidak ada + </Radio> + </Stack> + </RadioGroup> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.portal} + </div> + )} + </div> + </div> + <div className='flex flex-row justify-between items-start'> + <div className='w-2/5'> + <label className='form-label text-wrap '> + Apakah bersedia transaksi via website? + </label> + </div> + <div className='w-3/5 flex flex-col justify-start'> + <div className='flex gap-x-4' ref={bersediaRef}> + <RadioGroup + onChange={handleCheckboxBersediaChange} + value={form.bersedia} + > + <Stack direction='row'> + <Radio colorScheme='red' value='bersedia'> + Saya bersedia + </Radio> + <Radio colorScheme='red' value='tidakBersedia'> + Tidak bersedia + </Radio> + </Stack> + </RadioGroup> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.bersedia} + </div> + )} + </div> + </div> + + <div + className={`flex flex-row justify-between ${ + isKonfirmasi ? 'items-center' : 'items-start' + }`} + > + <div className='w-2/5 text-nowrap'> + <label className='form-label '> + Kategori Produk yang Digunakan + </label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + Pilih produk bisa lebih dari 1 + </span> + )} + </div> + <div className='w-3/5 flex flex-col'> + <div className='flex flex-row justify-between'> + <div + className='flex flex-col gap-2' + ref={categoryProdukRef} + > + {firstColumn.map((item) => ( + <Checkbox + colorScheme='red' + key={item.id} + onChange={() => handleCheckboxChange(item.id)} + isChecked={isChecked(item.id)} + > + {item.name} + </Checkbox> + ))} + </div> + <div className='flex flex-col gap-2'> + {secondColumn.map((item) => ( + <Checkbox + colorScheme='red' + key={item.id} + isChecked={isChecked(item.id)} + onChange={() => handleCheckboxChange(item.id)} + > + {item.name} + </Checkbox> + ))} + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.categoryProduk} + </div> + )} + </div> + </div> + </div> + </form> + </div> + )} + {isMobile && ( + <div className='text-sm'> + <h1 + className={`font-bold py-4 mt-8 ${ + isKonfirmasi ? 'hidden' : 'text-xl' + }`} + > + Informasi Perusahaan + </h1> + <form className='flex flex-col w-full '> + <div className='w-full grid grid-row-2 gap-4'> + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-nowrap'> + Nama Perusahaan + </label> + <input + id='name' + name='name' + placeholder='Format: PT. INDOTEKNIK DOTCOM GEMILANG' + type='text' + className='form-input' + aria-invalid={errors.name} + value={form.name} + ref={nameRef} + onChange={handleInputChange} + /> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + isi detail perusahaan sesuai dengan nama yang terdaftar{' '} + </span> + )} + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.name} + </div> + )} + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label text-nowrap'>Industri</label> + <div className='w-full' ref={industry_idRef}> + <Controller + name='industryId' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={industries} + placeholder={ + 'Pilih Industri yang sesuai dengan perusahaan' + } + /> + )} + /> + </div> + {!isKonfirmasi && selectedCategory && ( + <span className='text-gray_r-11 text-xs opacity-60'> + Kategori : {selectedCategory} + </span> + )} + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.industryId} + </div> + )} + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label '>Alamat Perusahaan</label> + <input + id='street' + name='street' + ref={streetRef} + placeholder='Masukkan alamat lengkap perusahaan' + type='text' + value={form.street} + className='form-input' + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.street} + </div> + )} + <div className='w-full text-warp gap-3 flex flex-col'> + <div className='sub-alamat flex flex-row w-full gap-3 '> + <div className='w-full' ref={stateRef}> + <Controller + name='state' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={states} + placeholder='Provinsi' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.state} + </div> + )} + </div> + <div className='w-full' ref={cityRef}> + <Controller + name='city' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={cities} + disabled={!watchState} + placeholder='Kota' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.city} + </div> + )} + </div> + </div> + <div className='flex flex-row gap-3'> + <div className='w-full' ref={districtRef}> + <Controller + name='district' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={districts} + disabled={!watchCity} + placeholder='Kecamatan' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.district} + </div> + )} + </div> + <div className='w-full' ref={subDistrictRef}> + <Controller + name='subDistrict' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={subDistricts} + disabled={!watchDistrict} + placeholder='Kelurahan' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.district} + </div> + )} + </div> + <div className='w-full'> + <Controller + name='zip' + control={control} + render={(props) => ( + <> + {/* Jika zips tidak kosong, tampilkan dropdown */} + {zips.length > 0 ? ( + <HookFormSelect + {...props} + options={zips} + disabled={!watchsubDistrict} + placeholder='Zip' + /> + ) : ( + // Jika zips kosong, tampilkan input manual + <input + id='zip' + name='zip' + ref={zipRef} + placeholder='Kode Pos' + type='number' + disabled={!watchsubDistrict} + value={form.zip} + className='form-input' + onChange={handleInputChange} + /> + )} + </> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.zip} + </div> + )} + </div> + </div> + </div> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + Isi detail alamat sesuai dengan yang terdaftar + </span> + )} + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label '>No. HP Perusahaan</label> + <input + id='mobile' + name='mobile' + ref={mobileRef} + placeholder='Format: 08123456789 / (021) 123 4567' + type='tel' + value={form.mobile} + className='form-input' + aria-invalid={errors.mobile} + onChange={handleInputChange} + /> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + Isi detail perusahaan sesuai dengan nama yang terdaftar + </span> + )} + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.mobile} + </div> + )} + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label'>Data Bank</label> + {!isKonfirmasi && ( + <span className='text-xs opacity-60'> + Isi data bank perusahaan sesuai dengan yang terdaftar + </span> + )} + <div className='flex gap-3 flex-row'> + <div> + <input + id='bankName' + name='bankName' + ref={bankNameRef} + placeholder='Nama bank' + type='text' + value={form.bankName} + className='form-input' + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.bankName} + </div> + )} + </div> + <div> + <input + id='accountName' + name='accountName' + ref={accountNameRef} + placeholder='Nama Rekening' + type='text' + value={form.accountName} + className='form-input' + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.accountName} + </div> + )} + </div> + <div> + <input + id='accountNumber' + name='accountNumber' + ref={accountNumberRef} + placeholder='Nomor Rekening' + type='text' + value={form.accountNumber} + className='form-input' + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.accountNumber} + </div> + )} + </div> + </div> + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label '> + Website <span className=' opacity-60'>(Opsional)</span> + </label> + <input + id='website' + name='website' + placeholder='Format: www.indoteknik.com' + type='text' + value={form.website} + className='form-input' + onChange={handleInputChange} + /> + </div> + + <div className='flex flex-col gap-2 justify-between items-start'> + <label className='form-label '> + Estimasi Pembelian pertahun{' '} + <span className=' opacity-60'>(Opsional)</span> + </label> + <input + id='estimasi' + name='estimasi' + ref={estimasiRef} + // {...register('estimasi', { + // setValueAs: (value) => value.replace(/^Rp\s*/, ''), // Menyimpan hanya angka + // })} + placeholder='Isi estimasi pembelian produk pertahun' + type='text' + className='form-input' // padding untuk memberi ruang untuk "RP" + value={formatRupiah(form.estimasi)} + onChange={handleChange} // Mengatur perubahan input + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.estimasi} + </div> + )} + </div> + <div className='h-[2px] bg-gray-300 w-[120%] inset-0 relative transform -translate-x-5'></div> + <div className='flex flex-col gap-2 justify-start items-start'> + <label className='form-label text-nowrap'>Durasi Tempo</label> + <div className='w-full' ref={tempoDurationRef}> + <Controller + name='tempoDuration' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={paymentTerm} + placeholder={'Pilih Durasi Tempo'} + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.tempoDuration} + </div> + )} + </div> + </div> + + <div className='text-red-500 text-xs'> + **Durasi tempo dapat berbeda dengan verifikasi oleh tim + indoteknik.com + </div> + <div className='h-[2px] bg-gray-300 w-[120%] inset-0 relative transform -translate-x-5'></div> + + <div className='flex flex-col gap justify-between items-start'> + <label className='form-label text-wrap '> + Apakah terdapat vendor portal pada perusahaan anda? + </label> + <div className='flex gap-x-4' ref={bersediaRef}> + <RadioGroup + size='sm' + onChange={handleCheckboxPortalChange} + value={form.portal} + > + <Stack direction='col'> + <Radio colorScheme='red' value='ada'> + Ya, ada + </Radio> + <Radio colorScheme='red' value='tidak'> + Tidak ada + </Radio> + </Stack> + </RadioGroup> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.portal} + </div> + )} + </div> + <div className='flex flex-col gap justify-between items-start'> + <label className='form-label text-wrap '> + Apakah bersedia transaksi via website? + </label> + <div className='flex gap-x-4' ref={bersediaRef}> + <RadioGroup + size='sm' + onChange={handleCheckboxBersediaChange} + value={form.bersedia} + > + <Stack direction='col'> + <Radio colorScheme='red' value='bersedia'> + Saya bersedia + </Radio> + <Radio colorScheme='red' value='tidakBersedia'> + Tidak bersedia + </Radio> + </Stack> + </RadioGroup> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.bersedia} + </div> + )} + </div> + <div className='h-[2px] bg-gray-300 w-[120%] inset-0 relative transform -translate-x-5'></div> + + <div + className={`flex flex-col gap-2 justify-between ${ + isKonfirmasi ? 'items-start' : 'items-start' + }`} + > + <label className='form-label '> + Kategori Produk yang Digunakan + </label> + <div className='flex flex-col justify-between gap-2 '> + <div className='flex flex-col gap-2' ref={categoryProdukRef}> + {firstColumn.map((item) => ( + <Checkbox + size='sm' + colorScheme='red' + key={item.id} + onChange={() => handleCheckboxChange(item.id)} + isChecked={isChecked(item.id)} + > + {item.name} + </Checkbox> + ))} + </div> + <div className='flex flex-col gap-2'> + {secondColumn.map((item) => ( + <Checkbox + size='sm' + colorScheme='red' + key={item.id} + isChecked={isChecked(item.id)} + onChange={() => handleCheckboxChange(item.id)} + > + {item.name} + </Checkbox> + ))} + </div> + </div> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errors.categoryProduk} + </div> + )} + </div> + </div> + </form> + </div> + )} + </> + ); +}; + +export default InformasiPerusahaan; diff --git a/src/lib/quotation/components/Quotation.jsx b/src/lib/quotation/components/Quotation.jsx index 5a2f63a5..2f4d6c46 100644 --- a/src/lib/quotation/components/Quotation.jsx +++ b/src/lib/quotation/components/Quotation.jsx @@ -37,6 +37,7 @@ const { checkoutApi } = require('@/lib/checkout/api/checkoutApi'); const { getProductsCheckout } = require('@/lib/checkout/api/checkoutApi'); const Quotation = () => { + const PPN = process.env.NEXT_PUBLIC_PPN ? parseFloat(process.env.NEXT_PUBLIC_PPN) : 0; const router = useRouter(); const auth = useAuth(); const query = router.query.source ?? null; @@ -307,7 +308,7 @@ const Quotation = () => { toast.error('Gagal melakukan transaksi, terjadi kesalahan internal'); }; - const taxTotal = (totalAmount - totalDiscountAmount) * 0.11; + const taxTotal = (totalAmount - totalDiscountAmount) * (PPN - 1); return ( <> @@ -425,7 +426,7 @@ const Quotation = () => { <div>{currencyFormat(cartCheckout?.subtotal)}</div> </div> <div className='flex gap-x-2 justify-between'> - <div className='text-gray_r-11'>PPN 11%</div> + <div className='text-gray_r-11'>PPN {((PPN - 1) * 100).toFixed(0)}%</div> <div>{currencyFormat(cartCheckout?.tax)}</div> </div> <div className='flex gap-x-2 justify-between'> @@ -574,7 +575,7 @@ const Quotation = () => { <div>{currencyFormat(cartCheckout?.subtotal)}</div> </div> <div className='flex gap-x-2 justify-between'> - <div className='text-gray_r-11'>PPN 11%</div> + <div className='text-gray_r-11'>PPN {((PPN - 1) * 100).toFixed(0)}%</div> <div>{currencyFormat(cartCheckout?.tax)}</div> </div> <div className='flex gap-x-2 justify-between'> diff --git a/src/lib/tempo/components/Tempo.jsx b/src/lib/tempo/components/Tempo.jsx new file mode 100644 index 00000000..c246f3d8 --- /dev/null +++ b/src/lib/tempo/components/Tempo.jsx @@ -0,0 +1,754 @@ +import { + CheckIcon, + ClockIcon, + EllipsisVerticalIcon, + MagnifyingGlassIcon, +} from '@heroicons/react/24/outline'; +import { Skeleton } from '@chakra-ui/react'; +import { div, toQuery } from 'lodash-contrib'; +import _ from 'lodash'; +import { useRouter } from 'next/router'; +import { useState, useEffect } from 'react'; +import useInvoices from '../../invoice/hooks/useInvoices'; +import Spinner from '@/core/components/elements/Spinner/Spinner'; +import Alert from '@/core/components/elements/Alert/Alert'; +import Pagination from '@/core/components/elements/Pagination/Pagination'; +import Link from '@/core/components/elements/Link/Link'; +import currencyFormat from '@/core/utils/currencyFormat'; +import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; +import { + downloadInvoice, + downloadTaxInvoice, +} from '../../invoice/utils/invoices'; +import MobileView from '@/core/components/views/MobileView'; +import DesktopView from '@/core/components/views/DesktopView'; +import Menu from '@/lib/auth/components/Menu'; +import odooApi from '@/core/api/odooApi'; +import { getAuth } from '@/core/utils/auth'; +import FooterBanner from '~/modules/footer-banner'; +import Image from '~/components/ui/image'; +import useDevice from '@/core/hooks/useDevice'; +import { Tooltip } from '@chakra-ui/react'; +import { InfoIcon } from 'lucide-react'; +const Tempo = () => { + const auth = getAuth(); + const router = useRouter(); + const { q = '', page = 1, limit = 15, status = '' } = router.query; + const { isDesktop, isMobile } = useDevice(); + const [pageNew, setPageNew] = useState(page); + const [limitNew, setLimitNew] = useState(limit); + const [statusNew, setStatusNew] = useState(status); + + const query = { + name: q, + offset: (pageNew - 1) * limitNew, + limit: limitNew, + status: statusNew, + }; + const { invoices } = useInvoices({ query }); + const [isLoading, setIsLoading] = useState(true); + const [tempo, setTempo] = useState([]); + const [inputQuery, setInputQuery] = useState(q); + const [toOthers, setToOthers] = useState(null); + + const pageCount = Math.ceil(invoices?.data?.invoiceTotal / limitNew); + let pageQuery = _.omit(query, ['limitNew', 'offset']); + pageQuery = _.pickBy(pageQuery, _.identity); + pageQuery = toQuery(pageQuery); + const handleSubmit = (e) => { + e.preventDefault(); + router.push(`/my/tempo?q=${inputQuery}`); + }; + useEffect(() => { + const loadTempo = async () => { + setIsLoading(true); + const dataTempo = await odooApi( + 'GET', + `/api/v1/check/${auth.partnerId}/tempo` + ); + setTempo(dataTempo); + setIsLoading(false); + }; + loadTempo(); + }, []); + const limitTempo = Math.round( + parseInt(tempo.amountDue + tempo.remainingLimit) + ); + const amountDue = Math.round(parseInt(tempo.amountDue + tempo.amountDueSale)); + + const startItem = 1 + (pageNew - 1) * limitNew; + const endItem = Math.min( + limitNew * pageNew, + invoices?.data?.invoiceTotal + + (statusNew === 0 || statusNew === 2 || statusNew === 4 + ? invoices?.data?.saleOrderTotal + : 0) + ); + const getDueDate = (invoice) => { + const [day, month, year] = invoice.split('/'); + const dueDate = new Date(year, month - 1, day); // Konversi ke objek Date + return dueDate; + }; + + const formatTanggal = (tanggalInput) => { + const [day, month, year] = tanggalInput.split('/'); + const date = new Date(year, month - 1, day); + const formattedDate = new Intl.DateTimeFormat('id-ID', { + day: '2-digit', + month: 'short', + year: 'numeric', + }).format(date); + + return formattedDate; + }; + + const handleClick = () => { + setStatusNew((prevStatus) => (prevStatus === 4 ? 0 : 4)); // Toggle antara 4 dan 0 + }; + const handleClickInvoice = () => { + setStatusNew((prevStatus) => (prevStatus === 1 ? 0 : 1)); // Toggle antara 4 dan 0 + }; + return ( + <> + <DesktopView> + <div className='container mx-auto flex py-10'> + <div className='w-3/12 pr-4'> + <Menu /> + </div> + <div className='w-9/12 p-4 bg-white border border-gray_r-6 rounded flex flex-col gap-y-4'> + <div className='flex flex-col mb-6 items-start justify-start'> + <h1 className='text-title-sm font-semibold'>Pembayaran Tempo</h1> + <div className='flex items-start mb-4'> + <p className='flex flex-row gap-2 w-full items-center'> + Kredit Limit Anda + <Skeleton + isLoaded={!isLoading} + h='fit' + w='fit' + className='text-3xl font-semibold text-green-600 min-w-56' + > + {limitTempo ? currencyFormat(limitTempo) : '-'} + </Skeleton> + </p> + <Tooltip + placement='top-start' + label='Jumlah kredit limit dapat berubah sesuai dengan ketentuan yang Indoteknik miliki.' + > + <div className='text-gray-600'> + <InfoIcon size={15} /> + </div> + </Tooltip> + </div> + <p className='flex flex-row gap-2 w-full'> + Status Detail Tempo Pembayaran Anda adalah{' '} + <Skeleton + isLoaded={!isLoading} + h='fit' + w={32} + className={`${ + tempo.paymentTerm ? 'badge-solid-green' : 'badge-yellow' + } px-1 text-sm flex items-center justify-center font-thin`} + > + {tempo.paymentTerm ? tempo.paymentTerm : 'Review'} + </Skeleton> + </p> + </div> + <div className='grid grid-flow-col gap-4'> + <div className='border w-full p-4'> + <p>Sisa Kredit Limit</p> + <Skeleton + isLoaded={!isLoading} + h='36px' + w='full' + className='text-3xl font-semibold text-green-600' + > + {limitTempo && amountDue + ? currencyFormat( + Math.round(parseInt(limitTempo - amountDue)) + ) + : '-'} + </Skeleton> + </div> + <div + className={`border w-full p-4 flex gap-4 items-center hover:cursor-pointer hover:shadow-lg ${ + statusNew == 4 ? 'bg-red-100' : '' + }`} + onClick={handleClick} + > + <div className=''> + <p>Kredit Limit Terpakai</p> + <Skeleton + isLoaded={!isLoading} + h='36px' + w='full' + className='text-3xl font-semibold text-red-600' + > + {amountDue ? currencyFormat(amountDue) : '-'} + </Skeleton> + </div> + <div className='w-fit h-full flex items-center text-red-600 text-lg text-nowrap font-medium '> + {tempo.amountDueTotal + ? tempo.amountDueTotal + + tempo.amountDueSaleTotal + + ` Transaksi` + : ''} + </div> + </div> + <div + className={`border w-full p-4 flex gap-4 items-center hover:cursor-pointer hover:shadow-lg ${ + statusNew == 1 ? 'bg-red-100' : '' + }`} + onClick={handleClickInvoice} + > + <div className=''> + <p>Jatuh Tempo</p> + <Skeleton + isLoaded={!isLoading} + h='36px' + w='full' + className='text-3xl font-semibold text-red-600' + > + {tempo.amountJatuhTempo + ? currencyFormat(tempo.amountJatuhTempo) + : '-'} + </Skeleton> + </div> + <div className='w-fit h-full flex items-center text-red-600 text-lg text-nowrap font-medium '> + {tempo.amountJatuhTempoTotal + ? tempo.amountJatuhTempoTotal + ` Invoice` + : ''} + </div> + </div> + {/* <div className='border w-full p-4'> + <p>Jatuh Tempo</p> + <Skeleton + isLoaded={!isLoading} + h='36px' + // w={16} + className='text-3xl font-semibold text-red-600' + > + {tempo.amountJatuhTempo + ? currencyFormat(tempo.amountJatuhTempo) + : '-'} + </Skeleton> + </div> */} + </div> + + {auth && auth?.tempoProgres == 'review' && !tempo.paymentTerm ? ( + <div className='flex justify-center'> + <Image + src='/images/ICON-DOKUMEN-VERIFIKASI.png' + alt='Registrasi Tempo' + width={isMobile ? 300 : 600} + height={isMobile ? 300 : 550} + /> + </div> + ) : ( + <div className='border p-4 rounded'> + <div className='flex mb-6 items-center justify-between mt-4'> + <form className='flex' onSubmit={handleSubmit}> + <button className='bg-gray_r-2 px-3' type='submit'> + <MagnifyingGlassIcon className='w-6' /> + </button> + <input + type='text' + className='form-input bg-gray_r-2 border-none' + placeholder='Cari Invoice...' + value={inputQuery} + onChange={(e) => setInputQuery(e.target.value)} + /> + </form> + {!invoices.isLoading && ( + <div className='flex flex-row gap-4 items-center justify-center'> + <p> + Menampilkan {startItem}-{endItem} dari{' '} + {invoices?.data?.invoiceTotal + + (statusNew === 0 || statusNew === 2 || statusNew === 4 + ? invoices?.data?.saleOrderTotal + : 0)} + </p> + <select + id='limitSelect' + value={limitNew} + onChange={(e) => { + setLimitNew(Number(e.target.value)); + setPageNew(1); + }} + className='border p-2' + > + <option value={10}>10</option> + <option value={15}>15</option> + <option value={20}>20</option> + </select> + <select + id='statusSelect' + value={statusNew ?? ''} // Gunakan nullish coalescing untuk default value + onChange={(e) => { + setStatusNew(Number(e.target.value)); + if (e.target.value == 0) { + if (inputQuery) { + router.push('/my/tempo/'); + } + } + }} + className='border p-2' + > + <option value='' disabled hidden> + Status + </option> + <option value={0}>All</option> + <option value={1}>Jatuh Tempo</option> + <option value={2}>Belum Jatuh Tempo</option> + <option value={3}>Lunas</option> + </select> + </div> + )} + </div> + + <table className='table-data'> + <thead> + <tr> + <th>No. Invoice</th> + <th>No. Transaksi</th> + <th className='!text-left'>Salesperson</th> + <th>Tanggal</th> + <th>Jatuh Tempo</th> + <th>Status</th> + <th className='!text-left'>Total</th> + </tr> + </thead> + <tbody> + {/* {invoices.isLoading && ( + <tr> + <td colSpan={6}> + <div className='flex justify-center my-2'> + <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' /> + </div> + </td> + </tr> + )} */} + {/* {!invoices.isLoading && + (!invoices?.data?.saleOrders || + invoices?.data?.saleOrders?.length == 0) && ( + <tr> + <td colSpan={6}>Tidak ada orders</td> + </tr> + )} */} + {(statusNew == 0 || statusNew == 2 || statusNew == 4) && + invoices.data?.saleOrders?.map((orders) => ( + <tr key={orders.id}> + <td>{orders.salesOrder || '-'}</td> + <td> + <Link href={`/my/transactions/${orders.id}`}> + {orders.name} + </Link> + </td> + <td className='!text-left'>{orders.sales}</td> + <td>{orders.dateOrder.split(' ')[0]}</td> + <td>-</td> + <td> + { + <div className=' h-fit mx-auto inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-600/20'> + Belum Jatuh Tempo + </div> + } + </td> + <td className='!text-left'> + {currencyFormat(orders.amountTotal)} + </td> + </tr> + ))} + </tbody> + <tbody> + {invoices.isLoading && ( + <tr> + <td colSpan={6}> + <div className='flex justify-center my-2'> + <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' /> + </div> + </td> + </tr> + )} + {!invoices.isLoading && + (!invoices?.data?.invoices || + invoices?.data?.invoices?.length == 0) && ( + <tr> + <td colSpan={6}>Tidak ada Invoice</td> + </tr> + )} + {invoices.data?.invoices?.map((invoice) => ( + <tr key={invoice.id}> + <td> + <Link href={`/my/invoices/${invoice.id}`}> + {invoice.name} + </Link> + </td> + <td>{invoice.salesOrder || '-'}</td> + <td className='!text-left'>{invoice.sales}</td> + <td>{invoice.invoiceDate}</td> + <td>{invoice.invoiceDateDue}</td> + <td> + {invoice.amountResidual > 0 ? ( + new Date() > getDueDate(invoice.invoiceDateDue) ? ( + <div className='badge-solid-red h-fit mx-auto'> + Jatuh Tempo + </div> + ) : ( + <div className=' h-fit mx-auto inline-flex items-center rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-600/20'> + Belum Jatuh Tempo + </div> + ) + ) : ( + <div className='badge-solid-green h-fit mx-auto'> + Lunas + </div> + )} + </td> + <td className='!text-left'> + {currencyFormat(invoice.amountTotal)} + </td> + </tr> + ))} + </tbody> + </table> + + <Pagination + pageCount={pageCount} + currentPage={parseInt(pageNew)} + // url={`/my/tempo${pageQuery}`} + url={`/my/tempo?${toQuery(_.omit(query, ['page']))}`} + className='mt-2 mb-2' + /> + </div> + )} + + <div className=''> + <FooterBanner /> + </div> + </div> + </div> + </DesktopView> + <MobileView> + <div className=' bg-[#FEF8E2] w-[110%] inset-0 relative transform -translate-x-5 px-8 py-4'> + <div className='flex flex-col gap-y-4 '> + <p className='flex flex-row gap-2 w-full text-sm items-center'> + <span className='opacity-70 text-nowrap'> + Kredit Limit Anda :{' '} + </span> + <Skeleton + isLoaded={!isLoading} + h='fit' + w='fit' + className='text-xl font-semibold text-green-700 min-w-56' + > + {limitTempo ? currencyFormat(limitTempo) : '-'} + </Skeleton> + </p> + <div className='flex flex-col items-center justify-center'> + <p className='flex flex-row w-full text-sm gap-2 items-center justify-start'> + <span className='opacity-70'> + Status Detail Tempo Pembayaran Anda adalah : + </span> + <Skeleton + isLoaded={!isLoading} + h='fit' + w={24} + className={`${ + tempo.paymentTerm ? 'badge-solid-green' : 'badge-yellow' + } px-1 text-xs flex items-center justify-center font-extralight text-nowrap`} + > + {tempo.paymentTerm ? tempo.paymentTerm : 'Review'} + </Skeleton> + </p> + </div> + <div className='flex items-start justify-around text-sm gap-3'> + <div className='w-[30%] flex flex-col gap-2'> + <p className='opacity-70 text-nowrap'>Sisa Kredit Limit</p> + <Skeleton + isLoaded={!isLoading} + // h='36px' + // w={16} + className='font-semibold text-sm text-nowrap text-green-700 ' + > + {limitTempo && amountDue + ? currencyFormat( + Math.round(parseInt(limitTempo - amountDue)) + ) + : '-'} + </Skeleton> + </div>{' '} + <div className='w-[40%] flex flex-col gap-2'> + <p className='opacity-70 text-nowrap'>Kredit Limit Terpakai</p> + <Skeleton + isLoaded={!isLoading} + // h='36px' + // w={16} + className='font-semibold text-sm text-nowrap text-green-700' + > + {amountDue ? currencyFormat(amountDue) : '-'} + </Skeleton> + </div> + <div className=' w-[30%] flex flex-col gap-2'> + <p className='opacity-70 text-nowrap'>Jatuh Tempo</p> + <Skeleton + isLoaded={!isLoading} + // h='36px' + // w={16} + className=' font-semibold text-sm text-nowrap text-red-600' + > + {tempo.amountJatuhTempo + ? currencyFormat(tempo.amountJatuhTempo) + : '-'} + </Skeleton> + </div> + </div> + </div> + </div> + <div className='p-4 flex flex-col gap-y-4 w-full'> + {auth && (!auth?.tempoProgres == 'review' || tempo.paymentTerm) && ( + <form + className='w-full flex gap-x-2 flex-row justify-between' + onSubmit={handleSubmit} + > + <div className='flex w-3/5'> + <input + type='text' + className='form-input' + placeholder='Cari Invoice...' + value={inputQuery} + onChange={(e) => setInputQuery(e.target.value)} + /> + </div> + <button className='btn-light bg-transparent px-3' type='submit'> + <MagnifyingGlassIcon className='w-6' /> + </button> + <div className='flex w-1/5 '> + <div className='flex flex-row gap-4 items-center justify-center'> + <select + id='statusSelect' + value={statusNew} + onChange={(e) => { + setStatusNew(Number(e.target.value)); + if (e.target.value == 0) { + setInputQuery(' '); + } + }} + className='border p-2 w-full h-full rounded-md' + > + <option value={0}>All</option> + <option value={1}>Jatuh Tempo</option> + <option value={2}>Belum Jatuh Tempo</option> + <option value={3}>Lunas</option> + </select> + </div> + </div> + </form> + )} + + {invoices.isLoading && ( + <div className='flex justify-center my-4'> + <Spinner className='w-6 text-gray_r-12/50 fill-gray_r-12' /> + </div> + )} + + {auth && auth?.tempoProgres == 'review' && !tempo.paymentTerm ? ( + // <Alert type='info' className='text-center'> + // Tidak ada invoice + // </Alert> + <div className='flex items-center justify-center'> + <Image + src='/images/ICON-DOKUMEN-VERIFIKASI.png' + alt='Registrasi Tempo' + width={isMobile ? 300 : 600} + height={isMobile ? 300 : 550} + /> + </div> + ) : ( + <div> + {(statusNew == 0 || statusNew == 2 || statusNew == 4) && + invoices.data?.saleOrders?.map((invoice, index) => ( + <div + className='p-4 shadow border border-gray_r-3 rounded-md' + key={index} + > + <div className='grid grid-cols-2'> + <Link href={`/my/quotations/${invoice.id}`}> + <span className='text-caption-2 text-gray_r-11'> + No. Transaksi + </span> + <h2 className='font-semibold text-black mt-1'> + {invoice.name} + </h2> + </Link> + <div className='flex gap-x-1 justify-end items-end '> + <div className='badge-solid-red h-fit ml-auto'> + Belum Jatuh Tempo + </div> + {/* <EllipsisVerticalIcon + className='w-5 h-5' + onClick={() => setToOthers(invoice)} + /> */} + </div> + </div> + <Link href={`/my/quotations/${invoice.id}`}> + <div className='grid grid-cols-2 text-caption-2 text-gray_r-11 mt-2 font-normal'> + <p className='opacity-70'> + {invoice.dateOrder.split(' ')[0]} + </p> + <p className='text-right text-xs text-red-500'> + Jatuh Tempo: - + </p> + </div> + <hr className='my-3' /> + <div className='grid grid-cols-2'> + <div> + <span className='text-caption-2 text-gray_r-11'> + No. Invoice + </span> + <p className='mt-1 font-semibold text-gray_r-12'>-</p> + </div> + <div className='text-right'> + <span className='opacity-65 text-caption-2 text-gray_r-11'> + Total Belanja + </span> + <p className='mt-1 font-semibold text-red-500 '> + {currencyFormat(invoice.amountTotal)} + </p> + </div> + </div> + </Link> + {/* {invoice.efaktur ? ( + <div className='badge-green h-fit mt-3 ml-auto flex items-center gap-x-0.5'> + <CheckIcon className='w-4 stroke-2' /> + Faktur Pajak + </div> + ) : ( + <div className='badge-red h-fit mt-3 ml-auto flex items-center gap-x-0.5'> + <ClockIcon className='w-4 stroke-2' /> + Faktur Pajak + </div> + )} */} + </div> + ))} + {invoices.data?.invoices?.map((invoice, index) => ( + <div + className='p-4 shadow border border-gray_r-3 rounded-md' + key={index} + > + <div className='grid grid-cols-2'> + <Link href={`/my/quotations/${invoice.salesOrderId}`}> + <span className='text-caption-2 text-gray_r-11'> + No. Transaksi + </span> + <h2 className='font-semibold text-black mt-1'> + {invoice.salesOrder} + </h2> + </Link> + <div className='flex gap-x-1 justify-end items-end '> + {invoice.amountResidual > 0 ? ( + new Date() > getDueDate(invoice.invoiceDateDue) ? ( + <div className='inline-flex items-end rounded-md bg-red-50 px-2 py-1 text-xs font-medium text-red-700 ring-1 ring-inset ring-red-600/20'> + Jatuh Tempo + </div> + ) : ( + <div className='badge-solid-red h-fit ml-auto'> + Belum Jatuh Tempo + </div> + ) + ) : ( + <div className='badge-solid-green h-fit ml-auto'> + Lunas + </div> + )} + {/* <EllipsisVerticalIcon + className='w-5 h-5' + onClick={() => setToOthers(invoice)} + /> */} + </div> + </div> + <Link href={`/my/invoices/${invoice.id}`}> + <div className='grid grid-cols-2 text-caption-2 text-gray_r-11 mt-2 font-normal'> + <p className='opacity-70'> + {formatTanggal(invoice.invoiceDate)} + </p> + <p className='text-right text-xs text-red-500'> + Jatuh Tempo: {formatTanggal(invoice.invoiceDateDue)} + </p> + </div> + <hr className='my-3' /> + <div className='grid grid-cols-2'> + <div> + <span className='text-caption-2 text-gray_r-11'> + No. Invoice + </span> + <p className='mt-1 font-semibold text-gray_r-12'> + {invoice.name || '-'} + </p> + </div> + <div className='text-right'> + <span className='opacity-65 text-caption-2 text-gray_r-11'> + Total Belanja + </span> + <p className='mt-1 font-semibold text-red-500 '> + {currencyFormat(invoice.amountTotal)} + </p> + </div> + </div> + </Link> + {/* {invoice.efaktur ? ( + <div className='badge-green h-fit mt-3 ml-auto flex items-center gap-x-0.5'> + <CheckIcon className='w-4 stroke-2' /> + Faktur Pajak + </div> + ) : ( + <div className='badge-red h-fit mt-3 ml-auto flex items-center gap-x-0.5'> + <ClockIcon className='w-4 stroke-2' /> + Faktur Pajak + </div> + )} */} + </div> + ))} + </div> + )} + + <Pagination + pageCount={pageCount} + currentPage={parseInt(pageNew)} + url={`/my/tempo?${toQuery(_.omit(query, ['page']))}`} + className='mt-2 mb-2' + /> + + <BottomPopup + title='Lainnya' + active={toOthers} + close={() => setToOthers(null)} + > + <div className='flex flex-col gap-y-4 mt-2'> + <button + className='text-left disabled:opacity-60' + onClick={() => { + downloadInvoice(toOthers); + setToOthers(null); + }} + > + Download Invoice + </button> + <button + className='text-left disabled:opacity-60' + disabled={!toOthers?.efaktur} + onClick={() => { + downloadTaxInvoice(toOthers); + setToOthers(null); + }} + > + Download Faktur Pajak + </button> + </div> + </BottomPopup> + </div> + </MobileView> + </> + ); +}; + +export default Tempo; diff --git a/src/lib/transaction/components/Transaction.jsx b/src/lib/transaction/components/Transaction.jsx index f5dc507a..b2fb2c17 100644 --- a/src/lib/transaction/components/Transaction.jsx +++ b/src/lib/transaction/components/Transaction.jsx @@ -42,6 +42,7 @@ import { gtagPurchase } from '@/core/utils/googleTag'; import { deleteItemCart } from '@/core/utils/cart'; import axios from 'axios'; const Transaction = ({ id }) => { + const PPN = process.env.NEXT_PUBLIC_PPN; const router = useRouter(); const [isModalOpen, setIsModalOpen] = useState(false); const [selectedProduct, setSelectedProduct] = useState(null); @@ -207,7 +208,7 @@ const Transaction = ({ id }) => { <p>{currencyFormat(transaction.data?.amountUntaxed)}</p> </div> <div className='flex justify-between mt-1'> - <p className='text-gray_r-12/70'>PPN 11%</p> + <p className='text-gray_r-12/70'>PPN {((PPN - 1) * 100).toFixed(0)}%</p> <p>{currencyFormat(transaction.data?.amountTax)}</p> </div> <div className='flex justify-between mt-1'> @@ -975,7 +976,7 @@ const Transaction = ({ id }) => { {currencyFormat(transaction.data?.amountUntaxed)} </div> - <div className='text-right'>PPN 11%</div> + <div className='text-right'>PPN {((PPN - 1) * 100).toFixed(0)}%</div> <div className='text-right font-medium'> {currencyFormat(transaction.data?.amountTax)} </div> |
