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';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useRouter } from 'next/navigation';
import { useFormik } from 'formik';
import { toast } from 'react-hot-toast';
import { Icon } from '@iconify/react';
import Button from '@/components/Button';
import TextInput from '@/components/input/TextInput';
import { useModal } from '@/components/Modal';
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 ExpenseKandangsTable from '@/components/pages/expense/form/ExpenseKandangsTable';
import DropFileInput from '@/components/input/DropFileInput';
import ExpenseRequestKandangDetailExpense from '@/components/pages/expense/form/ExpenseRequestKandangDetailExpense';
import {
ExpenseRequestFormSchema,
ExpenseRequestFormValues,
getExpenseFormInitialValues,
UpdateExpenseRequestFormSchema,
} from '@/components/pages/expense/form/ExpenseRequestForm.schema';
import { isResponseError } from '@/lib/api-helper';
@@ -27,6 +32,9 @@ import {
} from '@/types/api/expense';
import { ExpenseApi } from '@/services/api/expense';
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 {
type?: 'add' | 'edit' | 'detail';
@@ -49,7 +57,9 @@ const ExpenseRequestForm = ({
const createExpenseHandler = useCallback(
async (payload: CreateExpensePayload) => {
const createExpenseRes = await ExpenseApi.create(payload);
const createExpenseRes = await ExpenseApi.create(
ExpenseApi.convertPayloadToFormData(payload)
);
if (isResponseError(createExpenseRes)) {
setExpenseFormErrorMessage(createExpenseRes.message);
@@ -64,7 +74,10 @@ const ExpenseRequestForm = ({
const updateExpenseHandler = useCallback(
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') {
setExpenseFormErrorMessage(updateExpenseRes.message);
@@ -78,14 +91,8 @@ const ExpenseRequestForm = ({
[router]
);
const formikInitialValues = useMemo<ExpenseRequestFormValues>(() => {
return {
name: initialValues?.name ?? '',
};
}, [initialValues]);
const formik = useFormik<ExpenseRequestFormValues>({
initialValues: formikInitialValues,
initialValues: getExpenseFormInitialValues(initialValues),
validationSchema:
type === 'edit'
? UpdateExpenseRequestFormSchema
@@ -94,7 +101,22 @@ const ExpenseRequestForm = ({
setExpenseFormErrorMessage('');
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) {
@@ -114,6 +136,58 @@ const ExpenseRequestForm = ({
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 = () => {
deleteModal.openModal();
};
@@ -141,8 +215,12 @@ const ExpenseRequestForm = ({
};
useEffect(() => {
formikSetValues(formikInitialValues);
}, [formikSetValues, formikInitialValues]);
formikSetValues(getExpenseFormInitialValues(initialValues));
}, [formikSetValues, getExpenseFormInitialValues, initialValues]);
useEffect(() => {
formik.setFieldValue('kandangs', undefined);
}, [formik.values.location]);
return (
<>
@@ -172,30 +250,22 @@ const ExpenseRequestForm = ({
<div className='grid grid-cols-12 gap-4'>
<SelectInput
label='Lokasi'
required
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' }}
/>
{/* <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
name='transaction_date'
label='Tanggal Transaksi'
required
value={formik.values.transaction_date}
onChange={formik.handleChange}
className={{
wrapper: 'col-span-12 sm:col-span-6',
}}
@@ -203,11 +273,47 @@ const ExpenseRequestForm = ({
<ExpenseKandangsTable
type={type}
locationId={2}
locationId={formik.values.location?.value}
selectedKandangs={formik.values.kandangs ?? []}
onChange={kandangsChangeHandler}
className={{
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 className='flex flex-row justify-between gap-2 flex-wrap'>
@@ -287,7 +393,7 @@ const ExpenseRequestForm = ({
<ConfirmationModal
ref={deleteModal.ref}
type='error'
text={`Apakah anda yakin ingin menghapus data Expense ini (${initialValues?.name})?`}
text='Apakah anda yakin ingin menghapus data Expense ini?'
secondaryButton={{
text: 'Tidak',
}}