From 757893c75789b09ce1d422771d2b71c53885ca48 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Fri, 10 Oct 2025 13:43:30 +0700 Subject: [PATCH] feat(FE-62): add quantity validation for ekspedisi in MovementForm and filter product options --- .../inventory/movement/form/MovementForm.tsx | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index 22e39642..a2dd43c8 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -30,6 +30,7 @@ import { SupplierApi, WarehouseApi, } from '@/services/api/master-data'; +import { toast } from 'react-hot-toast'; interface MovementFormProps { type?: 'add' | 'edit' | 'detail'; @@ -251,6 +252,37 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { formikSetValues(formikInitialValues); }, [formikSetValues, formikInitialValues]); + const getFilteredProductOptions = useCallback(() => { + return ( + formik.values.product + ?.filter((p) => p.product) + .map((p) => ({ + value: p.product_id, + label: (p.product as OptionType)?.label, + })) ?? [] + ); + }, [formik.values.product]); + + const validateEkspedisiQty = (ekspedisiIdx: number, qty: number) => { + const productId = formik.values.ekspedisi?.[ekspedisiIdx]?.product_id; + if (!productId) return true; + + const relatedProduct = formik.values.product?.find( + (p) => p.product_id === productId + ); + if (!relatedProduct) return true; + + const totalQtyUsed = + formik.values.ekspedisi?.reduce((total, eks, i) => { + if (eks.product_id === productId && i !== ekspedisiIdx) { + return total + (Number(eks.qty) || 0); + } + return total; + }, 0) || 0; + + return totalQtyUsed + qty <= Number(relatedProduct.qty_product); + }; + return ( <>
@@ -419,6 +451,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { + {/* Products table */}
@@ -657,10 +690,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { `ekspedisi.${idx}.product_id`, (val as OptionType)?.value ); + formik.setFieldValue(`ekspedisi.${idx}.qty`, ''); }} - options={productOptions} - onInputChange={setProductSelectInputValue} - isLoading={isLoadingProducts} + options={getFilteredProductOptions()} isDisabled={type === 'detail'} isClearable isError={isRepeaterInputError( @@ -676,7 +708,16 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { type='number' name={`ekspedisi.${idx}.qty`} value={ekspedisi.qty ?? ''} - onChange={formik.handleChange} + onChange={(e) => { + const newQty = Number(e.target.value); + if (validateEkspedisiQty(idx, newQty)) { + formik.handleChange(e); + } else { + toast.error( + 'Quantity exceeds available product quantity' + ); + } + }} onBlur={formik.handleBlur} isError={isRepeaterInputError( 'ekspedisi',