From 13205ca80a2beee8f4b132c5a64a6060c5368fcf Mon Sep 17 00:00:00 2001 From: randy-ar Date: Thu, 8 Jan 2026 08:59:27 +0700 Subject: [PATCH 1/2] feat(FE): adding alert errors message for project flock and fixing bug approval status in chickin --- src/components/helper/form/FormErrors.tsx | 46 ++++++++++++ src/components/pages/ApprovalSteps.tsx | 2 +- .../form/InventoryAdjustmentForm.tsx | 1 + .../production/chickin/form/ChickinForm.tsx | 7 ++ .../chickin/form/tabs/ChickLogsView.tsx | 15 +++- .../form/ProjectFlockForm.schema.ts | 6 +- .../project-flock/form/ProjectFlockForm.tsx | 29 +++++++- src/lib/formik-helper.ts | 71 +++++++++++++++++++ 8 files changed, 169 insertions(+), 8 deletions(-) create mode 100644 src/components/helper/form/FormErrors.tsx create mode 100644 src/lib/formik-helper.ts diff --git a/src/components/helper/form/FormErrors.tsx b/src/components/helper/form/FormErrors.tsx new file mode 100644 index 00000000..a351227f --- /dev/null +++ b/src/components/helper/form/FormErrors.tsx @@ -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 ( + +
+
+ + + Terdapat {formErrorList.length} error pada form: + +
+ +
+ +
+ ); +}; + +export default AlertErrorList; diff --git a/src/components/pages/ApprovalSteps.tsx b/src/components/pages/ApprovalSteps.tsx index 6ae7c13a..27d03da3 100644 --- a/src/components/pages/ApprovalSteps.tsx +++ b/src/components/pages/ApprovalSteps.tsx @@ -309,7 +309,7 @@ const useApprovalSteps = ({ moduleId: string; params?: { page?: number; - limit: number; + limit: number | string; search?: string; group_step_number?: boolean; }; diff --git a/src/components/pages/inventory/adjustment/form/InventoryAdjustmentForm.tsx b/src/components/pages/inventory/adjustment/form/InventoryAdjustmentForm.tsx index f134369e..525b81bb 100644 --- a/src/components/pages/inventory/adjustment/form/InventoryAdjustmentForm.tsx +++ b/src/components/pages/inventory/adjustment/form/InventoryAdjustmentForm.tsx @@ -125,6 +125,7 @@ const InventoryAdjustmentForm = ({ const warehouseUrl = `${WarehouseApi.basePath}?${new URLSearchParams({ search: '', + limit: '100', }).toString()}`; const { data: warehouses, isLoading: isLoadingWarehouses } = useSWR( warehouseUrl, diff --git a/src/components/pages/production/chickin/form/ChickinForm.tsx b/src/components/pages/production/chickin/form/ChickinForm.tsx index 8c613737..b5b1dc4d 100644 --- a/src/components/pages/production/chickin/form/ChickinForm.tsx +++ b/src/components/pages/production/chickin/form/ChickinForm.tsx @@ -18,6 +18,7 @@ import { Icon } from '@iconify/react'; import Badge from '@/components/Badge'; import { CHICKINS_APPROVAL_LINE } from '@/config/approval-line'; import RequirePermission from '@/components/helper/RequirePermission'; +import { BaseApproval } from '@/types/api/api-general'; const ChickinFormKandang = ({ formType = 'add', initialValues, @@ -33,11 +34,16 @@ const ChickinFormKandang = ({ approvals, isLoading: approvalsLoading, refresh: refreshApprovals, + rawDataApprovals, } = useApprovalSteps({ latestApproval: initialValues?.chickin_approval, approvalLines: CHICKINS_APPROVAL_LINE, moduleName: 'CHICKINS', moduleId: initialValues?.id.toString() ?? '', + params: { + limit: 'limit', + group_step_number: false, + }, }); const afterSubmitFormChickin = () => { @@ -180,6 +186,7 @@ const ChickinFormKandang = ({ {openChickin && ( diff --git a/src/components/pages/production/chickin/form/tabs/ChickLogsView.tsx b/src/components/pages/production/chickin/form/tabs/ChickLogsView.tsx index 80846565..e800ee68 100644 --- a/src/components/pages/production/chickin/form/tabs/ChickLogsView.tsx +++ b/src/components/pages/production/chickin/form/tabs/ChickLogsView.tsx @@ -8,6 +8,7 @@ import PillBadge from '@/components/PillBadge'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { formatDate, formatNumber } from '@/lib/helper'; import { ChickinApi } from '@/services/api/production/chickin'; +import { BaseApproval } from '@/types/api/api-general'; import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; import { Icon } from '@iconify/react'; import { useState } from 'react'; @@ -16,9 +17,11 @@ import toast from 'react-hot-toast'; const ChickinLogsView = ({ initialValues, afterSubmit, + rawDataApprovals, }: { initialValues: ProjectFlockKandang; afterSubmit?: () => void; + rawDataApprovals: BaseApproval[]; }) => { const confirmModal = useModal(); const [isApproveLoading, setIsApproveLoading] = useState(false); @@ -60,9 +63,15 @@ const ChickinLogsView = ({ ) : ( (initialValues?.chickins || []).map((chickin, index) => { + const latestApproval = rawDataApprovals[0]; const isApproved = - initialValues.chickin_approval?.step_number === 2; - const isPending = initialValues.chickin_approval?.step_number === 1; + index == (initialValues?.chickins || []).length - 1 + ? latestApproval?.step_number === 2 + : true; + const isPending = + index == (initialValues?.chickins || []).length - 1 + ? latestApproval?.step_number === 1 + : false; const quantity = isApproved ? chickin.usage_qty : isPending @@ -82,7 +91,7 @@ const ChickinLogsView = ({ {/* Header with Status Badge */}
- Chick In #{index + 1} + Chick In #{index + 1} - {latestApproval?.step_number}
= diff --git a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx index 20a8aff4..7e90c94b 100644 --- a/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx +++ b/src/components/pages/production/project-flock/form/ProjectFlockForm.tsx @@ -6,6 +6,8 @@ import SelectInput, { useSelect, } from '@/components/input/SelectInput'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; +import { getUniqueFormikErrors } from '@/lib/formik-helper'; +import AlertErrorList from '@/components/helper/form/FormErrors'; import { AreaApi, FcrApi, @@ -64,6 +66,7 @@ const ProjectFlockForm = ({ const [projectFlockFormErrorMessage, setProjectFlockFormErrorMessage] = useState(''); + const [formErrorList, setFormErrorList] = useState([]); const [selectedArea, setSelectedArea] = useState(''); const [selectedLocation, setSelectedLocation] = useState(''); const [selectedCategory, setSelectedCategory] = useState(''); @@ -134,6 +137,7 @@ const ProjectFlockForm = ({ const kandangUrl = `${KandangApi.basePath}?${new URLSearchParams({ search: '', location_id: selectedLocation == '' ? '0' : selectedLocation, + limit: 'limit', }).toString()}`; const { data: kandang, @@ -638,6 +642,17 @@ const ProjectFlockForm = ({ 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 ( <>
@@ -697,7 +712,11 @@ const ProjectFlockForm = ({
{ + e.preventDefault(); + handleValidateForm(); + formik.handleSubmit(e); + }} onReset={formik.handleReset} > {/* Form Informasi Umum */} @@ -1063,6 +1082,14 @@ const ProjectFlockForm = ({
+ {/* Error List Alert */} + {formErrorList.length > 0 && ( + setFormErrorList([])} + /> + )} +
{formType !== 'detail' && ( ( + errors: FormikErrors, + 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, + `${fullKey}[${index}]` + ); + errorList.push(...nestedErrors); + } + }); + } else if (value && typeof value === 'object') { + // Nested object + const nestedErrors = parseFormikErrors( + value as FormikErrors, + 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(errors: FormikErrors): 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(errors: FormikErrors): ErrorMessage[] { + return parseFormikErrors(errors); +} From 0ed30e184b1fc54c6031d3473eaeb6753f22f1d3 Mon Sep 17 00:00:00 2001 From: randy-ar Date: Thu, 8 Jan 2026 09:19:55 +0700 Subject: [PATCH 2/2] feat(FE): implement alert error list in marketing module --- .../pages/marketing/form/MarketingForm.tsx | 32 +++++++++++++++- .../delivery-order/DeliverOrderProduct.tsx | 35 ++++++++++++++--- .../sales-order/SalesOrderProduct.schema.ts | 12 ++++-- .../sales-order/SalesOrderProductForm.tsx | 38 ++++++++++++++++--- 4 files changed, 99 insertions(+), 18 deletions(-) diff --git a/src/components/pages/marketing/form/MarketingForm.tsx b/src/components/pages/marketing/form/MarketingForm.tsx index 1c5322e1..51c20d8e 100644 --- a/src/components/pages/marketing/form/MarketingForm.tsx +++ b/src/components/pages/marketing/form/MarketingForm.tsx @@ -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 { DeliveryOrderProductFormValues } from '@/components/pages/marketing/form/repeater/delivery-order/DeliverOrderProduct.schema'; import RequirePermission from '@/components/helper/RequirePermission'; +import { getUniqueFormikErrors } from '@/lib/formik-helper'; +import AlertErrorList from '@/components/helper/form/FormErrors'; const MemoizedSalesOrderProductTable = memo(SalesOrderProductTable); const MemoizedSalesOrderProductForm = memo(SalesOrderProductForm); @@ -217,6 +219,7 @@ const MarketingForm = ({ const [deliveryFormState, setDeliveryFormState] = useState<'add' | 'edit'>( 'add' ); + const [formErrorList, setFormErrorList] = useState([]); const [deliveryOrderValues, setDeliveryOrderValues] = useState< DeliveryOrderProductFormValues[] >( @@ -558,11 +561,28 @@ const MarketingForm = ({ ); }, [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) => { + e.preventDefault(); + handleValidateForm(); + formik.handleSubmit(); + }; + return ( <>
+ {/* Error List Alert */} + {formErrorList.length > 0 && ( + setFormErrorList([])} + /> + )} + {/* Form Actions */}
+ {formErrorList.length > 0 && ( + setFormErrorList([])} + /> + )} +
diff --git a/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema.ts b/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema.ts index b2f42254..e62ed701 100644 --- a/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema.ts +++ b/src/components/pages/marketing/form/repeater/sales-order/SalesOrderProduct.schema.ts @@ -25,15 +25,19 @@ export const SalesOrderProductSchema: Yup.ObjectSchema { const [formErrorMessage, setFormErrorMessage] = useState(''); const [currentInput, setCurrentInput] = useState(''); + const [formErrorList, setFormErrorList] = useState([]); // ============ Formik ============ const formik = useFormik({ @@ -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) => { + e.preventDefault(); + handleBlurField(currentInput); + handleValidateForm(); + formik.handleSubmit(e); + }; + return ( <> { - e.preventDefault(); - handleBlurField(currentInput); - formik.handleSubmit(e); - }} + onSubmit={handleFormSubmit} onReset={handleResetForm} > {formErrorMessage && ( @@ -338,6 +355,15 @@ const SalesOrderProductForm = ({ placeholder='Masukan Total Penjualan' />
+ + {/* Error List Alert */} + {formErrorList.length > 0 && ( + setFormErrorList([])} + /> + )} +