refactor(FE-208,212): refine PurchaseRequestForm validation and state management

This commit is contained in:
rstubryan
2025-11-18 13:50:54 +07:00
parent 964a4500ab
commit edd59598f9
2 changed files with 120 additions and 98 deletions
@@ -21,7 +21,7 @@ type PurchaseRequestFormSchemaType = {
warehouse?: {
value: number;
label: string;
};
} | null;
warehouse_id: number;
notes: string | null;
items: {
@@ -74,35 +74,26 @@ const PurchaseRequestForm = ({
// ===== UTILITY FUNCTIONS =====
const getPurchaseItemError = (
idx: number,
field: 'warehouse_id' | 'product_warehouse_id' | 'product_id' | 'qty'
field: 'product_warehouse_id' | 'product_id' | 'qty'
): { isError: boolean; errorMessage: string } => {
const touchedItem = formik.touched.items?.[idx];
if (!formik.touched.items || !Array.isArray(formik.touched.items)) {
return {
isError: false,
errorMessage: '',
};
}
const touchedField = formik.touched.items[idx]?.[field];
const errorItem = formik.errors.items?.[idx] as
| Record<string, string>
| undefined;
if (!touchedItem) {
return { isError: false, errorMessage: '' };
}
const isTouched = (touchedItem as Record<string, boolean>)?.[field];
const errorMessage = errorItem?.[field] || '';
return {
isError: Boolean(isTouched && errorMessage),
errorMessage: isTouched && errorMessage ? errorMessage : '',
isError: Boolean(touchedField && Boolean(errorItem?.[field])),
errorMessage: touchedField && errorItem?.[field] ? errorItem[field] : '',
};
};
const getSupplierById = (supplierId: number): Supplier | null => {
if (!isResponseSuccess(supplierRawData)) return null;
return (
supplierRawData?.data.find(
(supplier: Supplier) => supplier.id === supplierId
) || null
);
};
// ===== SUBMISSION HANDLERS =====
const createPurchaseRequestHandler = useCallback(
async (payload: CreatePurchaseRequestPayload) => {
@@ -349,74 +340,6 @@ const PurchaseRequestForm = ({
);
}, [warehouses]);
// ===== FIELD CHANGE HANDLERS =====
const supplierChangeHandler = (val: OptionType | OptionType[] | null) => {
const supplier = val as OptionType | null;
formik.setFieldTouched('supplier', true);
formik.setFieldValue('supplier', supplier);
formik.setFieldTouched('supplier_id', true);
formik.setFieldValue('supplier_id', (supplier as OptionType)?.value || 0);
if (supplier?.value) {
const supplierId =
typeof supplier.value === 'string'
? parseInt(supplier.value)
: supplier.value;
const supplierData = getSupplierById(supplierId);
if (supplierData?.due_date) {
formik.setFieldTouched('credit_term', true);
formik.setFieldValue('credit_term', supplierData.due_date.toString());
}
} else {
// Reset credit_term field and its touched state when supplier is cleared
formik.setFieldTouched('credit_term', false);
formik.setFieldValue('credit_term', '');
}
};
const areaChangeHandler = (val: OptionType | OptionType[] | null) => {
const area = val as OptionType | null;
formik.setFieldTouched('area', true);
formik.setFieldValue('area', area);
formik.setFieldTouched('area_id', true);
formik.setFieldValue('area_id', (area as OptionType)?.value || 0);
// Reset area dependent fields
formik.setFieldTouched('location', false);
formik.setFieldValue('location', undefined);
formik.setFieldTouched('location_id', false);
formik.setFieldValue('location_id', 0);
setLocationSelectInputValue('');
// Reset area dependent fields in all purchase items
if (formik.values.items) {
formik.values.items.forEach((_, idx) => {
formik.setFieldValue(`items.${idx}.product_warehouse`, null);
formik.setFieldValue(`items.${idx}.product_warehouse_id`, null);
formik.setFieldValue(`items.${idx}.product`, null);
formik.setFieldValue(`items.${idx}.product_id`, '');
});
}
};
const locationChangeHandler = (val: OptionType | OptionType[] | null) => {
const location = val as OptionType | null;
formik.setFieldTouched('location', true);
formik.setFieldValue('location', location);
formik.setFieldTouched('location_id', true);
formik.setFieldValue('location_id', (location as OptionType)?.value || 0);
// Reset location dependent fields in all purchase items
if (formik.values.items) {
formik.values.items.forEach((_, idx) => {
formik.setFieldValue(`items.${idx}.product_warehouse`, null);
formik.setFieldValue(`items.${idx}.product_warehouse_id`, null);
formik.setFieldValue(`items.${idx}.product`, null);
formik.setFieldValue(`items.${idx}.product_id`, '');
});
}
};
// Purchase Items Handlers
const addPurchaseItem = () => {
const newPurchaseItems = [
@@ -456,6 +379,7 @@ const PurchaseRequestForm = ({
if (field === 'qty') {
const numValue =
typeof value === 'string' ? parseFloat(value) || 0 : value;
formik.setFieldTouched(`items.${idx}.qty`, true);
formik.setFieldValue(`items.${idx}.qty`, numValue);
}
};
@@ -497,12 +421,41 @@ const PurchaseRequestForm = ({
label='Vendor'
placeholder='Pilih Vendor...'
value={formik.values.supplier}
onChange={supplierChangeHandler}
onChange={(val) => {
const supplier = val as OptionType | null;
const supplierId = supplier?.value
? typeof supplier.value === 'string'
? parseInt(supplier.value)
: supplier.value
: 0;
formik.setFieldTouched('supplier_id', true);
formik.setFieldValue('supplier_id', supplierId);
formik.setFieldTouched('supplier', true);
formik.setFieldValue('supplier', supplier);
if (supplierId > 0 && isResponseSuccess(supplierRawData)) {
const supplierData = supplierRawData.data.find(
(s: Supplier) => s.id === supplierId
);
if (supplierData?.due_date) {
formik.setFieldTouched('credit_term', true);
formik.setFieldValue(
'credit_term',
supplierData.due_date.toString()
);
}
} else {
formik.setFieldTouched('credit_term', false);
formik.setFieldValue('credit_term', '');
}
}}
options={supplierOptions}
onInputChange={setSupplierSelectInputValue}
isLoading={isLoadingSuppliers}
isError={
formik.touched.supplier && Boolean(formik.errors.supplier_id)
formik.touched.supplier_id &&
Boolean(formik.errors.supplier_id)
}
errorMessage={formik.errors.supplier_id as string}
isDisabled={type === 'detail'}
@@ -537,11 +490,43 @@ const PurchaseRequestForm = ({
label='Area'
placeholder='Pilih Area...'
value={formik.values.area}
onChange={areaChangeHandler}
onChange={(val) => {
const area = val as OptionType | null;
formik.setFieldTouched('area_id', true);
formik.setFieldValue(
'area_id',
(area as OptionType)?.value || 0
);
formik.setFieldTouched('area', true);
formik.setFieldValue('area', area);
formik.setFieldTouched('location', false);
formik.setFieldValue('location', undefined);
formik.setFieldTouched('location_id', false);
formik.setFieldValue('location_id', 0);
setLocationSelectInputValue('');
if (formik.values.items) {
formik.values.items.forEach((_, idx) => {
formik.setFieldValue(
`items.${idx}.product_warehouse`,
null
);
formik.setFieldValue(
`items.${idx}.product_warehouse_id`,
null
);
formik.setFieldValue(`items.${idx}.product`, null);
formik.setFieldValue(`items.${idx}.product_id`, '');
});
}
}}
options={areaOptions}
onInputChange={setAreaSelectInputValue}
isLoading={isLoadingAreas}
isError={formik.touched.area && Boolean(formik.errors.area_id)}
isError={
formik.touched.area_id && Boolean(formik.errors.area_id)
}
errorMessage={formik.errors.area_id as string}
isDisabled={type === 'detail'}
isClearable
@@ -556,12 +541,37 @@ const PurchaseRequestForm = ({
: 'Pilih Lokasi...'
}
value={formik.values.area_id ? formik.values.location : null}
onChange={locationChangeHandler}
onChange={(val) => {
const location = val as OptionType | null;
formik.setFieldTouched('location_id', true);
formik.setFieldValue(
'location_id',
(location as OptionType)?.value || 0
);
formik.setFieldTouched('location', true);
formik.setFieldValue('location', location);
if (formik.values.items) {
formik.values.items.forEach((_, idx) => {
formik.setFieldValue(
`items.${idx}.product_warehouse`,
null
);
formik.setFieldValue(
`items.${idx}.product_warehouse_id`,
null
);
formik.setFieldValue(`items.${idx}.product`, null);
formik.setFieldValue(`items.${idx}.product_id`, '');
});
}
}}
options={locationOptions}
onInputChange={setLocationSelectInputValue}
isLoading={isLoadingLocations}
isError={
formik.touched.location && Boolean(formik.errors.location_id)
formik.touched.location_id &&
Boolean(formik.errors.location_id)
}
errorMessage={formik.errors.location_id as string}
isDisabled={type === 'detail' || !formik.values.area_id}
@@ -684,13 +694,13 @@ const PurchaseRequestForm = ({
value={formik.values.warehouse}
onChange={(val) => {
const warehouse = val as OptionType | null;
formik.setFieldTouched('warehouse', true);
formik.setFieldValue('warehouse', warehouse);
formik.setFieldTouched('warehouse_id', true);
formik.setFieldValue(
'warehouse_id',
(warehouse as OptionType)?.value || 0
);
formik.setFieldTouched('warehouse', true);
formik.setFieldValue('warehouse', warehouse);
if (formik.values.items) {
formik.values.items.forEach((_, idx) => {
formik.setFieldValue(
@@ -716,7 +726,7 @@ const PurchaseRequestForm = ({
onInputChange={setWarehouseSelectInputValue}
isLoading={isLoadingWarehouses}
isError={
formik.touched.warehouse &&
formik.touched.warehouse_id &&
Boolean(formik.errors.warehouse_id)
}
errorMessage={formik.errors.warehouse_id as string}
@@ -741,10 +751,18 @@ const PurchaseRequestForm = ({
onChange={(val) => {
const productWarehouse =
val as ProductWarehouseOptionType | null;
formik.setFieldTouched(
`items.${idx}.product_warehouse`,
true
);
formik.setFieldValue(
`items.${idx}.product_warehouse`,
productWarehouse
);
formik.setFieldTouched(
`items.${idx}.product_warehouse_id`,
true
);
formik.setFieldValue(
`items.${idx}.product_warehouse_id`,
(productWarehouse as ProductWarehouseOptionType)
@@ -754,6 +772,10 @@ const PurchaseRequestForm = ({
const productId =
(productWarehouse as ProductWarehouseOptionType)
?.product_id || 0;
formik.setFieldTouched(
`items.${idx}.product_id`,
true
);
formik.setFieldValue(
`items.${idx}.product_id`,
productId