feat(FE-188,193): add Vendor, Request Documents, and Kandang Detail Expense input

This commit is contained in:
ValdiANS
2025-11-06 15:26:54 +07:00
parent e9eee6eb3e
commit 2a71734583
@@ -1,22 +1,27 @@
'use client'; 'use client';
import { useCallback, useEffect, useMemo, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { toast } from 'react-hot-toast'; import { toast } from 'react-hot-toast';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import Button from '@/components/Button'; import Button from '@/components/Button';
import TextInput from '@/components/input/TextInput';
import { useModal } from '@/components/Modal'; import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModal from '@/components/modal/ConfirmationModal';
import SelectInput from '@/components/input/SelectInput'; import SelectInput, {
OptionType,
useSelect,
} from '@/components/input/SelectInput';
import DateInput from '@/components/input/DateInput'; import DateInput from '@/components/input/DateInput';
import ExpenseKandangsTable from '@/components/pages/expense/form/ExpenseKandangsTable'; import ExpenseKandangsTable from '@/components/pages/expense/form/ExpenseKandangsTable';
import DropFileInput from '@/components/input/DropFileInput';
import ExpenseRequestKandangDetailExpense from '@/components/pages/expense/form/ExpenseRequestKandangDetailExpense';
import { import {
ExpenseRequestFormSchema, ExpenseRequestFormSchema,
ExpenseRequestFormValues, ExpenseRequestFormValues,
getExpenseFormInitialValues,
UpdateExpenseRequestFormSchema, UpdateExpenseRequestFormSchema,
} from '@/components/pages/expense/form/ExpenseRequestForm.schema'; } from '@/components/pages/expense/form/ExpenseRequestForm.schema';
import { isResponseError } from '@/lib/api-helper'; import { isResponseError } from '@/lib/api-helper';
@@ -27,6 +32,9 @@ import {
} from '@/types/api/expense'; } from '@/types/api/expense';
import { ExpenseApi } from '@/services/api/expense'; import { ExpenseApi } from '@/services/api/expense';
import { cn, sleep } from '@/lib/helper'; import { cn, sleep } from '@/lib/helper';
import { LocationApi, SupplierApi } from '@/services/api/master-data';
import { ACCEPTED_FILE_TYPE } from '@/config/constant';
import { Supplier } from '@/types/api/master-data/supplier';
interface ExpenseFormProps { interface ExpenseFormProps {
type?: 'add' | 'edit' | 'detail'; type?: 'add' | 'edit' | 'detail';
@@ -49,7 +57,9 @@ const ExpenseRequestForm = ({
const createExpenseHandler = useCallback( const createExpenseHandler = useCallback(
async (payload: CreateExpensePayload) => { async (payload: CreateExpensePayload) => {
const createExpenseRes = await ExpenseApi.create(payload); const createExpenseRes = await ExpenseApi.create(
ExpenseApi.convertPayloadToFormData(payload)
);
if (isResponseError(createExpenseRes)) { if (isResponseError(createExpenseRes)) {
setExpenseFormErrorMessage(createExpenseRes.message); setExpenseFormErrorMessage(createExpenseRes.message);
@@ -64,7 +74,10 @@ const ExpenseRequestForm = ({
const updateExpenseHandler = useCallback( const updateExpenseHandler = useCallback(
async (expenseId: number, payload: UpdateExpensePayload) => { async (expenseId: number, payload: UpdateExpensePayload) => {
const updateExpenseRes = await ExpenseApi.update(expenseId, payload); const updateExpenseRes = await ExpenseApi.update(
expenseId,
ExpenseApi.convertPayloadToFormData(payload)
);
if (updateExpenseRes?.status === 'error') { if (updateExpenseRes?.status === 'error') {
setExpenseFormErrorMessage(updateExpenseRes.message); setExpenseFormErrorMessage(updateExpenseRes.message);
@@ -78,14 +91,8 @@ const ExpenseRequestForm = ({
[router] [router]
); );
const formikInitialValues = useMemo<ExpenseRequestFormValues>(() => {
return {
name: initialValues?.name ?? '',
};
}, [initialValues]);
const formik = useFormik<ExpenseRequestFormValues>({ const formik = useFormik<ExpenseRequestFormValues>({
initialValues: formikInitialValues, initialValues: getExpenseFormInitialValues(initialValues),
validationSchema: validationSchema:
type === 'edit' type === 'edit'
? UpdateExpenseRequestFormSchema ? UpdateExpenseRequestFormSchema
@@ -94,7 +101,22 @@ const ExpenseRequestForm = ({
setExpenseFormErrorMessage(''); setExpenseFormErrorMessage('');
const expensePayload: CreateExpensePayload = { const expensePayload: CreateExpensePayload = {
name: values.name, locationId: values.location?.value as number,
kandangIds: values.kandangs
? values.kandangs.map((item) => item.id)
: [],
transaction_date: values.transaction_date as string,
vendorId: values.vendor?.value as number,
request_documents: values.request_documents as File[],
kandang_expenses: values.kandangExpenses.map((kandangExpense) => ({
kandangId: kandangExpense.kandangId,
expenses: kandangExpense.expenses.map((expenseItem) => ({
nonstockId: expenseItem.nonstock?.value as number,
total_quantity: expenseItem.totalQuantity as number,
total_expense: expenseItem.totalExpense as number,
notes: expenseItem.notes,
})),
})),
}; };
switch (type) { switch (type) {
@@ -114,6 +136,58 @@ const ExpenseRequestForm = ({
const { setValues: formikSetValues } = formik; const { setValues: formikSetValues } = formik;
const {
setInputValue: setLocationInputValue,
options: locationOptions,
isLoadingOptions: isLoadingLocationOptions,
} = useSelect<Location>(LocationApi.basePath, 'id', 'name');
const {
setInputValue: setVendorInputValue,
options: vendorOptions,
isLoadingOptions: isLoadingVendorOptions,
} = useSelect<Supplier>(SupplierApi.basePath, 'id', 'name');
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
formik.setFieldTouched('location', true);
formik.setFieldValue('location', val);
};
const kandangsChangeHandler = (kandangs: { id: number; name: string }[]) => {
formik.setFieldTouched('kandangs', true);
formik.setFieldValue('kandangs', kandangs);
kandangs.forEach((kandangItem) => {
const isKandangExistInKandangExpense = formik.values.kandangExpenses.find(
(kandangExpenseItem) => kandangExpenseItem.kandangId === kandangItem.id
);
if (isKandangExistInKandangExpense) return;
formik.values.kandangExpenses.push({
kandangId: kandangItem.id,
expenses: [
{
nonstock: undefined,
totalExpense: undefined,
totalQuantity: undefined,
notes: '',
},
],
});
});
};
const vendorChangeHandler = (val: OptionType | OptionType[] | null) => {
formik.setFieldTouched('vendor', true);
formik.setFieldValue('vendor', val);
};
const requestDocumentsChangeHandler = (val: File[]) => {
formik.setFieldTouched('request_documents', true);
formik.setFieldValue('request_documents', val);
};
const deleteExpenseClickHandler = () => { const deleteExpenseClickHandler = () => {
deleteModal.openModal(); deleteModal.openModal();
}; };
@@ -141,8 +215,12 @@ const ExpenseRequestForm = ({
}; };
useEffect(() => { useEffect(() => {
formikSetValues(formikInitialValues); formikSetValues(getExpenseFormInitialValues(initialValues));
}, [formikSetValues, formikInitialValues]); }, [formikSetValues, getExpenseFormInitialValues, initialValues]);
useEffect(() => {
formik.setFieldValue('kandangs', undefined);
}, [formik.values.location]);
return ( return (
<> <>
@@ -172,30 +250,22 @@ const ExpenseRequestForm = ({
<div className='grid grid-cols-12 gap-4'> <div className='grid grid-cols-12 gap-4'>
<SelectInput <SelectInput
label='Lokasi' label='Lokasi'
required
placeholder='Pilih Lokasi' placeholder='Pilih Lokasi'
options={[]} value={formik.values.location}
onChange={locationChangeHandler}
options={locationOptions}
isLoading={isLoadingLocationOptions}
onInputChange={setLocationInputValue}
className={{ wrapper: 'col-span-12 sm:col-span-6' }} className={{ wrapper: 'col-span-12 sm:col-span-6' }}
/> />
{/* <TextInput
required
label='Nama'
name='name'
placeholder='Masukkan nama expense'
value={formik.values.name}
onChange={formik.handleChange}
onBlur={formik.handleBlur}
isError={formik.touched.name && Boolean(formik.errors.name)}
errorMessage={formik.errors.name}
readOnly={type === 'detail'}
className={{
wrapper: 'col-span-12 sm:col-span-6',
}}
/> */}
<DateInput <DateInput
name='transaction_date' name='transaction_date'
label='Tanggal Transaksi' label='Tanggal Transaksi'
required
value={formik.values.transaction_date}
onChange={formik.handleChange}
className={{ className={{
wrapper: 'col-span-12 sm:col-span-6', wrapper: 'col-span-12 sm:col-span-6',
}} }}
@@ -203,11 +273,47 @@ const ExpenseRequestForm = ({
<ExpenseKandangsTable <ExpenseKandangsTable
type={type} type={type}
locationId={2} locationId={formik.values.location?.value}
selectedKandangs={formik.values.kandangs ?? []}
onChange={kandangsChangeHandler}
className={{ className={{
wrapper: 'w-full col-span-12', wrapper: 'w-full col-span-12',
}} }}
/> />
<SelectInput
label='Vendor'
required
placeholder='Pilih Vendor'
value={formik.values.vendor}
onChange={vendorChangeHandler}
options={vendorOptions}
isLoading={isLoadingVendorOptions}
onInputChange={setVendorInputValue}
className={{ wrapper: 'col-span-12' }}
/>
<DropFileInput
label='Dokumen Pengajuan'
name='request_documents'
values={formik.values.request_documents}
onChange={requestDocumentsChangeHandler}
accept={{
...ACCEPTED_FILE_TYPE.PDF,
...ACCEPTED_FILE_TYPE.IMAGE,
}}
className={{
wrapper: 'col-span-12',
inputWrapper: 'h-12 flex items-center',
}}
/>
<ExpenseRequestKandangDetailExpense
formik={formik}
className={{
wrapper: 'col-span-12',
}}
/>
</div> </div>
<div className='flex flex-row justify-between gap-2 flex-wrap'> <div className='flex flex-row justify-between gap-2 flex-wrap'>
@@ -287,7 +393,7 @@ const ExpenseRequestForm = ({
<ConfirmationModal <ConfirmationModal
ref={deleteModal.ref} ref={deleteModal.ref}
type='error' type='error'
text={`Apakah anda yakin ingin menghapus data Expense ini (${initialValues?.name})?`} text='Apakah anda yakin ingin menghapus data Expense ini?'
secondaryButton={{ secondaryButton={{
text: 'Tidak', text: 'Tidak',
}} }}