mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-25 07:45:47 +00:00
feat(FE-188,193): add Vendor, Request Documents, and Kandang Detail Expense input
This commit is contained in:
@@ -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',
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user