'use client'; import { useCallback, useState } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; import { useFormik } from 'formik'; import toast from 'react-hot-toast'; import Link from 'next/link'; import { Icon } from '@iconify/react'; import Button from '@/components/Button'; import SelectInput, { OptionType, useSelect, } from '@/components/input/SelectInput'; import DateInput from '@/components/input/DateInput'; import DropFileInput from '@/components/input/DropFileInput'; import ExpenseKandangsTable from '@/components/pages/expense/form/ExpenseKandangsTable'; import ExpenseRealizationKandangDetailExpense from '@/components/pages/expense/form/ExpenseRealizationKandangDetailExpense'; import RequirePermission from '@/components/helper/RequirePermission'; import AlertErrorList from '@/components/helper/form/FormErrors'; import { CreateExpenseRealizationPayload, Expense, UpdateExpenseRealizationPayload, } from '@/types/api/expense'; import { ExpenseRealizationFormSchema, ExpenseRealizationFormValues, getExpenseRealizationFormInitialValues, UpdateExpenseRealizationFormSchema, } from '@/components/pages/expense/form/ExpenseRealizationForm.schema'; import { ExpenseApi } from '@/services/api/expense'; import { isResponseError } from '@/lib/api-helper'; import { LocationApi, SupplierApi } from '@/services/api/master-data'; import { Supplier } from '@/types/api/master-data/supplier'; import { ACCEPTED_FILE_TYPE } from '@/config/constant'; import { getExpenseListReturnTo } from '@/lib/expense-list-navigation'; import { cn } from '@/lib/helper'; import { useFormikErrorList } from '@/services/hooks/useFormikErrorList'; interface ExpenseRealizationFormProps { type?: 'add' | 'edit' | 'detail'; initialValues?: Expense; } const ExpenseRealizationForm = ({ type = 'add', initialValues, }: ExpenseRealizationFormProps) => { const router = useRouter(); const searchParams = useSearchParams(); const returnTo = getExpenseListReturnTo(searchParams); const [expenseFormErrorMessage, setExpenseFormErrorMessage] = useState(''); const createExpenseHandler = useCallback( async (payload: CreateExpenseRealizationPayload) => { const createExpenseRes = await ExpenseApi.createRealization( initialValues?.id as number, ExpenseApi.convertExpenseRealizationPayloadToFormData(payload) ); if (isResponseError(createExpenseRes)) { setExpenseFormErrorMessage(createExpenseRes.message); return; } toast.success(createExpenseRes?.message as string); router.push(returnTo); }, [initialValues?.id, returnTo, router] ); const updateExpenseHandler = useCallback( async (expenseId: number, payload: UpdateExpenseRealizationPayload) => { const updateExpenseRes = await ExpenseApi.updateRealization( expenseId, ExpenseApi.convertExpenseRealizationPayloadToFormData(payload) ); if (updateExpenseRes?.status === 'error') { setExpenseFormErrorMessage(updateExpenseRes.message); return; } toast.success(updateExpenseRes?.message as string); router.refresh(); router.push(returnTo); }, [returnTo, router] ); const formik = useFormik({ initialValues: getExpenseRealizationFormInitialValues(initialValues), enableReinitialize: true, validationSchema: type === 'edit' ? UpdateExpenseRealizationFormSchema : ExpenseRealizationFormSchema, onSubmit: async (values) => { setExpenseFormErrorMessage(''); const realizations: CreateExpenseRealizationPayload['realizations'] = []; values.realizations.forEach((realization) => { realization.cost_items.forEach((costItem) => { const realizationItem: { expense_nonstock_id: number; qty: number; price: number; notes: string; kandang_id?: number; } = { expense_nonstock_id: costItem.nonstock?.value as number, qty: parseFloat(String(costItem.quantity)) as number, price: parseFloat(String(costItem.price)) as number, notes: costItem.notes ?? '', }; if (realization.kandang_id && realization.kandang_id > 0) { realizationItem.kandang_id = realization.kandang_id; } realizations.push(realizationItem); }); }); const expensePayload: CreateExpenseRealizationPayload = { realization_date: values.realization_date as string, documents: values.documents as File[], realizations, }; switch (type) { case 'add': await createExpenseHandler(expensePayload); break; case 'edit': await updateExpenseHandler( initialValues?.id as number, expensePayload ); break; } }, }); const { formErrorList, close, handleFormSubmit } = useFormikErrorList(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', []); // Auto-create realization item for location (without kandang) formik.setFieldValue('realizations', [ { cost_items: [ { nonstock: undefined, quantity: undefined, price: undefined, notes: '', }, ], }, ]); }; const kandangsChangeHandler = ( kandangs: { id?: number; name?: string }[] ) => { formik.setFieldTouched('kandangs', true); formik.setFieldValue('kandangs', kandangs); // If no kandangs selected, create realization item for location if (kandangs.length === 0) { formik.setFieldValue('realizations', [ { cost_items: [ { nonstock: undefined, quantity: undefined, price: undefined, notes: '', }, ], }, ]); return; } // Start with empty array when kandangs are selected const newRealizations: typeof formik.values.realizations = []; // add new realizations for each kandang kandangs.forEach((kandangItem) => { if (isNaN(Number(kandangItem.id))) return; const existingRealization = formik.values.realizations?.find( (realizationItem) => realizationItem.kandang_id === kandangItem.id ); newRealizations.push({ kandang_id: kandangItem.id, cost_items: existingRealization?.cost_items || [ { nonstock: undefined, quantity: undefined, price: undefined, notes: '', }, ], }); }); formik.setFieldValue('realizations', newRealizations); }; const vendorChangeHandler = (val: OptionType | OptionType[] | null) => { formik.setFieldTouched('vendor', true); formik.setFieldValue('vendor', val); }; const realizationDocumentsChangeHandler = (val: File[]) => { formik.setFieldTouched('documents', true); const invalidFiles = val.filter((file) => file.size > 5 * 1024 * 1024); if (invalidFiles.length > 0) { toast.error('Ukuran dokumen maksimal 5 MB!'); return; } formik.setFieldValue('documents', val); }; const realizationDocumentsDeleteHandler = (deletedFileIdx: number) => { const newRequestDocuments = formik.values.documents; newRequestDocuments?.splice(deletedFileIdx, 1); formik.setFieldValue('documents', newRequestDocuments); }; return (

Realisasi Biaya Operasional

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