diff --git a/src/components/helper/form/FormActions.tsx b/src/components/helper/form/FormActions.tsx index 25b586c3..7ced46cd 100644 --- a/src/components/helper/form/FormActions.tsx +++ b/src/components/helper/form/FormActions.tsx @@ -8,6 +8,7 @@ interface FormActionsProps { formik: FormikContextType; editUrl?: string; onDelete?: () => void; + disableSubmit?: boolean; } export const FormActions = ({ @@ -15,6 +16,7 @@ export const FormActions = ({ formik, editUrl, onDelete, + disableSubmit = false, }: FormActionsProps) => { return (
@@ -71,7 +73,7 @@ export const FormActions = ({ color='primary' className='px-4' isLoading={formik.isSubmitting} - disabled={!formik.isValid || formik.isSubmitting} + disabled={disableSubmit || !formik.isValid || formik.isSubmitting} > Submit diff --git a/src/components/pages/inventory/movement/form/MovementForm.schema.ts b/src/components/pages/inventory/movement/form/MovementForm.schema.ts index aa138cac..453ca40b 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.schema.ts +++ b/src/components/pages/inventory/movement/form/MovementForm.schema.ts @@ -51,7 +51,17 @@ const EkspedisiObjectSchema: Yup.ObjectSchema = Yup.object({ qty: Yup.number() .required('Qty wajib diisi!') .min(1, 'Qty minimal 1!') - .typeError('Qty harus berupa angka!'), + .typeError('Qty harus berupa angka!') + .test('max-product-qty', 'Qty melebihi stok produk!', function (value) { + const { product_id } = this.parent; + const products = (this.options.context?.product ?? []) as { + product_id: number; + qty_product: number; + }[]; + const product = products.find((p) => p.product_id === product_id); + if (!product) return true; + return (value ?? 0) <= Number(product.qty_product); + }), supplier: Yup.object({ value: Yup.number().min(1).required(), label: Yup.string().required(), @@ -108,8 +118,12 @@ export const MovementFormSchema = Yup.object({ .typeError('Gudang tujuan wajib diisi!'), product: Yup.array() .of(ProductObjectSchema) - .min(1, 'Minimal harus ada 1 produk!'), - ekspedisi: Yup.array().of(EkspedisiObjectSchema).optional().default([]), + .min(1, 'Minimal harus ada 1 produk!') + .required('Produk wajib diisi!'), + ekspedisi: Yup.array() + .of(EkspedisiObjectSchema) + .min(1, 'Minimal harus ada 1 ekspedisi!') + .required('Ekspedisi wajib diisi!'), }); export const UpdateMovementFormSchema = MovementFormSchema; diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index 89546442..e5c90f4a 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -1,7 +1,7 @@ 'use client'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useFormik } from 'formik'; +import { FormikProps, useFormik } from 'formik'; import useSWR from 'swr'; import { Icon } from '@iconify/react'; @@ -32,13 +32,30 @@ import { } from '@/services/api/master-data'; import { toast } from 'react-hot-toast'; import FileInput from '@/components/input/FileInput'; -import { containsFile } from '@/lib/form-data'; interface MovementFormProps { type?: 'add' | 'edit' | 'detail'; initialValues?: Movement; } +function getEkspedisiFieldError( + formik: FormikProps, + idx: number, + field: keyof EkspedisiSchema +) { + const errorObj = formik.errors.ekspedisi?.[idx]; + const touched = formik.touched.ekspedisi?.[idx]?.[field]; + const isError = + touched && + typeof errorObj === 'object' && + !!(errorObj as Record)?.[field]; + const errorMessage = + typeof errorObj === 'object' + ? (errorObj as Record)?.[field] + : undefined; + return { isError, errorMessage }; +} + const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { const [, setMovementFormErrorMessage] = useState(''); const [selectedProducts, setSelectedProducts] = useState([]); @@ -63,12 +80,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { initialValues: formikInitialValues, validationSchema: type === 'edit' ? UpdateMovementFormSchema : MovementFormSchema, + validateOnChange: true, + validateOnBlur: true, onSubmit: async (values) => { - console.log( - 'Dokumen:', - values.ekspedisi?.map((e) => e.dokumen) - ); - setMovementFormErrorMessage(''); const payload: CreateMovementPayload = { alasan_transfer: values.alasan_transfer, @@ -95,9 +109,6 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { })), }; - console.log('containsFile:', containsFile(payload)); - console.log('payload:', payload); - switch (type) { case 'add': await createMovementHandler(payload); @@ -292,12 +303,10 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { 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) { @@ -305,10 +314,15 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { } return total; }, 0) || 0; - return totalQtyUsed + qty <= Number(relatedProduct.qty_product); }; + const invalidQtyRows = + formik.values.ekspedisi?.map((eks, idx) => { + const qty = Number(eks.qty) || 0; + return !validateEkspedisiQty(idx, qty); + }) ?? []; + return ( <>
@@ -735,22 +749,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { type='number' name={`ekspedisi.${idx}.qty`} value={ekspedisi.qty ?? ''} - onChange={(e) => { - const newQty = Number(e.target.value); - if (validateEkspedisiQty(idx, newQty)) { - formik.handleChange(e); - } else { - toast.error( - 'Quantity exceeds available product quantity' - ); - } - }} + onChange={formik.handleChange} onBlur={formik.handleBlur} - isError={isRepeaterInputError( - 'ekspedisi', - 'qty', - idx - )} + {...getEkspedisiFieldError(formik, idx, 'qty')} readOnly={type === 'detail'} className={{ wrapper: 'w-full min-w-24', @@ -790,10 +791,10 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { value={ekspedisi.plat_nomor ?? ''} onChange={formik.handleChange} onBlur={formik.handleBlur} - isError={isRepeaterInputError( - 'ekspedisi', - 'plat_nomor', - idx + {...getEkspedisiFieldError( + formik, + idx, + 'plat_nomor' )} readOnly={type === 'detail'} className={{ @@ -808,10 +809,10 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { value={ekspedisi.no_surat_jalan ?? ''} onChange={formik.handleChange} onBlur={formik.handleBlur} - isError={isRepeaterInputError( - 'ekspedisi', - 'no_surat_jalan', - idx + {...getEkspedisiFieldError( + formik, + idx, + 'no_surat_jalan' )} readOnly={type === 'detail'} className={{ @@ -866,10 +867,10 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { value={ekspedisi.biaya_ekspedisi ?? ''} onChange={formik.handleChange} onBlur={formik.handleBlur} - isError={isRepeaterInputError( - 'ekspedisi', - 'biaya_ekspedisi', - idx + {...getEkspedisiFieldError( + formik, + idx, + 'biaya_ekspedisi' )} readOnly={type === 'detail'} className={{ @@ -882,10 +883,10 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { disabled onChange={formik.handleChange} onBlur={formik.handleBlur} - isError={isRepeaterInputError( - 'ekspedisi', - 'biaya_ekspedisi_per_item', - idx + {...getEkspedisiFieldError( + formik, + idx, + 'biaya_ekspedisi_per_item' )} name={`ekspedisi.${idx}.biaya_ekspedisi_per_item`} value={ @@ -909,10 +910,10 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { value={ekspedisi.nama_sopir ?? ''} onChange={formik.handleChange} onBlur={formik.handleBlur} - isError={isRepeaterInputError( - 'ekspedisi', - 'nama_sopir', - idx + {...getEkspedisiFieldError( + formik, + idx, + 'nama_sopir' )} readOnly={type === 'detail'} className={{ @@ -982,6 +983,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { : undefined } onDelete={deleteMovementClickHandler} + disableSubmit={invalidQtyRows.some(Boolean)} /> {movementFormErrorMessage && (