From a2a57f758c0c7744dd9431aa85dc3e018bf8d5d2 Mon Sep 17 00:00:00 2001 From: rstubryan Date: Thu, 16 Oct 2025 16:24:42 +0700 Subject: [PATCH] refactor(FE-63,63,65): enhance MovementForm to fetch and display product details from ProductWarehouse --- .../movement/form/MovementForm.schema.ts | 96 +++++---- .../inventory/movement/form/MovementForm.tsx | 182 +++++++++++++++++- 2 files changed, 237 insertions(+), 41 deletions(-) diff --git a/src/components/pages/inventory/movement/form/MovementForm.schema.ts b/src/components/pages/inventory/movement/form/MovementForm.schema.ts index 148a7dce..d5d660ae 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.schema.ts +++ b/src/components/pages/inventory/movement/form/MovementForm.schema.ts @@ -119,43 +119,61 @@ export type MovementFormValues = Yup.InferType; export const getMovementFormInitialValues = ( initialValues?: Movement -): MovementFormValues => ({ - transfer_reason: initialValues?.transfer_reason ?? '', - transfer_date: initialValues?.transfer_date ?? '', - source_warehouse: initialValues?.source_warehouse - ? { - value: initialValues.source_warehouse.id, - label: initialValues.source_warehouse.name, - } - : null, - source_warehouse_id: initialValues?.source_warehouse?.id ?? 0, - destination_warehouse: initialValues?.destination_warehouse - ? { - value: initialValues.destination_warehouse.id, - label: initialValues.destination_warehouse.name, - } - : null, - destination_warehouse_id: initialValues?.destination_warehouse?.id ?? 0, - products: - initialValues?.details?.map((p) => ({ - product: { value: p.product_id, label: '' }, - product_id: p.product_id, - product_qty: p.quantity, - })) ?? [], - deliveries: - initialValues?.deliveries?.map((d) => ({ - 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: { 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, +): MovementFormValues => { + const detailIdToProductId = new Map(); + initialValues?.details?.forEach((detail) => { + detailIdToProductId.set(detail.id, detail.product_id); + }); + + return { + transfer_reason: initialValues?.transfer_reason ?? '', + transfer_date: initialValues?.transfer_date ?? '', + source_warehouse: initialValues?.source_warehouse + ? { + value: initialValues.source_warehouse.id, + label: initialValues.source_warehouse.name, + } + : null, + source_warehouse_id: initialValues?.source_warehouse?.id ?? 0, + destination_warehouse: initialValues?.destination_warehouse + ? { + value: initialValues.destination_warehouse.id, + label: initialValues.destination_warehouse.name, + } + : null, + destination_warehouse_id: initialValues?.destination_warehouse?.id ?? 0, + products: + initialValues?.details?.map((p) => ({ + product: { value: p.product_id, label: `Product ID: ${p.product_id}` }, + product_id: p.product_id, 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, + }; + }), + }; + }) ?? [], + }; +}; diff --git a/src/components/pages/inventory/movement/form/MovementForm.tsx b/src/components/pages/inventory/movement/form/MovementForm.tsx index a6a75394..adf6658d 100644 --- a/src/components/pages/inventory/movement/form/MovementForm.tsx +++ b/src/components/pages/inventory/movement/form/MovementForm.tsx @@ -25,7 +25,11 @@ import { DeliverySchema, } from '@/components/pages/inventory/movement/form/MovementForm.schema'; 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 { toast } from 'react-hot-toast'; import FileInput from '@/components/input/FileInput'; @@ -43,6 +47,9 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { ] = useState(''); const [selectedProducts, setSelectedProducts] = useState([]); const [selectedDeliveries, setSelectedDeliveries] = useState([]); + const [fetchedProductIds, setFetchedProductIds] = useState>( + new Set() + ); const { deleteModal, @@ -338,12 +345,183 @@ const MovementForm = ({ type = 'add', initialValues }: MovementFormProps) => { }, [formik.values.deliveries]); useEffect(() => { - if (formik.values.source_warehouse_id && type !== 'edit') { + if ( + formik.values.source_warehouse_id && + type !== 'edit' && + type !== 'detail' + ) { formik.setFieldValue('products', []); formik.setFieldValue('deliveries', []); } }, [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(); + 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(() => { return ( formik.values.products