From a51c7c44ececeeef1c77fd8059d8aa79d796e27a Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Fri, 31 Oct 2025 14:29:31 +0700 Subject: [PATCH] feat(FE-188): create ExpenseForm component --- .../pages/expense/form/ExpenseForm.tsx | 303 ++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 src/components/pages/expense/form/ExpenseForm.tsx diff --git a/src/components/pages/expense/form/ExpenseForm.tsx b/src/components/pages/expense/form/ExpenseForm.tsx new file mode 100644 index 00000000..9361c5c3 --- /dev/null +++ b/src/components/pages/expense/form/ExpenseForm.tsx @@ -0,0 +1,303 @@ +'use client'; + +import { useCallback, useEffect, useMemo, 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 { + ExpenseFormSchema, + ExpenseFormValues, + UpdateExpenseFormSchema, +} from '@/components/pages/expense/form/ExpenseForm.schema'; +import { isResponseError } from '@/lib/api-helper'; +import { + Expense, + CreateExpensePayload, + UpdateExpensePayload, +} from '@/types/api/expense'; +import { ExpenseApi } from '@/services/api/expense'; +import { cn, sleep } from '@/lib/helper'; + +interface ExpenseFormProps { + type?: 'add' | 'edit' | 'detail'; + initialValues?: Expense; +} + +// TODO: integrate this with real API +const ExpenseForm = ({ type = 'add', initialValues }: ExpenseFormProps) => { + const router = useRouter(); + + // Modal hooks + const deleteModal = useModal(); + const approveModal = useModal(); + const rejectModal = useModal(); + + const [expenseFormErrorMessage, setExpenseFormErrorMessage] = useState(''); + + const createExpenseHandler = useCallback( + async (payload: CreateExpensePayload) => { + const createExpenseRes = await ExpenseApi.create(payload); + + if (isResponseError(createExpenseRes)) { + setExpenseFormErrorMessage(createExpenseRes.message); + return; + } + + toast.success(createExpenseRes?.message as string); + router.push('/expense'); + }, + [router] + ); + + const updateExpenseHandler = useCallback( + async (expenseId: number, payload: UpdateExpensePayload) => { + const updateExpenseRes = await ExpenseApi.update(expenseId, payload); + + if (updateExpenseRes?.status === 'error') { + setExpenseFormErrorMessage(updateExpenseRes.message); + return; + } + + toast.success(updateExpenseRes?.message as string); + router.refresh(); + router.push('/expense'); + }, + [router] + ); + + const formikInitialValues = useMemo(() => { + return { + name: initialValues?.name ?? '', + }; + }, [initialValues]); + + const formik = useFormik({ + initialValues: formikInitialValues, + validationSchema: + type === 'edit' ? UpdateExpenseFormSchema : ExpenseFormSchema, + onSubmit: async (values) => { + setExpenseFormErrorMessage(''); + + const expensePayload: CreateExpensePayload = { + name: values.name, + }; + + switch (type) { + case 'add': + await createExpenseHandler(expensePayload); + break; + + case 'edit': + await updateExpenseHandler( + initialValues?.id as number, + expensePayload + ); + break; + } + }, + }); + + const { setValues: formikSetValues } = formik; + + const deleteExpenseClickHandler = () => { + deleteModal.openModal(); + }; + + const confirmationModalRejectClickHandler = async () => { + await sleep(750); + + rejectModal.closeModal(); + toast.success('Berhasil melakukan reject biaya operasional!'); + }; + + const confirmationModalApproveClickHandler = async () => { + await sleep(750); + + approveModal.closeModal(); + toast.success('Berhasil melakukan approve biaya operasional!'); + }; + + const confirmationModalDeleteClickHandler = async () => { + await ExpenseApi.delete(initialValues?.id as number); + + deleteModal.closeModal(); + toast.success('Successfully delete Expense!'); + router.push('/expense'); + }; + + useEffect(() => { + formikSetValues(formikInitialValues); + }, [formikSetValues, formikInitialValues]); + + return ( + <> +
+
+ + +

+ {type === 'add' && 'Tambah Biaya Operasional'} + {type === 'edit' && 'Edit Biaya Operasional'} + {type === 'detail' && 'Detail Biaya Operasional'} +

+
+ +
+
+ +
+ +
+ {type !== 'add' && ( +
+ + + {type !== 'edit' && ( + + )} +
+ )} + + {type !== 'detail' && ( +
+ + + +
+ )} +
+ + {expenseFormErrorMessage && ( +
+ + {expenseFormErrorMessage} +
+ )} +
+
+ + {type !== 'add' && ( + + )} + + {type === 'detail' && ( + <> + + + + + )} + + ); +}; + +export default ExpenseForm;