'use client'; import { useCallback, useMemo, useState } from 'react'; import { useFormik } from 'formik'; import useSWR from 'swr'; import { useRouter } from 'next/navigation'; import { Icon } from '@iconify/react'; import { toast } from 'react-hot-toast'; import Button from '@/components/Button'; import TextInput from '@/components/input/TextInput'; import ConfirmationModal from '@/components/modal/ConfirmationModal'; import { useModal } from '@/components/Modal'; import { FormHeader } from '@/components/helper/form/FormHeader'; import { FormActions } from '@/components/helper/form/FormActions'; import { PurchaseRequestFormSchema, PurchaseRequestFormValues, getPurchaseRequestFormInitialValues, UpdatePurchaseRequestFormSchema, } from './PurchaseRequestForm.schema'; import { SupplierApi } from '@/services/api/master-data'; import { WarehouseApi } from '@/services/api/master-data'; import { isResponseSuccess, isResponseError } from '@/lib/api-helper'; import { PurchaseApi } from '@/services/api/purchasing'; import Card from '@/components/Card'; import { CreatePurchaseRequestPayload, Purchase, } from '@/types/api/purchase/purchase'; interface PurchaseRequestFormProps { type?: 'add' | 'edit' | 'detail'; initialValues?: Purchase; } const PurchaseRequestForm = ({ type = 'add', initialValues, }: PurchaseRequestFormProps) => { const router = useRouter(); const deleteModal = useModal(); const [selectedPurchaseItems, setSelectedPurchaseItems] = useState( [] ); const [purchaseRequestFormErrorMessage, setPurchaseRequestFormErrorMessage] = useState(''); const [isDeleteLoading, setIsDeleteLoading] = useState(false); // ===== FORM HANDLERS ===== const createPurchaseRequestHandler = useCallback( async (payload: CreatePurchaseRequestPayload) => { const res = await PurchaseApi.create(payload); if (isResponseError(res)) { setPurchaseRequestFormErrorMessage(res.message); return; } toast.success(res?.message as string); router.push('/purchase'); }, [router] ); const updatePurchaseRequestHandler = useCallback( async ( purchaseRequestId: number, payload: CreatePurchaseRequestPayload ) => { const res = await PurchaseApi.update(purchaseRequestId, payload); if (isResponseError(res)) { setPurchaseRequestFormErrorMessage(res.message); return; } toast.success(res?.message as string); router.refresh(); router.push('/purchase'); }, [router] ); const deletePurchaseRequestClickHandler = useCallback(() => { deleteModal.openModal(); }, [deleteModal]); const confirmationModalDeleteClickHandler = useCallback(async () => { if (!initialValues?.id) return; setIsDeleteLoading(true); await PurchaseApi.delete(initialValues.id); deleteModal.closeModal(); toast.success('Successfully delete Purchase Request!'); setIsDeleteLoading(false); router.push('/purchase'); }, [deleteModal, initialValues?.id, router]); // ===== API DATA FETCHING ===== const { data: suppliers, isLoading: isLoadingSuppliers } = useSWR( SupplierApi.basePath, SupplierApi.getAllFetcher ); const { data: warehouses, isLoading: isLoadingWarehouses } = useSWR( WarehouseApi.basePath, WarehouseApi.getAllFetcher ); // ===== DATA PROCESSING ===== const supplierOptions = useMemo(() => { if (!isResponseSuccess(suppliers)) return []; return ( suppliers?.data.map((supplier) => ({ value: supplier.id, label: supplier.name, })) || [] ); }, [suppliers]); const warehouseOptions = useMemo(() => { if (!isResponseSuccess(warehouses)) return []; return ( warehouses?.data.map((warehouse) => ({ value: warehouse.id, label: warehouse.name, })) || [] ); }, [warehouses]); const formikInitialValues = useMemo( () => getPurchaseRequestFormInitialValues(initialValues), [initialValues] ); const formik = useFormik({ initialValues: formikInitialValues, validationSchema: type === 'edit' ? UpdatePurchaseRequestFormSchema : PurchaseRequestFormSchema, validateOnChange: true, validateOnBlur: true, onSubmit: async (values) => { const payload: CreatePurchaseRequestPayload = { supplier_id: values.supplier_id || 0, credit_term: values.credit_term || 0, notes: values.notes || '', purchase_items: (values.purchase_items || []).map((item) => ({ warehouse_id: item.warehouse_id || 0, product_id: item.product_id || 0, product_warehouse_id: item.product_warehouse_id || undefined, sub_qty: item.sub_qty || 0, total_qty: item.total_qty || 0, price: typeof item.price === 'number' ? item.price : parseFloat(item.price) || 0, })), }; switch (type) { case 'add': await createPurchaseRequestHandler(payload); break; case 'edit': await updatePurchaseRequestHandler( initialValues?.id as number, payload ); break; } }, }); // ===== EVENT HANDLERS ===== const supplierChangeHandler = (val: string) => { const supplierId = parseInt(val) || 0; formik.setFieldValue('supplier_id', supplierId); const selectedSupplier = supplierOptions.find( (option) => option.value === supplierId ); if (selectedSupplier) { formik.setFieldValue('supplier', selectedSupplier); } else { formik.setFieldValue('supplier', null); } }; // Purchase Items Handlers const addPurchaseItem = () => { const newPurchaseItems = [ ...(formik.values.purchase_items || []), { warehouse: null, warehouse_id: 0, product: null, product_id: 0, product_warehouse: null, product_warehouse_id: null, sub_qty: 0, total_qty: 0, price: 0, }, ]; formik.setFieldValue('purchase_items', newPurchaseItems); }; const removePurchaseItem = (idx: number) => { const updatedPurchaseItems = formik.values.purchase_items?.filter( (_, i) => i !== idx ); formik.setFieldValue('purchase_items', updatedPurchaseItems); }; const removeSelectedPurchaseItems = () => { const updatedPurchaseItems = formik.values.purchase_items?.filter( (_, idx) => !selectedPurchaseItems.includes(idx) ); formik.setFieldValue('purchase_items', updatedPurchaseItems); setSelectedPurchaseItems([]); }; const handlePurchaseItemChange = ( idx: number, field: string, value: string | number ) => { const integerFields = [ 'warehouse_id', 'product_id', 'product_warehouse_id', 'total_qty', ]; const floatFields = ['price', 'sub_qty']; if (integerFields.includes(field)) { const numValue = typeof value === 'string' ? parseInt(value) || 0 : value; formik.setFieldValue(`purchase_items.${idx}.${field}`, numValue); } else if (floatFields.includes(field)) { const numValue = typeof value === 'string' ? parseFloat(value) || 0 : value; formik.setFieldValue(`purchase_items.${idx}.${field}`, numValue); } else { formik.setFieldValue(`purchase_items.${idx}.${field}`, value); } }; return ( <>
{/* Basic Info Card */}
supplierChangeHandler(e.target.value)} onBlur={formik.handleBlur} isError={ formik.touched.supplier_id && Boolean(formik.errors.supplier_id) } errorMessage={formik.errors.supplier_id as string} readOnly={type === 'detail'} type='number' placeholder='Masukkan Supplier ID' />
{/* Purchase Items Table */}
{type !== 'detail' && ( )} {type !== 'detail' && } {formik.values.purchase_items?.map((item, idx) => ( {type !== 'detail' && ( )} {type !== 'detail' && ( )} ))}
0 } onChange={(e) => { if (e.target.checked) { setSelectedPurchaseItems( formik.values.purchase_items?.map( (_, idx) => idx ) ?? [] ); } else { setSelectedPurchaseItems([]); } }} /> Warehouse ID * Product ID * Product Warehouse ID * Sub Qty * Total Qty * Price * Action
{ if (e.target.checked) { setSelectedPurchaseItems([ ...selectedPurchaseItems, idx, ]); } else { setSelectedPurchaseItems( selectedPurchaseItems.filter((i) => i !== idx) ); } }} /> handlePurchaseItemChange( idx, 'warehouse_id', e.target.value ) } onBlur={formik.handleBlur} type='number' placeholder='Warehouse ID' readOnly={type === 'detail'} className={{ wrapper: 'min-w-24', }} /> handlePurchaseItemChange( idx, 'product_id', e.target.value ) } onBlur={formik.handleBlur} type='number' placeholder='Product ID' readOnly={type === 'detail'} className={{ wrapper: 'min-w-24', }} /> handlePurchaseItemChange( idx, 'product_warehouse_id', e.target.value ) } onBlur={formik.handleBlur} type='number' placeholder='Product Warehouse ID' readOnly={type === 'detail'} className={{ wrapper: 'min-w-24', }} /> handlePurchaseItemChange( idx, 'sub_qty', e.target.value ) } onBlur={formik.handleBlur} type='number' placeholder='Sub Qty' readOnly={type === 'detail'} className={{ wrapper: 'min-w-24', }} /> handlePurchaseItemChange( idx, 'total_qty', e.target.value ) } onBlur={formik.handleBlur} type='number' placeholder='Total Qty' readOnly={type === 'detail'} className={{ wrapper: 'min-w-24', }} /> handlePurchaseItemChange( idx, 'price', e.target.value ) } onBlur={formik.handleBlur} type='number' placeholder='Price' readOnly={type === 'detail'} className={{ wrapper: 'min-w-24', }} />
{type !== 'detail' && (
{selectedPurchaseItems.length > 0 && ( )}
)}
{/* Action buttons */} type={type} formik={formik} editUrl={ initialValues ? `/purchase/detail/edit/?purchaseId=${initialValues.id}` : undefined } onDelete={deletePurchaseRequestClickHandler} /> {purchaseRequestFormErrorMessage && (
{purchaseRequestFormErrorMessage}
)}
{type !== 'add' && ( )} ); }; export default PurchaseRequestForm;