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 { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModal from '@/components/modal/ConfirmationModal';
import RequirePermission from '@/components/helper/RequirePermission'; import RequirePermission from '@/components/helper/RequirePermission';
import { getUniqueFormikErrors } from '@/lib/formik-helper';
import AlertErrorList from '@/components/helper/form/FormErrors';
import { import {
ProductCategoryFormSchema, ProductCategoryFormSchema,
@@ -39,6 +41,7 @@ const ProductCategoryForm = ({
const deleteModal = useModal(); const deleteModal = useModal();
const [formErrorMessage, setFormErrorMessage] = useState(''); const [formErrorMessage, setFormErrorMessage] = useState('');
const [formErrorList, setFormErrorList] = useState<string[]>([]);
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const createProductCategoryHandler = useCallback( const createProductCategoryHandler = useCallback(
@@ -129,6 +132,22 @@ const ProductCategoryForm = ({
formikSetValues(formikInitialValues); formikSetValues(formikInitialValues);
}, [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 ( return (
<> <>
<section className='w-full max-w-2xl'> <section className='w-full max-w-2xl'>
@@ -150,7 +169,7 @@ const ProductCategoryForm = ({
</header> </header>
<form <form
onSubmit={formik.handleSubmit} onSubmit={handleFormSubmit}
onReset={formik.handleReset} onReset={formik.handleReset}
className='w-full mt-8 flex flex-col gap-6' className='w-full mt-8 flex flex-col gap-6'
> >
@@ -164,6 +183,15 @@ const ProductCategoryForm = ({
<span>{formErrorMessage}</span> <span>{formErrorMessage}</span>
</div> </div>
)} )}
{/* Error List Alert */}
{formErrorList.length > 0 && (
<AlertErrorList
formErrorList={formErrorList}
onClose={() => setFormErrorList([])}
/>
)}
<div className='flex flex-col gap-4'> <div className='flex flex-col gap-4'>
<TextInput <TextInput
required required
@@ -29,28 +29,28 @@ export const ProductFormSchema: Yup.ObjectSchema<ProductFormSchemaType> =
sku: Yup.string().required('SKU wajib diisi!'), sku: Yup.string().required('SKU wajib diisi!'),
uom: Yup.object({ uom: Yup.object({
value: Yup.number().min(1, 'Satuan wajib dipilih!').required(), value: Yup.number()
label: Yup.string().required(), .min(1, 'Satuan wajib dipilih!')
}) .required('Satuan wajib dipilih!'),
.nullable() label: Yup.string().required('Satuan wajib dipilih!'),
.required('Satuan wajib diisi!'), }).nullable(),
uom_id: Yup.number() uom_id: Yup.number()
.min(1, 'Satuan wajib dipilih!') .min(1, 'Satuan wajib dipilih!')
.required('Satuan wajib diisi!') .required('Satuan wajib dipilih!')
.typeError('Satuan wajib diisi!'), .typeError('Satuan wajib dipilih!'),
product_category: Yup.object({ product_category: Yup.object({
value: Yup.number().min(1, 'Kategori produk wajib dipilih!').required(), value: Yup.number()
label: Yup.string().required(), .min(1, 'Kategori produk wajib dipilih!')
}) .required('Kategori produk wajib dipilih!'),
.nullable() label: Yup.string().required('Kategori produk wajib dipilih!'),
.required('Kategori produk wajib diisi!'), }).nullable(),
product_category_id: Yup.number() product_category_id: Yup.number()
.min(1, 'Kategori produk wajib dipilih!') .min(1, 'Kategori produk wajib dipilih!')
.required('Kategori produk wajib diisi!') .required('Kategori produk wajib dipilih!')
.typeError('Kategori produk wajib diisi!'), .typeError('Kategori produk wajib dipilih!'),
product_price: Yup.number() product_price: Yup.number()
.required('Harga produk wajib diisi!') .required('Harga produk wajib diisi!')
@@ -17,6 +17,8 @@ import SelectInput, {
import { useModal } from '@/components/Modal'; import { useModal } from '@/components/Modal';
import ConfirmationModal from '@/components/modal/ConfirmationModal'; import ConfirmationModal from '@/components/modal/ConfirmationModal';
import RequirePermission from '@/components/helper/RequirePermission'; import RequirePermission from '@/components/helper/RequirePermission';
import { getUniqueFormikErrors } from '@/lib/formik-helper';
import AlertErrorList from '@/components/helper/form/FormErrors';
import { import {
ProductFormSchema, ProductFormSchema,
@@ -48,6 +50,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
const deleteModal = useModal(); const deleteModal = useModal();
const [productFormErrorMessage, setProductFormErrorMessage] = useState(''); const [productFormErrorMessage, setProductFormErrorMessage] = useState('');
const [formErrorList, setFormErrorList] = useState<string[]>([]);
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const createProductHandler = useCallback( const createProductHandler = useCallback(
@@ -201,6 +204,22 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
formikSetValues(formikInitialValues); formikSetValues(formikInitialValues);
}, [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 ( return (
<> <>
<section className='w-full max-w-2xl'> <section className='w-full max-w-2xl'>
@@ -220,7 +239,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
</h1> </h1>
</header> </header>
<form <form
onSubmit={formik.handleSubmit} onSubmit={handleFormSubmit}
onReset={formik.handleReset} onReset={formik.handleReset}
className='w-full mt-8 flex flex-col gap-6' className='w-full mt-8 flex flex-col gap-6'
> >
@@ -234,6 +253,15 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
<span>{productFormErrorMessage}</span> <span>{productFormErrorMessage}</span>
</div> </div>
)} )}
{/* Error List Alert */}
{formErrorList.length > 0 && (
<AlertErrorList
formErrorList={formErrorList}
onClose={() => setFormErrorList([])}
/>
)}
<div className='grid grid-cols-1 gap-4'> <div className='grid grid-cols-1 gap-4'>
<TextInput <TextInput
required required
@@ -247,7 +275,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
errorMessage={formik.errors.name} errorMessage={formik.errors.name}
readOnly={type === 'detail'} readOnly={type === 'detail'}
/> />
<div className='grid grid-cols-2 gap-4'> <div className='grid sm:grid-cols-2 gap-4'>
<TextInput <TextInput
required required
label='Merek' label='Merek'
@@ -273,7 +301,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
readOnly={type === 'detail'} readOnly={type === 'detail'}
/> />
</div> </div>
<div className='grid grid-cols-2 gap-4'> <div className='grid sm:grid-cols-2 gap-4'>
<SelectInput <SelectInput
required required
label='Satuan' label='Satuan'
@@ -310,7 +338,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
isClearable isClearable
/> />
</div> </div>
<div className='grid grid-cols-2 gap-4'> <div className='grid sm:grid-cols-2 gap-4'>
<NumberInput <NumberInput
required required
label='Harga Produk' label='Harga Produk'
@@ -352,7 +380,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
readOnly={type === 'detail'} readOnly={type === 'detail'}
/> />
</div> </div>
<div className='grid grid-cols-2 gap-4'> <div className='grid sm:grid-cols-2 gap-4'>
<NumberInput <NumberInput
required required
label='Pajak (%)' label='Pajak (%)'
@@ -391,7 +419,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
readOnly={type === 'detail'} readOnly={type === 'detail'}
/> />
</div> </div>
<div className='grid grid-cols-2 gap-4'> <div className='grid sm:grid-cols-2 gap-4'>
<SelectInput <SelectInput
required required
label='Supplier' label='Supplier'