feat(FE): Show unique form errors and improve product form

This commit is contained in:
rstubryan
2026-01-08 11:11:58 +07:00
parent 3dd4a9cebc
commit 0898892f15
3 changed files with 77 additions and 21 deletions
@@ -11,6 +11,8 @@ import TextInput from '@/components/input/TextInput';
import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
import RequirePermission from '@/components/helper/RequirePermission';
import { getUniqueFormikErrors } from '@/lib/formik-helper';
import AlertErrorList from '@/components/helper/form/FormErrors';
import {
ProductCategoryFormSchema,
@@ -39,6 +41,7 @@ const ProductCategoryForm = ({
const deleteModal = useModal();
const [formErrorMessage, setFormErrorMessage] = useState('');
const [formErrorList, setFormErrorList] = useState<string[]>([]);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const createProductCategoryHandler = useCallback(
@@ -129,6 +132,22 @@ const ProductCategoryForm = ({
formikSetValues(formikInitialValues);
}, [formikSetValues, formikInitialValues]);
const handleValidateForm = async () => {
const errors = await formik.validateForm();
if (Object.keys(errors).length > 0) {
const errorMessages = getUniqueFormikErrors(errors);
setFormErrorList(errorMessages);
return;
}
};
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
handleValidateForm();
formik.handleSubmit(e);
};
return (
<>
<section className='w-full max-w-2xl'>
@@ -150,7 +169,7 @@ const ProductCategoryForm = ({
</header>
<form
onSubmit={formik.handleSubmit}
onSubmit={handleFormSubmit}
onReset={formik.handleReset}
className='w-full mt-8 flex flex-col gap-6'
>
@@ -164,6 +183,15 @@ const ProductCategoryForm = ({
<span>{formErrorMessage}</span>
</div>
)}
{/* Error List Alert */}
{formErrorList.length > 0 && (
<AlertErrorList
formErrorList={formErrorList}
onClose={() => setFormErrorList([])}
/>
)}
<div className='flex flex-col gap-4'>
<TextInput
required
@@ -29,28 +29,28 @@ export const ProductFormSchema: Yup.ObjectSchema<ProductFormSchemaType> =
sku: Yup.string().required('SKU wajib diisi!'),
uom: Yup.object({
value: Yup.number().min(1, 'Satuan wajib dipilih!').required(),
label: Yup.string().required(),
})
.nullable()
.required('Satuan wajib diisi!'),
value: Yup.number()
.min(1, 'Satuan wajib dipilih!')
.required('Satuan wajib dipilih!'),
label: Yup.string().required('Satuan wajib dipilih!'),
}).nullable(),
uom_id: Yup.number()
.min(1, 'Satuan wajib dipilih!')
.required('Satuan wajib diisi!')
.typeError('Satuan wajib diisi!'),
.required('Satuan wajib dipilih!')
.typeError('Satuan wajib dipilih!'),
product_category: Yup.object({
value: Yup.number().min(1, 'Kategori produk wajib dipilih!').required(),
label: Yup.string().required(),
})
.nullable()
.required('Kategori produk wajib diisi!'),
value: Yup.number()
.min(1, 'Kategori produk wajib dipilih!')
.required('Kategori produk wajib dipilih!'),
label: Yup.string().required('Kategori produk wajib dipilih!'),
}).nullable(),
product_category_id: Yup.number()
.min(1, 'Kategori produk wajib dipilih!')
.required('Kategori produk wajib diisi!')
.typeError('Kategori produk wajib diisi!'),
.required('Kategori produk wajib dipilih!')
.typeError('Kategori produk wajib dipilih!'),
product_price: Yup.number()
.required('Harga produk wajib diisi!')
@@ -17,6 +17,8 @@ import SelectInput, {
import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal';
import RequirePermission from '@/components/helper/RequirePermission';
import { getUniqueFormikErrors } from '@/lib/formik-helper';
import AlertErrorList from '@/components/helper/form/FormErrors';
import {
ProductFormSchema,
@@ -48,6 +50,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
const deleteModal = useModal();
const [productFormErrorMessage, setProductFormErrorMessage] = useState('');
const [formErrorList, setFormErrorList] = useState<string[]>([]);
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const createProductHandler = useCallback(
@@ -201,6 +204,22 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
formikSetValues(formikInitialValues);
}, [formikSetValues, formikInitialValues]);
const handleValidateForm = async () => {
const errors = await formik.validateForm();
if (Object.keys(errors).length > 0) {
const errorMessages = getUniqueFormikErrors(errors);
setFormErrorList(errorMessages);
return;
}
};
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
handleValidateForm();
formik.handleSubmit(e);
};
return (
<>
<section className='w-full max-w-2xl'>
@@ -220,7 +239,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
</h1>
</header>
<form
onSubmit={formik.handleSubmit}
onSubmit={handleFormSubmit}
onReset={formik.handleReset}
className='w-full mt-8 flex flex-col gap-6'
>
@@ -234,6 +253,15 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
<span>{productFormErrorMessage}</span>
</div>
)}
{/* Error List Alert */}
{formErrorList.length > 0 && (
<AlertErrorList
formErrorList={formErrorList}
onClose={() => setFormErrorList([])}
/>
)}
<div className='grid grid-cols-1 gap-4'>
<TextInput
required
@@ -247,7 +275,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
errorMessage={formik.errors.name}
readOnly={type === 'detail'}
/>
<div className='grid grid-cols-2 gap-4'>
<div className='grid sm:grid-cols-2 gap-4'>
<TextInput
required
label='Merek'
@@ -273,7 +301,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
readOnly={type === 'detail'}
/>
</div>
<div className='grid grid-cols-2 gap-4'>
<div className='grid sm:grid-cols-2 gap-4'>
<SelectInput
required
label='Satuan'
@@ -310,7 +338,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
isClearable
/>
</div>
<div className='grid grid-cols-2 gap-4'>
<div className='grid sm:grid-cols-2 gap-4'>
<NumberInput
required
label='Harga Produk'
@@ -352,7 +380,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
readOnly={type === 'detail'}
/>
</div>
<div className='grid grid-cols-2 gap-4'>
<div className='grid sm:grid-cols-2 gap-4'>
<NumberInput
required
label='Pajak (%)'
@@ -391,7 +419,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
readOnly={type === 'detail'}
/>
</div>
<div className='grid grid-cols-2 gap-4'>
<div className='grid sm:grid-cols-2 gap-4'>
<SelectInput
required
label='Supplier'