From cadd5b09ba9353551f69e7121c56d32033e4e008 Mon Sep 17 00:00:00 2001 From: ValdiANS Date: Mon, 15 Dec 2025 13:06:24 +0700 Subject: [PATCH] feat: create FinanceForm component --- .../pages/finance/form/FinanceForm.tsx | 386 ++++++++++++++++++ 1 file changed, 386 insertions(+) create mode 100644 src/components/pages/finance/form/FinanceForm.tsx diff --git a/src/components/pages/finance/form/FinanceForm.tsx b/src/components/pages/finance/form/FinanceForm.tsx new file mode 100644 index 00000000..58c5e251 --- /dev/null +++ b/src/components/pages/finance/form/FinanceForm.tsx @@ -0,0 +1,386 @@ +'use client'; + +import { useModal } from '@/components/Modal'; +import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; +import { FinanceApi } from '@/services/api/finance'; +import { + Finance, + CreateFinancePayload, + UpdateFinancePayload, +} from '@/types/api/finance'; +import { useRouter } from 'next/navigation'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import toast from 'react-hot-toast'; +import { useFormik } from 'formik'; +import Button from '@/components/Button'; +import { Icon } from '@iconify/react'; +import TextInput from '@/components/input/TextInput'; +import NumberInput from '@/components/input/NumberInput'; +import { cn } from '@/lib/helper'; +import ConfirmationModal from '@/components/modal/ConfirmationModal'; +import TextArea from '@/components/input/TextArea'; +import SelectInput, { + OptionType, + useSelect, +} from '@/components/input/SelectInput'; +import DateInput from '@/components/input/DateInput'; +import { BankApi, CustomerApi } from '@/services/api/master-data'; +import { Customer } from '@/types/api/master-data/customer'; +import { Bank } from '@/types/api/master-data/bank'; +import { + FINANCE_PAYMENT_METHOD_OPTIONS, + FINANCE_TRANSACTION_TYPE_OPTIONS, +} from '@/config/constant'; +import { FinanceFormSchema, FinanceFormValues } from './FinanceForm.schema'; +import Link from 'next/link'; + +interface FinanceFormProps { + formType?: 'add' | 'edit' | 'detail'; + initialValues?: Finance; +} + +const FinanceForm = ({ formType = 'add', initialValues }: FinanceFormProps) => { + const router = useRouter(); + const deleteModal = useModal(); + + const [financeFormErrorMessage, fileFormErrorMessage] = useState(''); + const [isDeleteLoading, setIsDeleteLoading] = useState(false); + + // API Options + const { + options: customerOptions, + isLoadingOptions: isLoadingCustomerOptions, + setInputValue: setCustomerInputValue, + } = useSelect(CustomerApi.basePath, 'id', 'name'); + + const { + options: bankOptions, + isLoadingOptions: isLoadingBankOptions, + setInputValue: setBankInputValue, + } = useSelect(BankApi.basePath, 'id', 'name'); + + const createFinanceHandler = useCallback( + async (payload: CreateFinancePayload) => { + const createFinanceRes = await FinanceApi.create(payload); + + if (isResponseError(createFinanceRes)) { + fileFormErrorMessage(createFinanceRes.message); + return; + } + + toast.success(createFinanceRes?.message as string); + router.push('/finance'); + }, + [router] + ); + + // Note: Update and Delete handlers are not strictly needed for "add" page but good practice to structure for future + const deleteFinanceHandler = () => { + deleteModal.openModal(); + }; + + const confirmationModalDeleteclickHandler = async () => { + // Implement delete logic if needed + setIsDeleteLoading(true); + // await FinanceApi.delete(initialValues?.id as number); + deleteModal.closeModal(); + setIsDeleteLoading(false); + router.push('/finance'); + }; + + const formikInitialValues = useMemo(() => { + return { + transactionType: initialValues + ? { + value: initialValues.transaction_type, + label: initialValues.transaction_type, + } // Should map properly if labels differ + : null, + customerId: initialValues ? null : null, // ID not directly in BaseFinance without processing, assume add mode mostly + customer: initialValues + ? { value: 0, label: initialValues.customer_name } + : null, + paymentDate: initialValues?.payment_date ?? '', + paymentMethod: প্রাথমিকMethods(initialValues?.payment_method), + bankId: null, + bank: initialValues ? { value: 0, label: initialValues.bank_name } : null, + supplierBankAccountNumber: '', + referenceNumber: initialValues?.reference_number ?? '', + amount: initialValues + ? String(initialValues.revenue_amount || initialValues.expense_amount) + : '', + notes: '', + }; + }, [initialValues]); + + function প্রাথমিকMethods(method?: string) { + if (!method) return null; + return ( + FINANCE_PAYMENT_METHOD_OPTIONS.find((o) => o.value === method) ?? { + value: method, + label: method, + } + ); + } + + const formik = useFormik({ + initialValues: formikInitialValues, + validationSchema: FinanceFormSchema, + onSubmit: async (values) => { + fileFormErrorMessage(''); + + const payload: CreateFinancePayload = { + transaction_type: (values.transactionType as OptionType) + .value as string, + customer_id: values.customer ? (values.customer.value as number) : 0, + payment_date: values.paymentDate, + payment_method: (values.paymentMethod as OptionType).value as string, + bank_id: values.bank ? (values.bank.value as number) : 0, + supplier_bank_account_number: values.supplierBankAccountNumber, + reference_number: values.referenceNumber, + amount: Number(values.amount), + notes: values.notes, + }; + + if (formType === 'add') { + await createFinanceHandler(payload); + } + }, + }); + + const { setValues: formikSetValues } = formik; + + useEffect(() => { + if (formType !== 'add' && initialValues) { + // Hydrate logic would go here if editing + } + }, [formType, initialValues]); + + // Helper for select changes + const handleSelectChange = ( + fieldName: keyof FinanceFormValues, + val: OptionType | null + ) => { + formik.setFieldValue(fieldName, val); + }; + + return ( + <> +
+
+ + +

+ {formType === 'add' && 'Tambah Keuangan'} + {formType === 'edit' && 'Ubah Keuangan'} + {formType === 'detail' && 'Detail Keuangan'} +

+
+ +
+
+ + handleSelectChange('transactionType', val as OptionType) + } + isError={ + formik.touched.transactionType && + Boolean(formik.errors.transactionType) + } + errorMessage={formik.errors.transactionType as string} + isDisabled={formType === 'detail'} + /> + + + handleSelectChange('customer', val as OptionType) + } + isError={ + formik.touched.customer && Boolean(formik.errors.customer) + } + errorMessage={formik.errors.customer as string} // Schema logic handles this validation conditionally + isDisabled={formType === 'detail'} + /> + + + formik.setFieldValue('paymentDate', e.target.value) + } + isError={ + formik.touched.paymentDate && Boolean(formik.errors.paymentDate) + } + errorMessage={formik.errors.paymentDate} + readOnly={formType === 'detail'} + /> + + + handleSelectChange('paymentMethod', val as OptionType) + } + isError={ + formik.touched.paymentMethod && + Boolean(formik.errors.paymentMethod) + } + errorMessage={formik.errors.paymentMethod as string} + isDisabled={formType === 'detail'} + /> + + handleSelectChange('bank', val as OptionType)} + isError={formik.touched.bank && Boolean(formik.errors.bank)} // Actually controlled by bankId in schema but logic applies + errorMessage={formik.errors.bankId as string} // bankId error mapping + isDisabled={formType === 'detail'} + /> + + + + + + Rp + } + /> + +