mirror of
https://gitlab.com/mbugroup/lti-web-client.git
synced 2026-05-20 13:32:00 +00:00
Merge branch 'dev/randy' into 'development'
[FEAT/FE] Adding helper function for alert list error formik See merge request mbugroup/lti-web-client!144
This commit is contained in:
@@ -0,0 +1,46 @@
|
|||||||
|
import Alert from '@/components/Alert';
|
||||||
|
import Button from '@/components/Button';
|
||||||
|
import { Icon } from '@iconify/react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alert Unique Error List
|
||||||
|
* @param formErrorList - Array of error messages
|
||||||
|
* @param onClose - Function to close the alert
|
||||||
|
*/
|
||||||
|
const AlertErrorList = ({
|
||||||
|
formErrorList,
|
||||||
|
onClose,
|
||||||
|
}: {
|
||||||
|
formErrorList: string[];
|
||||||
|
onClose: () => void;
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Alert color='error' className='flex flex-col gap-2 px-4 m-4'>
|
||||||
|
<div className='flex justify-between items-center gap-2 w-full'>
|
||||||
|
<div className='flex items-center gap-2'>
|
||||||
|
<Icon icon='material-symbols:error-outline' width={24} height={24} />
|
||||||
|
<span className='font-semibold'>
|
||||||
|
Terdapat {formErrorList.length} error pada form:
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
onClick={onClose}
|
||||||
|
variant='link'
|
||||||
|
className='ml-auto p-0 w-fit text-white'
|
||||||
|
color='none'
|
||||||
|
>
|
||||||
|
<Icon icon='material-symbols:close' width={24} height={24} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<ul className='list-disc list-inside pl-8 space-y-1 w-full'>
|
||||||
|
{formErrorList.map((error, index) => (
|
||||||
|
<li key={index} className='text-sm'>
|
||||||
|
{error}
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AlertErrorList;
|
||||||
@@ -309,7 +309,7 @@ const useApprovalSteps = ({
|
|||||||
moduleId: string;
|
moduleId: string;
|
||||||
params?: {
|
params?: {
|
||||||
page?: number;
|
page?: number;
|
||||||
limit: number;
|
limit: number | string;
|
||||||
search?: string;
|
search?: string;
|
||||||
group_step_number?: boolean;
|
group_step_number?: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ const InventoryAdjustmentForm = ({
|
|||||||
|
|
||||||
const warehouseUrl = `${WarehouseApi.basePath}?${new URLSearchParams({
|
const warehouseUrl = `${WarehouseApi.basePath}?${new URLSearchParams({
|
||||||
search: '',
|
search: '',
|
||||||
|
limit: '100',
|
||||||
}).toString()}`;
|
}).toString()}`;
|
||||||
const { data: warehouses, isLoading: isLoadingWarehouses } = useSWR(
|
const { data: warehouses, isLoading: isLoadingWarehouses } = useSWR(
|
||||||
warehouseUrl,
|
warehouseUrl,
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ import DeliveryOrderProductForm from '@/components/pages/marketing/form/repeater
|
|||||||
import { SalesOrderProductFormValues } from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema';
|
import { SalesOrderProductFormValues } from '@/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema';
|
||||||
import { DeliveryOrderProductFormValues } from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema';
|
import { DeliveryOrderProductFormValues } from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema';
|
||||||
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';
|
||||||
|
|
||||||
const MemoizedSalesOrderProductTable = memo(SalesOrderProductTable);
|
const MemoizedSalesOrderProductTable = memo(SalesOrderProductTable);
|
||||||
const MemoizedSalesOrderProductForm = memo(SalesOrderProductForm);
|
const MemoizedSalesOrderProductForm = memo(SalesOrderProductForm);
|
||||||
@@ -217,6 +219,7 @@ const MarketingForm = ({
|
|||||||
const [deliveryFormState, setDeliveryFormState] = useState<'add' | 'edit'>(
|
const [deliveryFormState, setDeliveryFormState] = useState<'add' | 'edit'>(
|
||||||
'add'
|
'add'
|
||||||
);
|
);
|
||||||
|
const [formErrorList, setFormErrorList] = useState<string[]>([]);
|
||||||
const [deliveryOrderValues, setDeliveryOrderValues] = useState<
|
const [deliveryOrderValues, setDeliveryOrderValues] = useState<
|
||||||
DeliveryOrderProductFormValues[]
|
DeliveryOrderProductFormValues[]
|
||||||
>(
|
>(
|
||||||
@@ -558,11 +561,28 @@ const MarketingForm = ({
|
|||||||
);
|
);
|
||||||
}, [memoSalesOrder]);
|
}, [memoSalesOrder]);
|
||||||
|
|
||||||
|
const handleValidateForm = async () => {
|
||||||
|
const errors = await formik.validateForm();
|
||||||
|
|
||||||
|
if (Object.keys(errors).length > 0) {
|
||||||
|
// Parse and display errors
|
||||||
|
const errorMessages = getUniqueFormikErrors(errors);
|
||||||
|
setFormErrorList(errorMessages);
|
||||||
|
return; // Stop submission
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleValidateForm();
|
||||||
|
formik.handleSubmit();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<form
|
<form
|
||||||
className='flex flex-col gap-4'
|
className='flex flex-col gap-4'
|
||||||
onSubmit={formik.handleSubmit}
|
onSubmit={handleFormSubmit}
|
||||||
onReset={formik.handleReset}
|
onReset={formik.handleReset}
|
||||||
>
|
>
|
||||||
<FormHeader
|
<FormHeader
|
||||||
@@ -666,6 +686,14 @@ const MarketingForm = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Error List Alert */}
|
||||||
|
{formErrorList.length > 0 && (
|
||||||
|
<AlertErrorList
|
||||||
|
formErrorList={formErrorList}
|
||||||
|
onClose={() => setFormErrorList([])}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Form Actions */}
|
{/* Form Actions */}
|
||||||
<div className='flex flex-row items-start justify-center gap-2 mt-4'>
|
<div className='flex flex-row items-start justify-center gap-2 mt-4'>
|
||||||
<Button type='reset' color='warning' disabled={formik.isSubmitting}>
|
<Button type='reset' color='warning' disabled={formik.isSubmitting}>
|
||||||
@@ -673,7 +701,7 @@ const MarketingForm = ({
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type='submit'
|
type='submit'
|
||||||
disabled={!formik.isValid || formik.isSubmitting}
|
disabled={formik.isSubmitting}
|
||||||
isLoading={formik.isSubmitting}
|
isLoading={formik.isSubmitting}
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
|
|||||||
+29
-6
@@ -16,6 +16,8 @@ import Badge from '@/components/Badge';
|
|||||||
import { SalesProductToFieldValues } from '@/components/pages/marketing/form/MarketingForm';
|
import { SalesProductToFieldValues } from '@/components/pages/marketing/form/MarketingForm';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import { getUniqueFormikErrors } from '@/lib/formik-helper';
|
||||||
|
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||||
|
|
||||||
const DeliveryOrderProductForm = ({
|
const DeliveryOrderProductForm = ({
|
||||||
formState,
|
formState,
|
||||||
@@ -40,6 +42,8 @@ const DeliveryOrderProductForm = ({
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
const [currentInput, setCurrentInput] = useState<string>('');
|
const [currentInput, setCurrentInput] = useState<string>('');
|
||||||
|
const [formErrorList, setFormErrorList] = useState<string[]>([]);
|
||||||
|
|
||||||
const salesOrder = salesOrders.find(
|
const salesOrder = salesOrders.find(
|
||||||
(item) => item.id === initialValues?.marketing_product_id
|
(item) => item.id === initialValues?.marketing_product_id
|
||||||
);
|
);
|
||||||
@@ -164,15 +168,27 @@ const DeliveryOrderProductForm = ({
|
|||||||
}
|
}
|
||||||
}, [initialValues]);
|
}, [initialValues]);
|
||||||
|
|
||||||
|
const handleValidateForm = () => {
|
||||||
|
formik.validateForm();
|
||||||
|
const formErrorList = getUniqueFormikErrors(formik.errors);
|
||||||
|
setFormErrorList(formErrorList);
|
||||||
|
if (formErrorList.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleBlurField(currentInput);
|
||||||
|
handleValidateForm();
|
||||||
|
formik.handleSubmit(e);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<form
|
<form
|
||||||
className='size-full'
|
className='size-full'
|
||||||
onSubmit={(e) => {
|
onSubmit={handleFormSubmit}
|
||||||
e.preventDefault();
|
|
||||||
handleBlurField(currentInput);
|
|
||||||
formik.handleSubmit(e);
|
|
||||||
}}
|
|
||||||
onReset={handleResetForm}
|
onReset={handleResetForm}
|
||||||
>
|
>
|
||||||
{formikErrorMessage && (
|
{formikErrorMessage && (
|
||||||
@@ -372,6 +388,13 @@ const DeliveryOrderProductForm = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{formErrorList.length > 0 && (
|
||||||
|
<AlertErrorList
|
||||||
|
formErrorList={formErrorList}
|
||||||
|
onClose={() => setFormErrorList([])}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className='flex flex-row justify-end gap-3 mt-4'>
|
<div className='flex flex-row justify-end gap-3 mt-4'>
|
||||||
<Button type='reset' color='warning'>
|
<Button type='reset' color='warning'>
|
||||||
Reset
|
Reset
|
||||||
@@ -379,7 +402,7 @@ const DeliveryOrderProductForm = ({
|
|||||||
<Button
|
<Button
|
||||||
type='submit'
|
type='submit'
|
||||||
isLoading={formik.isSubmitting}
|
isLoading={formik.isSubmitting}
|
||||||
disabled={!formik.isValid || formik.isSubmitting}
|
disabled={formik.isSubmitting}
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
+8
-4
@@ -25,15 +25,19 @@ export const SalesOrderProductSchema: Yup.ObjectSchema<SalesOrderProductSchemaTy
|
|||||||
id: Yup.number(),
|
id: Yup.number(),
|
||||||
vehicle_number: Yup.string().required('Nomor Kendaraan wajib diisi!'),
|
vehicle_number: Yup.string().required('Nomor Kendaraan wajib diisi!'),
|
||||||
kandang: Yup.object({
|
kandang: Yup.object({
|
||||||
value: Yup.number().min(1).required(),
|
value: Yup.number()
|
||||||
label: Yup.string().required(),
|
.min(1, 'Kandang wajib diisi!')
|
||||||
|
.required('Kandang wajib diisi!'),
|
||||||
|
label: Yup.string().required('Kandang wajib diisi!'),
|
||||||
}).nullable(),
|
}).nullable(),
|
||||||
kandang_id: Yup.number()
|
kandang_id: Yup.number()
|
||||||
.min(1, 'Kandang wajib diisi!')
|
.min(1, 'Kandang wajib diisi!')
|
||||||
.required('Kandang wajib diisi!'),
|
.required('Kandang wajib diisi!'),
|
||||||
product_warehouse: Yup.object({
|
product_warehouse: Yup.object({
|
||||||
value: Yup.number().min(1).required(),
|
value: Yup.number()
|
||||||
label: Yup.string().required(),
|
.min(1, 'Produk wajib diisi!')
|
||||||
|
.required('Produk wajib diisi!'),
|
||||||
|
label: Yup.string().required('Produk wajib diisi!'),
|
||||||
}).nullable(),
|
}).nullable(),
|
||||||
product_warehouse_id: Yup.number()
|
product_warehouse_id: Yup.number()
|
||||||
.min(1, 'Produk wajib diisi!')
|
.min(1, 'Produk wajib diisi!')
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import {
|
|||||||
} from '@/lib/helper';
|
} from '@/lib/helper';
|
||||||
import PatternInput from '@/components/input/PatternInput';
|
import PatternInput from '@/components/input/PatternInput';
|
||||||
import Alert from '@/components/Alert';
|
import Alert from '@/components/Alert';
|
||||||
|
import { getUniqueFormikErrors } from '@/lib/formik-helper';
|
||||||
|
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||||
|
|
||||||
const SalesOrderProductForm = ({
|
const SalesOrderProductForm = ({
|
||||||
initialValues,
|
initialValues,
|
||||||
@@ -37,6 +39,7 @@ const SalesOrderProductForm = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const [formErrorMessage, setFormErrorMessage] = useState('');
|
const [formErrorMessage, setFormErrorMessage] = useState('');
|
||||||
const [currentInput, setCurrentInput] = useState<string>('');
|
const [currentInput, setCurrentInput] = useState<string>('');
|
||||||
|
const [formErrorList, setFormErrorList] = useState<string[]>([]);
|
||||||
|
|
||||||
// ============ Formik ============
|
// ============ Formik ============
|
||||||
const formik = useFormik<SalesOrderProductFormValues>({
|
const formik = useFormik<SalesOrderProductFormValues>({
|
||||||
@@ -169,15 +172,29 @@ const SalesOrderProductForm = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleValidateForm = async () => {
|
||||||
|
const errors = await formik.validateForm();
|
||||||
|
|
||||||
|
if (Object.keys(errors).length > 0) {
|
||||||
|
// Parse and display errors
|
||||||
|
const errorMessages = getUniqueFormikErrors(errors);
|
||||||
|
setFormErrorList(errorMessages);
|
||||||
|
return; // Stop submission
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFormSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleBlurField(currentInput);
|
||||||
|
handleValidateForm();
|
||||||
|
formik.handleSubmit(e);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<form
|
<form
|
||||||
className='size-full'
|
className='size-full'
|
||||||
onSubmit={(e) => {
|
onSubmit={handleFormSubmit}
|
||||||
e.preventDefault();
|
|
||||||
handleBlurField(currentInput);
|
|
||||||
formik.handleSubmit(e);
|
|
||||||
}}
|
|
||||||
onReset={handleResetForm}
|
onReset={handleResetForm}
|
||||||
>
|
>
|
||||||
{formErrorMessage && (
|
{formErrorMessage && (
|
||||||
@@ -338,6 +355,15 @@ const SalesOrderProductForm = ({
|
|||||||
placeholder='Masukan Total Penjualan'
|
placeholder='Masukan Total Penjualan'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Error List Alert */}
|
||||||
|
{formErrorList.length > 0 && (
|
||||||
|
<AlertErrorList
|
||||||
|
formErrorList={formErrorList}
|
||||||
|
onClose={() => setFormErrorList([])}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className='flex flex-row justify-end gap-3 mt-4'>
|
<div className='flex flex-row justify-end gap-3 mt-4'>
|
||||||
<Button type='reset' color='warning' onClick={handleResetForm}>
|
<Button type='reset' color='warning' onClick={handleResetForm}>
|
||||||
Reset
|
Reset
|
||||||
@@ -345,7 +371,7 @@ const SalesOrderProductForm = ({
|
|||||||
<Button
|
<Button
|
||||||
type='submit'
|
type='submit'
|
||||||
isLoading={formik.isSubmitting}
|
isLoading={formik.isSubmitting}
|
||||||
disabled={!formik.isValid || formik.isSubmitting}
|
disabled={formik.isSubmitting}
|
||||||
>
|
>
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { Icon } from '@iconify/react';
|
|||||||
import Badge from '@/components/Badge';
|
import Badge from '@/components/Badge';
|
||||||
import { CHICKINS_APPROVAL_LINE } from '@/config/approval-line';
|
import { CHICKINS_APPROVAL_LINE } from '@/config/approval-line';
|
||||||
import RequirePermission from '@/components/helper/RequirePermission';
|
import RequirePermission from '@/components/helper/RequirePermission';
|
||||||
|
import { BaseApproval } from '@/types/api/api-general';
|
||||||
const ChickinFormKandang = ({
|
const ChickinFormKandang = ({
|
||||||
formType = 'add',
|
formType = 'add',
|
||||||
initialValues,
|
initialValues,
|
||||||
@@ -33,11 +34,16 @@ const ChickinFormKandang = ({
|
|||||||
approvals,
|
approvals,
|
||||||
isLoading: approvalsLoading,
|
isLoading: approvalsLoading,
|
||||||
refresh: refreshApprovals,
|
refresh: refreshApprovals,
|
||||||
|
rawDataApprovals,
|
||||||
} = useApprovalSteps({
|
} = useApprovalSteps({
|
||||||
latestApproval: initialValues?.chickin_approval,
|
latestApproval: initialValues?.chickin_approval,
|
||||||
approvalLines: CHICKINS_APPROVAL_LINE,
|
approvalLines: CHICKINS_APPROVAL_LINE,
|
||||||
moduleName: 'CHICKINS',
|
moduleName: 'CHICKINS',
|
||||||
moduleId: initialValues?.id.toString() ?? '',
|
moduleId: initialValues?.id.toString() ?? '',
|
||||||
|
params: {
|
||||||
|
limit: 'limit',
|
||||||
|
group_step_number: false,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const afterSubmitFormChickin = () => {
|
const afterSubmitFormChickin = () => {
|
||||||
@@ -180,6 +186,7 @@ const ChickinFormKandang = ({
|
|||||||
</div>
|
</div>
|
||||||
{openChickin && (
|
{openChickin && (
|
||||||
<ChickinLogsView
|
<ChickinLogsView
|
||||||
|
rawDataApprovals={rawDataApprovals as BaseApproval[]}
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
afterSubmit={afterSubmitFormChickin}
|
afterSubmit={afterSubmitFormChickin}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import PillBadge from '@/components/PillBadge';
|
|||||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
import { formatDate, formatNumber } from '@/lib/helper';
|
import { formatDate, formatNumber } from '@/lib/helper';
|
||||||
import { ChickinApi } from '@/services/api/production/chickin';
|
import { ChickinApi } from '@/services/api/production/chickin';
|
||||||
|
import { BaseApproval } from '@/types/api/api-general';
|
||||||
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
|
||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
@@ -16,9 +17,11 @@ import toast from 'react-hot-toast';
|
|||||||
const ChickinLogsView = ({
|
const ChickinLogsView = ({
|
||||||
initialValues,
|
initialValues,
|
||||||
afterSubmit,
|
afterSubmit,
|
||||||
|
rawDataApprovals,
|
||||||
}: {
|
}: {
|
||||||
initialValues: ProjectFlockKandang;
|
initialValues: ProjectFlockKandang;
|
||||||
afterSubmit?: () => void;
|
afterSubmit?: () => void;
|
||||||
|
rawDataApprovals: BaseApproval[];
|
||||||
}) => {
|
}) => {
|
||||||
const confirmModal = useModal();
|
const confirmModal = useModal();
|
||||||
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
||||||
@@ -60,9 +63,15 @@ const ChickinLogsView = ({
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
(initialValues?.chickins || []).map((chickin, index) => {
|
(initialValues?.chickins || []).map((chickin, index) => {
|
||||||
|
const latestApproval = rawDataApprovals[0];
|
||||||
const isApproved =
|
const isApproved =
|
||||||
initialValues.chickin_approval?.step_number === 2;
|
index == (initialValues?.chickins || []).length - 1
|
||||||
const isPending = initialValues.chickin_approval?.step_number === 1;
|
? latestApproval?.step_number === 2
|
||||||
|
: true;
|
||||||
|
const isPending =
|
||||||
|
index == (initialValues?.chickins || []).length - 1
|
||||||
|
? latestApproval?.step_number === 1
|
||||||
|
: false;
|
||||||
const quantity = isApproved
|
const quantity = isApproved
|
||||||
? chickin.usage_qty
|
? chickin.usage_qty
|
||||||
: isPending
|
: isPending
|
||||||
@@ -82,7 +91,7 @@ const ChickinLogsView = ({
|
|||||||
{/* Header with Status Badge */}
|
{/* Header with Status Badge */}
|
||||||
<div className='flex flex-row justify-between items-center'>
|
<div className='flex flex-row justify-between items-center'>
|
||||||
<div className='text-lg font-semibold'>
|
<div className='text-lg font-semibold'>
|
||||||
Chick In #{index + 1}
|
Chick In #{index + 1} - {latestApproval?.step_number}
|
||||||
</div>
|
</div>
|
||||||
<PillBadge
|
<PillBadge
|
||||||
content={
|
content={
|
||||||
|
|||||||
@@ -64,9 +64,9 @@ export const ProjectFlockBudgetsSchema: Yup.ObjectSchema<ProjectFlockBudgetsSche
|
|||||||
.min(1, 'Harga minimal 1!')
|
.min(1, 'Harga minimal 1!')
|
||||||
.required('Harga wajib diisi!'),
|
.required('Harga wajib diisi!'),
|
||||||
total_price: Yup.number()
|
total_price: Yup.number()
|
||||||
.typeError('Harga harus berupa angka!')
|
.typeError('Total Harga harus berupa angka!')
|
||||||
.min(1, 'Harga minimal 1!')
|
.min(1, 'Total Harga minimal 1!')
|
||||||
.required('Harga wajib diisi!'),
|
.required('Total Harga wajib diisi!'),
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ProjectFlockFormSchema: Yup.ObjectSchema<ProjectFlockFormSchemaType> =
|
export const ProjectFlockFormSchema: Yup.ObjectSchema<ProjectFlockFormSchemaType> =
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import SelectInput, {
|
|||||||
useSelect,
|
useSelect,
|
||||||
} from '@/components/input/SelectInput';
|
} from '@/components/input/SelectInput';
|
||||||
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
|
||||||
|
import { getUniqueFormikErrors } from '@/lib/formik-helper';
|
||||||
|
import AlertErrorList from '@/components/helper/form/FormErrors';
|
||||||
import {
|
import {
|
||||||
AreaApi,
|
AreaApi,
|
||||||
FcrApi,
|
FcrApi,
|
||||||
@@ -64,6 +66,7 @@ const ProjectFlockForm = ({
|
|||||||
|
|
||||||
const [projectFlockFormErrorMessage, setProjectFlockFormErrorMessage] =
|
const [projectFlockFormErrorMessage, setProjectFlockFormErrorMessage] =
|
||||||
useState('');
|
useState('');
|
||||||
|
const [formErrorList, setFormErrorList] = useState<string[]>([]);
|
||||||
const [selectedArea, setSelectedArea] = useState('');
|
const [selectedArea, setSelectedArea] = useState('');
|
||||||
const [selectedLocation, setSelectedLocation] = useState('');
|
const [selectedLocation, setSelectedLocation] = useState('');
|
||||||
const [selectedCategory, setSelectedCategory] = useState('');
|
const [selectedCategory, setSelectedCategory] = useState('');
|
||||||
@@ -134,6 +137,7 @@ const ProjectFlockForm = ({
|
|||||||
const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({
|
const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({
|
||||||
search: '',
|
search: '',
|
||||||
location_id: selectedLocation == '' ? '0' : selectedLocation,
|
location_id: selectedLocation == '' ? '0' : selectedLocation,
|
||||||
|
limit: 'limit',
|
||||||
}).toString()}`;
|
}).toString()}`;
|
||||||
const {
|
const {
|
||||||
data: kandang,
|
data: kandang,
|
||||||
@@ -638,6 +642,17 @@ const ProjectFlockForm = ({
|
|||||||
return !isNonstockAlreadyInBudgets;
|
return !isNonstockAlreadyInBudgets;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleValidateForm = async () => {
|
||||||
|
const errors = await formik.validateForm();
|
||||||
|
|
||||||
|
if (Object.keys(errors).length > 0) {
|
||||||
|
// Parse and display errors
|
||||||
|
const errorMessages = getUniqueFormikErrors(errors);
|
||||||
|
setFormErrorList(errorMessages);
|
||||||
|
return; // Stop submission
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section className='w-full'>
|
<section className='w-full'>
|
||||||
@@ -697,7 +712,11 @@ const ProjectFlockForm = ({
|
|||||||
|
|
||||||
<form
|
<form
|
||||||
className='w-auto h-auto'
|
className='w-auto h-auto'
|
||||||
onSubmit={formik.handleSubmit}
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
handleValidateForm();
|
||||||
|
formik.handleSubmit(e);
|
||||||
|
}}
|
||||||
onReset={formik.handleReset}
|
onReset={formik.handleReset}
|
||||||
>
|
>
|
||||||
{/* Form Informasi Umum */}
|
{/* Form Informasi Umum */}
|
||||||
@@ -1063,6 +1082,14 @@ const ProjectFlockForm = ({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Error List Alert */}
|
||||||
|
{formErrorList.length > 0 && (
|
||||||
|
<AlertErrorList
|
||||||
|
formErrorList={formErrorList}
|
||||||
|
onClose={() => setFormErrorList([])}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className='flex flex-row justify-center gap-2 flex-wrap my-6 px-4'>
|
<div className='flex flex-row justify-center gap-2 flex-wrap my-6 px-4'>
|
||||||
{formType !== 'detail' && (
|
{formType !== 'detail' && (
|
||||||
<RequirePermission
|
<RequirePermission
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import { FormikErrors } from 'formik';
|
||||||
|
|
||||||
|
export type ErrorMessage = {
|
||||||
|
key: string;
|
||||||
|
message: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse Formik errors object into a flat array of error messages
|
||||||
|
* @param errors - Formik errors object
|
||||||
|
* @param parentKey - Parent key for nested objects (used internally for recursion)
|
||||||
|
* @returns Array of error messages
|
||||||
|
*/
|
||||||
|
export function parseFormikErrors<T>(
|
||||||
|
errors: FormikErrors<T>,
|
||||||
|
parentKey: string = ''
|
||||||
|
): ErrorMessage[] {
|
||||||
|
const errorList: ErrorMessage[] = [];
|
||||||
|
|
||||||
|
Object.keys(errors).forEach((key) => {
|
||||||
|
const value = errors[key as keyof typeof errors];
|
||||||
|
const fullKey = parentKey ? `${parentKey}.${key}` : key;
|
||||||
|
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
// Direct error message
|
||||||
|
errorList.push({ key: fullKey, message: value });
|
||||||
|
} else if (Array.isArray(value)) {
|
||||||
|
// Array of errors
|
||||||
|
value.forEach((item, index) => {
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
errorList.push({ key: `${fullKey}[${index}]`, message: item });
|
||||||
|
} else if (item && typeof item === 'object') {
|
||||||
|
// Nested object in array
|
||||||
|
const nestedErrors = parseFormikErrors(
|
||||||
|
item as FormikErrors<unknown>,
|
||||||
|
`${fullKey}[${index}]`
|
||||||
|
);
|
||||||
|
errorList.push(...nestedErrors);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (value && typeof value === 'object') {
|
||||||
|
// Nested object
|
||||||
|
const nestedErrors = parseFormikErrors(
|
||||||
|
value as FormikErrors<unknown>,
|
||||||
|
fullKey
|
||||||
|
);
|
||||||
|
errorList.push(...nestedErrors);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return errorList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get unique error messages from Formik errors
|
||||||
|
* @param errors - Formik errors object
|
||||||
|
* @returns Array of unique error messages
|
||||||
|
*/
|
||||||
|
export function getUniqueFormikErrors<T>(errors: FormikErrors<T>): string[] {
|
||||||
|
const errorList = parseFormikErrors(errors);
|
||||||
|
return Array.from(new Set(errorList.map((e) => e.message)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all error messages from Formik errors
|
||||||
|
* @param errors - Formik errors object
|
||||||
|
* @returns Array of error messages
|
||||||
|
*/
|
||||||
|
export function getAllFormikErrors<T>(errors: FormikErrors<T>): ErrorMessage[] {
|
||||||
|
return parseFormikErrors(errors);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user