refactor(FE-63,63,65): enhance MovementForm to fetch and display product details from ProductWarehouse

This commit is contained in:
rstubryan
2025-10-16 16:24:42 +07:00
parent 157dfc75ed
commit 501a68267e
2 changed files with 238 additions and 42 deletions
@@ -119,43 +119,61 @@ export type MovementFormValues = Yup.InferType<typeof MovementFormSchema>;
export const getMovementFormInitialValues = ( export const getMovementFormInitialValues = (
initialValues?: Movement initialValues?: Movement
): MovementFormValues => ({ ): MovementFormValues => {
transfer_reason: initialValues?.transfer_reason ?? '', const detailIdToProductId = new Map<number, number>();
transfer_date: initialValues?.transfer_date ?? '', initialValues?.details?.forEach((detail) => {
source_warehouse: initialValues?.source_warehouse detailIdToProductId.set(detail.id, detail.product_id);
? { });
value: initialValues.source_warehouse.id,
label: initialValues.source_warehouse.name, return {
} transfer_reason: initialValues?.transfer_reason ?? '',
: null, transfer_date: initialValues?.transfer_date ?? '',
source_warehouse_id: initialValues?.source_warehouse?.id ?? 0, source_warehouse: initialValues?.source_warehouse
destination_warehouse: initialValues?.destination_warehouse ? {
? { value: initialValues.source_warehouse.id,
value: initialValues.destination_warehouse.id, label: initialValues.source_warehouse.name,
label: initialValues.destination_warehouse.name, }
} : null,
: null, source_warehouse_id: initialValues?.source_warehouse?.id ?? 0,
destination_warehouse_id: initialValues?.destination_warehouse?.id ?? 0, destination_warehouse: initialValues?.destination_warehouse
products: ? {
initialValues?.details?.map((p) => ({ value: initialValues.destination_warehouse.id,
product: { value: p.product_id, label: '' }, label: initialValues.destination_warehouse.name,
product_id: p.product_id, }
product_qty: p.quantity, : null,
})) ?? [], destination_warehouse_id: initialValues?.destination_warehouse?.id ?? 0,
deliveries: products:
initialValues?.deliveries?.map((d) => ({ initialValues?.details?.map((p) => ({
delivery_cost: d.shipping_cost_total, product: { value: p.product_id, label: `Product ID: ${p.product_id}` },
delivery_cost_per_item: d.shipping_cost_item, product_id: p.product_id,
document_index: 0,
document: d.document_path || null,
driver_name: d.driver_name,
vehicle_plate: d.vehicle_plate,
supplier: { value: d.supplier.id, label: d.supplier.name },
supplier_id: d.supplier_id,
products: d.items.map((p) => ({
product: { value: 0, label: '' },
product_id: 0,
product_qty: p.quantity, product_qty: p.quantity,
})), })) ?? [],
})) ?? [], deliveries:
}); initialValues?.deliveries?.map((d) => {
return {
delivery_cost: d.shipping_cost_total,
delivery_cost_per_item: d.shipping_cost_item,
document_index: 0,
document: d.document_path || null,
driver_name: d.driver_name,
vehicle_plate: d.vehicle_plate,
supplier: d.supplier
? { value: d.supplier.id, label: d.supplier.name }
: null,
supplier_id: d.supplier_id,
products: d.items.map((item) => {
const productId =
detailIdToProductId.get(item.stock_transfer_detail_id) ?? 0;
return {
product:
productId > 0
? { value: productId, label: `Product ID: ${productId}` }
: null,
product_id: productId,
product_qty: item.quantity,
};
}),
};
}) ?? [],
};
};
@@ -25,7 +25,11 @@ import {
DeliverySchema, DeliverySchema,
} from '@/components/pages/inventory/movement/form/MovementForm.schema'; } from '@/components/pages/inventory/movement/form/MovementForm.schema';
import { useMovementFormHandlers } from './useMovementFormHandlers'; import { useMovementFormHandlers } from './useMovementFormHandlers';
import { SupplierApi, WarehouseApi } from '@/services/api/master-data'; import {
SupplierApi,
WarehouseApi,
ProductApi,
} from '@/services/api/master-data';
import { ProductWarehouseApi } from '@/services/api/inventory'; import { ProductWarehouseApi } from '@/services/api/inventory';
import { toast } from 'react-hot-toast'; import { toast } from 'react-hot-toast';
import FileInput from '@/components/input/FileInput'; import FileInput from '@/components/input/FileInput';
@@ -43,6 +47,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
] = useState(''); ] = useState('');
const [selectedProducts, setSelectedProducts] = useState<number[]>([]); const [selectedProducts, setSelectedProducts] = useState<number[]>([]);
const [selectedDeliveries, setSelectedDeliveries] = useState<number[]>([]); const [selectedDeliveries, setSelectedDeliveries] = useState<number[]>([]);
const [fetchedProductIds, setFetchedProductIds] = useState<Set<number>>(
new Set()
);
const { const {
deleteModal, deleteModal,
@@ -338,12 +345,183 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
}, [formik.values.deliveries]); }, [formik.values.deliveries]);
useEffect(() => { useEffect(() => {
if (formik.values.source_warehouse_id && type !== 'edit') { if (
formik.values.source_warehouse_id &&
type !== 'edit' &&
type !== 'detail'
) {
formik.setFieldValue('products', []); formik.setFieldValue('products', []);
formik.setFieldValue('deliveries', []); formik.setFieldValue('deliveries', []);
} }
}, [formik.values.source_warehouse_id]); }, [formik.values.source_warehouse_id]);
// Effect to populate product labels from ProductWarehouse data
useEffect(() => {
if (!productWarehouses || !isResponseSuccess(productWarehouses)) return;
if (type !== 'edit' && type !== 'detail') return;
let hasUpdates = false;
const updatedProducts = formik.values.products?.map((product) => {
if (product.product && product.product.label.startsWith('Product ID:')) {
const productWarehouse = productWarehouses.data.find(
(pw) => pw.product.id === product.product_id
);
if (productWarehouse) {
hasUpdates = true;
return {
...product,
product: {
value: productWarehouse.product.id,
label: productWarehouse.product.name,
},
};
}
}
return product;
});
if (hasUpdates && updatedProducts) {
formik.setFieldValue('products', updatedProducts);
const updatedDeliveries = formik.values.deliveries?.map((delivery) => {
const updatedDeliveryProducts = delivery.products.map(
(deliveryProduct) => {
if (
deliveryProduct.product &&
deliveryProduct.product.label.startsWith('Product ID:')
) {
const productWarehouse = productWarehouses.data.find(
(pw) => pw.product.id === deliveryProduct.product_id
);
if (productWarehouse) {
return {
...deliveryProduct,
product: {
value: productWarehouse.product.id,
label: productWarehouse.product.name,
},
};
}
}
return deliveryProduct;
}
);
return {
...delivery,
products: updatedDeliveryProducts,
};
});
formik.setFieldValue('deliveries', updatedDeliveries);
}
}, [productWarehouses, type]);
useEffect(() => {
if (type !== 'edit' && type !== 'detail') return;
const productIdsToFetch: number[] = [];
formik.values.products?.forEach((product) => {
if (
product.product &&
product.product.label.startsWith('Product ID:') &&
product.product_id > 0 &&
!fetchedProductIds.has(product.product_id)
) {
productIdsToFetch.push(product.product_id);
}
});
formik.values.deliveries?.forEach((delivery) => {
delivery.products.forEach((deliveryProduct) => {
if (
deliveryProduct.product &&
deliveryProduct.product.label.startsWith('Product ID:') &&
deliveryProduct.product_id > 0 &&
!fetchedProductIds.has(deliveryProduct.product_id)
) {
if (!productIdsToFetch.includes(deliveryProduct.product_id)) {
productIdsToFetch.push(deliveryProduct.product_id);
}
}
});
});
if (productIdsToFetch.length === 0) return;
const fetchProducts = async () => {
const productMap = new Map<number, { id: number; name: string }>();
const newFetchedIds = new Set(fetchedProductIds);
for (const productId of productIdsToFetch) {
try {
const response = await ProductApi.getSingle(productId);
if (isResponseSuccess(response)) {
const product = response.data;
productMap.set(product.id, { id: product.id, name: product.name });
newFetchedIds.add(productId);
}
} catch (error) {
console.error(`Failed to fetch product ${productId}:`, error);
newFetchedIds.add(productId); // Mark as fetched to avoid retry loops
}
}
if (productMap.size > 0) {
const updatedProducts = formik.values.products?.map((p) => {
const productData = productMap.get(p.product_id);
if (productData) {
return {
...p,
product: {
value: productData.id,
label: productData.name,
},
};
}
return p;
});
const updatedDeliveries = formik.values.deliveries?.map((delivery) => {
const updatedDeliveryProducts = delivery.products.map(
(deliveryProduct) => {
const productData = productMap.get(deliveryProduct.product_id);
if (productData) {
return {
...deliveryProduct,
product: {
value: productData.id,
label: productData.name,
},
};
}
return deliveryProduct;
}
);
return {
...delivery,
products: updatedDeliveryProducts,
};
});
if (updatedProducts) {
formik.setFieldValue('products', updatedProducts);
}
if (updatedDeliveries) {
formik.setFieldValue('deliveries', updatedDeliveries);
}
}
setFetchedProductIds(newFetchedIds);
};
fetchProducts();
}, [
formik.values.products,
formik.values.deliveries,
type,
fetchedProductIds,
]);
const getFilteredProductWarehouseOptions = useCallback(() => { const getFilteredProductWarehouseOptions = useCallback(() => {
return ( return (
formik.values.products formik.values.products
@@ -484,7 +662,7 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => {
return ( return (
<> <>
<section className='w-full max-w-5xl'> <section className='w-full'>
<FormHeader <FormHeader
type={type} type={type}
title='Movement' title='Movement'