diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index f723e763..6bee5ac1 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -323,6 +323,8 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { }, }); + const { setFieldValue, setFieldTouched, setFieldError } = formik; + const prevSourceWarehouseIdRef = useRef( formik.values.source_warehouse_id ); @@ -336,14 +338,14 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { prevSourceWarehouseId !== currentSourceWarehouseId && prevSourceWarehouseId !== null ) { - formik.setFieldValue('products', [ + setFieldValue('products', [ { product: null, product_id: 0, product_qty: '', }, ]); - formik.setFieldTouched('products', false); + setFieldTouched('products', false); const updatedDeliveries = formik.values.deliveries.map( (delivery: DeliverySchema) => ({ @@ -357,12 +359,17 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ], }) ); - formik.setFieldValue('deliveries', updatedDeliveries); - formik.setFieldTouched('deliveries', false); + setFieldValue('deliveries', updatedDeliveries); + setFieldTouched('deliveries', false); } prevSourceWarehouseIdRef.current = currentSourceWarehouseId; - }, [formik.values.source_warehouse_id, formik.values.deliveries]); + }, [ + formik.values.source_warehouse_id, + formik.values.deliveries, + setFieldValue, + setFieldTouched, + ]); // ===== PRODUCT WAREHOUSE FETCHING (after form initialization) ===== const { @@ -455,9 +462,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { // ===== EVENT HANDLERS ===== const handleTransferDateChange = useCallback( (e: React.ChangeEvent) => { - formik.setFieldValue('transfer_date', e.target.value); + setFieldValue('transfer_date', e.target.value); }, - [] + [setFieldValue] ); const handleSourceWarehouseChange = useCallback( @@ -477,14 +484,16 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { return; } - formik.setFieldTouched('source_warehouse', true); - formik.setFieldValue('source_warehouse', val); - formik.setFieldTouched('source_warehouse_id', true); - formik.setFieldValue('source_warehouse_id', newSourceWarehouseId); + setFieldTouched('source_warehouse', true); + setFieldValue('source_warehouse', val); + setFieldTouched('source_warehouse_id', true); + setFieldValue('source_warehouse_id', newSourceWarehouseId); }, [ formik.values.destination_warehouse_id, formik.values.destination_warehouse, + setFieldTouched, + setFieldValue, ] ); @@ -505,15 +514,17 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { return; } - formik.setFieldTouched('destination_warehouse', true); - formik.setFieldValue('destination_warehouse', val); - formik.setFieldTouched('destination_warehouse_id', true); - formik.setFieldValue( - 'destination_warehouse_id', - newDestinationWarehouseId - ); + setFieldTouched('destination_warehouse', true); + setFieldValue('destination_warehouse', val); + setFieldTouched('destination_warehouse_id', true); + setFieldValue('destination_warehouse_id', newDestinationWarehouseId); }, - [formik.values.source_warehouse_id, formik.values.source_warehouse] + [ + formik.values.source_warehouse_id, + formik.values.source_warehouse, + setFieldTouched, + setFieldValue, + ] ); const addProduct = useCallback(() => { @@ -525,15 +536,15 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { product_qty: '', }, ]; - formik.setFieldValue('products', newProducts); - }, [formik.values.products]); + setFieldValue('products', newProducts); + }, [formik.values.products, setFieldValue]); const removeProduct = useCallback( (i: number) => { const updatedProducts = formik.values.products?.filter( (_, idx) => idx !== i ); - formik.setFieldValue('products', updatedProducts); + setFieldValue('products', updatedProducts); setSelectedProducts([]); @@ -542,7 +553,12 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { setProductQtyErrorShown(false); } }, - [formik.values.products, productQtyErrorShown, setSelectedProducts] + [ + formik.values.products, + productQtyErrorShown, + setSelectedProducts, + setFieldValue, + ] ); const bulkRemoveProduct = useCallback(() => { @@ -550,26 +566,32 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { formik.values.products?.filter( (_, idx) => !selectedProducts.includes(idx) ) ?? []; - formik.setFieldValue('products', updatedProducts); + setFieldValue('products', updatedProducts); setSelectedProducts([]); if (productQtyErrorShown) { toast.dismiss(); setProductQtyErrorShown(false); } - }, [formik, selectedProducts, setSelectedProducts, productQtyErrorShown]); + }, [ + selectedProducts, + setSelectedProducts, + productQtyErrorShown, + setFieldValue, + formik.values.products, + ]); 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( + setFieldTouched(`products.${idx}.product`, true); + setFieldValue(`products.${idx}.product`, val); + setFieldTouched(`products.${idx}.product_id`, true); + setFieldValue( `products.${idx}.product_id`, (val as ProductWarehouseOptionType)?.value ); }, - [] + [setFieldTouched, setFieldValue] ); const handleProductSelectAllChange = useCallback( @@ -596,7 +618,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ); const addDelivery = useCallback(() => { - formik.setFieldValue('deliveries', [ + setFieldValue('deliveries', [ ...(formik.values.deliveries || []), { delivery_cost: '', @@ -615,14 +637,14 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ], }, ]); - }, [formik.values.deliveries]); + }, [formik.values.deliveries, setFieldValue]); const removeDelivery = useCallback( (i: number) => { const updatedDeliveries = formik.values.deliveries?.filter( (_, idx) => idx !== i ); - formik.setFieldValue('deliveries', updatedDeliveries); + setFieldValue('deliveries', updatedDeliveries); setSelectedDeliveries([]); @@ -631,7 +653,12 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { setDeliveryQtyErrorShown(false); } }, - [formik.values.deliveries, deliveryQtyErrorShown, setSelectedDeliveries] + [ + formik.values.deliveries, + deliveryQtyErrorShown, + setSelectedDeliveries, + setFieldValue, + ] ); const bulkRemoveDelivery = useCallback(() => { @@ -639,7 +666,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { formik.values.deliveries?.filter( (_, idx) => !selectedDeliveries.includes(idx) ) ?? []; - formik.setFieldValue('deliveries', updatedDeliveries); + setFieldValue('deliveries', updatedDeliveries); setSelectedDeliveries([]); if (deliveryQtyErrorShown) { @@ -647,10 +674,11 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { setDeliveryQtyErrorShown(false); } }, [ - formik, selectedDeliveries, setSelectedDeliveries, deliveryQtyErrorShown, + setFieldValue, + formik.values.deliveries, ]); const handleDeliverySelectAllChange = useCallback( @@ -680,34 +708,28 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { 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( + setFieldTouched(`deliveries.${deliveryIdx}.products.0.product`, true); + setFieldValue(`deliveries.${deliveryIdx}.products.0.product`, val); + setFieldTouched(`deliveries.${deliveryIdx}.products.0.product_id`, true); + setFieldValue( `deliveries.${deliveryIdx}.products.0.product_id`, (val as OptionType)?.value ); }, - [] + [setFieldTouched, setFieldValue] ); 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( + setFieldTouched(`deliveries.${deliveryIdx}.supplier`, true); + setFieldValue(`deliveries.${deliveryIdx}.supplier`, val); + setFieldTouched(`deliveries.${deliveryIdx}.supplier_id`, true); + setFieldValue( `deliveries.${deliveryIdx}.supplier_id`, (val as OptionType)?.value ); }, - [] + [setFieldTouched, setFieldValue] ); const handleDeliveryDocumentChange = useCallback( @@ -719,15 +741,15 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { e.target.value = ''; return; } - formik.setFieldValue(`deliveries.${deliveryIdx}.document`, file); + setFieldValue(`deliveries.${deliveryIdx}.document`, file); } }, - [] + [setFieldValue] ); const handleDeliveryCostChange = useCallback( (idx: number, value: number) => { - formik.setFieldValue(`deliveries.${idx}.delivery_cost`, value); + setFieldValue(`deliveries.${idx}.delivery_cost`, value); const delivery = formik.values.deliveries?.[idx]; if (delivery) { @@ -737,21 +759,18 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ); if (productQty > 0 && value > 0) { const perItem = value / productQty; - formik.setFieldValue( - `deliveries.${idx}.delivery_cost_per_item`, - perItem - ); + setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, perItem); } else if (value === 0) { - formik.setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, 0); + setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, 0); } } }, - [formik.values.deliveries] + [formik.values.deliveries, setFieldValue] ); const handleDeliveryCostPerItemChange = useCallback( (idx: number, value: number) => { - formik.setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, value); + setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, value); const delivery = formik.values.deliveries?.[idx]; if (delivery) { @@ -761,13 +780,13 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ); if (productQty > 0 && value > 0) { const totalCost = value * productQty; - formik.setFieldValue(`deliveries.${idx}.delivery_cost`, totalCost); + setFieldValue(`deliveries.${idx}.delivery_cost`, totalCost); } else if (value === 0) { - formik.setFieldValue(`deliveries.${idx}.delivery_cost`, 0); + setFieldValue(`deliveries.${idx}.delivery_cost`, 0); } } }, - [formik.values.deliveries] + [formik.values.deliveries, setFieldValue] ); const handleDeliveryCostChangeWrapper = useCallback( @@ -1044,12 +1063,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { return !validateDeliveryQty(deliveryIdx, productIdx, qty); }) ) ?? []), - [ - formik.values.deliveries, - formik.values.products, - validateDeliveryQty, - type, - ] + [formik.values.deliveries, validateDeliveryQty, type] ); const hasInvalidQty = useMemo( @@ -1066,6 +1080,27 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ); }, [formik.values.products, getProductQtyError, type]); + const deliveryCostDepString = useMemo( + () => + formik.values.deliveries + ?.map((d, idx) => ({ + idx, + productQty: d.products.reduce( + (sum, p) => sum + (parseInt(p.product_qty.toString()) || 0), + 0 + ), + deliveryCost: parseInt((d.delivery_cost || '').toString()) || 0, + deliveryCostPerItem: + parseInt((d.delivery_cost_per_item || '').toString()) || 0, + })) + .map( + (item) => + `${item.idx}:${item.productQty}:${item.deliveryCost}:${item.deliveryCostPerItem}` + ) + .join('|'), + [formik.values.deliveries] + ); + // ===== EFFECTS ===== useEffect(() => { formik.values.deliveries?.forEach((delivery, idx) => { @@ -1082,36 +1117,16 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { if (deliveryCost > 0 && productQty > 0) { const perItem = deliveryCost / productQty; if (Math.abs(deliveryCostPerItem - perItem) > 0.01) { - formik.setFieldValue( - `deliveries.${idx}.delivery_cost_per_item`, - perItem - ); + setFieldValue(`deliveries.${idx}.delivery_cost_per_item`, perItem); } } else if (deliveryCostPerItem > 0 && productQty > 0) { const totalCost = deliveryCostPerItem * productQty; if (Math.abs(deliveryCost - totalCost) > 0.01) { - formik.setFieldValue(`deliveries.${idx}.delivery_cost`, totalCost); + setFieldValue(`deliveries.${idx}.delivery_cost`, totalCost); } } }); - }, [ - formik.values.deliveries - ?.map((d, idx) => ({ - idx, - productQty: d.products.reduce( - (sum, p) => sum + (parseInt(p.product_qty.toString()) || 0), - 0 - ), - deliveryCost: parseInt((d.delivery_cost || '').toString()) || 0, - deliveryCostPerItem: - parseInt((d.delivery_cost_per_item || '').toString()) || 0, - })) - .map( - (item) => - `${item.idx}:${item.productQty}:${item.deliveryCost}:${item.deliveryCostPerItem}` - ) - .join('|'), - ]); + }, [deliveryCostDepString, setFieldValue, formik.values.deliveries]); useEffect(() => { if ( @@ -1121,7 +1136,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { !isInitialized ) { if (formik.values.products.length === 0) { - formik.setFieldValue('products', [ + setFieldValue('products', [ { product: null, product_id: 0, @@ -1130,7 +1145,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ]); } if (formik.values.deliveries.length === 0) { - formik.setFieldValue('deliveries', [ + setFieldValue('deliveries', [ { delivery_cost: undefined, delivery_cost_per_item: undefined, @@ -1152,7 +1167,14 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { } setIsInitialized(true); } - }, [formik.values.source_warehouse_id, isInitialized, type]); + }, [ + formik.values.source_warehouse_id, + isInitialized, + type, + setFieldValue, + formik.values.products.length, + formik.values.deliveries.length, + ]); useEffect(() => { if ( @@ -1161,7 +1183,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { formik.values.source_warehouse_id === formik.values.destination_warehouse_id ) { - formik.setFieldError( + setFieldError( 'destination_warehouse_id', 'Gudang tujuan tidak boleh sama dengan gudang asal!' ); @@ -1170,13 +1192,14 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { formik.errors.destination_warehouse_id === 'Gudang tujuan tidak boleh sama dengan gudang asal!' ) { - formik.setFieldError('destination_warehouse_id', undefined); + setFieldError('destination_warehouse_id', undefined); } } }, [ formik.values.source_warehouse_id, formik.values.destination_warehouse_id, formik.errors.destination_warehouse_id, + setFieldError, ]); useEffect(() => { @@ -1212,29 +1235,37 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ); if (hasChanges) { - formik.setFieldValue('deliveries', updatedDeliveries); + setFieldValue('deliveries', updatedDeliveries); } } - }, [formik.values.products]); + }, [formik.values.products, formik.values.deliveries, setFieldValue]); + + const productQtyDepString = useMemo( + () => formik.values.products?.map((p) => p.product_qty).join(','), + [formik.values.products] + ); useEffect(() => { if (productQtyErrorShown) { toast.dismiss(); setProductQtyErrorShown(false); } - }, [formik.values.products?.map((p) => p.product_qty).join(',')]); + }, [productQtyErrorShown]); + + const deliveryProductQtyDepString = useMemo( + () => + formik.values.deliveries + ?.map((d) => d.products.map((p) => p.product_qty).join(',')) + .join('|'), + [formik.values.deliveries] + ); useEffect(() => { if (deliveryQtyErrorShown) { toast.dismiss(); setDeliveryQtyErrorShown(false); } - }, [ - formik.values.deliveries - ?.map((d) => d.products.map((p) => p.product_qty).join(',')) - .join('|'), - formik.values.products?.map((p) => p.product_qty).join(','), - ]); + }, [deliveryProductQtyDepString, productQtyDepString, deliveryQtyErrorShown]); useEffect(() => { if (hasExceededStock && !productQtyErrorShown && type !== 'detail') {