refactor(FE-208,212): streamline PurchaseRequestForm structure, enhance state management, and improve field handling for purchase items

This commit is contained in:
rstubryan
2025-11-04 09:20:39 +07:00
parent 289c8d5672
commit 39dbf57d7f
@@ -52,19 +52,16 @@ const PurchaseRequestForm = ({
}: PurchaseRequestFormProps) => {
const router = useRouter();
const deleteModal = useModal();
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [locationSelectInputValue, setLocationSelectInputValue] = useState('');
const [selectedPurchaseItems, setSelectedPurchaseItems] = useState<number[]>(
[]
);
const [purchaseRequestFormErrorMessage, setPurchaseRequestFormErrorMessage] =
useState('');
const [isDeleteLoading, setIsDeleteLoading] = useState(false);
const [
productWarehouseSelectInputValue,
setProductWarehouseSelectInputValue,
] = useState('');
// ===== INTERFACES =====
// ===== TYPE DEFINITIONS =====
interface ProductWarehouseOptionType extends OptionType {
product_id: number;
warehouse_id: number;
@@ -72,7 +69,7 @@ const PurchaseRequestForm = ({
quantity: number;
}
// ===== HELPER FUNCTIONS =====
// ===== UTILITY FUNCTIONS =====
const getPurchaseItemError = (
idx: number,
field: 'warehouse_id' | 'product_warehouse_id' | 'product_id' | 'sub_qty'
@@ -104,7 +101,7 @@ const PurchaseRequestForm = ({
);
};
// ===== FORM HANDLERS =====
// ===== SUBMISSION HANDLERS =====
const createPurchaseRequestHandler = useCallback(
async (payload: CreatePurchaseRequestPayload) => {
const res = await PurchaseApi.create(payload);
@@ -150,14 +147,7 @@ const PurchaseRequestForm = ({
router.push('/purchase');
}, [deleteModal, initialValues?.id, router]);
// ===== API DATA FETCHING =====
const allProductWarehousesUrl = `${ProductWarehouseApi.basePath}`;
const { data: allProductWarehouses } = useSWR(
allProductWarehousesUrl,
ProductWarehouseApi.getAllFetcher
);
// ===== USE SELECT HOOKS =====
// ===== SELECT INPUT DATA =====
const {
inputValue: supplierSelectInputValue,
setInputValue: setSupplierSelectInputValue,
@@ -173,65 +163,13 @@ const PurchaseRequestForm = ({
isLoadingOptions: isLoadingAreas,
} = useSelect(AreaApi.basePath, 'id', 'name', 'search');
const [locationSelectInputValue, setLocationSelectInputValue] = useState('');
const {
inputValue: warehouseSelectInputValue,
setInputValue: setWarehouseSelectInputValue,
isLoadingOptions: isLoadingWarehouses,
} = useSelect(WarehouseApi.basePath, 'id', 'name', 'search');
// ===== PRODUCT WAREHOUSE FETCHING =====
const getProductWarehousesUrl = useCallback(() => {
const productWarehouseParams = new URLSearchParams({
search: productWarehouseSelectInputValue,
});
return `${ProductWarehouseApi.basePath}?${productWarehouseParams.toString()}`;
}, [productWarehouseSelectInputValue]);
const productWarehousesUrl = getProductWarehousesUrl();
const { data: productWarehouses, isLoading: isLoadingProductWarehouses } =
useSWR(productWarehousesUrl, ProductWarehouseApi.getAllFetcher);
// Filter product warehouses per item based on selected warehouse
const getProductWarehouseOptionsForItem = useCallback(
(warehouseId: number | string) => {
if (!isResponseSuccess(productWarehouses)) return [];
const warehouseIdNum =
typeof warehouseId === 'string'
? parseInt(warehouseId) || 0
: warehouseId;
if (warehouseIdNum === 0) return [];
return (
productWarehouses?.data
.filter((pw) => pw.warehouse.id === warehouseIdNum)
.map((pw) => ({
value: pw.product.id,
label: pw.product.name,
product_id: pw.product.id,
warehouse_id: pw.warehouse.id,
warehouse_name: pw.warehouse.name,
quantity: pw.quantity,
})) || []
);
},
[productWarehouses]
);
const productWarehouseOptions = isResponseSuccess(productWarehouses)
? productWarehouses?.data.map((pw) => ({
value: pw.product.id,
label: pw.product.name,
product_id: pw.product.id,
warehouse_id: pw.warehouse.id,
warehouse_name: pw.warehouse.name,
quantity: pw.quantity,
}))
: [];
// ===== FORM CONFIGURATION =====
const formikInitialValues = useMemo<PurchaseRequestFormValues>(
() => getPurchaseRequestFormInitialValues(initialValues),
[initialValues]
@@ -290,7 +228,37 @@ const PurchaseRequestForm = ({
},
});
// ===== PRODUCT DATA FETCHING =====
// ===== API DATA FETCHING =====
const allProductWarehousesUrl = `${ProductWarehouseApi.basePath}`;
const { data: productWarehouses, isLoading: isLoadingProductWarehouses } =
useSWR(allProductWarehousesUrl, ProductWarehouseApi.getAllFetcher);
const getProductWarehouseOptionsForItem = useCallback(
(warehouseId: number | string) => {
if (!isResponseSuccess(productWarehouses)) return [];
const warehouseIdNum =
typeof warehouseId === 'string'
? parseInt(warehouseId) || 0
: warehouseId;
if (warehouseIdNum === 0) return [];
return (
productWarehouses?.data
.filter((pw) => pw.warehouse.id === warehouseIdNum)
.map((pw) => ({
value: pw.product.id,
label: pw.product.name,
product_id: pw.product.id,
warehouse_id: pw.warehouse.id,
warehouse_name: pw.warehouse.name,
quantity: pw.quantity,
})) || []
);
},
[productWarehouses]
);
const productUrl = useMemo(() => {
const productIds =
formik.values.purchase_items
@@ -320,8 +288,6 @@ const PurchaseRequestForm = ({
});
return data;
}, [productsResponse]);
// ===== API DATA FETCHING =====
const locationsUrl = useMemo(() => {
const params = new URLSearchParams({
search: locationSelectInputValue,
@@ -386,7 +352,7 @@ const PurchaseRequestForm = ({
);
}, [warehouses]);
// ===== EVENT HANDLERS =====
// ===== FIELD CHANGE HANDLERS =====
const supplierChangeHandler = (val: OptionType | OptionType[] | null) => {
const supplier = val as OptionType | null;
formik.setFieldTouched('supplier', true);
@@ -494,28 +460,19 @@ const PurchaseRequestForm = ({
setSelectedPurchaseItems([]);
};
// ===== PURCHASE ITEM OPERATIONS =====
const handlePurchaseItemChange = (
idx: number,
field: string,
field: 'sub_qty' | 'price',
value: string | number
) => {
const integerFields = [
'warehouse_id',
'product_id',
'product_warehouse_id',
'total_qty',
];
const floatFields = ['price', 'sub_qty'];
if (integerFields.includes(field)) {
if (field === 'sub_qty') {
const numValue = typeof value === 'string' ? parseInt(value) || 0 : value;
formik.setFieldValue(`purchase_items.${idx}.${field}`, numValue);
} else if (floatFields.includes(field)) {
formik.setFieldValue(`purchase_items.${idx}.sub_qty`, numValue);
} else if (field === 'price') {
const numValue =
typeof value === 'string' ? parseFloat(value) || 0 : value;
formik.setFieldValue(`purchase_items.${idx}.${field}`, numValue);
} else {
formik.setFieldValue(`purchase_items.${idx}.${field}`, value);
formik.setFieldValue(`purchase_items.${idx}.price`, numValue);
}
};
@@ -731,6 +688,7 @@ const PurchaseRequestForm = ({
<SelectInput
required
value={item.warehouse}
key={`warehouse-${idx}`}
onChange={(val) => {
const warehouse = val as OptionType | null;
formik.setFieldValue(
@@ -741,17 +699,29 @@ const PurchaseRequestForm = ({
`purchase_items.${idx}.warehouse_id`,
(warehouse as OptionType)?.value || 0
);
formik.setFieldTouched(
`purchase_items.${idx}.product_warehouse`,
false
);
formik.setFieldValue(
`purchase_items.${idx}.product_warehouse`,
null
);
formik.setFieldTouched(
`purchase_items.${idx}.product_warehouse_id`,
false
);
formik.setFieldValue(
`purchase_items.${idx}.product_warehouse_id`,
null
0
);
formik.setFieldTouched(
`purchase_items.${idx}.product_id`,
false
);
formik.setFieldValue(
`purchase_items.${idx}.product_id`,
''
0
);
}}
options={warehouseOptions}
@@ -776,6 +746,7 @@ const PurchaseRequestForm = ({
<SelectInput
required
value={item.product_warehouse}
key={`product-warehouse-${idx}-${item.warehouse_id}`}
onChange={(val) => {
const productWarehouse =
val as ProductWarehouseOptionType | null;
@@ -799,7 +770,6 @@ const PurchaseRequestForm = ({
options={getProductWarehouseOptionsForItem(
item.warehouse_id
)}
onInputChange={setProductWarehouseSelectInputValue}
isLoading={isLoadingProductWarehouses}
isError={
getPurchaseItemError(idx, 'product_warehouse_id')