'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 RequirePermission from '@/components/helper/RequirePermission'; 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; } 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.convertExpenseRequestPayloadToFormData(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, deletedDocumentIds: number[] ) => { const updateExpenseRes = await ExpenseApi.update( expenseId, ExpenseApi.convertExpenseRequestUpdatePayloadToFormData(payload), deletedDocumentIds ); 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 = { category: formik.values.category?.value as 'BOP' | 'NON-BOP', transaction_date: values?.transaction_date as string, supplier_id: values.supplier?.value as number, documents: values.documents as File[], expense_nonstocks: values.expense_nonstocks.map((expenseNonstock) => ({ kandang_id: expenseNonstock.kandang_id, cost_items: expenseNonstock.cost_items.map((costItem) => ({ nonstock_id: costItem.nonstock?.value as number, quantity: parseFloat(String(costItem.quantity)) as number, price: parseFloat(String(costItem.price)) as number, notes: costItem.notes ?? '', })), })), }; switch (type) { case 'add': await createExpenseHandler(expensePayload); break; case 'edit': const expenseUpdatePayload: UpdateExpensePayload = { category: formik.values.category?.value as 'BOP' | 'NON-BOP', transaction_date: values?.transaction_date as string, supplier_id: values.supplier?.value as number, documents: values.documents as File[], expense_nonstocks: values.expense_nonstocks.map( (expenseNonstock) => ({ kandang_id: expenseNonstock.kandang_id, cost_items: expenseNonstock.cost_items.map((costItem) => ({ nonstock_id: costItem.nonstock?.value as number, quantity: parseFloat(String(costItem.quantity)) as number, price: parseFloat(String(costItem.price)) as number, notes: costItem.notes ?? '', })), }) ), }; await updateExpenseHandler( initialValues?.id as number, expenseUpdatePayload, formik.values.deleted_documents ?? [] ); break; } }, }); const { setValues: formikSetValues } = formik; const { setInputValue: setLocationInputValue, options: locationOptions, isLoadingOptions: isLoadingLocationOptions, } = useSelect(LocationApi.basePath, 'id', 'name'); const { setInputValue: setVendorInputValue, options: supplierOptions, isLoadingOptions: isLoadingVendorOptions, } = useSelect(SupplierApi.basePath, 'id', 'name'); const categoryChangeHandler = (val: OptionType | OptionType[] | null) => { formik.setFieldTouched('category', true); formik.setFieldValue('category', val); }; const locationChangeHandler = (val: OptionType | OptionType[] | null) => { formik.setFieldTouched('location', true); formik.setFieldValue('location', val); formik.setFieldValue('kandangs', []); formik.setFieldValue('expense_nonstocks', []); }; const kandangsChangeHandler = (kandangs: { id: number; name: string }[]) => { formik.setFieldTouched('kandangs', true); formik.setFieldValue('kandangs', kandangs); const newExpenseNonstocks = [...(formik.values.expense_nonstocks ?? [])]; // add new expense_nonstocks kandangs.forEach((kandangItem) => { const isKandangExistInExpenseNonstocks = newExpenseNonstocks.find( (expenseNonstockItem) => expenseNonstockItem.kandang_id === kandangItem.id ); if (isKandangExistInExpenseNonstocks) return; newExpenseNonstocks.push({ kandang_id: kandangItem.id, cost_items: [ { nonstock: undefined, quantity: undefined, price: undefined, notes: '', }, ], }); }); // prune expense_nonstocks const kandangIds = new Set(kandangs.map((kandang) => kandang.id)); const deletedExpenseNonstocksIdx: number[] = []; newExpenseNonstocks.forEach((expenseNonstock, idx) => { const isExpenseNonstockValid = kandangIds.has(expenseNonstock.kandang_id); if (!isExpenseNonstockValid) { deletedExpenseNonstocksIdx.push(idx); } }); deletedExpenseNonstocksIdx.forEach((deletedExpenseNonstockIdx) => { newExpenseNonstocks.splice(deletedExpenseNonstockIdx, 1); }); formik.setFieldValue('expense_nonstocks', newExpenseNonstocks); }; const supplierChangeHandler = (val: OptionType | OptionType[] | null) => { formik.setFieldTouched('supplier', true); formik.setFieldValue('supplier', val); }; const requestDocumentsChangeHandler = (val: File[]) => { formik.setFieldTouched('documents', true); formik.setFieldValue('documents', val); }; const requestDocumentsDeleteHandler = (deletedFileIdx: number) => { const newRequestDocuments = formik.values.documents; newRequestDocuments?.splice(deletedFileIdx, 1); formik.setFieldValue('documents', newRequestDocuments); }; const deleteDocumentClickHandler = ( deletedDocumentIdx: number, deletedDocumentId: number ) => { const newDeletedDocumentIds = [...(formik.values.deleted_documents ?? [])]; const newExistingDocuments = [ ...(formik.values.existing_documents ?? []), ].filter((_, idx) => idx !== deletedDocumentIdx); newDeletedDocumentIds.push(deletedDocumentId); formik.setFieldTouched('deleted_documents', true); formik.setFieldValue('deleted_documents', newDeletedDocumentIds); formik.setFieldTouched('existing_documents', true); formik.setFieldValue('existing_documents', newExistingDocuments); }; 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' && ( )}
)} {expenseFormErrorMessage && (
{expenseFormErrorMessage}
)} {type !== 'detail' && (
)}
{type !== 'add' && ( )} {type === 'detail' && ( <> )} ); }; export default ExpenseRequestForm;