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-migrate | |
| 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-migrate')
| -rw-r--r-- | src-migrate/modules/cart/components/CartSummaryMobile.tsx | 3 | ||||
| -rw-r--r-- | src-migrate/modules/cart/components/Summary.tsx | 3 | ||||
| -rw-r--r-- | src-migrate/modules/cart/stores/useCartStore.ts | 14 | ||||
| -rw-r--r-- | src-migrate/modules/product-card/components/ProductCard.tsx | 3 | ||||
| -rw-r--r-- | src-migrate/modules/product-detail/components/PriceAction.tsx | 3 | ||||
| -rw-r--r-- | src-migrate/modules/register/components/FormBisnis.tsx | 232 | ||||
| -rw-r--r-- | src-migrate/modules/register/components/RegistrasiBisnis.tsx | 28 | ||||
| -rw-r--r-- | src-migrate/modules/register/index.tsx | 5 | ||||
| -rw-r--r-- | src-migrate/modules/register/stores/usePengajuanTempoStore.ts | 334 | ||||
| -rw-r--r-- | src-migrate/modules/register/stores/useRegisterStore.ts | 58 | ||||
| -rw-r--r-- | src-migrate/types/auth.ts | 3 | ||||
| -rw-r--r-- | src-migrate/types/tempo.ts | 117 | ||||
| -rw-r--r-- | src-migrate/validations/auth.ts | 129 | ||||
| -rw-r--r-- | src-migrate/validations/tempo.ts | 181 |
14 files changed, 1011 insertions, 102 deletions
diff --git a/src-migrate/modules/cart/components/CartSummaryMobile.tsx b/src-migrate/modules/cart/components/CartSummaryMobile.tsx index d9f72e0e..02258204 100644 --- a/src-migrate/modules/cart/components/CartSummaryMobile.tsx +++ b/src-migrate/modules/cart/components/CartSummaryMobile.tsx @@ -29,6 +29,7 @@ const CartSummaryMobile = ({ isLoaded = false, }: Props) => { const [showPopup, setShowPopup] = useState(false); + const PPN : number = process.env.NEXT_PUBLIC_PPN ? parseFloat(process.env.NEXT_PUBLIC_PPN) : 0; return ( <> <BottomPopup @@ -63,7 +64,7 @@ const CartSummaryMobile = ({ </Skeleton> <Skeleton isLoaded={isLoaded} className={style.line}> - <span className={style.label}>Tax 11%</span> + <span className={style.label}>Tax {((PPN - 1) * 100).toFixed(0)}%</span> <span className={style.value}>Rp {formatCurrency(tax || 0)}</span> </Skeleton> diff --git a/src-migrate/modules/cart/components/Summary.tsx b/src-migrate/modules/cart/components/Summary.tsx index 2e55c8df..0af5ab18 100644 --- a/src-migrate/modules/cart/components/Summary.tsx +++ b/src-migrate/modules/cart/components/Summary.tsx @@ -25,6 +25,7 @@ const CartSummary = ({ grandTotal, isLoaded = false, }: Props) => { + const PPN : number = process.env.NEXT_PUBLIC_PPN ? parseFloat(process.env.NEXT_PUBLIC_PPN) : 0; return ( <> <div className='text-h-sm font-medium'>Ringkasan Pesanan</div> @@ -50,7 +51,7 @@ const CartSummary = ({ </Skeleton> <Skeleton isLoaded={isLoaded} className={style.line}> - <span className={style.label}>Tax 11%</span> + <span className={style.label}>Tax {((PPN - 1) * 100).toFixed(0)}%</span> <span className={style.value}>Rp {formatCurrency(tax || 0)}</span> </Skeleton> diff --git a/src-migrate/modules/cart/stores/useCartStore.ts b/src-migrate/modules/cart/stores/useCartStore.ts index c2ebf50f..e7d2cdd3 100644 --- a/src-migrate/modules/cart/stores/useCartStore.ts +++ b/src-migrate/modules/cart/stores/useCartStore.ts @@ -43,17 +43,20 @@ export const useCartStore = create<State & Action>((set, get) => ({ updateCartItem: (updatedCart) => { const cart = get().cart; if (!cart) return; - + set({ cart: updatedCart }); const summary = computeSummary(updatedCart); set({ summary }); }, - + })); const computeSummary = (cart: CartProps) => { let subtotal = 0; let discount = 0; + + const PPN: number = process.env.NEXT_PUBLIC_PPN ? parseFloat(process.env.NEXT_PUBLIC_PPN) : 0; + for (const item of cart?.products) { if (!item.selected) continue; @@ -67,8 +70,9 @@ const computeSummary = (cart: CartProps) => { discount += price - item.price.price_discount * item.quantity; } let total = subtotal - discount; - let tax = Math.round(total * 0.11); - let grandTotal = total + tax; + let grandTotal = total * PPN; + let tax = grandTotal - total; + // let grandTotal = total + tax; - return { subtotal, discount, total, tax, grandTotal }; + return { subtotal, discount, total, grandTotal, tax }; };
\ No newline at end of file diff --git a/src-migrate/modules/product-card/components/ProductCard.tsx b/src-migrate/modules/product-card/components/ProductCard.tsx index a439cdc8..8d3b55fb 100644 --- a/src-migrate/modules/product-card/components/ProductCard.tsx +++ b/src-migrate/modules/product-card/components/ProductCard.tsx @@ -16,6 +16,7 @@ type Props = { layout?: 'vertical' | 'horizontal'; }; +const PPN : number = process.env.NEXT_PUBLIC_PPN ? parseFloat(process.env.NEXT_PUBLIC_PPN) : 0; const ProductCard = ({ product, layout = 'vertical' }: Props) => { const utmSource = useUtmSource(); const { isDesktop, isMobile } = useDevice(); @@ -127,7 +128,7 @@ const ProductCard = ({ product, layout = 'vertical' }: Props) => { <div className={style['price-inc']}> Inc PPN: Rp{' '} - {formatCurrency(Math.round(product.lowest_price.price * 1.11))} + {formatCurrency(Math.round(product.lowest_price.price * PPN))} </div> <div className='h-1' /> diff --git a/src-migrate/modules/product-detail/components/PriceAction.tsx b/src-migrate/modules/product-detail/components/PriceAction.tsx index 0b27b1b3..9348bbfb 100644 --- a/src-migrate/modules/product-detail/components/PriceAction.tsx +++ b/src-migrate/modules/product-detail/components/PriceAction.tsx @@ -17,6 +17,7 @@ type Props = { product: IProductDetail; }; +const PPN : number = process.env.NEXT_PUBLIC_PPN ? parseFloat(process.env.NEXT_PUBLIC_PPN) : 0; const PriceAction = ({ product }: Props) => { const { activePrice, @@ -101,7 +102,7 @@ const PriceAction = ({ product }: Props) => { <div className='h-1' /> <div className={style['secondary-text']}> Termasuk PPN: Rp{' '} - {formatCurrency(Math.round(activePrice.price_discount * 1.11))} + {formatCurrency(Math.round(activePrice.price_discount * PPN))} </div> </> )} diff --git a/src-migrate/modules/register/components/FormBisnis.tsx b/src-migrate/modules/register/components/FormBisnis.tsx index 12397956..0ead23ba 100644 --- a/src-migrate/modules/register/components/FormBisnis.tsx +++ b/src-migrate/modules/register/components/FormBisnis.tsx @@ -1,4 +1,12 @@ -import { ChangeEvent, useEffect, useMemo, useRef, useState } from 'react'; +import { + ChangeEvent, + useEffect, + useMemo, + useRef, + useState, + MouseEvent, +} from 'react'; +import { useMutation } from 'react-query'; import { useRegisterStore } from '../stores/useRegisterStore'; import { Button, @@ -11,10 +19,16 @@ import getFileBase64 from '@/core/utils/getFileBase64'; import { Controller, useForm } from 'react-hook-form'; import HookFormSelect from '@/core/components/elements/Select/HookFormSelect'; import odooApi from '~/libs/odooApi'; -import { EyeIcon } from '@heroicons/react/24/outline'; +import { toast } from 'react-hot-toast'; +import { + EyeIcon, + CheckCircleIcon, + XCircleIcon, +} from '@heroicons/react/24/outline'; import BottomPopup from '@/core/components/elements/Popup/BottomPopup'; import Image from 'next/image'; import useDevice from '@/core/hooks/useDevice'; +import useAuth from '@/core/hooks/useAuth'; interface FormProps { type: string; required: boolean; @@ -33,6 +47,10 @@ interface companyType { value: string; label: string; } +interface Auth { + id: string; + parentId: string; +} const form: React.FC<FormProps> = ({ type, @@ -50,12 +68,11 @@ const form: React.FC<FormProps> = ({ // Inside your component const [formattedNpwp, setFormattedNpwp] = useState<string>(''); // State for formatted NPWP const [unformattedNpwp, setUnformattedNpwp] = useState<string>(''); // State for unformatted NPWP - + const auth = useAuth() as unknown as Auth; const [industries, setIndustries] = useState<industry_id[]>([]); const [companyTypes, setCompanyTypes] = useState<companyType[]>([]); const toast = useToast(); - const emailRef = useRef<HTMLInputElement>(null); const businessNameRef = useRef<HTMLInputElement>(null); const companyTypeRef = useRef<HTMLInputElement>(null); @@ -80,30 +97,21 @@ const form: React.FC<FormProps> = ({ label: o.name, })) ); + // setValue('companyType', form.company_type_id); }; loadCompanyTypes(); }, []); useEffect(() => { - const selectedCompanyType = companyTypes.find( - (company) => company.value === watch('companyType') - ); - if (selectedCompanyType) { - updateForm('company_type_id', `${selectedCompanyType?.value}`); - validate(); + if (form.company_type_id) { + setValue('companyType', parseInt(form.company_type_id)); } - }, [watch('companyType'), companyTypes]); - + }, [form.company_type_id]); useEffect(() => { - const selectedIndustryType = industries.find( - (industry) => industry.value === watch('industry_id') - ); - if (selectedIndustryType) { - updateForm('industry_id', `${selectedIndustryType?.value}`); - setSelectedCategory(selectedIndustryType.category); - validate(); + if (form.industry_id) { + setValue('industry_id', parseInt(form.industry_id)); } - }, [watch('industry_id'), industries]); + }, [form.industry_id]); useEffect(() => { const loadIndustries = async () => { @@ -115,6 +123,7 @@ const form: React.FC<FormProps> = ({ category: o.category, })) ); + // setValue('industry_id', form.industry_id); }; loadIndustries(); }, []); @@ -225,7 +234,7 @@ const form: React.FC<FormProps> = ({ toast({ ...toastProps, title: - 'Format file yang diijinkan adalah .pdf, .doc, .docx, .png, .jpg, atau .jpeg', + 'Format file yang diijinkan adalah .pdf, .png, .jpg, atau .jpeg', status: 'error', }); event.target.value = ''; @@ -233,10 +242,10 @@ const form: React.FC<FormProps> = ({ } // Check for file size - if (file.size > 5000000) { + if (file.size > 2000000) { toast({ ...toastProps, - title: 'Maksimal ukuran file adalah 5MB', + title: 'Maksimal ukuran file adalah 2MB', status: 'error', }); event.target.value = ''; @@ -303,9 +312,121 @@ const form: React.FC<FormProps> = ({ }; loadIndustries(); }, [buttonSubmitClick, chekValid]); + + const handleCompanyTypeChange = (event: MouseEvent<HTMLDivElement>) => { + const selectedCompanyTypeValue = watch('companyType'); + const selectedCompanyType = companyTypes.find( + (company) => company.value === selectedCompanyTypeValue + ); + + if (selectedCompanyType !== undefined) { + updateForm('company_type_id', `${selectedCompanyType.value}`); + validate(); + } + }; + + const handleIndustriIdChange = (event: MouseEvent<HTMLDivElement>) => { + const selectedIndustriIdValue = watch('industry_id'); + const selectedIndustryType = industries.find( + (industry) => industry.value === selectedIndustriIdValue + ); + if (selectedIndustryType) { + updateForm('industry_id', `${selectedIndustryType?.value}`); + setSelectedCategory(selectedIndustryType.category); + validate(); + } + }; + + const [fileUrl, setFileUrl] = useState(''); + const [popUpNPWP, setPopUpNpwp] = useState(false); + const [popSppkp, setPopUpSppkp] = useState(false); + const [hasNPWP, setHasNpwp] = useState(false); + const [hasSPPKP, setHasSPPKP] = useState(false); + const [fileUrlSppkp, setFileUrlSppkp] = useState(''); + useEffect(() => { + const checkUrlNPWP = async (url: string | URL | Request) => { + try { + const response = await odooApi( + 'GET', + `/api/v1/user/chek/npwp/${auth?.parentId}` + ); + return response.status; // Returns true if status is 200-299 + } catch (error) { + console.error('Error accessing URL:', url, error); + return false; + } + }; + const checkUrlSPPKP = async (url: string | URL | Request) => { + try { + const response = await odooApi( + 'GET', + `/api/v1/user/chek/sppkp/${auth?.parentId}` + ); + + return response.status; // Returns true if status is 200-299 + } catch (error) { + console.error('Error accessing URL:', url, error); + return false; + } + }; + + const npwpUrl = `${process.env.NEXT_PUBLIC_ODOO_API_HOST}/api/v1/user/download/npwp/${auth?.parentId}`; + const sppkpUrl = `${process.env.NEXT_PUBLIC_ODOO_API_HOST}/api/v1/user/download/sppkp/${auth?.parentId}`; + + if (auth?.parentId) { + checkUrlNPWP(npwpUrl).then((isAccessible) => { + if (isAccessible) { + setFileUrl(npwpUrl); + setHasNpwp(true); + updateForm('npwp_document', ' '); + } + }); + + checkUrlSPPKP(sppkpUrl).then((isAccessible) => { + if (isAccessible) { + setFileUrlSppkp(sppkpUrl); + setHasSPPKP(true); + updateForm('sppkp_document', ' '); + } + }); + } + }, [auth?.parentId]); + return ( <> <BottomPopup + className='h-full object-contain object-center w-full' + title='Dokumen NPWP' + active={popUpNPWP} + close={() => setPopUpNpwp(false)} + > + <div className='flex p-2'> + <iframe + src={fileUrl} + width='800px' + height='600px' + title='NPWP File' + frameBorder='0' + ></iframe> + </div> + </BottomPopup> + <BottomPopup + className='h-full object-contain object-center w-full' + title='Dokumen SPPKP' + active={popSppkp} + close={() => setPopUpSppkp(false)} + > + <div className='flex p-2'> + <iframe + src={fileUrlSppkp} + width='800px' + height='600px' + title='NPWP File' + frameBorder='0' + ></iframe> + </div> + </BottomPopup> + <BottomPopup className='' title='Contoh SPPKP' active={isExample} @@ -326,7 +447,7 @@ const form: React.FC<FormProps> = ({ className={` ${ type === 'bisnis' ? 'mt-6 grid grid-cols-1 gap-y-4' - : 'mt-6 grid grid-cols-2 gap-x-4 gap-y-2' + : 'mt-6 grid grid-cols-2 gap-x-4 gap-y-6 auto-rows-min' }`} > <div> @@ -370,6 +491,7 @@ const form: React.FC<FormProps> = ({ <div className='w-4/5 pr-1 max-h-11 transition-all duration-500' ref={companyTypeRef} + onClick={handleCompanyTypeChange} > <Controller name='companyType' @@ -417,6 +539,7 @@ const form: React.FC<FormProps> = ({ <div className='max-h-10 transition-all duration-500' ref={industryRef} + onClick={handleIndustriIdChange} > <Controller name='industry_id' @@ -575,7 +698,13 @@ const form: React.FC<FormProps> = ({ readOnly={required} ref={npwpRef} placeholder='000.000.000.0-000.000' - value={!required ? formattedNpwp : ''} + value={ + !required + ? formattedNpwp === '' + ? form.npwp + : formattedNpwp + : '' + } maxLength={21} // Set maximum length to 16 characters onChange={(e) => { if (!required) { @@ -665,14 +794,36 @@ const form: React.FC<FormProps> = ({ ref={docsNpwpRef} readOnly={required} onChange={handleFileChange} - accept='.pdf,.doc,.docx,.png,.jpg,.jpeg' // Filter file types + accept='.pdf,.png,.jpg,.jpeg' // Filter file types /> {chekValid && isPKP && !required && !!errors.npwp_document && ( <span className='form-msg-danger'>{errors.npwp_document}</span> )} + <span className='form-msg-danger ml-4 text-xs mb-2'> + Format: .pdf, .png, .jpg, atau .jpeg, Maks 2MB + </span> + {hasNPWP && ( + <div className='px-3'> + <div className='inline-flex flex-row items-center justify-between w-full rounded-md h-12 bg-green-50 px-2 py-1 text-sm font-medium text-green-700 ring-1 ring-inset ring-green-600/20'> + <div className='w-4/5 flex flex-row items-center justify-start '> + <CheckCircleIcon className='w-6 mr-2' /> + Dokumen sudah diupload + </div> + <div className='w-1/5 flex flex-row justify-end'> + <EyeIcon + onClick={() => setPopUpNpwp(true)} + className={`w-6 mr-2 hover:scale-110 transition-transform duration-200}`} + /> + <XCircleIcon + onClick={() => setHasNpwp(false)} + className='w-6 hover:scale-110 transition-transform duration-200' + /> + </div> + </div> + </div> + )} </div> - <div> <label htmlFor='sppkp_document' className='font-bold'> Dokumen SPPKP{' '} @@ -693,12 +844,35 @@ const form: React.FC<FormProps> = ({ ref={docsSppkpRef} readOnly={required} onChange={handleFileChange} - accept='.pdf,.doc,.docx,.png,.jpg,.jpeg' // Filter file types + accept='.pdf,.png,.jpg,.jpeg' + content='lagu.jpg' /> - {chekValid && isPKP && !required && !!errors.sppkp_document && ( <span className='form-msg-danger'>{errors.sppkp_document}</span> )} + <span className='form-msg-danger ml-4 text-xs mb-2'> + Format: .pdf, .png, .jpg, atau .jpeg, Maks 2MB + </span> + {hasSPPKP && ( + <div className='px-3'> + <div className='inline-flex flex-row items-center justify-between w-full rounded-md h-12 bg-green-50 px-2 py-1 text-sm font-medium text-green-700 ring-1 ring-inset ring-green-600/20'> + <div className='w-4/5 flex flex-row items-center justify-start '> + <CheckCircleIcon className='w-6 mr-2' /> + Dokumen sudah di upload + </div> + <div className='w-1/5 flex flex-row justify-end'> + <EyeIcon + onClick={() => setPopUpSppkp(true)} + className={`w-6 mr-2 hover:scale-110 transition-transform duration-200}`} + /> + <XCircleIcon + onClick={() => setHasSPPKP(false)} + className='w-6 hover:scale-110 transition-transform duration-200' + /> + </div> + </div> + </div> + )} </div> </form> </> diff --git a/src-migrate/modules/register/components/RegistrasiBisnis.tsx b/src-migrate/modules/register/components/RegistrasiBisnis.tsx index 40caed65..332c5358 100644 --- a/src-migrate/modules/register/components/RegistrasiBisnis.tsx +++ b/src-migrate/modules/register/components/RegistrasiBisnis.tsx @@ -149,19 +149,21 @@ const RegistrasiBisnis: React.FC<FormProps> = ({ </Stack> </RadioGroup> </div> - <div className='mt-4'> - <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> - <Radio colorScheme='red' value='Non-PKP' className='ml-4'> - Non-PKP - </Radio> - </Stack> - </RadioGroup> - </div> + {!isTerdaftar && ( + <div className='mt-4'> + <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> + <Radio colorScheme='red' value='Non-PKP' className='ml-4'> + Non-PKP + </Radio> + </Stack> + </RadioGroup> + </div> + )} <FormBisnis type='bisnis' required={isTerdaftar} diff --git a/src-migrate/modules/register/index.tsx b/src-migrate/modules/register/index.tsx index 0308e397..39f4771c 100644 --- a/src-migrate/modules/register/index.tsx +++ b/src-migrate/modules/register/index.tsx @@ -163,7 +163,7 @@ const Register = () => { )} </div> <section className='mt-2'> - <FormCaptcha /> + {/* <FormCaptcha /> */} <TermCondition /> <Button type='submit' @@ -172,7 +172,8 @@ const Register = () => { size='lg' onClick={handleSubmit} isDisabled={ - !isCheckedTNC || mutation.isLoading || !isValidCaptcha + // !isCheckedTNC || mutation.isLoading || !isValidCaptcha + !isCheckedTNC || mutation.isLoading } > {mutation.isLoading ? 'Loading...' : 'Daftar'} diff --git a/src-migrate/modules/register/stores/usePengajuanTempoStore.ts b/src-migrate/modules/register/stores/usePengajuanTempoStore.ts new file mode 100644 index 00000000..1e086c06 --- /dev/null +++ b/src-migrate/modules/register/stores/usePengajuanTempoStore.ts @@ -0,0 +1,334 @@ +import { create } from 'zustand'; +import { + TempoProps, + TempoPropsKontakPerson, + TempoPropsPengiriman, + TempoPropsSupplier, + TempoPropsDokumen, +} from '~/types/tempo'; +import { + TempoSchema, + TempoSchemaKontakPerson, + TempoSchemaPengiriman, + TempoSchemaSupplier, + TempoSchemaDokumen, +} from '~/validations/tempo'; +import { boolean, ZodError } from 'zod'; + +type State = { + form: TempoProps; + errors: { + [key in keyof TempoProps]?: string; + }; + isCheckedTNC: boolean; + isOpenTNC: boolean; + isValidCaptcha: boolean; +}; + +type Action = { + updateForm: (name: string, value: string) => void; + updateValidCaptcha: (value: boolean) => void; + toggleCheckTNC: () => void; + openTNC: () => void; + closeTNC: () => void; + validate: () => void; + resetForm: () => void; +}; + +export const usePengajuanTempoStore = create<State & Action>((set, get) => ({ + form: { + name: '', + industryId: '', + street: '', + state: '', + city: '', + district: '', + subDistrict: '', + zip: '', + mobile: '', + bankName: '', + accountName: '', + accountNumber: '', + estimasi: '', + tempoDuration: '', + bersedia: '', + portal: '', + website: '', + categoryProduk: '', + tempoLimit: '', + }, + updateForm: (name, value) => + set((state) => ({ form: { ...state.form, [name]: value } })), + + errors: {}, + validate: () => { + try { + TempoSchema.parse(get().form); + set({ errors: {} }); + } catch (error) { + if (error instanceof ZodError) { + const errors: State['errors'] = {}; + error.errors.forEach( + (e) => (errors[e.path[0] as keyof TempoProps] = e.message) + ); + set({ errors }); + } + } + }, + + isCheckedTNC: false, + toggleCheckTNC: () => set((state) => ({ isCheckedTNC: !state.isCheckedTNC })), + + isOpenTNC: false, + openTNC: () => set(() => ({ isOpenTNC: true })), + closeTNC: () => set(() => ({ isOpenTNC: false })), + + isValidCaptcha: false, + updateValidCaptcha: (value) => set(() => ({ isValidCaptcha: value })), + + resetForm: () => + set({ + form: { + name: '', + industryId: '', + street: '', + state: '', + city: '', + district: '', + subDistrict: '', + zip: '', + mobile: '', + bankName: '', + accountName: '', + accountNumber: '', + website: '', + estimasi: '', + tempoDuration: '', + bersedia: '', + portal: '', + categoryProduk: '', + }, + }), +})); + +type StateKontakPerson = { + formKontakPerson: TempoPropsKontakPerson; + errorsKontakPerson: { + [key in keyof TempoPropsKontakPerson]?: string; + }; +}; +type ActionKontakPerson = { + updateFormKontakPerson: (name: string, value: string) => void; + + validateKontakPerson: () => void; +}; +export const usePengajuanTempoStoreKontakPerson = create< + StateKontakPerson & ActionKontakPerson +>((set, get) => ({ + formKontakPerson: { + direkturTittle: '', + direkturName: '', + direkturMobile: '', + direkturEmail: '', + purchasingTittle: '', + purchasingName: '', + purchasingEmail: '', + financeMobile: '', + financeTittle: '', + financeName: '', + financeEmail: '', + purchasingMobile: '', + }, + updateFormKontakPerson: (name, value) => + set((state) => ({ + formKontakPerson: { ...state.formKontakPerson, [name]: value }, + })), + + errorsKontakPerson: {}, + validateKontakPerson: () => { + try { + TempoSchemaKontakPerson.parse(get().formKontakPerson); + set({ errorsKontakPerson: {} }); + } catch (error) { + if (error instanceof ZodError) { + const errorsKontakPerson: StateKontakPerson['errorsKontakPerson'] = {}; + error.errors.forEach( + (e) => + (errorsKontakPerson[e.path[0] as keyof TempoPropsKontakPerson] = + e.message) + ); + set({ errorsKontakPerson }); + } + } + }, +})); + +type StatePengiriman = { + formPengiriman: TempoPropsPengiriman; + errorsPengiriman: { + [key in keyof TempoPropsPengiriman]?: string; + }; +}; +type ActionPengiriman = { + updateFormPengiriman: (name: string, value: string) => void; + + validatePengiriman: () => void; +}; +export const usePengajuanTempoStorePengiriman = create< + StatePengiriman & ActionPengiriman +>((set, get) => ({ + formPengiriman: { + PICTittle: '', + PICName: '', + streetPengiriman: '', + statePengiriman: '', + cityPengiriman: '', + districtPengiriman: '', + subDistrictPengiriman: '', + zipPengiriman: '', + invoicePicTittle: '', + invoicePic: '', + isSameAddrees: '', + isSameAddreesStreet: '', + streetInvoice: '', + stateInvoice: '', + cityInvoice: '', + districtInvoice: '', + subDistrictInvoice: '', + zipInvoice: '', + tukarInvoiceInput: '', + tukarInvoiceInputPembayaran: '', + dokumenPengiriman: '', + dokumenPengirimanInput: '', + dokumenKirimInput: '', + dokumenPengirimanInvoice: '', + }, + updateFormPengiriman: (name, value) => + set((state) => ({ + formPengiriman: { ...state.formPengiriman, [name]: value }, + })), + + errorsPengiriman: {}, + validatePengiriman: () => { + try { + TempoSchemaPengiriman.parse(get().formPengiriman); + set({ errorsPengiriman: {} }); + } catch (error) { + if (error instanceof ZodError) { + const errorsPengiriman: StatePengiriman['errorsPengiriman'] = {}; + error.errors.forEach( + (e) => + (errorsPengiriman[e.path[0] as keyof TempoPropsPengiriman] = + e.message) + ); + set({ errorsPengiriman }); + } + } + }, +})); +type StateDokumen = { + formDokumen: TempoPropsDokumen; + errorsDokumen: { + [key in keyof TempoPropsDokumen]?: string; + }; +}; +type ActionDokumen = { + updateFormDokumen: ( + name: string, + fileName: string, + fileFormat: string, + value: string + ) => void; + + validateDokumen: () => void; + getJumlahDokumenDiisi: () => void; +}; +export const usePengajuanTempoStoreDokumen = create< + StateDokumen & ActionDokumen +>((set, get) => ({ + formDokumen: { + dokumenNib: { name: '', format: '', base64: '' }, + dokumenNpwp: { name: '', format: '', base64: '' }, + dokumenSppkp: { name: '', format: '', base64: '' }, + dokumenSiup: { name: '', format: '', base64: '' }, + dokumenTdp: { name: '', format: '', base64: '' }, + dokumenSkdp: { name: '', format: '', base64: '' }, + dokumenSkt: { name: '', format: '', base64: '' }, + dokumenAktaPerubahan: { name: '', format: '', base64: '' }, + dokumenKtpDirut: { name: '', format: '', base64: '' }, + dokumenAktaPendirian: { name: '', format: '', base64: '' }, + dokumenLaporanKeuangan: { name: '', format: '', base64: '' }, + dokumenFotoKantor: { name: '', format: '', base64: '' }, + dokumenTempatBekerja: { name: '', format: '', base64: '' }, + }, + + // Memperbarui dokumen dengan name, format, dan base64 + updateFormDokumen: (name, fileName, fileFormat, value) => + set((state) => ({ + formDokumen: { + ...state.formDokumen, + [name]: { + name: fileName, + format: fileFormat, + base64: value, + }, + }, + })), + + errorsDokumen: {}, + validateDokumen: () => { + try { + TempoSchemaDokumen.parse(get().formDokumen); + set({ errorsDokumen: {} }); + } catch (error) { + if (error instanceof ZodError) { + const errorsDokumen: StateDokumen['errorsDokumen'] = {}; + error.errors.forEach( + (e) => + (errorsDokumen[e.path[0] as keyof TempoPropsDokumen] = e.message) + ); + set({ errorsDokumen }); + } + } + }, + + getJumlahDokumenDiisi: () => { + const formDokumen = get().formDokumen; + // Menghitung jumlah field yang base64 tidak kosong + const jumlahTerisi = Object.values(formDokumen).filter( + (dokumen) => dokumen.base64 !== '' + ).length; + return jumlahTerisi; + }, +})); + +type StateSupplier = { + hasSavedata: boolean; + formSupplier: []; + errorsSupplier: { + [key in keyof TempoPropsSupplier]?: string; + }; +}; +type ActionSupplier = { + updateFormSupplier: (data: []) => void; + updateHasSave: (data: boolean) => void; + validateSupplier: () => void; +}; +export const usePengajuanTempoStoreSupplier = create< + StateSupplier & ActionSupplier +>((set, get) => ({ + formSupplier: [], + hasSavedata: true, + updateFormSupplier: (data) => { + set(() => ({ + formSupplier: data, + })); + }, + updateHasSave: (data) => { + set(() => ({ + hasSavedata: data, + })); + }, + errorsSupplier: {}, + validateSupplier: () => {}, +})); diff --git a/src-migrate/modules/register/stores/useRegisterStore.ts b/src-migrate/modules/register/stores/useRegisterStore.ts index 273472be..815b92d9 100644 --- a/src-migrate/modules/register/stores/useRegisterStore.ts +++ b/src-migrate/modules/register/stores/useRegisterStore.ts @@ -28,7 +28,7 @@ export const useRegisterStore = create<State & Action>((set, get) => ({ company_type_id: '', business_name: '', name: '', - nama_wajib_pajak : '', + nama_wajib_pajak: '', email: '', email_partner: '', password: '', @@ -39,14 +39,14 @@ export const useRegisterStore = create<State & Action>((set, get) => ({ npwp: '', sppkp: '', is_pkp: '', - type_acc:'', - is_terdaftar:'', - alamat_bisnis:'', - alamat_wajib_pajak:'', + type_acc: '', + is_terdaftar: '', + alamat_bisnis: '', + alamat_wajib_pajak: '', + parent_id: '', }, updateForm: (name, value) => set((state) => ({ form: { ...state.form, [name]: value } })), - errors: {}, validate: () => { @@ -74,26 +74,28 @@ export const useRegisterStore = create<State & Action>((set, get) => ({ isValidCaptcha: false, updateValidCaptcha: (value) => set(() => ({ isValidCaptcha: value })), - resetForm: () => set({ - form: { - company_type_id: '', - business_name: '', - name: '', - nama_wajib_pajak : '', - email: '', - email_partner: '', - password: '', - phone: '', - sppkp_document: '', - npwp_document: '', - industry_id: '', - npwp: '', - sppkp: '', - is_pkp: '', - type_acc:'', - is_terdaftar:'', - alamat_bisnis:'', - alamat_wajib_pajak:'', - }, - }), + resetForm: () => + set({ + form: { + company_type_id: '', + business_name: '', + name: '', + nama_wajib_pajak: '', + email: '', + email_partner: '', + password: '', + phone: '', + sppkp_document: '', + npwp_document: '', + industry_id: '', + npwp: '', + sppkp: '', + is_pkp: '', + type_acc: '', + is_terdaftar: '', + alamat_bisnis: '', + alamat_wajib_pajak: '', + parent_id: '', + }, + }), })); diff --git a/src-migrate/types/auth.ts b/src-migrate/types/auth.ts index 8feac2e1..1b400e95 100644 --- a/src-migrate/types/auth.ts +++ b/src-migrate/types/auth.ts @@ -20,7 +20,8 @@ export type AuthProps = { onlyReadyStock: boolean; soApproval: boolean; }; - partner_tempo: boolean; + partner_tempo: string; + tempo_progres: string; }; export type AuthApiProps = OdooApiRes<AuthProps>; diff --git a/src-migrate/types/tempo.ts b/src-migrate/types/tempo.ts new file mode 100644 index 00000000..d043e2d6 --- /dev/null +++ b/src-migrate/types/tempo.ts @@ -0,0 +1,117 @@ +import { + TempoSchema, + TempoSchemaKontakPerson, + TempoSchemaPengiriman, + TempoSchemaSupplier, + TempoSchemaDokumen, +} from '~/validations/tempo'; +import { OdooApiRes } from './odoo'; +import { z } from 'zod'; + +export type tempoProps = { + name: string; + industry_id: string; + street: string; + state: string; + city: string; + zip: string; + mobile: string; + bankName: string; + accountName: string; + accountNumber: string; + estimasi: string; + tempoDuration: string; + bersedia: string; +}; + +export type tempoPropsKontakPerson = { + direkturName: string; + direkturMobile: string; + direkturEmail: string; + purchasingName: string; + purchasingEmail: string; + financeMobile: string; + financeEmail: string; + financeName: string; + purchasingMobile: string; +}; +export type tempoPropsPengiriman = { + PICName: string; + streetPengiriman: string; + statePengiriman: string; + cityPengiriman: string; + streetInvoice: string; + zip: string; + invoicePic: string; + isSameAddrees: string; + stateInvoice: string; + cityInvoice: string; + tukarInvoiceInput: string; + tukarInvoiceInputPembayaran: string; + dokumenPengiriman: string; + dokumenPengirimanInput: string; + dokumenPengirimanInvoice: string; + dokumenPengirimanInvoiceInput: string; +}; +export type tempoPropsSupplier = { + supplier: string; + pic: string; + telepon: string; + durasiTempo: string; + creditLimit: string; +}; +export type tempoPropsDokumen = { + dokumenNib: { name: string; format: string; base64: string }; + dokumenNpwp: { name: string; format: string; base64: string }; + dokumenSppkp: { name: string; format: string; base64: string }; + dokumenAktaPerubahan: { name: string; format: string; base64: string }; + dokumenKtpDirut: { name: string; format: string; base64: string }; + dokumenAktaPendirian: { name: string; format: string; base64: string }; + dokumenLaporanKeuangan: { name: string; format: string; base64: string }; + dokumenFotoKantor: { name: string; format: string; base64: string }; + dokumenTempatBekerja: { name: string; format: string; base64: string }; +}; + +export type TempoApiProps = OdooApiRes<TempoProps>; + +export type TempoProps = z.infer<typeof TempoSchema>; +export type TempoPropsKontakPerson = z.infer<typeof TempoSchemaKontakPerson>; +export type TempoPropsPengiriman = z.infer<typeof TempoSchemaPengiriman>; +export type TempoPropsSupplier = z.infer<typeof TempoSchemaSupplier>; +export type TempoPropsDokumen = z.infer<typeof TempoSchemaDokumen>; + +export type TempoResApiProps = { + Tempo: boolean; + reason: 'EMAIL_USED' | 'NOT_ACTIVE' | null; +}; + +type ActivationResProps = { + activation: boolean; + user: TempoProps | null; +}; + +export type ActivationTokenProps = { + token: string; +}; + +export type ActivationTokenResApiProps = ActivationResProps & { + reason: 'INVALID_TOKEN' | null; +}; + +export type ActivationOtpProps = { + email: string; + otp: string; +}; + +export type ActivationOtpResApiProps = ActivationResProps & { + reason: 'INVALID_OTP' | null; +}; + +export type ActivationReqProps = { + email: string; +}; + +export type ActivationReqResApiProps = { + activation_request: boolean; + reason: 'NOT_FOUND' | 'ACTIVE' | null; +}; diff --git a/src-migrate/validations/auth.ts b/src-migrate/validations/auth.ts index 3abdfb57..6cdd930b 100644 --- a/src-migrate/validations/auth.ts +++ b/src-migrate/validations/auth.ts @@ -1,8 +1,77 @@ import { z } from 'zod'; - +const forbiddenWords = [ + 'anjing', + 'babi', + 'monyet', + 'bangsat', + ' tai ', + 'kontol', + 'memek', + ' bol ', + 'pantat', + 'jembut', + 'mencret', + 'ngehe', + 'ngewe', + 'ngentot', + 'ngews', + 'jembudh', + 'jembud', + 'b4b1', + 'b4bi', + 'bab1', + 'mati', + 'perkosa', + 'bunuh', + 'membunuh', + 't41', + 't4i', + 'ta1', + 'anjay', + 'anjir', + 'anying', + 'tokay', + 'peler', + 'meki', + 'tetek', + 'teteq', + 'tobrut', + 'toket', + 'pentil', + 'pantek', + 'bangke', + 'kampret', + 'sialan', + 'beol', + 'bego', + 'goblok', + 'tolol', + 'jancok', + 'jablay', + 'jalang', + 'lonte', + 'jancuk', + 'pelacur', + 'pelakor', +]; export const registerSchema = z .object({ - name: z.string().min(1, { message: 'Nama harus diisi' }), + name: z + .string() + .min(1, { message: 'Nama harus diisi' }) + .refine( + (value) => { + const lowerValue = value.toLowerCase(); + const hasForbiddenWord = forbiddenWords.some((word) => + lowerValue.includes(word) + ); + const isStandaloneTai = /\b(tai)\b/.test(lowerValue); + const isStandaloneBol = /\b(bol)\b/.test(lowerValue); + + return !hasForbiddenWord && !isStandaloneTai && !isStandaloneBol; + }, + { message: 'Nama mengandung kata yang tidak diperbolehkan' } + ), email: z .string() .min(1, { message: 'Email harus diisi' }) @@ -11,13 +80,14 @@ export const registerSchema = z phone: z .string() .min(1, { message: 'Nomor telepon harus diisi' }) - .refine((val) => /^\d{10,12}$/.test(val), { + .refine((val) => /^\d{9,13}$/.test(val), { message: 'Format nomor telepon tidak valid, contoh: 081234567890', }), type_acc: z.string().optional(), nama_wajib_pajak: z.string().optional(), alamat_bisnis: z.string().optional(), alamat_wajib_pajak: z.string().optional(), + parent_id: z.string().optional(), is_pkp: z.string(), is_terdaftar: z.string(), sppkp_document: z.string().optional(), @@ -27,25 +97,42 @@ export const registerSchema = z business_name: z.string().optional(), company_type_id: z.string().optional(), isChekBox: z.string().optional(), - npwp: z.string().optional().refine((val) => !val || /^\d{15,16}$/.test(val), { - message: 'Format NPWP tidak valid, NPWP harus terdiri dari 15-16 digit angka.', - }), + npwp: z + .string() + .optional() + .refine((val) => !val || /^\d{15,16}$/.test(val), { + message: + 'Format NPWP tidak valid, NPWP harus terdiri dari 15-16 digit angka.', + }), sppkp: z.string().optional(), }) .superRefine((data, ctx) => { if (data.type_acc === 'business') { if (data.is_terdaftar === 'false') { if (data.is_pkp === 'true') { - const requiredFields: { field: keyof typeof data; message: string }[] = [ + const requiredFields: { + field: keyof typeof data; + message: string; + }[] = [ { field: 'business_name', message: 'Nama perusahaan harus diisi' }, - { field: 'alamat_bisnis', message: 'Alamat perusahaan harus diisi' }, + { + field: 'alamat_bisnis', + message: 'Alamat perusahaan harus diisi', + }, // { field: 'alamat_wajib_pajak', message: 'Alamat wajib pajak harus diisi' }, { field: 'company_type_id', message: 'Badan usaha wajib dipilih' }, { field: 'industry_id', message: 'Jenis usaha harus dipilih' }, { field: 'sppkp_document', message: 'Document harus diisi' }, { field: 'npwp_document', message: 'Document harus diisi' }, - { field: 'npwp', message: 'Format NPWP tidak valid, NPWP harus terdiri dari 15-16 digit angka.' }, - { field: 'nama_wajib_pajak', message: 'Nama wajib pajak harus diisi' }, + { + field: 'npwp', + message: + 'Format NPWP tidak valid, NPWP harus terdiri dari 15-16 digit angka.', + }, + { + field: 'nama_wajib_pajak', + message: 'Nama wajib pajak harus diisi', + }, ]; requiredFields.forEach(({ field, message }) => { @@ -57,15 +144,19 @@ export const registerSchema = z }); } }); - - if (!data.email_partner || !z.string().email().safeParse(data.email_partner).success) { + + if ( + !data.email_partner || + !z.string().email().safeParse(data.email_partner).success + ) { ctx.addIssue({ code: 'custom', path: ['email_partner'], - message: 'Email partner harus diisi dengan format example@mail.com', + message: + 'Email partner harus diisi dengan format example@mail.com', }); } - if(data.isChekBox === 'false'){ + if (data.isChekBox === 'false') { if (!data.alamat_wajib_pajak) { ctx.addIssue({ code: 'custom', @@ -74,7 +165,6 @@ export const registerSchema = z }); } } - } else { if (!data.business_name) { ctx.addIssue({ @@ -110,12 +200,12 @@ export const registerSchema = z ctx.addIssue({ code: 'custom', path: ['npwp'], - message: 'Format NPWP tidak valid, NPWP harus terdiri dari 15-16 digit angka.', + message: + 'Format NPWP tidak valid, NPWP harus terdiri dari 15-16 digit angka.', }); } - } - }else{ + } else { if (data.is_pkp === 'true') { if (!data.business_name) { ctx.addIssue({ @@ -141,7 +231,6 @@ export const registerSchema = z // path: ['business_name'], // message: 'Nama perusahaan harus diisi', // }); - }else{ - + } else { } }); diff --git a/src-migrate/validations/tempo.ts b/src-migrate/validations/tempo.ts new file mode 100644 index 00000000..f019275c --- /dev/null +++ b/src-migrate/validations/tempo.ts @@ -0,0 +1,181 @@ +import { z } from 'zod'; + +export const TempoSchema = z.object({ + name: z.string().min(1, { message: 'Nama harus diisi' }), + street: z.string().min(1, { message: 'Alamat harus diisi' }), + industryId: z.string().min(1, { message: 'Jenis usaha harus dipilih' }), + zip: z.string().min(1, { message: 'Kode pos harus diisi' }), + state: z.string().min(1, { message: 'Provinsi harus dipilih' }), + city: z.string().min(1, { message: 'Kota harus dipilih' }), + district: z.string().min(1, { message: 'Kecamatan harus dipilih' }), + subDistrict: z.string().min(1, { message: 'Kelurahan harus dipilih' }), + mobile: z + .string() + .min(1, { message: 'Nomor telepon harus diisi' }) + .refine((val) => /^\d{10,12}$/.test(val), { + message: 'Format nomor telepon tidak valid, contoh: 081234567890', + }), + bankName: z.string().min(1, { message: 'Nama bank harus diisi' }), + accountName: z.string().min(1, { message: 'Nama rekening harus diisi' }), + accountNumber: z + .string() + .min(1, { message: 'Nomor rekening harus diisi' }) + .refine((val) => /^\d+$/.test(val), { + message: 'Nomor rekening hanya boleh mengandung angka', + }), + estimasi: z.string().optional(), + website: z.string().optional(), + tempoDuration: z.string().min(1, { message: 'Durasi tempo harus dipilih' }), + bersedia: z.string().min(1, { message: 'Harus dipilih' }), + portal: z.string().min(1, { message: 'Harus dipilih' }), + categoryProduk: z + .string() + .min(1, { message: 'Category produk harus dipilih' }), +}); + +export const TempoSchemaKontakPerson = z.object({ + direkturName: z.string().min(1, { message: 'Nama harus diisi' }), + direkturTittle: z.string().min(1, { message: 'Tittle harus dipilih' }), + financeName: z.string().min(1, { message: 'Nama harus diisi' }), + direkturMobile: z.string().optional(), + financeMobile: z + .string() + .min(1, { message: 'Nomor telepon harus diisi' }) + .refine((val) => /^\d{10,12}$/.test(val), { + message: 'Format nomor telepon tidak valid, contoh: 081234567890', + }), + purchasingTittle: z.string().min(1, { message: 'Tittle harus dipilih' }), + financeTittle: z.string().min(1, { message: 'Tittle harus dipilih' }), + purchasingMobile: z + .string() + .min(1, { message: 'Nomor telepon harus diisi' }) + .refine((val) => /^\d{10,12}$/.test(val), { + message: 'Format nomor telepon tidak valid, contoh: 081234567890', + }), + direkturEmail: z + .string() + .min(1, { message: 'Email harus diisi' }) + .email({ message: 'Email harus menggunakan format example@mail.com' }), + purchasingEmail: z + .string() + .min(1, { message: 'Email harus diisi' }) + .email({ message: 'Email harus menggunakan format example@mail.com' }), + financeEmail: z + .string() + .min(1, { message: 'Email harus diisi' }) + .email({ message: 'Email harus menggunakan format example@mail.com' }), + purchasingName: z.string().min(1, { message: 'Nama harus diisi' }), +}); +export const TempoSchemaPengiriman = z.object({ + PICTittle: z.string().min(1, { message: 'Tittle harus dipilih' }), + PICName: z.string().min(1, { message: 'Nama harus diisi' }), + streetPengiriman: z.string().min(1, { message: 'Alamat harus diisi' }), + statePengiriman: z.string().min(1, { message: 'Provinsi harus dipilih' }), + cityPengiriman: z.string().min(1, { message: 'Kota harus dipilih' }), + districtPengiriman: z.string().min(1, { message: 'Kecamatan harus dipilih' }), + subDistrictPengiriman: z + .string() + .min(1, { message: 'Kelurahan harus dipilih' }), + zipPengiriman: z.string().min(1, { message: 'Kode pos harus diisi' }), + invoicePicTittle: z.string().min(1, { message: 'Tittle harus dipilih' }), + invoicePic: z.string().min(1, { message: 'Nama pic invoice harus diisi' }), + streetInvoice: z.string().min(1, { message: 'Alamat invoice harus diisi' }), + stateInvoice: z + .string() + .min(1, { message: 'Provinsi invoice harus dipilih' }), + cityInvoice: z.string().min(1, { message: 'Kota invoice harus dipilih' }), + districtInvoice: z + .string() + .min(1, { message: 'Kecamatan invoice harus dipilih' }), + subDistrictInvoice: z + .string() + .min(1, { message: 'Kelurahan invoice harus dipilih' }), + zipInvoice: z.string().min(1, { message: 'Kode pos harus diisi' }), + isSameAddrees: z.string(), + isSameAddreesStreet: z.string(), + tukarInvoiceInput: z.string().optional(), + tukarInvoiceInputPembayaran: z.string().optional(), + dokumenPengiriman: z.string().optional(), + dokumenPengirimanInput: z.string().optional(), + dokumenKirimInput: z.string().optional(), + dokumenPengirimanInvoiceInput: z.string().optional(), +}); +export const TempoSchemaSupplier = z.object({ + supplier: z.string().min(1, { message: 'Nama supplier harus diisi' }), + pic: z.string().min(1, { message: 'Nama PIC harus diisi' }), + telepon: z + .string() + .min(1, { message: 'Nomor telepon harus diisi' }) + .refine((val) => /^\d{10,12}$/.test(val), { + message: 'Format nomor telepon tidak valid, contoh: 081234567890', + }), + durasiTempo: z.string().min(1, { message: 'Durasi tempo harus diisi' }), + creditLimit: z.string().min(1, { message: 'Limit Kredit harus diisi' }), +}); +export const TempoSchemaDokumen = z.object({ + dokumenNib: z.object({ + name: z.string().min(1, { message: 'Nama file harus diisi' }), + format: z.string().optional(), + base64: z.string().optional(), + }), + dokumenNpwp: z.object({ + name: z.string().min(1, { message: 'Nama file harus diisi' }), + format: z.string().optional(), + base64: z.string().optional(), + }), + dokumenSppkp: z.object({ + name: z.string().min(1, { message: 'Nama file harus diisi' }), + format: z.string().optional(), + base64: z.string().optional(), + }), + dokumenSiup: z.object({ + name: z.string().optional(), + format: z.string().optional(), + base64: z.string().optional(), + }), + dokumenTdp: z.object({ + name: z.string().optional(), + format: z.string().optional(), + base64: z.string().optional(), + }), + dokumenSkdp: z.object({ + name: z.string().optional(), + format: z.string().optional(), + base64: z.string().optional(), + }), + dokumenSkt: z.object({ + name: z.string().optional(), + format: z.string().optional(), + base64: z.string().optional(), + }), + dokumenAktaPerubahan: z.object({ + name: z.string().optional(), + format: z.string().optional(), + base64: z.string().optional(), + }), + dokumenKtpDirut: z.object({ + name: z.string().optional(), + format: z.string().optional(), + base64: z.string().optional(), + }), + dokumenAktaPendirian: z.object({ + name: z.string().optional(), + format: z.string().optional(), + base64: z.string().optional(), + }), + dokumenLaporanKeuangan: z.object({ + name: z.string().optional(), + format: z.string().optional(), + base64: z.string().optional(), + }), + dokumenFotoKantor: z.object({ + name: z.string().min(1, { message: 'Nama file harus diisi' }), + format: z.string().optional(), + base64: z.string().optional(), + }), + dokumenTempatBekerja: z.object({ + name: z.string().min(1, { message: 'Nama file harus diisi' }), + format: z.string().optional(), + base64: z.string().optional(), + }), +}); |
