2026-01-27 18:27:29 +05:30
|
|
|
import { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
|
|
|
|
import { Form, Input, AutoComplete, Select, Tooltip, Modal } from 'antd';
|
|
|
|
|
import { BsUpcScan } from 'react-icons/bs';
|
|
|
|
|
import { ArrowRightOutlined, DeleteFilled } from '@ant-design/icons';
|
|
|
|
|
import { useDispatch, useSelector } from 'react-redux';
|
|
|
|
|
import FormHeader from '../PageComponents/FormHeader.jsx';
|
|
|
|
|
import FloatLabel from '../../Components/Forms/FloatLabel/index.jsx';
|
|
|
|
|
import { Messages } from '../../Components/Notifications/Messages.jsx';
|
|
|
|
|
import Buttons from '../../Components/Forms/Buttons.jsx';
|
|
|
|
|
import './CreateTransfer.scss';
|
|
|
|
|
import { getPurProdvaraiantdata } from '../../Features/PurchaseOrder/PurchaseOrder.js';
|
|
|
|
|
import { getProductDataPageNo } from '../../Features/ProductPage/ProductPage.js';
|
|
|
|
|
import { DropDowns } from '../../Components/Forms/DropDown.jsx';
|
|
|
|
|
import { getSession } from '../../Services/Others.js';
|
|
|
|
|
import { getCompBranchData } from '../../Features/BrachLogin/BranchLogin.js';
|
|
|
|
|
import { changeBreadCrumb } from '../../Features/AppPage/CenterPage.js';
|
|
|
|
|
import { useLocation, useNavigate } from 'react-router-dom';
|
|
|
|
|
import { postRequestStock } from '../../Features/RequestStock/RequestStock.js';
|
2026-02-20 15:31:31 +05:30
|
|
|
import BarCodeScan from '../../Services/BarCodeScan.jsx';
|
|
|
|
|
import { isMobile } from 'react-device-detect';
|
2026-01-27 18:27:29 +05:30
|
|
|
const subDirectory = import.meta.env.BASE_URL;
|
|
|
|
|
|
|
|
|
|
function debounce(func, wait) {
|
|
|
|
|
let timeout;
|
|
|
|
|
return function executedFunction(...args) {
|
|
|
|
|
const later = () => {
|
|
|
|
|
clearTimeout(timeout);
|
|
|
|
|
func(...args);
|
|
|
|
|
};
|
|
|
|
|
clearTimeout(timeout);
|
|
|
|
|
timeout = setTimeout(later, wait);
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const CreateTransfer = () => {
|
|
|
|
|
const dispatch = useDispatch();
|
|
|
|
|
const formProductRef = useRef();
|
|
|
|
|
const productInputRef = useRef(null);
|
|
|
|
|
const location = useLocation();
|
|
|
|
|
const state = location?.state;
|
|
|
|
|
const status = state.transform;
|
|
|
|
|
const navigate = useNavigate();
|
|
|
|
|
const CompId = getSession('CompId');
|
|
|
|
|
const BranchId = getSession('BranchId');
|
|
|
|
|
const AppId = getSession('AppId');
|
|
|
|
|
const UserId = getSession('UserId');
|
|
|
|
|
|
|
|
|
|
const [messageType, setMessageType] = useState(null);
|
|
|
|
|
const [messageData, setMessageData] = useState(null);
|
|
|
|
|
const [productSearchText, setProductSearchText] = useState('');
|
|
|
|
|
const [searchText, setSearchText] = useState('');
|
|
|
|
|
const [tableData, setTableData] = useState([]);
|
|
|
|
|
const [scanner, setScanner] = useState(false);
|
|
|
|
|
const [productdata, setProductData] = useState([]);
|
|
|
|
|
const [variantDetails, setVariantDetails] = useState([]);
|
|
|
|
|
const [showVariantModal, setShowVariantModal] = useState(false);
|
|
|
|
|
const [selectedProductForVariant, setSelectedProductForVariant] =
|
|
|
|
|
useState(null);
|
|
|
|
|
const [selectedVariants, setSelectedVariants] = useState([]);
|
|
|
|
|
const [isProcessing, setIsProcessing] = useState(false);
|
|
|
|
|
const [isManualSelection, setIsManualSelection] = useState(false);
|
|
|
|
|
const [branchData, setBranchData] = useState();
|
|
|
|
|
const [selectedBranchId, setSelectedBranchId] = useState();
|
|
|
|
|
const [selectedComponent, setselectedComponent] = useState('Create');
|
|
|
|
|
const items = [
|
|
|
|
|
{
|
|
|
|
|
name: 'Home',
|
|
|
|
|
link: `${subDirectory}app-page/home`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Transfer List',
|
|
|
|
|
link: `${subDirectory}setting/stock-transferlist`,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: status === 'Create' ? 'Create' : 'Request ',
|
|
|
|
|
link: null,
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
dispatch(changeBreadCrumb({ items: items }));
|
|
|
|
|
fetchbranchData();
|
|
|
|
|
fetchProductData();
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const onComplete = useCallback(() => {
|
|
|
|
|
setMessageType(null);
|
|
|
|
|
setMessageData(null);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const productOptions = useMemo(
|
|
|
|
|
() =>
|
|
|
|
|
productdata?.map((p) => ({
|
|
|
|
|
value: p.ProdId,
|
|
|
|
|
label: p.ProdName,
|
|
|
|
|
})) || [],
|
|
|
|
|
[productdata]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const filteredTableData = useMemo(() => {
|
|
|
|
|
if (!searchText) return tableData;
|
|
|
|
|
return tableData.filter(
|
|
|
|
|
(item) =>
|
|
|
|
|
item.ProdName?.toLowerCase().includes(searchText.toLowerCase()) ||
|
|
|
|
|
item.ProdVariantName?.toLowerCase().includes(searchText.toLowerCase())
|
|
|
|
|
);
|
|
|
|
|
}, [tableData, searchText]);
|
|
|
|
|
|
|
|
|
|
console.log(filteredTableData, 'AaADaAadaadAD');
|
|
|
|
|
|
|
|
|
|
const fetchbranchData = async () => {
|
|
|
|
|
const data = {
|
|
|
|
|
AppId: AppId,
|
|
|
|
|
CompId: CompId,
|
|
|
|
|
};
|
|
|
|
|
const res = await dispatch(getCompBranchData(data)).unwrap();
|
|
|
|
|
if (res?.data?.statusCode === 1) {
|
|
|
|
|
const filteredBranches = res?.data?.data?.filter(
|
|
|
|
|
(branch) => branch.BrId !== BranchId
|
|
|
|
|
);
|
|
|
|
|
setBranchData(filteredBranches);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
const fetchProductData = async () => {
|
|
|
|
|
try {
|
|
|
|
|
const ProdData = {
|
|
|
|
|
AppId: AppId,
|
|
|
|
|
CompId: CompId,
|
|
|
|
|
BranchId: BranchId,
|
|
|
|
|
};
|
|
|
|
|
const res = await dispatch(getProductDataPageNo(ProdData)).unwrap();
|
|
|
|
|
if (res?.data?.statusCode === 1) {
|
|
|
|
|
setProductData(res?.data?.data || []);
|
|
|
|
|
} else {
|
|
|
|
|
setMessageType('error');
|
|
|
|
|
setMessageData('Failed to load products');
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error fetching products:', error);
|
|
|
|
|
setMessageType('error');
|
|
|
|
|
setMessageData('Error loading products');
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const branchDropDownChange = async (e) => {
|
|
|
|
|
setSelectedBranchId(e);
|
|
|
|
|
};
|
|
|
|
|
const addProductToTable = useCallback((product, variant = null) => {
|
|
|
|
|
const newProduct = {
|
|
|
|
|
...product,
|
|
|
|
|
...(variant && {
|
|
|
|
|
ProdVariantId: variant.ProdVariantId,
|
|
|
|
|
ProdVariantName: variant.ProdVariantName,
|
|
|
|
|
InwardDtlId: variant.StockDetails?.[0]?.InwardDtlId,
|
|
|
|
|
MRP: variant.StockDetails?.[0]?.MRP,
|
|
|
|
|
SellPrice: variant.StockDetails?.[0]?.SellPrice,
|
|
|
|
|
OverAllQty: variant.OverAllQty,
|
|
|
|
|
}),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let success = false;
|
|
|
|
|
setTableData((prev) => {
|
|
|
|
|
const safePrev = Array.isArray(prev) ? prev : [];
|
|
|
|
|
const exists = safePrev.some(
|
|
|
|
|
(p) =>
|
|
|
|
|
p.ProdId === newProduct.ProdId &&
|
|
|
|
|
(variant
|
|
|
|
|
? p.ProdVariantId === variant.ProdVariantId
|
|
|
|
|
: !p.ProdVariantId)
|
|
|
|
|
);
|
|
|
|
|
if (exists) {
|
|
|
|
|
return safePrev;
|
|
|
|
|
}
|
|
|
|
|
success = true;
|
|
|
|
|
// return [...safePrev, newProduct];
|
|
|
|
|
return [newProduct, ...safePrev];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
|
setMessageType('warning');
|
|
|
|
|
setMessageData(
|
|
|
|
|
variant ? `Already added in the table` : 'This product is already Added'
|
|
|
|
|
);
|
|
|
|
|
}
|
2026-01-29 09:40:03 +05:30
|
|
|
if (scanner) {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
productInputRef.current?.focus();
|
|
|
|
|
setProductSearchText('');
|
|
|
|
|
}, 100);
|
|
|
|
|
}
|
2026-01-27 18:27:29 +05:30
|
|
|
|
|
|
|
|
formProductRef?.current?.resetFields();
|
2026-01-29 09:40:03 +05:30
|
|
|
if (!scanner) {
|
2026-01-27 18:27:29 +05:30
|
|
|
setProductSearchText('');
|
2026-01-29 09:40:03 +05:30
|
|
|
}
|
2026-01-27 18:27:29 +05:30
|
|
|
|
|
|
|
|
return success;
|
2026-01-29 09:40:03 +05:30
|
|
|
}, [scanner]);
|
2026-01-27 18:27:29 +05:30
|
|
|
|
|
|
|
|
const focusProductInput = () => {
|
|
|
|
|
// give React a tick to close modal/input blur before refocusing
|
|
|
|
|
setTimeout(() => productInputRef.current?.focus(), 0);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleProductSearch = (value) => {
|
|
|
|
|
setProductSearchText(value);
|
|
|
|
|
if (scanner && value.length >= 3 && !isManualSelection) {
|
|
|
|
|
debouncedScannerSearch(value);
|
|
|
|
|
}
|
|
|
|
|
if (isManualSelection) {
|
|
|
|
|
setIsManualSelection(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const debouncedScannerSearch = useCallback(
|
|
|
|
|
debounce((value) => {
|
|
|
|
|
handleScannerResult(value);
|
|
|
|
|
}, 300),
|
|
|
|
|
[productdata]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const handleScannerResult = async (qrCode) => {
|
|
|
|
|
if (isProcessing) return; // Prevent multiple simultaneous processing
|
|
|
|
|
|
|
|
|
|
setIsProcessing(true);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const selectedProduct = productdata.find(
|
|
|
|
|
(p) => p.QRCode === qrCode || p.ProdId === qrCode
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (!selectedProduct) {
|
|
|
|
|
setMessageType('error');
|
|
|
|
|
setMessageData('Product not found for scanned code');
|
|
|
|
|
setProductSearchText('');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await processSelectedProduct(selectedProduct, true); // Pass true to indicate scanner mode
|
|
|
|
|
} finally {
|
|
|
|
|
setIsProcessing(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleProductSelect = async (prodId, option) => {
|
|
|
|
|
if (isProcessing) return;
|
|
|
|
|
setIsManualSelection(true);
|
|
|
|
|
setIsProcessing(true);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const selectedProduct = productdata.find((p) => p.ProdId === prodId);
|
|
|
|
|
|
|
|
|
|
if (!selectedProduct) {
|
|
|
|
|
setMessageType('error');
|
|
|
|
|
setMessageData('Product not found');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await processSelectedProduct(selectedProduct, false);
|
|
|
|
|
} finally {
|
|
|
|
|
setIsProcessing(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const processSelectedProduct = async (selectedProduct, isScanner = false) => {
|
|
|
|
|
if (!selectedProduct) {
|
|
|
|
|
setMessageType('error');
|
|
|
|
|
setMessageData('Product not found');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
formProductRef.current?.setFieldsValue({ ProdId: selectedProduct.ProdId });
|
|
|
|
|
setProductSearchText(selectedProduct.ProdName);
|
|
|
|
|
setSelectedProductForVariant(selectedProduct);
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const data = {
|
|
|
|
|
CompId,
|
|
|
|
|
AppId,
|
|
|
|
|
BranchId,
|
|
|
|
|
prodName: selectedProduct.ProdName,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const variantValues = await dispatch(
|
|
|
|
|
getPurProdvaraiantdata(data)
|
|
|
|
|
).unwrap();
|
|
|
|
|
|
|
|
|
|
if (variantValues?.data?.statusCode === 1) {
|
|
|
|
|
const variants =
|
|
|
|
|
variantValues?.data?.data?.filter(
|
|
|
|
|
(prod) =>
|
|
|
|
|
prod?.ProductDetail?.[0]?.ProdId === selectedProduct?.ProdId
|
|
|
|
|
)?.[0]?.ProductDetail?.[0]?.ProdVariantDetails || [];
|
|
|
|
|
|
|
|
|
|
// Add unique identifier to each variant if not present
|
|
|
|
|
const variantsWithId = variants.map((variant, index) => ({
|
|
|
|
|
...variant,
|
|
|
|
|
ProdVariantId:
|
|
|
|
|
variant.ProdVariantId ||
|
|
|
|
|
variant.StockDetails?.[0]?.InwardDtlId ||
|
|
|
|
|
`${selectedProduct.ProdId}_${variant.ProdVariantName}_${index}`,
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
if (variantsWithId.length > 1) {
|
|
|
|
|
setVariantDetails(variantsWithId);
|
|
|
|
|
setSelectedVariants([]); // Reset selected variants
|
|
|
|
|
setShowVariantModal(true);
|
|
|
|
|
} else if (variantsWithId.length === 1) {
|
|
|
|
|
addProductToTable(selectedProduct, variantsWithId[0]);
|
|
|
|
|
} else {
|
|
|
|
|
addProductToTable(selectedProduct);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
addProductToTable(selectedProduct);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error('Error fetching variants:', error);
|
|
|
|
|
addProductToTable(selectedProduct);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleFieldChange = (field, index, value) => {
|
|
|
|
|
setTableData((prev) => {
|
|
|
|
|
const updated = [...prev];
|
|
|
|
|
const actualItem = filteredTableData[index];
|
|
|
|
|
const actualIndex = prev?.findIndex(
|
|
|
|
|
(p) =>
|
|
|
|
|
p.ProdId === actualItem.ProdId &&
|
|
|
|
|
p.ProdVariantId === actualItem.ProdVariantId
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (actualIndex !== -1) {
|
|
|
|
|
updated[actualIndex][field] = value;
|
|
|
|
|
}
|
|
|
|
|
return updated;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleVariantCheckboxChange = (variantId, checked) => {
|
|
|
|
|
setSelectedVariants((prev) => {
|
|
|
|
|
if (checked) {
|
|
|
|
|
return [...prev, variantId];
|
|
|
|
|
} else {
|
|
|
|
|
return prev.filter((id) => id !== variantId);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleAddSelectedVariants = () => {
|
|
|
|
|
if (selectedVariants.length === 0) {
|
|
|
|
|
setMessageType('warning');
|
|
|
|
|
setMessageData('Please select at least one variant');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (selectedProductForVariant) {
|
|
|
|
|
let addedCount = 0;
|
|
|
|
|
|
|
|
|
|
selectedVariants.forEach((variantId) => {
|
|
|
|
|
const variant = variantDetails.find(
|
|
|
|
|
(v) => v.ProdVariantId === variantId
|
|
|
|
|
);
|
|
|
|
|
if (variant) {
|
|
|
|
|
const success = addProductToTable(selectedProductForVariant, variant);
|
|
|
|
|
if (success) addedCount++;
|
|
|
|
|
}
|
|
|
|
|
if (addedCount > 0) {
|
|
|
|
|
setMessageType('success');
|
|
|
|
|
setMessageData(
|
|
|
|
|
`${variant.ProdVariantName} added to transfer successfully`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setShowVariantModal(false);
|
|
|
|
|
setVariantDetails([]);
|
|
|
|
|
setSelectedProductForVariant(null);
|
|
|
|
|
setSelectedVariants([]);
|
2026-01-29 09:40:03 +05:30
|
|
|
|
|
|
|
|
if (scanner) {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
productInputRef.current?.focus();
|
|
|
|
|
setProductSearchText('');
|
|
|
|
|
}, 100);
|
|
|
|
|
}
|
2026-01-27 18:27:29 +05:30
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleDeleteRow = (index) => {
|
|
|
|
|
const productToDelete = filteredTableData[index];
|
|
|
|
|
|
|
|
|
|
setTableData((prev) =>
|
|
|
|
|
prev?.filter(
|
|
|
|
|
(p) =>
|
|
|
|
|
!(
|
|
|
|
|
p.ProdId === productToDelete.ProdId &&
|
|
|
|
|
p.ProdVariantId === productToDelete.ProdVariantId
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
setMessageType('success');
|
|
|
|
|
setMessageData(`${productToDelete.ProdName} removed successfully`);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const onFinish = async () => {
|
|
|
|
|
if (tableData.length === 0) {
|
|
|
|
|
setMessageType('warning');
|
|
|
|
|
setMessageData('Please add at least one product');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const invalidRow = tableData.find(
|
|
|
|
|
(item) => !item.ReceivedQty || Number(item.ReceivedQty) <= 0
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (invalidRow) {
|
|
|
|
|
setMessageType('warning');
|
|
|
|
|
setMessageData('Required Qty must be greater than 0 for all products');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!selectedBranchId) {
|
|
|
|
|
setMessageType('warning');
|
|
|
|
|
setMessageData('Please select To Location (Branch)');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const payload = {
|
|
|
|
|
CompId: CompId,
|
|
|
|
|
BranchId: BranchId,
|
|
|
|
|
AppId: AppId,
|
|
|
|
|
FromLocationId:
|
|
|
|
|
selectedComponent === 'Create' ? selectedBranchId : BranchId,
|
|
|
|
|
ToLocationId:
|
|
|
|
|
selectedComponent === 'Create' ? BranchId : selectedBranchId,
|
|
|
|
|
RequestStatus: selectedComponent,
|
|
|
|
|
RequestStock: tableData?.map((item) => ({
|
|
|
|
|
ProdId: item.ProdId,
|
|
|
|
|
InwardDtlId: item.InwardDtlId,
|
|
|
|
|
RequestedQty: Number(item.ReceivedQty),
|
|
|
|
|
})),
|
|
|
|
|
CreatedBy: UserId,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const response = await dispatch(postRequestStock(payload)).unwrap();
|
|
|
|
|
if (response?.data?.statusCode === 1) {
|
|
|
|
|
setMessageType('success');
|
|
|
|
|
setMessageData(response?.data?.response);
|
|
|
|
|
setTableData('');
|
|
|
|
|
setSelectedBranchId('');
|
|
|
|
|
navigate(`${subDirectory}setting/stock-transferlist`);
|
|
|
|
|
} else {
|
|
|
|
|
setMessageType('error');
|
|
|
|
|
setMessageData(response?.data?.response);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (status && status !== selectedComponent) {
|
|
|
|
|
setselectedComponent(status);
|
|
|
|
|
}
|
|
|
|
|
}, [status]);
|
|
|
|
|
const ComponentSelect = (value) => {
|
|
|
|
|
setselectedComponent(value);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const handleScannerToggle = () => {
|
|
|
|
|
setScanner((prev) => {
|
|
|
|
|
const next = !prev;
|
|
|
|
|
if (next) {
|
|
|
|
|
setIsManualSelection(false);
|
|
|
|
|
setTimeout(() => productInputRef.current?.focus(), 0);
|
|
|
|
|
}
|
|
|
|
|
return next;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="pageOverAll CreateTransferMaster">
|
|
|
|
|
<div className="userPage">
|
|
|
|
|
<div className="userPageContent">
|
|
|
|
|
<Messages
|
|
|
|
|
messageType={messageType}
|
|
|
|
|
messageData={messageData}
|
|
|
|
|
onComplete={onComplete}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
{/* <FormHeader title="Create Transfer" /> */}
|
|
|
|
|
{/* <div
|
|
|
|
|
className="defaultandCustomisedTheme"
|
|
|
|
|
>
|
|
|
|
|
{status === 'Create' && <div
|
|
|
|
|
className={selectedComponent === 'Create' ? 'selected' : ''}
|
|
|
|
|
onClick={() => ComponentSelect(status)}
|
|
|
|
|
>
|
|
|
|
|
{status} Transfer
|
|
|
|
|
</div>}
|
|
|
|
|
{status === 'Request' && <div
|
|
|
|
|
className={selectedComponent === 'Request' ? 'selected' : ''}
|
|
|
|
|
onClick={() => ComponentSelect(status)}
|
|
|
|
|
>
|
|
|
|
|
{status} Transfer
|
|
|
|
|
</div>}
|
|
|
|
|
</div> */}
|
|
|
|
|
<Form ref={formProductRef} layout="vertical">
|
|
|
|
|
<div style={{ display: 'flex', gap: '10px' }}>
|
|
|
|
|
<div className="product-selection-row">
|
|
|
|
|
<div className="product-name-wrapper">
|
|
|
|
|
<FloatLabel label="Product Name" value={productSearchText}>
|
|
|
|
|
<AutoComplete
|
|
|
|
|
ref={productInputRef}
|
|
|
|
|
options={productOptions}
|
|
|
|
|
value={productSearchText}
|
|
|
|
|
onSelect={(value, option) =>
|
|
|
|
|
handleProductSelect(value, option)
|
|
|
|
|
}
|
|
|
|
|
onChange={handleProductSearch}
|
|
|
|
|
filterOption={(input, option) =>
|
|
|
|
|
option.label.toLowerCase().includes(input.toLowerCase())
|
|
|
|
|
}
|
|
|
|
|
disabled={isProcessing}
|
|
|
|
|
/>
|
|
|
|
|
</FloatLabel>
|
2026-02-20 15:31:31 +05:30
|
|
|
{isMobile ? (
|
|
|
|
|
<div className="IsmoBileBarcode">
|
|
|
|
|
<BarCodeScan
|
|
|
|
|
onScan={(value) => {
|
|
|
|
|
debouncedScannerSearch(value);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
2026-01-27 18:27:29 +05:30
|
|
|
<Tooltip title="BarCode / QR Code">
|
|
|
|
|
<BsUpcScan
|
|
|
|
|
className={`product-scan-icon ${scanner ? 'active' : ''}`}
|
|
|
|
|
onClick={handleScannerToggle}
|
|
|
|
|
/>
|
|
|
|
|
</Tooltip>
|
2026-02-20 15:31:31 +05:30
|
|
|
)}
|
2026-01-27 18:27:29 +05:30
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<DropDowns
|
|
|
|
|
label={<label className="required">Branch</label>}
|
|
|
|
|
className="field-DropDown"
|
|
|
|
|
options={branchData?.map((option) => ({
|
|
|
|
|
value: option.BrId,
|
|
|
|
|
label: option.BrName,
|
|
|
|
|
}))}
|
|
|
|
|
onChangeFunction={(e) => branchDropDownChange(e)}
|
|
|
|
|
valueData={selectedBranchId}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
{/* <div>
|
|
|
|
|
<Input
|
|
|
|
|
placeholder="Search products in table..."
|
|
|
|
|
value={searchText}
|
|
|
|
|
allowClear
|
|
|
|
|
onChange={(e) => setSearchText(e.target.value)}
|
|
|
|
|
className="transfer-search-input"
|
|
|
|
|
/>
|
|
|
|
|
</div> */}
|
|
|
|
|
</div>
|
|
|
|
|
</Form>
|
|
|
|
|
|
|
|
|
|
<Form onFinish={onFinish} className="transfer-form">
|
|
|
|
|
<div className="transfer-table-container">
|
|
|
|
|
<table className="transfer-table">
|
|
|
|
|
<thead className="transfer-table-head">
|
|
|
|
|
<tr>
|
|
|
|
|
<th style={{ width: '60px' }}>S.No</th>
|
|
|
|
|
<th style={{ textAlign: 'left' }}>Product Name</th>
|
|
|
|
|
<th>Product Variant</th>
|
|
|
|
|
<th>UOM Name</th>
|
|
|
|
|
{selectedComponent === 'Create' && <th>Balance Qty</th>}
|
|
|
|
|
<th>Required Qty</th>
|
|
|
|
|
<th style={{ width: '100px' }}>Action</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
|
|
|
|
|
<tbody className="transfer-table-body">
|
|
|
|
|
{filteredTableData.length === 0 ? (
|
|
|
|
|
<tr>
|
|
|
|
|
<td colSpan="7" className="transfer-no-data">
|
|
|
|
|
No products added yet.
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
) : (
|
|
|
|
|
filteredTableData?.map((record, index) => (
|
|
|
|
|
<tr
|
|
|
|
|
key={`${record.ProdId}-${record.ProdVariantId || 'no-variant'}`}
|
|
|
|
|
>
|
|
|
|
|
<td>{index + 1}</td>
|
|
|
|
|
<td style={{ textAlign: 'left' }}>{record.ProdName}</td>
|
|
|
|
|
<td>{record.ProdVariantName || 'N/A'}</td>
|
|
|
|
|
<td>{record.UomName}</td>
|
|
|
|
|
{selectedComponent === 'Create' && (
|
|
|
|
|
<td>{record.OverAllQty}</td>
|
|
|
|
|
)}
|
|
|
|
|
<td>
|
|
|
|
|
<Input
|
|
|
|
|
className="transfer-input"
|
|
|
|
|
// value={record.ReceivedQty}
|
|
|
|
|
placeholder="0"
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
handleFieldChange(
|
|
|
|
|
'ReceivedQty',
|
|
|
|
|
index,
|
|
|
|
|
e.target.value.replace(/[^0-9.]/g, '')
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
/>
|
|
|
|
|
</td>
|
|
|
|
|
<td className="transfer-action">
|
|
|
|
|
<DeleteFilled
|
|
|
|
|
style={{
|
|
|
|
|
color: '#FF4D4F',
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
fontSize: '16px',
|
|
|
|
|
}}
|
|
|
|
|
onClick={() => handleDeleteRow(index)}
|
|
|
|
|
/>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
))
|
|
|
|
|
)}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="buttonStyle">
|
|
|
|
|
<Buttons
|
|
|
|
|
icon={<ArrowRightOutlined />}
|
|
|
|
|
buttonText={status}
|
|
|
|
|
color="901D77"
|
|
|
|
|
htmlType="submit"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
</Form>
|
|
|
|
|
|
|
|
|
|
<Modal
|
|
|
|
|
title="Select Product Variant"
|
|
|
|
|
open={showVariantModal}
|
|
|
|
|
onCancel={() => {
|
|
|
|
|
setShowVariantModal(false);
|
|
|
|
|
setVariantDetails([]);
|
|
|
|
|
setSelectedProductForVariant(null);
|
|
|
|
|
setSelectedVariants([]);
|
2026-01-29 09:40:03 +05:30
|
|
|
|
|
|
|
|
if (scanner) {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
productInputRef.current?.focus();
|
|
|
|
|
setProductSearchText('');
|
|
|
|
|
}, 100);
|
|
|
|
|
}
|
2026-01-27 18:27:29 +05:30
|
|
|
}}
|
|
|
|
|
onOk={handleAddSelectedVariants}
|
|
|
|
|
okText="Add Selected Variants"
|
|
|
|
|
cancelText="Cancel"
|
|
|
|
|
width={500}
|
|
|
|
|
>
|
|
|
|
|
<div className="variant-modal-content">
|
|
|
|
|
{variantDetails && variantDetails.length > 0 ? (
|
|
|
|
|
<div>
|
|
|
|
|
<p style={{ marginBottom: '1rem', fontWeight: 500 }}>
|
|
|
|
|
Select variant(s) for{' '}
|
|
|
|
|
<strong>{selectedProductForVariant?.ProdName}</strong>
|
|
|
|
|
</p>
|
|
|
|
|
{variantDetails.map((variant) => (
|
|
|
|
|
<div
|
|
|
|
|
key={variant.ProdVariantId}
|
|
|
|
|
className="variant-option"
|
|
|
|
|
style={{
|
|
|
|
|
padding: '12px',
|
|
|
|
|
border: '1px solid #d9d9d9',
|
|
|
|
|
borderRadius: '4px',
|
|
|
|
|
marginBottom: '8px',
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
transition: 'all 0.3s',
|
|
|
|
|
backgroundColor: selectedVariants.includes(
|
|
|
|
|
variant.ProdVariantId
|
|
|
|
|
)
|
|
|
|
|
? '#f0f5ff'
|
|
|
|
|
: 'transparent',
|
|
|
|
|
}}
|
|
|
|
|
onClick={() =>
|
|
|
|
|
handleVariantCheckboxChange(
|
|
|
|
|
variant.ProdVariantId,
|
|
|
|
|
!selectedVariants.includes(variant.ProdVariantId)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
>
|
|
|
|
|
<div
|
|
|
|
|
style={{
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
gap: '12px',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
<input
|
|
|
|
|
type="checkbox"
|
|
|
|
|
checked={selectedVariants.includes(
|
|
|
|
|
variant.ProdVariantId
|
|
|
|
|
)}
|
|
|
|
|
onChange={(e) =>
|
|
|
|
|
handleVariantCheckboxChange(
|
|
|
|
|
variant.ProdVariantId,
|
|
|
|
|
e.target.checked
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
onClick={(e) => e.stopPropagation()}
|
|
|
|
|
style={{
|
|
|
|
|
width: '18px',
|
|
|
|
|
height: '18px',
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
accentColor: '#901D77',
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<div style={{ flex: 1 }}>
|
|
|
|
|
<p style={{ margin: '0 0 4px 0', fontWeight: 500 }}>
|
|
|
|
|
{variant.ProdVariantName}
|
|
|
|
|
</p>
|
|
|
|
|
<p
|
|
|
|
|
style={{
|
|
|
|
|
margin: '0',
|
|
|
|
|
fontSize: '12px',
|
|
|
|
|
color: '#666',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
MRP: ₹{variant.StockDetails?.[0]?.MRP || 0} | Sell
|
|
|
|
|
Price: ₹{variant.StockDetails?.[0]?.SellPrice || 0}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
<p
|
|
|
|
|
style={{
|
|
|
|
|
marginTop: '12px',
|
|
|
|
|
fontSize: '12px',
|
|
|
|
|
color: '#666',
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
Selected: {selectedVariants.length} variant(s)
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
<p>No variants available</p>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
</Modal>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
export default CreateTransfer;
|