'use client'; 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 Link from 'next/link'; import Button from '@/components/Button'; import { useModal } from '@/components/Modal'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; 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'; import { Expense, CreateExpensePayload, UpdateExpensePayload, } 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'; initialValues?: Expense; } // TODO: integrate this with real API const ExpenseRequestForm = ({ 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( ExpenseApi.convertPayloadToFormData(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, ExpenseApi.convertPayloadToFormData(payload) ); if (updateExpenseRes?.status === 'error') { setExpenseFormErrorMessage(updateExpenseRes.message); return; } toast.success(updateExpenseRes?.message as string); router.refresh(); router.push('/expense'); }, [router] ); const formik = useFormik({ initialValues: getExpenseFormInitialValues(initialValues), validationSchema: type === 'edit' ? UpdateExpenseRequestFormSchema : ExpenseRequestFormSchema, onSubmit: async (values) => { setExpenseFormErrorMessage(''); const expensePayload: CreateExpensePayload = { 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) { case 'add': await createExpenseHandler(expensePayload); break; case 'edit': await updateExpenseHandler( initialValues?.id as number, expensePayload ); break; } }, }); const { setValues: formikSetValues } = formik; const { setInputValue: setLocationInputValue, options: locationOptions, isLoadingOptions: isLoadingLocationOptions, } = useSelect(LocationApi.basePath, 'id', 'name'); const { setInputValue: setVendorInputValue, options: vendorOptions, isLoadingOptions: isLoadingVendorOptions, } = useSelect(SupplierApi.basePath, 'id', 'name'); const locationChangeHandler = (val: OptionType | OptionType[] | null) => { formik.setFieldTouched('location', true); formik.setFieldValue('location', val); formik.setFieldValue('kandangs', []); formik.setFieldValue('kandangExpenses', []); }; const kandangsChangeHandler = (kandangs: { id: number; name: string }[]) => { formik.setFieldTouched('kandangs', true); formik.setFieldValue('kandangs', kandangs); const newKandangExpenses = [...(formik.values.kandangExpenses ?? [])]; // add new kandangExpenses kandangs.forEach((kandangItem) => { const isKandangExistInKandangExpense = newKandangExpenses.find( (kandangExpenseItem) => kandangExpenseItem.kandangId === kandangItem.id ); if (isKandangExistInKandangExpense) return; newKandangExpenses.push({ kandangId: kandangItem.id, expenses: [ { nonstock: undefined, totalExpense: undefined, totalQuantity: undefined, notes: '', }, ], }); }); // prune kandangExpenses const kandangIds = new Set(kandangs.map((kandang) => kandang.id)); const deletedKandangExpensesIdx: number[] = []; newKandangExpenses.forEach((kandangExpense, idx) => { const isKandangExpenseValid = kandangIds.has(kandangExpense.kandangId); if (!isKandangExpenseValid) { deletedKandangExpensesIdx.push(idx); } }); deletedKandangExpensesIdx.forEach((deletedKandangExpenseIdx) => { newKandangExpenses.splice(deletedKandangExpenseIdx, 1); }); formik.setFieldValue('kandangExpenses', newKandangExpenses); }; 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(); }; 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(getExpenseFormInitialValues(initialValues)); }, [formikSetValues, getExpenseFormInitialValues, initialValues]); return ( <>

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

{formik.values.existing_documents && formik.values.existing_documents.length > 0 && (
    {formik.values.existing_documents.map( (existingDocument, existingDocumentIdx) => (
  • {existingDocument.name}{' '}
  • ) )}
)}
{type !== 'add' && (
{type !== 'edit' && ( )}
)} {type !== 'detail' && (
)}
{expenseFormErrorMessage && (
{expenseFormErrorMessage}
)}
{type !== 'add' && ( )} {type === 'detail' && ( <> )} ); }; export default ExpenseRequestForm;