Merge branch 'dev/randy' into 'fix/submit-form'

[FIX/FE] Create Custom Hooks for Formik Error List

See merge request mbugroup/lti-web-client!158
This commit is contained in:
Rivaldi A N S
2026-01-12 10:34:36 +00:00
33 changed files with 301 additions and 266 deletions
+4 -1
View File
@@ -1,6 +1,7 @@
import Alert from '@/components/Alert'; import Alert from '@/components/Alert';
import Button from '@/components/Button'; import Button from '@/components/Button';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { useState } from 'react';
/** /**
* Alert Unique Error List * Alert Unique Error List
@@ -14,8 +15,10 @@ const AlertErrorList = ({
formErrorList: string[]; formErrorList: string[];
onClose: () => void; onClose: () => void;
}) => { }) => {
if (formErrorList.length === 0) return null;
return ( return (
<Alert color='error' className='flex flex-col gap-2 px-4 m-4'> <Alert color='error' className='w-full flex flex-col gap-2 px-4 m-4'>
<div className='flex justify-between items-center gap-2 w-full'> <div className='flex justify-between items-center gap-2 w-full'>
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<Icon icon='material-symbols:error-outline' width={24} height={24} /> <Icon icon='material-symbols:error-outline' width={24} height={24} />
@@ -22,7 +22,6 @@ import HppExpeditionReportTable from './hpp-ekspedisi/HppExpeditionReportTable';
import ClosingKandangList from '@/components/pages/closing/ClosingKandangList'; import ClosingKandangList from '@/components/pages/closing/ClosingKandangList';
import { ProjectFlock } from '@/types/api/production/project-flock'; import { ProjectFlock } from '@/types/api/production/project-flock';
import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang'; import { ProjectFlockKandang } from '@/types/api/production/project-flock-kandang';
interface ClosingDetailProps { interface ClosingDetailProps {
id: number; id: number;
initialValue?: ClosingGeneralInformation; initialValue?: ClosingGeneralInformation;
@@ -56,7 +55,6 @@ const ClosingDetail: React.FC<ClosingDetailProps> = ({
<ClosingSapronakCalculationTabContent <ClosingSapronakCalculationTabContent
closingGeneralInformation={initialValue} closingGeneralInformation={initialValue}
projectFlockId={id} projectFlockId={id}
projectKandangId={kandangData?.id}
/> />
), ),
}, },
@@ -5,13 +5,11 @@ import { ClosingGeneralInformation } from '@/types/api/closing';
interface ClosingSapronakCalculationTabContentProps { interface ClosingSapronakCalculationTabContentProps {
projectFlockId?: number; projectFlockId?: number;
projectKandangId?: number;
closingGeneralInformation?: ClosingGeneralInformation; closingGeneralInformation?: ClosingGeneralInformation;
} }
const ClosingSapronakCalculationTabContent = ({ const ClosingSapronakCalculationTabContent = ({
projectFlockId, projectFlockId,
projectKandangId,
closingGeneralInformation, closingGeneralInformation,
}: ClosingSapronakCalculationTabContentProps) => { }: ClosingSapronakCalculationTabContentProps) => {
return ( return (
@@ -21,7 +19,6 @@ const ClosingSapronakCalculationTabContent = ({
<ClosingSapronakCalculationTable <ClosingSapronakCalculationTable
closingGeneralInformation={closingGeneralInformation} closingGeneralInformation={closingGeneralInformation}
projectFlockId={projectFlockId} projectFlockId={projectFlockId}
projectKandangId={projectKandangId}
/> />
</> </>
)} )}
@@ -14,21 +14,23 @@ import useSWR from 'swr';
import { ClosingApi } from '@/services/api/closing'; import { ClosingApi } from '@/services/api/closing';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import { ClosingGeneralInformation } from '@/types/api/closing'; import { ClosingGeneralInformation } from '@/types/api/closing';
import { useSearchParams } from 'next/navigation';
interface ClosingSapronakCalculationTableProps { interface ClosingSapronakCalculationTableProps {
projectFlockId: number; projectFlockId: number;
projectKandangId?: number;
closingGeneralInformation?: ClosingGeneralInformation; closingGeneralInformation?: ClosingGeneralInformation;
} }
const ClosingSapronakCalculationTable = ({ const ClosingSapronakCalculationTable = ({
projectFlockId, projectFlockId,
closingGeneralInformation, closingGeneralInformation,
projectKandangId,
}: ClosingSapronakCalculationTableProps) => { }: ClosingSapronakCalculationTableProps) => {
const searchParams = useSearchParams();
const kandangId = searchParams.get('kandangId');
const { data: sapronakCalculation, isLoading } = useSWR( const { data: sapronakCalculation, isLoading } = useSWR(
`/closing/sapronak-calculation/${projectFlockId}${projectKandangId ? `/${projectKandangId}` : ''}`, `/closing/sapronak-calculation/${projectFlockId}${kandangId ? `/${kandangId}` : ''}`,
() => ClosingApi.getPerhitunganSapronak(projectFlockId, projectKandangId), () => ClosingApi.getPerhitunganSapronak(projectFlockId, Number(kandangId)),
{ {
keepPreviousData: true, keepPreviousData: true,
} }
@@ -180,7 +182,7 @@ const ClosingSapronakCalculationTable = ({
{/* Table DOC jika kategori Project Flock Growing */} {/* Table DOC jika kategori Project Flock Growing */}
<Card <Card
title={ title={
closingGeneralInformation?.project_category === 'GROWING' closingGeneralInformation?.project_type == 'GROWING'
? 'DOC' ? 'DOC'
: 'Pullet' : 'Pullet'
} }
@@ -14,10 +14,8 @@ import { DashboardApi } from '@/services/api/dashboard';
import { useFormik } from 'formik'; import { useFormik } from 'formik';
import { ProjectFlockApi } from '@/services/api/production'; import { ProjectFlockApi } from '@/services/api/production';
import { KandangApi, LocationApi } from '@/services/api/master-data'; import { KandangApi, LocationApi } from '@/services/api/master-data';
import Alert from '@/components/Alert';
import { import {
DashboardFilterSchema,
DashboardFilterType, DashboardFilterType,
getDashboardFilterSchema, getDashboardFilterSchema,
} from '@/components/pages/dashboard/filter/DashboardProductionFilter.schema'; } from '@/components/pages/dashboard/filter/DashboardProductionFilter.schema';
@@ -31,7 +29,7 @@ import {
import DashboardStats from '@/components/pages/dashboard/chart/DashboardStats'; import DashboardStats from '@/components/pages/dashboard/chart/DashboardStats';
import { isResponseSuccess } from '@/lib/api-helper'; import { isResponseSuccess } from '@/lib/api-helper';
import AlertErrorList from '@/components/helper/form/FormErrors'; import AlertErrorList from '@/components/helper/form/FormErrors';
import { getUniqueFormikErrors } from '@/lib/formik-helper'; import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
// Helper function to normalize values to array // Helper function to normalize values to array
const normalizeToArray = ( const normalizeToArray = (
@@ -51,7 +49,6 @@ const DashboardProduction = () => {
); );
const [endpointUrl, setEndpointUrl] = useState('/dashboards'); const [endpointUrl, setEndpointUrl] = useState('/dashboards');
const [selectedLocationIds, setSelectedLocationIds] = useState<number[]>([]); const [selectedLocationIds, setSelectedLocationIds] = useState<number[]>([]);
const [formErrorList, setFormErrorList] = useState<string[]>([]);
// ===== FETCH DATA ===== // ===== FETCH DATA =====
const { const {
@@ -149,22 +146,8 @@ const DashboardProduction = () => {
formik.resetForm(); formik.resetForm();
}; };
const handleValidateForm = async () => { // ===== Formik Error List =====
const errors = await formik.validateForm(); const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
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();
};
if (isLoadingDashboardProductionData) { if (isLoadingDashboardProductionData) {
return ( return (
@@ -482,13 +465,7 @@ const DashboardProduction = () => {
</div> </div>
)} )}
{/* Error List Alert */} <AlertErrorList formErrorList={formErrorList} onClose={close} />
{formErrorList.length > 0 && (
<AlertErrorList
formErrorList={formErrorList}
onClose={() => setFormErrorList([])}
/>
)}
{/* Action Buttons */} {/* Action Buttons */}
<div className='flex justify-between gap-4 py-4 mt-8 border-t border-gray-300 bg-gray-100'> <div className='flex justify-between gap-4 py-4 mt-8 border-t border-gray-300 bg-gray-100'>
@@ -1,7 +1,7 @@
'use client'; 'use client';
import Button from '@/components/Button'; import Button from '@/components/Button';
import Card from '@/components/Card'; import AlertErrorList from '@/components/helper/form/FormErrors';
import { FormHeader } from '@/components/helper/form/FormHeader'; import { FormHeader } from '@/components/helper/form/FormHeader';
import DateInput from '@/components/input/DateInput'; import DateInput from '@/components/input/DateInput';
import NumberInput from '@/components/input/NumberInput'; import NumberInput from '@/components/input/NumberInput';
@@ -21,6 +21,7 @@ import {
} from '@/config/constant'; } from '@/config/constant';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { formatDate, formatTitleCase } from '@/lib/helper'; import { formatDate, formatTitleCase } from '@/lib/helper';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
import { FinanceApi } from '@/services/api/finance'; import { FinanceApi } from '@/services/api/finance';
import { BankApi, CustomerApi, SupplierApi } from '@/services/api/master-data'; import { BankApi, CustomerApi, SupplierApi } from '@/services/api/master-data';
import { import {
@@ -104,6 +105,9 @@ const FormFinanceAdd = ({
}, },
}); });
// ===== Formik Error List =====
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
// ===== Options ===== // ===== Options =====
const { const {
options: partyOptions, options: partyOptions,
@@ -180,7 +184,7 @@ const FormFinanceAdd = ({
title={`${type === 'add' ? 'Tambah' : 'Ubah'} Data Keuangan`} title={`${type === 'add' ? 'Tambah' : 'Ubah'} Data Keuangan`}
backUrl='/finance' backUrl='/finance'
/> />
<form className='flex flex-col gap-4' onSubmit={formik.handleSubmit}> <form className='flex flex-col gap-4' onSubmit={handleFormSubmit}>
<SelectInput <SelectInput
label='Jenis Transaksi' label='Jenis Transaksi'
placeholder='Pilih jenis transaksi' placeholder='Pilih jenis transaksi'
@@ -384,6 +388,7 @@ const FormFinanceAdd = ({
} }
required required
/> />
<AlertErrorList formErrorList={formErrorList} onClose={close} />
<div className='flex justify-center gap-4'> <div className='flex justify-center gap-4'>
<Button <Button
type='reset' type='reset'
@@ -1,12 +1,10 @@
'use client'; 'use client';
import Button from '@/components/Button'; import Button from '@/components/Button';
import AlertErrorList from '@/components/helper/form/FormErrors';
import { FormHeader } from '@/components/helper/form/FormHeader'; import { FormHeader } from '@/components/helper/form/FormHeader';
import NumberInput from '@/components/input/NumberInput'; import NumberInput from '@/components/input/NumberInput';
import SelectInput, { import SelectInput, { useSelect } from '@/components/input/SelectInput';
OptionType,
useSelect,
} from '@/components/input/SelectInput';
import TextArea from '@/components/input/TextArea'; import TextArea from '@/components/input/TextArea';
import TextInput from '@/components/input/TextInput'; import TextInput from '@/components/input/TextInput';
import { import {
@@ -17,6 +15,7 @@ import {
FINANCE_INITIAL_BALANCE_TYPE_OPTIONS, FINANCE_INITIAL_BALANCE_TYPE_OPTIONS,
FINANCE_PARTY_TYPE_OPTIONS, FINANCE_PARTY_TYPE_OPTIONS,
} from '@/config/constant'; } from '@/config/constant';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { formatTitleCase } from '@/lib/helper'; import { formatTitleCase } from '@/lib/helper';
import { FinanceApi } from '@/services/api/finance'; import { FinanceApi } from '@/services/api/finance';
@@ -173,6 +172,9 @@ const FormFinanceAddInitialBalance = ({
[router] [router]
); );
// ===== Formik Error List =====
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
return ( return (
<> <>
<section className='w-full max-w-xl mx-auto'> <section className='w-full max-w-xl mx-auto'>
@@ -181,7 +183,7 @@ const FormFinanceAddInitialBalance = ({
title={`${type === 'add' ? 'Tambah' : 'Ubah'} Saldo Awal`} title={`${type === 'add' ? 'Tambah' : 'Ubah'} Saldo Awal`}
backUrl='/finance' backUrl='/finance'
/> />
<form className='flex flex-col gap-4' onSubmit={formik.handleSubmit}> <form className='flex flex-col gap-4' onSubmit={handleFormSubmit}>
<SelectInput <SelectInput
label='Jenis Pihak' label='Jenis Pihak'
placeholder='Pilih jenis pihak' placeholder='Pilih jenis pihak'
@@ -352,6 +354,7 @@ const FormFinanceAddInitialBalance = ({
} }
required required
/> />
<AlertErrorList formErrorList={formErrorList} onClose={close} />
<div className='flex justify-center gap-4'> <div className='flex justify-center gap-4'>
<Button <Button
type='reset' type='reset'
@@ -1,18 +1,17 @@
'use client'; 'use client';
import Button from '@/components/Button'; import Button from '@/components/Button';
import AlertErrorList from '@/components/helper/form/FormErrors';
import { FormHeader } from '@/components/helper/form/FormHeader'; import { FormHeader } from '@/components/helper/form/FormHeader';
import DateInput from '@/components/input/DateInput'; import DateInput from '@/components/input/DateInput';
import NumberInput from '@/components/input/NumberInput'; import NumberInput from '@/components/input/NumberInput';
import SelectInput, { import SelectInput, { useSelect } from '@/components/input/SelectInput';
OptionType,
useSelect,
} from '@/components/input/SelectInput';
import TextArea from '@/components/input/TextArea'; import TextArea from '@/components/input/TextArea';
import { import {
InjectionFormSchema, InjectionFormSchema,
InjectionFormValues, InjectionFormValues,
} from '@/components/pages/finance/add/injection/FormFinanceInjection.schema'; } from '@/components/pages/finance/add/injection/FormFinanceInjection.schema';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
import { isResponseError, isResponseSuccess } from '@/lib/api-helper'; import { isResponseError, isResponseSuccess } from '@/lib/api-helper';
import { formatDate } from '@/lib/helper'; import { formatDate } from '@/lib/helper';
import { FinanceApi } from '@/services/api/finance'; import { FinanceApi } from '@/services/api/finance';
@@ -128,6 +127,9 @@ const FormFinanceInjection = ({
[router] [router]
); );
// ===== Formik Error List =====
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
return ( return (
<> <>
<section className='w-full max-w-xl mx-auto'> <section className='w-full max-w-xl mx-auto'>
@@ -136,7 +138,7 @@ const FormFinanceInjection = ({
title={`${type === 'add' ? 'Tambah' : 'Ubah'} Injeksi Dana`} title={`${type === 'add' ? 'Tambah' : 'Ubah'} Injeksi Dana`}
backUrl='/finance' backUrl='/finance'
/> />
<form className='flex flex-col gap-4' onSubmit={formik.handleSubmit}> <form className='flex flex-col gap-4' onSubmit={handleFormSubmit}>
<SelectInput <SelectInput
label='Bank' label='Bank'
placeholder='Pilih bank' placeholder='Pilih bank'
@@ -223,6 +225,7 @@ const FormFinanceInjection = ({
} }
required required
/> />
<AlertErrorList formErrorList={formErrorList} onClose={close} />
<div className='flex justify-center gap-4'> <div className='flex justify-center gap-4'>
<Button <Button
type='reset' type='reset'
@@ -15,6 +15,7 @@ import { Icon } from '@iconify/react';
import { ColumnDef, ColumnSort, SortingState } from '@tanstack/react-table'; import { ColumnDef, ColumnSort, SortingState } from '@tanstack/react-table';
import { useCallback, useEffect, useState } from 'react'; import { useCallback, useEffect, useState } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
const InventoryAdjustmentTable = () => { const InventoryAdjustmentTable = () => {
const { const {
@@ -1,26 +1,42 @@
import * as Yup from 'yup'; import * as Yup from 'yup';
import { OptionType } from '@/components/input/SelectInput';
export const InventoryAdjustmentFormSchema = Yup.object({ export const InventoryAdjustmentFormSchema = Yup.object({
product_category: Yup.object({ product_category: Yup.mixed<OptionType>()
value: Yup.number().required('ID Kategori Produk wajib diisi!'), .nullable()
label: Yup.string().required('Nama Kategori Produk wajib diisi!'), .test(
}).nullable(), 'is-valid-option',
'Kategori Produk wajib diisi!',
(value) => value !== null && value !== undefined
),
product_category_id: Yup.number().nullable(), product_category_id: Yup.number().nullable(),
product: Yup.object({ product: Yup.mixed<OptionType>()
value: Yup.number().required('ID Produk wajib diisi!'), .nullable()
label: Yup.string().required('Nama Produk wajib diisi!'), .test(
}).nullable(), 'is-valid-option',
'Produk wajib diisi!',
(value) => value !== null && value !== undefined
),
product_id: Yup.number().nullable(), product_id: Yup.number()
.nullable()
.required('Produk wajib diisi!')
.min(1, 'Produk wajib diisi!'),
warehouse: Yup.object({ warehouse: Yup.mixed<OptionType>()
value: Yup.number().required('ID Gudang wajib diisi!'), .nullable()
label: Yup.string().required('Nama Gudang wajib diisi!'), .test(
}).nullable(), 'is-valid-option',
'Warehouse wajib diisi!',
(value) => value !== null && value !== undefined
),
warehouse_id: Yup.number().nullable(), warehouse_id: Yup.number()
.nullable()
.required('Warehouse wajib diisi!')
.min(1, 'Warehouse wajib diisi!'),
transaction_type: Yup.string() transaction_type: Yup.string()
.oneOf(['increase', 'decrease'], 'Tipe transaksi tidak valid') .oneOf(['increase', 'decrease'], 'Tipe transaksi tidak valid')
@@ -26,6 +26,8 @@ import SelectInput, { OptionType } from '@/components/input/SelectInput';
import TextInput from '@/components/input/TextInput'; import TextInput from '@/components/input/TextInput';
import { RadioGroup } from '@/components/input/RadioInput'; import { RadioGroup } from '@/components/input/RadioInput';
import TextArea from '@/components/input/TextArea'; import TextArea from '@/components/input/TextArea';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
import AlertErrorList from '@/components/helper/form/FormErrors';
interface InventoryAdjustmentFormProps { interface InventoryAdjustmentFormProps {
type?: 'add' | 'edit' | 'detail'; type?: 'add' | 'edit' | 'detail';
@@ -245,6 +247,9 @@ const InventoryAdjustmentForm = ({
return decimal ? `${formattedInteger}.${decimal}` : formattedInteger; return decimal ? `${formattedInteger}.${decimal}` : formattedInteger;
}; };
// ===== Formik Error List =====
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
// Render // Render
return ( return (
<> <>
@@ -266,7 +271,7 @@ const InventoryAdjustmentForm = ({
</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'
> >
@@ -390,6 +395,7 @@ const InventoryAdjustmentForm = ({
readOnly={type === 'detail'} readOnly={type === 'detail'}
/> />
</div> </div>
<AlertErrorList formErrorList={formErrorList} onClose={close} />
<div className='flex flex-row justify-between gap-2 flex-wrap'> <div className='flex flex-row justify-between gap-2 flex-wrap'>
{type !== 'detail' && ( {type !== 'detail' && (
<div className='flex flex-row justify-end gap-2'> <div className='flex flex-row justify-end gap-2'>
@@ -405,11 +411,7 @@ const InventoryAdjustmentForm = ({
type='submit' type='submit'
color='primary' color='primary'
isLoading={formik.isSubmitting} isLoading={formik.isSubmitting}
disabled={ disabled={formik.isSubmitting}
!formik.isValid ||
formik.isSubmitting ||
formik.values.product == undefined
}
className='px-4' className='px-4'
> >
Submit Submit
@@ -48,8 +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'; import AlertErrorList from '@/components/helper/form/FormErrors';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
const MemoizedSalesOrderProductTable = memo(SalesOrderProductTable); const MemoizedSalesOrderProductTable = memo(SalesOrderProductTable);
const MemoizedSalesOrderProductForm = memo(SalesOrderProductForm); const MemoizedSalesOrderProductForm = memo(SalesOrderProductForm);
@@ -219,7 +219,6 @@ 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[]
>( >(
@@ -561,22 +560,8 @@ const MarketingForm = ({
); );
}, [memoSalesOrder]); }, [memoSalesOrder]);
const handleValidateForm = async () => { // ===== Formik Error List =====
const errors = await formik.validateForm(); const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
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 (
<> <>
@@ -686,13 +671,7 @@ const MarketingForm = ({
</div> </div>
</div> </div>
{/* Error List Alert */} <AlertErrorList formErrorList={formErrorList} onClose={close} />
{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'>
@@ -16,8 +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'; import AlertErrorList from '@/components/helper/form/FormErrors';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
const DeliveryOrderProductForm = ({ const DeliveryOrderProductForm = ({
formState, formState,
@@ -42,7 +42,6 @@ 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
@@ -168,21 +167,8 @@ const DeliveryOrderProductForm = ({
} }
}, [initialValues]); }, [initialValues]);
const handleValidateForm = () => { // ===== Formik Error List =====
formik.validateForm(); const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
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 (
<> <>
@@ -388,12 +374,7 @@ const DeliveryOrderProductForm = ({
/> />
</div> </div>
{formErrorList.length > 0 && ( <AlertErrorList formErrorList={formErrorList} onClose={close} />
<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'>
@@ -24,8 +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'; import AlertErrorList from '@/components/helper/form/FormErrors';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
const SalesOrderProductForm = ({ const SalesOrderProductForm = ({
initialValues, initialValues,
@@ -39,7 +39,6 @@ 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>({
@@ -172,23 +171,8 @@ const SalesOrderProductForm = ({
} }
}; };
const handleValidateForm = async () => { // ===== Formik Error List =====
const errors = await formik.validateForm(); const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
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 (
<> <>
@@ -356,13 +340,7 @@ const SalesOrderProductForm = ({
/> />
</div> </div>
{/* Error List Alert */} <AlertErrorList formErrorList={formErrorList} onClose={close} />
{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}>
@@ -25,6 +25,8 @@ import {
} from '@/types/api/master-data/area'; } from '@/types/api/master-data/area';
import { AreaApi } from '@/services/api/master-data'; import { AreaApi } from '@/services/api/master-data';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
import AlertErrorList from '@/components/helper/form/FormErrors';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
interface AreaFormProps { interface AreaFormProps {
type?: 'add' | 'edit' | 'detail'; type?: 'add' | 'edit' | 'detail';
@@ -118,6 +120,9 @@ const AreaForm = ({ type = 'add', initialValues }: AreaFormProps) => {
formikSetValues(formikInitialValues); formikSetValues(formikInitialValues);
}, [formikSetValues, formikInitialValues]); }, [formikSetValues, formikInitialValues]);
// ===== Formik Error List =====
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
return ( return (
<> <>
<section className='w-full max-w-xl'> <section className='w-full max-w-xl'>
@@ -139,7 +144,7 @@ const AreaForm = ({ type = 'add', initialValues }: AreaFormProps) => {
</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'
> >
@@ -199,6 +204,8 @@ const AreaForm = ({ type = 'add', initialValues }: AreaFormProps) => {
</div> </div>
)} )}
<AlertErrorList formErrorList={formErrorList} onClose={close} />
{type !== 'detail' && ( {type !== 'detail' && (
<div <div
className={cn('flex flex-row justify-end gap-2', { className={cn('flex flex-row justify-end gap-2', {
@@ -213,7 +220,7 @@ const AreaForm = ({ type = 'add', initialValues }: AreaFormProps) => {
type='submit' type='submit'
color='primary' color='primary'
isLoading={formik.isSubmitting} isLoading={formik.isSubmitting}
disabled={!formik.isValid || formik.isSubmitting} disabled={formik.isSubmitting}
className='px-4' className='px-4'
> >
Submit Submit
@@ -25,6 +25,8 @@ import {
} from '@/types/api/master-data/bank'; } from '@/types/api/master-data/bank';
import { BankApi } from '@/services/api/master-data'; import { BankApi } from '@/services/api/master-data';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
import AlertErrorList from '@/components/helper/form/FormErrors';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
interface BankFormProps { interface BankFormProps {
type?: 'add' | 'edit' | 'detail'; type?: 'add' | 'edit' | 'detail';
@@ -124,6 +126,9 @@ const BankForm = ({ type = 'add', initialValues }: BankFormProps) => {
formikSetValues(formikInitialValues); formikSetValues(formikInitialValues);
}, [formikSetValues, formikInitialValues]); }, [formikSetValues, formikInitialValues]);
// ===== Formik Error List =====
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
return ( return (
<> <>
<section className='w-full max-w-xl'> <section className='w-full max-w-xl'>
@@ -145,7 +150,7 @@ const BankForm = ({ type = 'add', initialValues }: BankFormProps) => {
</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'
> >
@@ -247,6 +252,8 @@ const BankForm = ({ type = 'add', initialValues }: BankFormProps) => {
</div> </div>
)} )}
<AlertErrorList formErrorList={formErrorList} onClose={close} />
{type !== 'detail' && ( {type !== 'detail' && (
<div <div
className={cn('flex flex-row justify-end gap-2', { className={cn('flex flex-row justify-end gap-2', {
@@ -261,7 +268,7 @@ const BankForm = ({ type = 'add', initialValues }: BankFormProps) => {
type='submit' type='submit'
color='primary' color='primary'
isLoading={formik.isSubmitting} isLoading={formik.isSubmitting}
disabled={!formik.isValid || formik.isSubmitting} disabled={formik.isSubmitting}
className='px-4' className='px-4'
> >
Submit Submit
@@ -28,6 +28,8 @@ import useSWR from 'swr';
import { UserApi } from '@/services/api/user'; import { UserApi } from '@/services/api/user';
import { TYPE_OPTIONS } from '@/config/constant'; import { TYPE_OPTIONS } from '@/config/constant';
import RequirePermission from '@/components/helper/RequirePermission'; import RequirePermission from '@/components/helper/RequirePermission';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
import AlertErrorList from '@/components/helper/form/FormErrors';
interface CustomerFormProps { interface CustomerFormProps {
formType?: 'add' | 'edit' | 'detail'; formType?: 'add' | 'edit' | 'detail';
@@ -191,6 +193,9 @@ const CustomerForm = ({
formikSetValues(formikInitialValues); formikSetValues(formikInitialValues);
}, [formikSetValues, formikInitialValues]); }, [formikSetValues, formikInitialValues]);
// ===== Formik Error List =====
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
// Render // Render
return ( return (
<> <>
@@ -213,7 +218,7 @@ const CustomerForm = ({
</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'
> >
@@ -358,6 +363,8 @@ const CustomerForm = ({
</div> </div>
)} )}
<AlertErrorList formErrorList={formErrorList} onClose={close} />
{formType !== 'detail' && ( {formType !== 'detail' && (
<div <div
className={cn('flex flex-row justify-end gap-2', { className={cn('flex flex-row justify-end gap-2', {
@@ -372,7 +379,7 @@ const CustomerForm = ({
type='submit' type='submit'
color='primary' color='primary'
isLoading={formik.isSubmitting} isLoading={formik.isSubmitting}
disabled={!formik.isValid || formik.isSubmitting} disabled={formik.isSubmitting}
className='px-4' className='px-4'
> >
Submit Submit
@@ -26,6 +26,8 @@ import {
} from '@/types/api/master-data/fcr'; } from '@/types/api/master-data/fcr';
import { FcrApi } from '@/services/api/master-data'; import { FcrApi } from '@/services/api/master-data';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
import AlertErrorList from '@/components/helper/form/FormErrors';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
interface FcrFormProps { interface FcrFormProps {
type?: 'add' | 'edit' | 'detail'; type?: 'add' | 'edit' | 'detail';
@@ -158,6 +160,9 @@ const FcrForm = ({ type = 'add', initialValues }: FcrFormProps) => {
formikSetValues(formikInitialValues); formikSetValues(formikInitialValues);
}, [formikSetValues, formikInitialValues]); }, [formikSetValues, formikInitialValues]);
// ===== Formik Error List =====
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
return ( return (
<> <>
<section className='w-full max-w-5xl'> <section className='w-full max-w-5xl'>
@@ -179,7 +184,7 @@ const FcrForm = ({ type = 'add', initialValues }: FcrFormProps) => {
</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'
> >
@@ -294,6 +299,8 @@ const FcrForm = ({ type = 'add', initialValues }: FcrFormProps) => {
)} )}
</div> </div>
<AlertErrorList formErrorList={formErrorList} onClose={close} />
<div className='flex flex-row justify-between gap-2 flex-wrap'> <div className='flex flex-row justify-between gap-2 flex-wrap'>
{type !== 'add' && ( {type !== 'add' && (
<div className='flex flex-row justify-start gap-2'> <div className='flex flex-row justify-start gap-2'>
@@ -349,7 +356,7 @@ const FcrForm = ({ type = 'add', initialValues }: FcrFormProps) => {
type='submit' type='submit'
color='primary' color='primary'
isLoading={formik.isSubmitting} isLoading={formik.isSubmitting}
disabled={!formik.isValid || formik.isSubmitting} disabled={formik.isSubmitting}
className='px-4' className='px-4'
> >
Submit Submit
@@ -17,6 +17,8 @@ import TextInput from '@/components/input/TextInput';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
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 AlertErrorList from '@/components/helper/form/FormErrors';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
interface FlockCustomProps { interface FlockCustomProps {
formType?: 'add' | 'edit' | 'detail'; formType?: 'add' | 'edit' | 'detail';
@@ -86,6 +88,9 @@ const FlockForm = ({ formType = 'add', initialValues }: FlockCustomProps) => {
formikSetValues(formikInitialValue); formikSetValues(formikInitialValue);
}, [formikSetValues, formikInitialValue]); }, [formikSetValues, formikInitialValue]);
// ===== Formik Error List =====
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
// Render // Render
return ( return (
<> <>
@@ -107,7 +112,7 @@ const FlockForm = ({ formType = 'add', initialValues }: FlockCustomProps) => {
</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'
> >
@@ -168,6 +173,8 @@ const FlockForm = ({ formType = 'add', initialValues }: FlockCustomProps) => {
</div> </div>
)} )}
<AlertErrorList formErrorList={formErrorList} onClose={close} />
{formType !== 'detail' && ( {formType !== 'detail' && (
<div <div
className={cn('flex flex-row justify-end gap-2', { className={cn('flex flex-row justify-end gap-2', {
@@ -182,7 +189,7 @@ const FlockForm = ({ formType = 'add', initialValues }: FlockCustomProps) => {
type='submit' type='submit'
color='primary' color='primary'
isLoading={formik.isSubmitting} isLoading={formik.isSubmitting}
disabled={!formik.isValid || formik.isSubmitting} disabled={formik.isSubmitting}
className='px-4' className='px-4'
> >
Submit Submit
@@ -29,6 +29,8 @@ import { LocationApi, KandangApi } from '@/services/api/master-data';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
import { UserApi } from '@/services/api/user'; import { UserApi } from '@/services/api/user';
import NumberInput from '@/components/input/NumberInput'; import NumberInput from '@/components/input/NumberInput';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
import AlertErrorList from '@/components/helper/form/FormErrors';
interface KandangFormProps { interface KandangFormProps {
type?: 'add' | 'edit' | 'detail'; type?: 'add' | 'edit' | 'detail';
@@ -198,6 +200,9 @@ const KandangForm = ({ type = 'add', initialValues }: KandangFormProps) => {
formikSetValues(formikInitialValues); formikSetValues(formikInitialValues);
}, [formikSetValues, formikInitialValues]); }, [formikSetValues, formikInitialValues]);
// ===== Formik Error List =====
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
return ( return (
<> <>
<section className='w-full max-w-xl'> <section className='w-full max-w-xl'>
@@ -219,7 +224,7 @@ const KandangForm = ({ type = 'add', initialValues }: KandangFormProps) => {
</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'
> >
@@ -324,6 +329,8 @@ const KandangForm = ({ type = 'add', initialValues }: KandangFormProps) => {
</div> </div>
)} )}
<AlertErrorList formErrorList={formErrorList} onClose={close} />
{type !== 'detail' && ( {type !== 'detail' && (
<div <div
className={cn('flex flex-row justify-end gap-2', { className={cn('flex flex-row justify-end gap-2', {
@@ -338,7 +345,7 @@ const KandangForm = ({ type = 'add', initialValues }: KandangFormProps) => {
type='submit' type='submit'
color='primary' color='primary'
isLoading={formik.isSubmitting} isLoading={formik.isSubmitting}
disabled={!formik.isValid || formik.isSubmitting} disabled={formik.isSubmitting}
className='px-4' className='px-4'
> >
Submit Submit
@@ -27,6 +27,8 @@ import {
} from '@/types/api/master-data/location'; } from '@/types/api/master-data/location';
import { AreaApi, LocationApi } from '@/services/api/master-data'; import { AreaApi, LocationApi } from '@/services/api/master-data';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
import AlertErrorList from '@/components/helper/form/FormErrors';
interface LocationFormProps { interface LocationFormProps {
type?: 'add' | 'edit' | 'detail'; type?: 'add' | 'edit' | 'detail';
@@ -160,6 +162,9 @@ const LocationForm = ({ type = 'add', initialValues }: LocationFormProps) => {
formikSetValues(formikInitialValues); formikSetValues(formikInitialValues);
}, [formikSetValues, formikInitialValues]); }, [formikSetValues, formikInitialValues]);
// ===== Formik Error List =====
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
return ( return (
<> <>
<section className='w-full max-w-xl'> <section className='w-full max-w-xl'>
@@ -181,7 +186,7 @@ const LocationForm = ({ type = 'add', initialValues }: LocationFormProps) => {
</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'
> >
@@ -268,6 +273,8 @@ const LocationForm = ({ type = 'add', initialValues }: LocationFormProps) => {
</div> </div>
)} )}
<AlertErrorList formErrorList={formErrorList} onClose={close} />
{type !== 'detail' && ( {type !== 'detail' && (
<div <div
className={cn('flex flex-row justify-end gap-2', { className={cn('flex flex-row justify-end gap-2', {
@@ -282,7 +289,7 @@ const LocationForm = ({ type = 'add', initialValues }: LocationFormProps) => {
type='submit' type='submit'
color='primary' color='primary'
isLoading={formik.isSubmitting} isLoading={formik.isSubmitting}
disabled={!formik.isValid || formik.isSubmitting} disabled={formik.isSubmitting}
className='px-4' className='px-4'
> >
Submit Submit
@@ -29,6 +29,8 @@ import { NonstockApi, SupplierApi, UomApi } from '@/services/api/master-data';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
import { flags } from '@/types/api/api-general'; import { flags } from '@/types/api/api-general';
import { SUPPLIER_FLAG_OPTIONS } from '@/config/constant'; import { SUPPLIER_FLAG_OPTIONS } from '@/config/constant';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
import AlertErrorList from '@/components/helper/form/FormErrors';
interface NonstockFormProps { interface NonstockFormProps {
type?: 'add' | 'edit' | 'detail'; type?: 'add' | 'edit' | 'detail';
@@ -213,6 +215,9 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => {
formikSetValues(formikInitialValues); formikSetValues(formikInitialValues);
}, [formikSetValues, formikInitialValues]); }, [formikSetValues, formikInitialValues]);
// ===== Formik Error List =====
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
return ( return (
<> <>
<section className='w-full max-w-xl'> <section className='w-full max-w-xl'>
@@ -234,7 +239,7 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => {
</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'
> >
@@ -337,6 +342,8 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => {
</div> </div>
)} )}
<AlertErrorList formErrorList={formErrorList} onClose={close} />
{type !== 'detail' && ( {type !== 'detail' && (
<div <div
className={cn('flex flex-row justify-end gap-2', { className={cn('flex flex-row justify-end gap-2', {
@@ -351,7 +358,7 @@ const NonstockForm = ({ type = 'add', initialValues }: NonstockFormProps) => {
type='submit' type='submit'
color='primary' color='primary'
isLoading={formik.isSubmitting} isLoading={formik.isSubmitting}
disabled={!formik.isValid || formik.isSubmitting} disabled={formik.isSubmitting}
className='px-4' className='px-4'
> >
Submit Submit
@@ -11,7 +11,6 @@ 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 AlertErrorList from '@/components/helper/form/FormErrors';
import { import {
@@ -27,6 +26,7 @@ import {
} from '@/types/api/master-data/product-category'; } from '@/types/api/master-data/product-category';
import { ProductCategoryApi } from '@/services/api/master-data'; import { ProductCategoryApi } from '@/services/api/master-data';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
interface ProductCategoryFormProps { interface ProductCategoryFormProps {
type?: 'add' | 'edit' | 'detail'; type?: 'add' | 'edit' | 'detail';
@@ -41,7 +41,6 @@ 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(
@@ -132,21 +131,8 @@ const ProductCategoryForm = ({
formikSetValues(formikInitialValues); formikSetValues(formikInitialValues);
}, [formikSetValues, formikInitialValues]); }, [formikSetValues, formikInitialValues]);
const handleValidateForm = async () => { // ===== Formik Error List =====
const errors = await formik.validateForm(); const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
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 (
<> <>
@@ -184,13 +170,7 @@ const ProductCategoryForm = ({
</div> </div>
)} )}
{/* Error List Alert */} <AlertErrorList formErrorList={formErrorList} onClose={close} />
{formErrorList.length > 0 && (
<AlertErrorList
formErrorList={formErrorList}
onClose={() => setFormErrorList([])}
/>
)}
<div className='flex flex-col gap-4'> <div className='flex flex-col gap-4'>
<TextInput <TextInput
@@ -39,6 +39,7 @@ import {
} from '@/services/api/master-data'; } from '@/services/api/master-data';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
import { PRODUCT_FLAG_OPTIONS } from '@/config/constant'; import { PRODUCT_FLAG_OPTIONS } from '@/config/constant';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
interface ProductFormProps { interface ProductFormProps {
type?: 'add' | 'edit' | 'detail'; type?: 'add' | 'edit' | 'detail';
@@ -50,7 +51,6 @@ 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(
@@ -204,21 +204,8 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
formikSetValues(formikInitialValues); formikSetValues(formikInitialValues);
}, [formikSetValues, formikInitialValues]); }, [formikSetValues, formikInitialValues]);
const handleValidateForm = async () => { // ===== Formik Error List =====
const errors = await formik.validateForm(); const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
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 (
<> <>
@@ -254,13 +241,7 @@ const ProductForm = ({ type = 'add', initialValues }: ProductFormProps) => {
</div> </div>
)} )}
{/* Error List Alert */} <AlertErrorList formErrorList={formErrorList} onClose={close} />
{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
@@ -9,6 +9,7 @@ import {
ProductionStandardRepeaterFormSchemaValues, ProductionStandardRepeaterFormSchemaValues,
ProductionStandardFormValues, ProductionStandardFormValues,
createProductionStandardRepeaterFormSchema, createProductionStandardRepeaterFormSchema,
ProductionStandardFormSchema,
} from '@/components/pages/master-data/production-standard/form/ProductionStandardForm.schema'; } from '@/components/pages/master-data/production-standard/form/ProductionStandardForm.schema';
import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table'; import Table, { TABLE_DEFAULT_STYLING } from '@/components/Table';
import { FLOCK_CATEGORY_OPTIONS } from '@/config/constant'; import { FLOCK_CATEGORY_OPTIONS } from '@/config/constant';
@@ -31,6 +32,8 @@ import { useModal } from '@/components/Modal';
import RequirePermission from '@/components/helper/RequirePermission'; import RequirePermission from '@/components/helper/RequirePermission';
import Tooltip from '@/components/Tooltip'; import Tooltip from '@/components/Tooltip';
import Alert from '@/components/Alert'; import Alert from '@/components/Alert';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
import AlertErrorList from '@/components/helper/form/FormErrors';
type TableRowsType = { type TableRowsType = {
customRow: boolean; customRow: boolean;
@@ -207,6 +210,7 @@ const ProductionStandardForm = ({
initialValues: formikInitialValues as ProductionStandardFormValues, initialValues: formikInitialValues as ProductionStandardFormValues,
// Only enable reinitialize for edit/detail mode, not add mode // Only enable reinitialize for edit/detail mode, not add mode
enableReinitialize: formType !== 'add', enableReinitialize: formType !== 'add',
validationSchema: ProductionStandardFormSchema,
onSubmit: (values) => { onSubmit: (values) => {
switch (formType) { switch (formType) {
case 'add': case 'add':
@@ -723,7 +727,8 @@ const ProductionStandardForm = ({
router.push('/master-data/production-standard'); router.push('/master-data/production-standard');
}; };
// ===== Function ===== // ===== Formik Error List =====
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
return ( return (
<> <>
@@ -1210,9 +1215,26 @@ const ProductionStandardForm = ({
return null; return null;
}} }}
/> />
<AlertErrorList formErrorList={formErrorList} onClose={close} />
{productionStandardFormErrorMessage && (
<Alert color='error' className='w-full'>
<div className='flex items-center gap-2 stretch'>
<Icon icon='mdi:alert' />
<span>{productionStandardFormErrorMessage}</span>
</div>
<Icon
icon='mdi:close'
onClick={() => setProductionStandardFormErrorMessage('')}
className='ms-auto'
/>
</Alert>
)}
<form <form
className='flex justify-between mt-6 gap-2 flex-wrap' className='flex justify-between mt-6 gap-2 flex-wrap'
onSubmit={formik.handleSubmit} onSubmit={handleFormSubmit}
> >
{formType === 'detail' && ( {formType === 'detail' && (
<div className='gap-2 flex items-center'> <div className='gap-2 flex items-center'>
@@ -1293,19 +1315,6 @@ const ProductionStandardForm = ({
</div> </div>
)} )}
</form> </form>
{productionStandardFormErrorMessage && (
<Alert color='error' className='w-full'>
<div className='flex items-center gap-2 stretch'>
<Icon icon='mdi:alert' />
<span>{productionStandardFormErrorMessage}</span>
</div>
<Icon
icon='mdi:close'
onClick={() => setProductionStandardFormErrorMessage('')}
className='ms-auto'
/>
</Alert>
)}
</div> </div>
<ConfirmationModal <ConfirmationModal
@@ -25,6 +25,8 @@ import TextArea from '@/components/input/TextArea';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
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 { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
import AlertErrorList from '@/components/helper/form/FormErrors';
interface SupplierCustomProps { interface SupplierCustomProps {
formType?: 'add' | 'edit' | 'detail'; formType?: 'add' | 'edit' | 'detail';
@@ -199,6 +201,9 @@ const SupplierForm = ({
formik.setFieldValue('category', val); formik.setFieldValue('category', val);
}; };
// ===== Formik Error List =====
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
// Render // Render
return ( return (
<> <>
@@ -221,7 +226,7 @@ const SupplierForm = ({
</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'
> >
@@ -444,6 +449,8 @@ const SupplierForm = ({
</div> </div>
)} )}
<AlertErrorList formErrorList={formErrorList} onClose={close} />
{formType !== 'detail' && ( {formType !== 'detail' && (
<div <div
className={cn('flex flex-row justify-end gap-2', { className={cn('flex flex-row justify-end gap-2', {
@@ -458,7 +465,7 @@ const SupplierForm = ({
type='submit' type='submit'
color='primary' color='primary'
isLoading={formik.isSubmitting} isLoading={formik.isSubmitting}
disabled={!formik.isValid || formik.isSubmitting} disabled={formik.isSubmitting}
className='px-4' className='px-4'
> >
Submit Submit
@@ -25,6 +25,8 @@ import {
} from '@/types/api/master-data/uom'; } from '@/types/api/master-data/uom';
import { UomApi } from '@/services/api/master-data'; import { UomApi } from '@/services/api/master-data';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
import AlertErrorList from '@/components/helper/form/FormErrors';
interface UomFormProps { interface UomFormProps {
type?: 'add' | 'edit' | 'detail'; type?: 'add' | 'edit' | 'detail';
@@ -118,6 +120,9 @@ const UomForm = ({ type = 'add', initialValues }: UomFormProps) => {
formikSetValues(formikInitialValues); formikSetValues(formikInitialValues);
}, [formikSetValues, formikInitialValues]); }, [formikSetValues, formikInitialValues]);
// ===== Formik Error List =====
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
return ( return (
<> <>
<section className='w-full max-w-xl'> <section className='w-full max-w-xl'>
@@ -139,7 +144,7 @@ const UomForm = ({ type = 'add', initialValues }: UomFormProps) => {
</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'
> >
@@ -199,6 +204,8 @@ const UomForm = ({ type = 'add', initialValues }: UomFormProps) => {
</div> </div>
)} )}
<AlertErrorList formErrorList={formErrorList} onClose={close} />
{type !== 'detail' && ( {type !== 'detail' && (
<div <div
className={cn('flex flex-row justify-end gap-2', { className={cn('flex flex-row justify-end gap-2', {
@@ -213,7 +220,7 @@ const UomForm = ({ type = 'add', initialValues }: UomFormProps) => {
type='submit' type='submit'
color='primary' color='primary'
isLoading={formik.isSubmitting} isLoading={formik.isSubmitting}
disabled={!formik.isValid || formik.isSubmitting} disabled={formik.isSubmitting}
className='px-4' className='px-4'
> >
Submit Submit
@@ -33,6 +33,8 @@ import {
} from '@/services/api/master-data'; } from '@/services/api/master-data';
import { cn } from '@/lib/helper'; import { cn } from '@/lib/helper';
import { WAREHOUSE_TYPE_OPTIONS } from '@/config/constant'; import { WAREHOUSE_TYPE_OPTIONS } from '@/config/constant';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
import AlertErrorList from '@/components/helper/form/FormErrors';
interface WarehouseFormProps { interface WarehouseFormProps {
type?: 'add' | 'edit' | 'detail'; type?: 'add' | 'edit' | 'detail';
@@ -323,6 +325,9 @@ const WarehouseForm = ({ type = 'add', initialValues }: WarehouseFormProps) => {
formikSetValues(formikInitialValues); formikSetValues(formikInitialValues);
}, [formikSetValues, formikInitialValues]); }, [formikSetValues, formikInitialValues]);
// ===== Formik Error List =====
const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
return ( return (
<> <>
<section className='w-full max-w-xl'> <section className='w-full max-w-xl'>
@@ -344,7 +349,7 @@ const WarehouseForm = ({ type = 'add', initialValues }: WarehouseFormProps) => {
</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'
> >
@@ -474,6 +479,8 @@ const WarehouseForm = ({ type = 'add', initialValues }: WarehouseFormProps) => {
</div> </div>
)} )}
<AlertErrorList formErrorList={formErrorList} onClose={close} />
{type !== 'detail' && ( {type !== 'detail' && (
<div <div
className={cn('flex flex-row justify-end gap-2', { className={cn('flex flex-row justify-end gap-2', {
@@ -488,7 +495,7 @@ const WarehouseForm = ({ type = 'add', initialValues }: WarehouseFormProps) => {
type='submit' type='submit'
color='primary' color='primary'
isLoading={formik.isSubmitting} isLoading={formik.isSubmitting}
disabled={!formik.isValid || formik.isSubmitting} disabled={formik.isSubmitting}
className='px-4' className='px-4'
> >
Submit Submit
@@ -209,20 +209,6 @@ const ProjectFlockDetail = ({
</Badge> </Badge>
</div> </div>
{/* <div className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2'>
<Icon width={14} height={14} icon={'mdi:clock'} /> History
</div>
<div className='col-span-2'>
<Button variant='outline' className='py-1 text-sm'>
See History{' '}
<Icon
icon='mdi:arrow-top-right-thin'
width={11}
height={11}
/>
</Button>
</div> */}
{/* BARIS 1 */} {/* BARIS 1 */}
<div <div
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2 className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
@@ -252,6 +238,18 @@ const ProjectFlockDetail = ({
</div> </div>
<div className='col-span-2'>{projectFlock?.fcr?.name}</div> <div className='col-span-2'>{projectFlock?.fcr?.name}</div>
<div
className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2
relative
before:content-[""] before:absolute before:left-[5px] before:top-[90%] before:bottom-[-100%] before:w-[1px] before:border-1 before:border-dashed before:border-gray-400'
>
<Icon width={14} height={14} icon='mdi:circle-slice-8' />{' '}
Standard
</div>
<div className='col-span-2'>
{projectFlock?.production_standard?.name ?? '-'}
</div>
{/* BARIS 3 (Terakhir - TIDAK PERLU garis di bawahnya) */} {/* BARIS 3 (Terakhir - TIDAK PERLU garis di bawahnya) */}
<div className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2'> <div className='col-span-1 flex flex-row items-center text-gray-400 font-semibold gap-2'>
<Icon width={14} height={14} icon='mdi:circle-slice-8' />{' '} <Icon width={14} height={14} icon='mdi:circle-slice-8' />{' '}
@@ -6,7 +6,6 @@ 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 AlertErrorList from '@/components/helper/form/FormErrors';
import { import {
AreaApi, AreaApi,
@@ -47,6 +46,7 @@ import { Nonstock } from '@/types/api/master-data/nonstock';
import { useUiStore } from '@/stores/ui/ui.store'; import { useUiStore } from '@/stores/ui/ui.store';
import RequirePermission from '@/components/helper/RequirePermission'; import RequirePermission from '@/components/helper/RequirePermission';
import DrawerHeader from '@/components/helper/drawer/DrawerHeader'; import DrawerHeader from '@/components/helper/drawer/DrawerHeader';
import { useFormikErrorList } from '@/services/hooks/useFormikErrorList';
interface ProjectFlockFormProps { interface ProjectFlockFormProps {
formType?: 'add' | 'edit' | 'detail'; formType?: 'add' | 'edit' | 'detail';
@@ -66,7 +66,6 @@ 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('');
@@ -642,16 +641,8 @@ const ProjectFlockForm = ({
return !isNonstockAlreadyInBudgets; return !isNonstockAlreadyInBudgets;
}); });
const handleValidateForm = async () => { // ===== Formik Error List =====
const errors = await formik.validateForm(); const { formErrorList, close, handleFormSubmit } = useFormikErrorList(formik);
if (Object.keys(errors).length > 0) {
// Parse and display errors
const errorMessages = getUniqueFormikErrors(errors);
setFormErrorList(errorMessages);
return; // Stop submission
}
};
return ( return (
<> <>
@@ -712,11 +703,7 @@ const ProjectFlockForm = ({
<form <form
className='w-auto h-auto' className='w-auto h-auto'
onSubmit={(e) => { onSubmit={handleFormSubmit}
e.preventDefault();
handleValidateForm();
formik.handleSubmit(e);
}}
onReset={formik.handleReset} onReset={formik.handleReset}
> >
{/* Form Informasi Umum */} {/* Form Informasi Umum */}
@@ -1082,13 +1069,7 @@ const ProjectFlockForm = ({
</div> </div>
</div> </div>
{/* Error List Alert */} <AlertErrorList formErrorList={formErrorList} onClose={close} />
{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' && (
+62
View File
@@ -0,0 +1,62 @@
import { getUniqueFormikErrors } from '@/lib/formik-helper';
import { FormikProps } from 'formik';
import { useState } from 'react';
interface UseFormikErrorListOptions {
onBeforeSubmit?: (e: React.FormEvent<HTMLFormElement>) => boolean | void;
onAfterValidation?: () => void | Promise<void>;
}
export const useFormikErrorList = <T>(
formik: FormikProps<T>,
options?: UseFormikErrorListOptions
) => {
const [formErrorList, setFormErrorList] = useState<string[]>([]);
const handleValidateForm = async () => {
const errors = await formik.validateForm();
if (Object.keys(errors).length > 0) {
const errorMessages = getUniqueFormikErrors(errors);
setFormErrorList(errorMessages);
return false;
}
return true;
};
const handleFormSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
// Call onBeforeSubmit callback
if (options?.onBeforeSubmit) {
const shouldContinue = options.onBeforeSubmit(e);
if (shouldContinue === false) {
return; // Cancel submit
}
}
// Validate form
const isValid = await handleValidateForm();
// Call onAfterValidation callback if validation passed
if (options?.onAfterValidation) {
await options.onAfterValidation();
}
// Submit form
formik.handleSubmit();
};
const close = () => {
setFormErrorList([]);
};
return {
formErrorList,
setFormErrorList,
close,
handleValidateForm,
handleFormSubmit,
};
};
+1
View File
@@ -63,6 +63,7 @@ export type BaseClosing = {
location_id: number; location_id: number;
location_name: string; location_name: string;
project_category: 'GROWING' | 'LAYING'; project_category: 'GROWING' | 'LAYING';
project_type?: 'GROWING' | 'LAYING'; // berubah dari BE?
period: number; period: number;
closing_date?: string; closing_date?: string;
shed_label: string; shed_label: string;
+1
View File
@@ -5,6 +5,7 @@ import { Kandang } from '@/types/api/master-data/kandang';
import { Location } from '@/types/api/master-data/location'; import { Location } from '@/types/api/master-data/location';
import { BaseApproval, BaseMetadata } from '@/types/api/api-general'; import { BaseApproval, BaseMetadata } from '@/types/api/api-general';
import { Nonstock } from '@/types/api/master-data/nonstock'; import { Nonstock } from '@/types/api/master-data/nonstock';
import { ProductionStandard } from '@/types/api/master-data/production-standard';
export type BaseProjectFlock = { export type BaseProjectFlock = {
id: number; id: number;