diff options
| author | it-fixcomart <it@fixcomart.co.id> | 2024-10-18 17:08:55 +0700 |
|---|---|---|
| committer | it-fixcomart <it@fixcomart.co.id> | 2024-10-18 17:08:55 +0700 |
| commit | 548e2b48b1c2f6521037765f96083a8d79f611d6 (patch) | |
| tree | b2b1d8d924ec973d683e8fb0157b302bc7b037de | |
| parent | 87ffd2fa7edc240693ddd81401ef23c5cd1bbb3e (diff) | |
<iman> add pengiriman section
| -rw-r--r-- | src-migrate/modules/register/stores/usePengajuanTempoStore.ts | 71 | ||||
| -rw-r--r-- | src-migrate/types/tempo.ts | 13 | ||||
| -rw-r--r-- | src-migrate/validations/tempo.ts | 9 | ||||
| -rw-r--r-- | src/lib/pengajuan-tempo/component/KontakPerusahaan.jsx | 4 | ||||
| -rw-r--r-- | src/lib/pengajuan-tempo/component/PengajuanTempo.jsx | 6 | ||||
| -rw-r--r-- | src/lib/pengajuan-tempo/component/Pengiriman.jsx | 604 |
6 files changed, 701 insertions, 6 deletions
diff --git a/src-migrate/modules/register/stores/usePengajuanTempoStore.ts b/src-migrate/modules/register/stores/usePengajuanTempoStore.ts index 7f1bcbb0..0d397c78 100644 --- a/src-migrate/modules/register/stores/usePengajuanTempoStore.ts +++ b/src-migrate/modules/register/stores/usePengajuanTempoStore.ts @@ -1,6 +1,14 @@ import { create } from 'zustand'; -import { TempoProps, TempoPropsKontakPerson } from '~/types/tempo'; -import { TempoSchema, TempoSchemaKontakPerson } from '~/validations/tempo'; +import { + TempoProps, + TempoPropsKontakPerson, + TempoPropsPengiriman, +} from '~/types/tempo'; +import { + TempoSchema, + TempoSchemaKontakPerson, + TempoSchemaPengiriman, +} from '~/validations/tempo'; import { boolean, ZodError } from 'zod'; type State = { @@ -156,3 +164,62 @@ export const usePengajuanTempoStoreKontakPerson = create< }, }), })); + +type StatePengiriman = { + formPengiriman: TempoPropsPengiriman; + errorsPengiriman: { + [key in keyof TempoPropsPengiriman]?: string; + }; +}; +type ActionPengiriman = { + updateFormPengiriman: (name: string, value: string) => void; + + validatePengiriman: () => void; + resetFormPengiriman: () => void; +}; +export const usePengajuanTempoStorePengiriman = create< + StatePengiriman & ActionPengiriman +>((set, get) => ({ + formPengiriman: { + PICName: '', + streetPengiriman: '', + statePengiriman: '', + cityPengiriman: '', + zip: '', + invoicePic: '', + }, + 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 }); + } + } + }, + + resetFormPengiriman: () => + set({ + formPengiriman: { + PICName: '', + streetPengiriman: '', + statePengiriman: '', + cityPengiriman: '', + zip: '', + invoicePic: '', + }, + }), +})); diff --git a/src-migrate/types/tempo.ts b/src-migrate/types/tempo.ts index 85680cba..6e3f2502 100644 --- a/src-migrate/types/tempo.ts +++ b/src-migrate/types/tempo.ts @@ -1,4 +1,8 @@ -import { TempoSchema, TempoSchemaKontakPerson } from '~/validations/tempo'; +import { + TempoSchema, + TempoSchemaKontakPerson, + TempoSchemaPengiriman, +} from '~/validations/tempo'; import { OdooApiRes } from './odoo'; import { z } from 'zod'; @@ -29,11 +33,18 @@ export type tempoPropsKontakPerson = { financeName: string; purchasingMobile: string; }; +export type tempoPropsPengiriman = { + PICName: string; + streetPengiriman: string; + statePengiriman: string; + cityPengiriman: 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 TempoResApiProps = { Tempo: boolean; diff --git a/src-migrate/validations/tempo.ts b/src-migrate/validations/tempo.ts index 756bb722..7adfa780 100644 --- a/src-migrate/validations/tempo.ts +++ b/src-migrate/validations/tempo.ts @@ -26,6 +26,7 @@ export const TempoSchema = z.object({ .string() .min(1, { message: 'Category produk harus dipilih' }), }); + export const TempoSchemaKontakPerson = z.object({ direkturName: z.string().min(1, { message: 'Nama harus diisi' }), financeName: z.string().min(1, { message: 'Nama harus diisi' }), @@ -61,3 +62,11 @@ export const TempoSchemaKontakPerson = z.object({ .email({ message: 'Email harus menggunakan format example@mail.com' }), purchasingName: z.string().min(1, { message: 'Nama harus diisi' }), }); +export const TempoSchemaPengiriman = z.object({ + 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' }), + zip: z.string().min(1, { message: 'Kode pos harus diisi' }), + invoicePic: z.string().min(1, { message: 'Nama pic invoice harus diisi' }), +}); diff --git a/src/lib/pengajuan-tempo/component/KontakPerusahaan.jsx b/src/lib/pengajuan-tempo/component/KontakPerusahaan.jsx index 861a75ba..c09aaf57 100644 --- a/src/lib/pengajuan-tempo/component/KontakPerusahaan.jsx +++ b/src/lib/pengajuan-tempo/component/KontakPerusahaan.jsx @@ -86,6 +86,10 @@ const KontakPerusahaan = ({ chekValid, buttonSubmitClick }) => { }; loadIndustries(); }, [buttonSubmitClick, chekValid]); + + useEffect(() => { + validateKontakPerson(); + }, [buttonSubmitClick]); return ( <> <div className='flex justify-start'> diff --git a/src/lib/pengajuan-tempo/component/PengajuanTempo.jsx b/src/lib/pengajuan-tempo/component/PengajuanTempo.jsx index 14ea1805..257648ca 100644 --- a/src/lib/pengajuan-tempo/component/PengajuanTempo.jsx +++ b/src/lib/pengajuan-tempo/component/PengajuanTempo.jsx @@ -3,6 +3,7 @@ import { useMemo, useState, useEffect, useRef } from 'react'; import Stepper from './Stepper'; import InformasiPerusahaan from './informasiPerusahaan'; import KontakPerusahaan from './KontakPerusahaan'; +import Pengiriman from './Pengiriman'; import { Controller, useForm } from 'react-hook-form'; import { usePengajuanTempoStore, @@ -31,8 +32,7 @@ const PengajuanTempo = () => { chekValid={notValid} buttonSubmitClick={buttonSubmitClick} />, - <div>Kontak Person</div>, - <div>Pengiriman</div>, + <Pengiriman chekValid={notValid} buttonSubmitClick={buttonSubmitClick} />, <div>Referensi</div>, <div>Dokumen</div>, <div>Konfirmasi</div>, @@ -85,7 +85,7 @@ const PengajuanTempo = () => { top: 0, behavior: 'smooth', }); - }, [currentStep]); + }, [currentStep, buttonSubmitClick]); useEffect(() => { const cachedData = getFromLocalStorage(stepLabels[currentStep]); diff --git a/src/lib/pengajuan-tempo/component/Pengiriman.jsx b/src/lib/pengajuan-tempo/component/Pengiriman.jsx new file mode 100644 index 00000000..91772c7f --- /dev/null +++ b/src/lib/pengajuan-tempo/component/Pengiriman.jsx @@ -0,0 +1,604 @@ +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 { Radio, RadioGroup, Stack, Checkbox } from '@chakra-ui/react'; +import { usePengajuanTempoStorePengiriman } from '../../../../src-migrate/modules/register/stores/usePengajuanTempoStore'; +const Pengiriman = ({ chekValid, buttonSubmitClick }) => { + const { control, watch } = useForm(); + const { + formPengiriman, + errorsPengiriman, + validatePengiriman, + updateFormPengiriman, + } = usePengajuanTempoStorePengiriman(); + const [states, setState] = useState([]); + const [cities, setCities] = useState([]); + const [sameAddress, setSameAddress] = useState(false); + + useEffect(() => { + const loadState = async () => { + let dataState = await stateApi(); + dataState = dataState.map((state) => ({ + value: state.id, + label: state.name, + })); + setState(dataState); + }; + loadState(); + }, []); + + const watchState = watch('state'); + useEffect(() => { + updateFormPengiriman('city', ''); + if (watchState) { + updateFormPengiriman('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 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 zipRef = useRef(null); + const invoicePicRef = 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 (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.zip && zipRef.current) { + zipRef.current.scrollIntoView(options); + return; + } + if (errorsPengiriman.invoicePic && invoicePicRef.current) { + invoicePicRef.current.scrollIntoView(options); + return; + } + + if (errorsPengiriman.direkturEmail && direkturEmailRef.current) { + direkturEmailRef.current.scrollIntoView(options); + return; + } + if (errorsPengiriman.purchasingName && purchasingNameRef.current) { + purchasingNameRef.current.scrollIntoView(options); + return; + } + if (errorsPengiriman.purchasingMobile && purchasingMobileRef.current) { + purchasingMobileRef.current.scrollIntoView(options); + return; + } + if (errorsPengiriman.purchasingEmail && purchasingEmailRef.current) { + purchasingEmailRef.current.scrollIntoView(options); + return; + } + if (errorsPengiriman.financeName && financeNameRef.current) { + financeNameRef.current.scrollIntoView(options); + return; + } + if (errorsPengiriman.financeMobile && financeMobileRef.current) { + financeMobileRef.current.scrollIntoView(options); + return; + } + if (errorsPengiriman.financeEmail && financeEmailRef.current) { + financeEmailRef.current.scrollIntoView(options); + return; + } + } + }; + loadIndustries(); + }, [buttonSubmitClick, chekValid]); + + useEffect(() => { + validatePengiriman(); + }, [buttonSubmitClick]); + useEffect(() => { + if (sameAddress) { + updateFormPengiriman('streetPengiriman', formPengiriman.streetPengiriman); + updateFormPengiriman('statePengiriman', formPengiriman.statePengiriman); + updateFormPengiriman('cityPengiriman', formPengiriman.cityPengiriman); + updateFormPengiriman('zip', formPengiriman.zip); + } + }, [sameAddress]); + return ( + <> + <div className='flex justify-start'> + <h1 className='font-bold'>Pengiriman</h1> + </div> + <form className='flex mt-4 flex-col w-full '> + <div className='w-full grid grid-row-2 gap-5'> + <div className='flex flex-row justify-between items-start'> + <div> + <label className='form-label w-2/5 text-nowrap'> + Nama PIC Penerimaan Barang + </label> + </div> + <div className='w-3/5'> + <input + value={formPengiriman.PICName} + id='PICName' + name='PICName' + placeholder='Masukkan nama pic pengiriman disini' + type='text' + className='form-input' + aria-invalid={errorsPengiriman.PICName} + ref={PICNameRef} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.PICName} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div> + <label className='form-label w-2/5 text-nowrap'> + Alamat Pengiriman Barang + </label> + <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'> + <div> + <input + id='streetPengiriman' + name='streetPengiriman' + ref={streetPengirimanRef} + 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-3'> + <div className='w-2/5' ref={statePengirimanRef}> + <Controller + name='statePengiriman' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={states} + placeholder='Provinsi' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.statePengiriman} + </div> + )} + </div> + <div className='w-1/3' ref={cityPengirimanRef}> + <Controller + name='cityPengiriman' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={cities} + disabled={!watchState} + placeholder='Kota' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.cityPengiriman} + </div> + )} + </div> + <div className='w-1/3'> + <input + id='zip' + name='zip' + ref={zipRef} + placeholder='Kode Pos' + type='number' + value={formPengiriman.zip} + className='form-input' + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.zip} + </div> + )} + </div> + </div> + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div> + <label className='form-label w-2/5 text-nowrap'> + Nama PIC Penerimaan Invoice + </label> + </div> + <div className='w-3/5'> + <input + value={formPengiriman.invoicePic} + id='invoicePic' + name='invoicePic' + placeholder='Masukkan nama pic invoice disini' + type='text' + className='form-input' + aria-invalid={errorsPengiriman.invoicePic} + ref={invoicePicRef} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.invoicePic} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div> + <label className='form-label w-2/5 text-nowrap'> + Alamat Pengiriman Invoice + </label> + <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={() => setSameAddress(!sameAddress)} + > + Alamat invoice sama dengan alamat pengiriman + </Checkbox> + </div> + {!sameAddress && ( + <> + <div> + <input + id='streetPengiriman' + name='streetPengiriman' + ref={streetPengirimanRef} + 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-3'> + <div className='w-2/5' ref={statePengirimanRef}> + <Controller + name='statePengiriman' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={states} + placeholder='Provinsi' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.statePengiriman} + </div> + )} + </div> + <div className='w-1/3' ref={cityPengirimanRef}> + <Controller + name='cityPengiriman' + control={control} + render={(props) => ( + <HookFormSelect + {...props} + options={cities} + disabled={!watchState} + placeholder='Kota' + /> + )} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.cityPengiriman} + </div> + )} + </div> + <div className='w-1/3'> + <input + id='zip' + name='zip' + ref={zipRef} + placeholder='Kode Pos' + type='number' + value={formPengiriman.zip} + className='form-input' + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.zip} + </div> + )} + </div> + </div> + </> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div> + <label className='form-label w-2/5 text-nowrap'> + Email Direktur + </label> + <span className='text-xs opacity-60'> + isi email Direktur yang sesuai + </span> + </div> + <div className='w-3/5'> + <input + id='direkturEmail' + name='direkturEmail' + ref={direkturEmailRef} + placeholder='contoh@email.com' + type='email' + value={formPengiriman.direkturEmail} + className='form-input' + aria-invalid={errorsPengiriman.direkturEmail} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.direkturEmail} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div> + <label className='form-label w-2/5 text-nowrap'> + Nama Purchasing + </label> + <span className='text-xs opacity-60'> + isi nama purchasing yang bertanggung jawab di perusahaan anda + </span> + </div> + <div className='w-3/5'> + <input + id='purchasingName' + name='purchasingName' + ref={purchasingNameRef} + placeholder='Masukkan nama purchasing anda' + value={formPengiriman.purchasingName} + type='text' + className='form-input' + aria-invalid={errorsPengiriman.purchasingName} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.purchasingName} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div> + <label className='form-label w-2/5 text-nowrap'> + No. Telpon Purchasing + </label> + <span className='text-xs opacity-60'> + isi nomor purchasing yang bertanggung jawab di perusahaan anda + </span> + </div> + <div className='w-3/5'> + <input + id='purchasingMobile' + name='purchasingMobile' + ref={financeMobileRef} + placeholder='Masukkan nomor purchasing anda' + value={formPengiriman.purchasingMobile} + type='tel' + className='form-input' + aria-invalid={errorsPengiriman.purchasingMobile} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.purchasingMobile} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div> + <label className='form-label w-2/5 text-nowrap'> + Email Purchasing + </label> + <span className='text-xs opacity-60'> + isi email purchasing benar + </span> + </div> + <div className='w-3/5'> + <input + id='purchasingEmail' + name='purchasingEmail' + ref={purchasingEmailRef} + placeholder='contoh@email.com' + value={formPengiriman.purchasingEmail} + type='email' + className='form-input' + aria-invalid={errorsPengiriman.purchasingEmail} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.purchasingEmail} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div> + <label className='form-label w-2/5 text-nowrap'> + Nama Finance + </label> + <span className='text-xs opacity-60'> + isi nama finance yang bertanggung jawab di perusahaan anda + </span> + </div> + <div className='w-3/5'> + <input + id='financeName' + name='financeName' + ref={financeNameRef} + placeholder='Masukkan nama finance' + value={formPengiriman.financeName} + type='text' + className='form-input' + aria-invalid={errorsPengiriman.financeName} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.financeName} + </div> + )} + </div> + </div> + <div className='flex flex-row justify-between items-start'> + <div> + <label className='form-label w-2/5 text-nowrap'> + No. Telpon Finance + </label> + <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={formPengiriman.financeMobile} + type='tel' + className='form-input' + aria-invalid={errorsPengiriman.financeMobile} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.financeMobile} + </div> + )} + </div> + </div> + + <div className='flex flex-row justify-between items-start'> + <div> + <label className='form-label w-2/5 text-nowrap'> + Email Finance + </label> + <span className='text-xs opacity-60'> + isi email finance dengan benar + </span> + </div> + <div className='w-3/5'> + <input + id='financeEmail' + name='financeEmail' + ref={financeEmailRef} + placeholder='contoh@email.com' + type='email' + value={formPengiriman.financeEmail} + className='form-input' + aria-invalid={errorsPengiriman.financeEmail} + onChange={handleInputChange} + /> + {chekValid && ( + <div className='text-caption-2 text-danger-500 mt-1'> + {errorsPengiriman.financeEmail} + </div> + )} + </div> + </div> + </div> + </form> + </> + ); +}; + +export default Pengiriman; |
