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