diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index 6bc41b2c..8137260f 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -349,13 +349,111 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { }; }; - const handleTransferDateChange = (e: React.ChangeEvent) => { - formik.setFieldValue('transfer_date', e.target.value); - }; - // ===== EVENT HANDLERS ===== - // Product Handlers - const addProduct = () => { + const handleTransferDateChange = useCallback( + (e: React.ChangeEvent) => { + formik.setFieldValue('transfer_date', e.target.value); + }, + [] + ); + + const handleSourceWarehouseChange = useCallback( + (val: OptionType | OptionType[] | null) => { + const newSourceWarehouseId = (val as WarehouseOptionType)?.value; + + if (newSourceWarehouseId) { + if (newSourceWarehouseId === formik.values.destination_warehouse_id) { + const destinationWarehouseName = + (formik.values.destination_warehouse as WarehouseOptionType) + ?.label || 'gudang tujuan'; + + toast.error( + `Tidak bisa memilih gudang yang sama. Gudang asal tidak boleh sama dengan ${destinationWarehouseName}.` + ); + return; + } + } + + formik.setFieldTouched('source_warehouse', true); + formik.setFieldValue('source_warehouse', val); + formik.setFieldTouched('source_warehouse_id', true); + formik.setFieldValue('source_warehouse_id', newSourceWarehouseId); + + if ( + formik.errors.destination_warehouse_id === + 'Gudang tujuan tidak boleh sama dengan gudang asal!' + ) { + formik.setFieldError('destination_warehouse_id', undefined); + } + + if ( + newSourceWarehouseId && + newSourceWarehouseId !== formik.values.source_warehouse_id + ) { + formik.setFieldValue('products', [ + { + product: null, + product_id: 0, + product_qty: '', + }, + ]); + formik.setFieldTouched('products', false); + + const updatedDeliveries = formik.values.deliveries.map( + (delivery: DeliverySchema) => ({ + ...delivery, + products: [ + { + product: null, + product_id: 0, + product_qty: '', + }, + ], + }) + ); + formik.setFieldValue('deliveries', updatedDeliveries); + formik.setFieldTouched('deliveries', false); + } + }, + [] + ); + + const handleDestinationWarehouseChange = useCallback( + (val: OptionType | OptionType[] | null) => { + const newDestinationWarehouseId = (val as WarehouseOptionType)?.value; + + if (newDestinationWarehouseId) { + if (newDestinationWarehouseId === formik.values.source_warehouse_id) { + const sourceWarehouseName = + (formik.values.source_warehouse as WarehouseOptionType)?.label || + 'gudang asal'; + + toast.error( + `Tidak bisa memilih gudang yang sama. Gudang tujuan tidak boleh sama dengan ${sourceWarehouseName}.` + ); + return; + } + } + + formik.setFieldTouched('destination_warehouse', true); + formik.setFieldValue('destination_warehouse', val); + formik.setFieldTouched('destination_warehouse_id', true); + formik.setFieldValue( + 'destination_warehouse_id', + newDestinationWarehouseId + ); + + if ( + formik.errors.destination_warehouse_id === + 'Gudang tujuan tidak boleh sama dengan gudang asal!' + ) { + formik.setFieldError('destination_warehouse_id', undefined); + } + }, + [] + ); + + const addProduct = useCallback(() => { const newProducts = [ ...(formik.values.products || []), { @@ -365,22 +463,19 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { }, ]; formik.setFieldValue('products', newProducts); - }; + }, []); - const removeProduct = useCallback( - (i: number) => { - const updatedProducts = - formik.values.products?.reduce((acc: ProductSchema[], item, index) => { - if (index !== i) { - acc.push(item); - } - return acc; - }, []) ?? []; + const removeProduct = useCallback((i: number) => { + const updatedProducts = + formik.values.products?.reduce((acc: ProductSchema[], item, index) => { + if (index !== i) { + acc.push(item); + } + return acc; + }, []) ?? []; - formik.setFieldValue('products', updatedProducts); - }, - [formik] - ); + formik.setFieldValue('products', updatedProducts); + }, []); const bulkRemoveProduct = useCallback(() => { const updatedProducts = @@ -389,10 +484,60 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ) ?? []; formik.setFieldValue('products', updatedProducts); setSelectedProducts([]); - }, [formik, selectedProducts]); + }, [formik, selectedProducts, setSelectedProducts]); - // Delivery Handlers - const addDelivery = () => { + const handleProductChange = useCallback( + (idx: number, val: OptionType | OptionType[] | null) => { + formik.setFieldTouched(`products.${idx}.product`, true); + formik.setFieldValue(`products.${idx}.product`, val); + formik.setFieldTouched(`products.${idx}.product_id`, true); + formik.setFieldValue( + `products.${idx}.product_id`, + (val as ProductWarehouseOptionType)?.value + ); + + const updatedDeliveries = formik.values.deliveries.map( + (delivery: DeliverySchema) => ({ + ...delivery, + products: [ + { + product: null, + product_id: 0, + product_qty: '', + }, + ], + }) + ); + formik.setFieldValue('deliveries', updatedDeliveries); + formik.setFieldTouched('deliveries', false); + }, + [] + ); + + const handleProductSelectAllChange = useCallback( + (e: React.ChangeEvent) => { + if (e.target.checked) { + setSelectedProducts(formik.values.products?.map((_, idx) => idx) ?? []); + } else { + setSelectedProducts([]); + } + }, + [formik.values.products, setSelectedProducts] + ); + + const handleProductCheckboxChange = useCallback( + (e: React.ChangeEvent) => { + const idx = Number(e.target.name.replace('product-', '')); + if (e.target.checked) { + setSelectedProducts((prev) => [...prev, idx]); + } else { + setSelectedProducts((prev) => prev.filter((i) => i !== idx)); + } + }, + [setSelectedProducts] + ); + + const addDelivery = useCallback(() => { formik.setFieldValue('deliveries', [ ...(formik.values.deliveries || []), { @@ -412,25 +557,19 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ], }, ]); - }; + }, []); - const removeDelivery = useCallback( - (i: number) => { - const updatedDeliveries = - formik.values.deliveries?.reduce( - (acc: DeliverySchema[], item, index) => { - if (index !== i) { - acc.push(item); - } - return acc; - }, - [] - ) ?? []; + const removeDelivery = useCallback((i: number) => { + const updatedDeliveries = + formik.values.deliveries?.reduce((acc: DeliverySchema[], item, index) => { + if (index !== i) { + acc.push(item); + } + return acc; + }, []) ?? []; - formik.setFieldValue('deliveries', updatedDeliveries); - }, - [formik] - ); + formik.setFieldValue('deliveries', updatedDeliveries); + }, []); const bulkRemoveDelivery = useCallback(() => { const updatedDeliveries = @@ -439,33 +578,101 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ) ?? []; formik.setFieldValue('deliveries', updatedDeliveries); setSelectedDeliveries([]); - }, [formik, selectedDeliveries]); + }, [formik, selectedDeliveries, setSelectedDeliveries]); - // Cost Calculation Handlers - const handleDeliveryCostChange = useCallback( - (idx: number, value: number) => { - formik.setFieldValue(`deliveries.${idx}.delivery_cost`, value); - - const delivery = formik.values.deliveries?.[idx]; - if (delivery) { - const productQty = delivery.products.reduce( - (sum, p) => sum + (parseInt(p.product_qty.toString()) || 0), - 0 + const handleDeliverySelectAllChange = useCallback( + (e: React.ChangeEvent) => { + if (e.target.checked) { + setSelectedDeliveries( + formik.values.deliveries?.map((_, idx) => idx) ?? [] ); - if (productQty > 0 && value > 0) { - const perItem = value / productQty; - formik.setFieldValue( - `deliveries.${idx}.delivery_cost_per_item`, - perItem - ); - } else if (value === 0) { - formik.setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, 0); - } + } else { + setSelectedDeliveries([]); } }, - [formik] + [formik.values.deliveries, setSelectedDeliveries] ); + const handleDeliveryCheckboxChange = useCallback( + (e: React.ChangeEvent) => { + const idx = Number(e.target.name.replace('delivery-', '')); + if (e.target.checked) { + setSelectedDeliveries((prev) => [...prev, idx]); + } else { + setSelectedDeliveries((prev) => prev.filter((i) => i !== idx)); + } + }, + [setSelectedDeliveries] + ); + + const handleDeliveryProductChange = useCallback( + (deliveryIdx: number, val: OptionType | OptionType[] | null) => { + formik.setFieldTouched( + `deliveries.${deliveryIdx}.products.0.product`, + true + ); + formik.setFieldValue(`deliveries.${deliveryIdx}.products.0.product`, val); + formik.setFieldTouched( + `deliveries.${deliveryIdx}.products.0.product_id`, + true + ); + formik.setFieldValue( + `deliveries.${deliveryIdx}.products.0.product_id`, + (val as OptionType)?.value + ); + }, + [] + ); + + const handleDeliverySupplierChange = useCallback( + (deliveryIdx: number, val: OptionType | OptionType[] | null) => { + formik.setFieldTouched(`deliveries.${deliveryIdx}.supplier`, true); + formik.setFieldValue(`deliveries.${deliveryIdx}.supplier`, val); + formik.setFieldTouched(`deliveries.${deliveryIdx}.supplier_id`, true); + formik.setFieldValue( + `deliveries.${deliveryIdx}.supplier_id`, + (val as OptionType)?.value + ); + }, + [] + ); + + const handleDeliveryDocumentChange = useCallback( + (deliveryIdx: number, e: React.ChangeEvent) => { + const file = e.target.files?.[0]; + if (file) { + if (file.size > 5 * 1024 * 1024) { + toast.error('Ukuran dokumen maksimal 5 MB!'); + e.target.value = ''; + return; + } + formik.setFieldValue(`deliveries.${deliveryIdx}.document`, file); + } + }, + [] + ); + + const handleDeliveryCostChange = useCallback((idx: number, value: number) => { + formik.setFieldValue(`deliveries.${idx}.delivery_cost`, value); + + const delivery = formik.values.deliveries?.[idx]; + if (delivery) { + const productQty = delivery.products.reduce( + (sum, p) => sum + (parseInt(p.product_qty.toString()) || 0), + 0 + ); + if (productQty > 0 && value > 0) { + const perItem = value / productQty; + formik.setFieldValue( + `deliveries.${idx}.delivery_cost_per_item`, + perItem + ); + } else if (value === 0) { + formik.setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, 0); + } + } + }, []); + const handleDeliveryCostPerItemChange = useCallback( (idx: number, value: number) => { formik.setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, value); @@ -484,7 +691,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { } } }, - [formik] + [] ); const handleDeliveryCostChangeWrapper = useCallback( @@ -959,72 +1166,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { label='Gudang' placeholder='Pilih gudang asal...' value={formik.values.source_warehouse} - onChange={(val) => { - const newSourceWarehouseId = (val as WarehouseOptionType) - ?.value; - - if (newSourceWarehouseId) { - if ( - newSourceWarehouseId === - formik.values.destination_warehouse_id - ) { - const destinationWarehouseName = - ( - formik.values - .destination_warehouse as WarehouseOptionType - )?.label || 'gudang tujuan'; - - toast.error( - `Tidak bisa memilih gudang yang sama. Gudang asal tidak boleh sama dengan ${destinationWarehouseName}.` - ); - return; - } - } - - formik.setFieldTouched('source_warehouse', true); - formik.setFieldValue('source_warehouse', val); - formik.setFieldTouched('source_warehouse_id', true); - formik.setFieldValue( - 'source_warehouse_id', - newSourceWarehouseId - ); - - if ( - formik.errors.destination_warehouse_id === - 'Gudang tujuan tidak boleh sama dengan gudang asal!' - ) { - formik.setFieldError('destination_warehouse_id', undefined); - } - - if ( - newSourceWarehouseId && - newSourceWarehouseId !== formik.values.source_warehouse_id - ) { - formik.setFieldValue('products', [ - { - product: null, - product_id: 0, - product_qty: '', - }, - ]); - formik.setFieldTouched('products', false); - - const updatedDeliveries = formik.values.deliveries.map( - (delivery) => ({ - ...delivery, - products: [ - { - product: null, - product_id: 0, - product_qty: '', - }, - ], - }) - ); - formik.setFieldValue('deliveries', updatedDeliveries); - formik.setFieldTouched('deliveries', false); - } - }} + onChange={handleSourceWarehouseChange} options={warehouseOptions} onInputChange={setWarehouseSelectInputValue} onMenuScrollToBottom={loadMoreWarehouses} @@ -1088,41 +1230,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { label='Gudang' placeholder='Pilih gudang tujuan...' value={formik.values.destination_warehouse} - onChange={(val) => { - const newDestinationWarehouseId = (val as WarehouseOptionType) - ?.value; - - if (newDestinationWarehouseId) { - if ( - newDestinationWarehouseId === - formik.values.source_warehouse_id - ) { - const sourceWarehouseName = - (formik.values.source_warehouse as WarehouseOptionType) - ?.label || 'gudang asal'; - - toast.error( - `Tidak bisa memilih gudang yang sama. Gudang tujuan tidak boleh sama dengan ${sourceWarehouseName}.` - ); - return; - } - } - - formik.setFieldTouched('destination_warehouse', true); - formik.setFieldValue('destination_warehouse', val); - formik.setFieldTouched('destination_warehouse_id', true); - formik.setFieldValue( - 'destination_warehouse_id', - newDestinationWarehouseId - ); - - if ( - formik.errors.destination_warehouse_id === - 'Gudang tujuan tidak boleh sama dengan gudang asal!' - ) { - formik.setFieldError('destination_warehouse_id', undefined); - } - }} + onChange={handleDestinationWarehouseChange} options={warehouseOptions} onInputChange={setWarehouseSelectInputValue} isLoading={isLoadingWarehouses} @@ -1196,18 +1304,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { selectedProducts.length && formik.values.products?.length > 0 } - onChange={( - e: React.ChangeEvent - ) => { - if (e.target.checked) { - setSelectedProducts( - formik.values.products?.map((_, idx) => idx) ?? - [] - ); - } else { - setSelectedProducts([]); - } - }} + onChange={handleProductSelectAllChange} classNames={{ wrapper: 'flex justify-center', checkbox: 'checkbox checkbox-sm', @@ -1244,17 +1341,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { - ) => { - if (e.target.checked) { - setSelectedProducts([...selectedProducts, idx]); - } else { - setSelectedProducts( - selectedProducts.filter((i) => i !== idx) - ); - } - }} + onChange={handleProductCheckboxChange} classNames={{ wrapper: 'flex justify-center', checkbox: 'checkbox checkbox-sm', @@ -1266,41 +1353,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { { - formik.setFieldTouched( - `products.${idx}.product`, - true - ); - formik.setFieldValue( - `products.${idx}.product`, - val - ); - formik.setFieldTouched( - `products.${idx}.product_id`, - true - ); - formik.setFieldValue( - `products.${idx}.product_id`, - (val as ProductWarehouseOptionType)?.value - ); - - const updatedDeliveries = - formik.values.deliveries.map((delivery) => ({ - ...delivery, - products: [ - { - product: null, - product_id: 0, - product_qty: '', - }, - ], - })); - formik.setFieldValue( - 'deliveries', - updatedDeliveries - ); - formik.setFieldTouched('deliveries', false); - }} + onChange={(val) => handleProductChange(idx, val)} options={productWarehouseOptions} onInputChange={setProductWarehouseSelectInputValue} onMenuScrollToBottom={loadMoreProductWarehouses} @@ -1427,19 +1480,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { selectedDeliveries.length && formik.values.deliveries?.length > 0 } - onChange={( - e: React.ChangeEvent - ) => { - if (e.target.checked) { - setSelectedDeliveries( - formik.values.deliveries?.map( - (_, idx) => idx - ) ?? [] - ); - } else { - setSelectedDeliveries([]); - } - }} + onChange={handleDeliverySelectAllChange} classNames={{ wrapper: 'flex justify-center', checkbox: 'checkbox checkbox-sm', @@ -1522,20 +1563,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { - ) => { - if (e.target.checked) { - setSelectedDeliveries([ - ...selectedDeliveries, - idx, - ]); - } else { - setSelectedDeliveries( - selectedDeliveries.filter((i) => i !== idx) - ); - } - }} + onChange={handleDeliveryCheckboxChange} classNames={{ wrapper: 'flex justify-center', checkbox: 'checkbox checkbox-sm', @@ -1548,24 +1576,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { required placeholder='Pilih produk...' value={delivery.products[0]?.product ?? undefined} - onChange={(val) => { - formik.setFieldTouched( - `deliveries.${idx}.products.0.product`, - true - ); - formik.setFieldValue( - `deliveries.${idx}.products.0.product`, - val - ); - formik.setFieldTouched( - `deliveries.${idx}.products.0.product_id`, - true - ); - formik.setFieldValue( - `deliveries.${idx}.products.0.product_id`, - (val as OptionType)?.value - ); - }} + onChange={(val) => + handleDeliveryProductChange(idx, val) + } options={getFilteredProductWarehouseOptions()} isDisabled={type === 'detail'} isClearable @@ -1616,24 +1629,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { required placeholder='Pilih supplier...' value={delivery.supplier} - onChange={(val) => { - formik.setFieldTouched( - `deliveries.${idx}.supplier`, - true - ); - formik.setFieldValue( - `deliveries.${idx}.supplier`, - val - ); - formik.setFieldTouched( - `deliveries.${idx}.supplier_id`, - true - ); - formik.setFieldValue( - `deliveries.${idx}.supplier_id`, - (val as OptionType)?.value - ); - }} + onChange={(val) => + handleDeliverySupplierChange(idx, val) + } options={supplierOptions} onInputChange={setSupplierSelectInputValue} isLoading={isLoadingSuppliers} @@ -1725,20 +1723,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { { - const file = e.target.files?.[0]; - if (file) { - if (file.size > 5 * 1024 * 1024) { - toast.error('Ukuran dokumen maksimal 5 MB!'); - e.target.value = ''; - return; - } - formik.setFieldValue( - `deliveries.${idx}.document`, - file - ); - } - }} + onChange={(e) => + handleDeliveryDocumentChange(idx, e) + } {...isRepeaterInputError( 'deliveries', 'document',