Android_Retail/src/Pages/StockTransfer/Dispatch.jsx

1542 lines
48 KiB
JavaScript

import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { getSession } from '../../Services/Others';
import { useCallback, useEffect, useRef, useState } from 'react';
import { Messages } from '../../Components/Notifications/Messages';
import { changeBreadCrumb } from '../../Features/AppPage/CenterPage';
import { Tables } from '../../Components/Tables/Table';
import Search from '../../Components/Forms/Search';
import FormHeader from '../PageComponents/FormHeader';
import { Input, Form, DatePicker, Collapse, Tooltip } from 'antd';
import { IoEye } from 'react-icons/io5';
import './Dispatch.scss';
import "../../Styles/BookingScreen/Components/SelectionComponent/SelectionComponent.scss"
import { DefaultModal } from '../../Components/Modal/DefaultModal';
import {
getRequestedStockQty,
postDispatchStock,
getPendingAndCompletedDispatches,
getDispatchSummary,
} from '../../Features/StockTransfer/StockTransfer';
import { ArrowRightOutlined, PrinterFilled } from '@ant-design/icons';
import Buttons from '../../Components/Forms/Buttons';
import { isMobile } from 'react-device-detect';
import { printDiv } from '../../Services/WSOthers';
import { getPrintSelectionComponentData } from '../../Features/ThemeChange/ThemeChange';
import DeliveryPrint from '../DeliveryChallan/DeliveryPrint';
import { DCPrintStyleFunction } from '../DeliveryChallan/DCPrintStyleFunction';
// import { IoEye } from "react-icons/io5";
const subDirectory = import.meta.env.BASE_URL;
const items = [
{
name: 'Home',
link: `${subDirectory}app-page/home`,
},
{
name: 'Stock Dispatch',
link: `${subDirectory}setting/stock-dispatch`,
},
];
const Dispatch = () => {
const formatDate = (dateString = '') => {
const date = new Date(dateString);
const day = date.getDate().toString().padStart(2, '0');
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
const month = months[date.getMonth()];
const year = date.getFullYear();
return day && month && year ? `${day}-${month}-${year}` : '-';
};
const formatTime12 = (dateString = '') => {
if (!dateString) return '-';
const date = new Date(dateString);
if (isNaN(date)) return '-';
let hours = date.getHours();
const minutes = date.getMinutes().toString().padStart(2, '0');
const ampm = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12 || 12;
return `${hours}:${minutes} ${ampm}`;
};
const queueColumns = [
{
title: 'SL.NO',
dataIndex: 'slno',
key: 'slno',
align: 'center',
width: '80px',
render: (text, record, index) => (
<span>{(page - 1) * 10 + index + 1}</span>
),
},
{
title: 'Queue No',
dataIndex: 'RequestId',
key: 'RequestId',
},
{
title: 'Queue Date',
dataIndex: 'CreatedDate',
key: 'CreatedDate',
render: (text) => formatDate(text),
},
{
title: 'Requested From',
dataIndex: 'FromLocationName',
key: 'FromLocationName',
},
{
title: 'Requested By',
dataIndex: 'RequestByName',
key: 'RequestByName',
align: 'center',
},
{
title: 'View',
dataIndex: 'view',
key: 'view',
align: 'center',
render: (text, record) => (
<IoEye
style={{
color: record?.ProductDetail?.length === 0 ? 'grey' : '#1292EE',
fontSize: '25px',
cursor:
record?.ProductDetail?.length === 0 ? 'not-allowed' : 'pointer',
}}
onClick={
record?.ProductDetail?.length === 0
? () => {}
: () => viewProductDetails(record.ProductDetail)
}
/>
),
},
{
title: 'Action',
dataIndex: 'action',
key: 'action',
align: 'center',
render: (text, record) => (
<div
className={`dispatch-btn ${record?.ProductDetail?.length === 0 ? 'disable' : ''}`}
onClick={
record?.ProductDetail?.length === 0
? () => {}
: () => openDispatchModal(record.ProductDetail, record)
}
>
Dispatch
</div>
),
},
];
const dispatchedColumns = [
{
title: 'SL.NO',
dataIndex: 'slno',
key: 'slno',
align: 'center',
width: '60px',
render: (text, record, index) => index + 1,
},
{
title: 'Request Id',
dataIndex: 'RequestId',
key: 'RequestId',
},
{
title: 'Dispatch No',
dataIndex: 'DispatchId',
key: 'DispatchId',
},
{
title: 'Dispatch Date',
dataIndex: 'CreatedDate',
key: 'CreatedDate',
render: (text) => formatDate(text),
},
{
title: 'Branch Name',
dataIndex: 'ToBranchName',
key: 'ToBranchName',
},
{
title: "Receiver's Name",
dataIndex: 'ReceiverName',
key: 'ReceiverName',
align: 'center',
},
{
title: 'Total Items Accepted',
dataIndex: 'TotalItemsAccepted',
key: 'TotalItemsAccepted',
align: 'center',
render: (text, record, index) =>
record?.ProductDetails?.filter((prod) => prod?.AcceptedQty > 0)?.length,
},
{
title: 'Total Qty Accepted',
dataIndex: 'TotalItemsAccepted',
key: 'TotalItemsAccepted',
align: 'center',
render: (text, record) =>
record?.ProductDetails?.reduce(
(acc, item) => acc + (Number(item?.AcceptedQty) || 0),
0
) || 0,
},
{
title: 'View',
dataIndex: 'view',
key: 'view',
align: 'center',
render: (text, record) => (
<IoEye
style={{ color: '#1292EE', fontSize: '25px', cursor: 'pointer' }}
onClick={() => openCompleteModal(record.ProductDetails)}
/>
),
},
];
const navigate = useNavigate();
const dispatch = useDispatch();
const CompId = getSession('CompId');
const BranchId = getSession('BranchId');
const AppId = getSession('AppId');
const UserId = getSession('UserId');
const UserType = getSession('UserType');
const formRef = useRef(null);
const [messageType, setMessageType] = useState(null);
const [messageData, setMessageData] = useState(null);
const [dispatchData, setDispatchData] = useState([]);
const [deliveryData, setDeliveryData] = useState(null);
const totalQty = deliveryData?.ProductDetails?.reduce((sum, p) => sum + (p.DispatchedQty || 0), 0) || 0;
console.log(dispatchData, 'dispatchData');
const [filteredInfo, setFilteredInfo] = useState({});
const [searchedText, setSearchedText] = useState('');
const [sortedInfo, setSortedInfo] = useState({});
const [page, setpage] = useState(1);
const [prodDtlsPage, setProdDtlsPage] = useState(1);
const [isDispatchQueue, setIsDispatchQueue] = useState('queue');
const [viewModalVisible, setViewModalVisible] = useState(false);
const [dispatchModalVisible, setDispatchModalVisible] = useState(false);
const [selectedProducts, setSelectedProducts] = useState([]);
const [dispatchProducts, setDispatchProducts] = useState([]);
const [allocatedDispatch, setAllocatedDispatch] = useState([]);
const [dateRange, setDateRange] = useState([]);
const [originalData, setOriginalData] = useState([]);
const [count, setCount] = useState(null);
const [printStyle, setPrintStyle] = useState(null);
const [printTemplateDtl, setPrintTemplateDtl] = useState(null);
const [transportMode, setTransportMode] = useState('');
const [vehicleNumber, setVehicleNumber] = useState('');
const [driverMobile, setDriverMobile] = useState('');
const [driverLicense, setDriverLicense] = useState('');
const [completeModalVisible, setCompleteModalVisible] = useState(false);
const [completeProducts, setCompleteProducts] = useState([]);
const [remarks, setRemarks] = useState({});
const [allocateModalVisible, setAllocateModalVisible] = useState(false);
const [currentProduct, setCurrentProduct] = useState(null);
const [currentProductIndex, setCurrentProductIndex] = useState(null);
const [allocatedStocks, setAllocatedStocks] = useState({});
const [initialFetch, setInitialFetch] = useState(true);
console.log(dateRange, 'dateRange');
useEffect(() => {
try {
dispatch(changeBreadCrumb({ items: items }));
fetchData();
} catch (error) {
console.log(error);
}
}, [isDispatchQueue]);
// useEffect(() => {
// if (!initialFetch) {
// fetchData();
// }
// }, [dateRange])
useEffect(() => {
filterData(searchedText);
}, [originalData]);
useEffect(() => {
getPrintData();
}, []);
const onSearch = (value) => {
setSearchedText(value);
filterData(value);
};
const onSearchChange = (e) => {
const value = e?.target?.value;
setSearchedText(value);
filterData(value);
};
const filterData = (searchText) => {
if (!searchText) {
setDispatchData(originalData);
return;
}
const filtered = originalData.filter((item) => {
const searchLower = searchText.toLowerCase();
// Filter by branch name
const branchName = (
isDispatchQueue === 'queue'
? item.FromLocationName
: item?.ToBranchName || ''
).toLowerCase();
if (branchName.includes(searchLower)) return true;
// Filter by ID numbers based on current tab
if (isDispatchQueue === 'queue') {
const queueNo = (
item.RequestId ||
item.FromLocationName ||
''
).toLowerCase();
if (queueNo.includes(searchLower)) return true;
} else if (isDispatchQueue === 'transit') {
const transitNo = (
item.RequestId ||
item.DispatchId ||
item?.ToBranchName ||
''
).toLowerCase();
if (transitNo.includes(searchLower)) return true;
} else if (isDispatchQueue === 'dispatched') {
const dispatchNo = (
item.RequestId ||
item.DispatchId ||
item?.ToBranchName ||
''
).toLowerCase();
if (dispatchNo.includes(searchLower)) return true;
}
return false;
});
setpage(1);
setDispatchData(filtered);
};
const fetchData = async (fromDate = null, toDate = null) => {
try {
let data;
if (isDispatchQueue === 'queue') {
// Call dispatch queue API
const response = await dispatch(
getRequestedStockQty({
CompId,
BranchId,
AppId,
UserId,
UserType,
fromDate,
toDate,
})
)?.unwrap();
if (response?.data?.statusCode === 1) {
const filteredData = response?.data?.data?.filter((parent) =>
parent?.ProductDetail?.some((item) => Number(item?.PendingQty) >= 1)
);
data = filteredData || response?.data?.data || [];
} else {
data = [];
}
// data = queueSampleData;
} else if (isDispatchQueue === 'dispatched') {
// Call dispatched API
const response = await dispatch(
getPendingAndCompletedDispatches({
CompId,
BranchId,
AppId,
Type: 'C',
fromDate,
toDate,
})
)?.unwrap();
if (response?.data?.statusCode === 1) {
data = response?.data?.data || [];
} else {
data = [];
}
} else if (isDispatchQueue === 'transit') {
const response = await dispatch(
getPendingAndCompletedDispatches({
CompId,
BranchId,
AppId,
Type: 'P',
fromDate,
toDate,
})
)?.unwrap();
if (response?.data?.statusCode === 1) {
data = response?.data?.data || [];
} else {
data = [];
}
}
const res = await dispatch(
getDispatchSummary({ CompId, BranchId, AppId, fromDate, toDate })
)?.unwrap();
if (res?.data?.statusCode === 1 && res?.data?.data?.length > 0) {
console?.log(res?.data?.data, 'res?.data?.data');
setCount(res?.data?.data?.[0]);
} else {
setCount(null);
}
setOriginalData(data);
setDispatchData(data);
setInitialFetch(false);
} catch (error) {
console.log(error);
}
};
const handleToggleChange = (checked) => {
setIsDispatchQueue(checked);
};
const handleChange = (pagination, filters, sorter) => {
setFilteredInfo(filters);
setSortedInfo(sorter);
};
const handlePageChange = (current) => {
setpage(current);
};
const handleProdDtlsPageChange = (current) => {
setProdDtlsPage(current);
};
const convertRangeToYYYYMMDD = (dates) => {
if (!dates || dates.length !== 2) return [];
return dates.map((d) => d?.format('YYYY-MM-DD'));
};
const handleDateRangeChange = async (dates) => {
setDateRange(dates);
if (dates && dates.length === 2) {
const [startDate, endDate] = convertRangeToYYYYMMDD(dates);
if (startDate && endDate) {
await fetchData(startDate, endDate);
}
// const dateField =
// isDispatchQueue === 'queue'
// ? 'CreatedDate' : isDispatchQueue === 'transit'
// ? 'transit_date'
// : 'dispatch_date';
// const start = new Date(startDate);
// start.setHours(0, 0, 0, 0);
// const end = new Date(endDate);
// end.setHours(23, 59, 59, 999);
// const filtered = originalData.filter(item => {
// const itemDate = new Date(item[dateField]);
// return itemDate >= start && itemDate <= end;
// });
// setDispatchData(filtered);
} else {
await fetchData();
}
};
const transitColumns = [
{
title: 'SL.NO',
dataIndex: 'slno',
key: 'slno',
align: 'center',
width: '60px',
render: (text, record, index) => index + 1,
},
{
title: 'Request Id',
dataIndex: 'RequestId',
key: 'RequestId',
},
{
title: 'Dispatch No',
dataIndex: 'DispatchId',
key: 'DispatchId',
},
{
title: 'Transit Date',
dataIndex: 'CreatedDate',
key: 'CreatedDate',
align: 'center',
render: (text) => formatDate(text),
},
{
title: 'Transit Time',
dataIndex: 'CreatedDate',
key: 'CreatedDate',
align: 'right',
render: (text) => formatTime12(text),
},
{
title: 'Dispatched To',
dataIndex: 'ToBranchName',
key: 'ToBranchName',
align: 'center',
},
{
title: 'Dispatched By',
dataIndex: 'DispatcherName',
key: 'DispatcherName',
},
{
title: 'View',
dataIndex: 'view',
key: 'view',
align: 'center',
render: (text, record) => (
<IoEye
style={{ color: '#1292EE', fontSize: '25px', cursor: 'pointer' }}
onClick={() => viewProductDetails(record.ProductDetails)}
/>
),
},
{
title: 'Print',
key: 'Print',
dataIndex: 'Print',
width: '60px',
align: 'center',
render: (_, record) => (
<PrinterFilled
style={{ fontSize: '20px' }}
onClick={() => generateDeliveryChallan(record)}
/>
),
},
];
const generateDeliveryChallan = async (record) => {
setDeliveryData([{ ...record, dispatchPrint: true }]);
const DCstyle = await DCPrintStyleFunction(
(printStyle == undefined || printStyle === 'TaxInvoice') ? 'Style 13' : printStyle);
const stylesMap = {
'Style 1': 'DCPrintStyle1',
'Style 2': 'DCPrintStyle2',
'Style 3': 'DCPrintStyle3',
'Style 4': 'DCPrintStyle4',
'Style 5': 'DCPrintStyle5',
'Style 6': 'DCPrintStyle6',
'Style 7': 'DCPrintStyle7',
'Style 8': 'DCPrintStyle8',
'Style 9': 'DCPrintStyle9',
'Style 10': 'DCPrintStyle10',
'Style 11': 'DCPrintStyle11',
'Style 12': 'DCPrintStyle12',
'Style 13': 'DC-Default-Print',
A4: 'DCPrintStyleA4',
A5: 'DCPrintStyleA5',
A4Standard: 'DCPrintStyleA4Standard',
};
const selectedStyle =
stylesMap[
(printStyle == undefined || printStyle === 'TaxInvoice') ? 'Style 13' : printStyle
];
console.log(selectedStyle, DCstyle, 'selectedStyle');
setTimeout(() => {
printDiv(`${selectedStyle}`, DCstyle);
}, 100);
};
const getPrintData = async () => {
let data = {
CompId: CompId,
BranchId: BranchId,
AppId: AppId,
};
let response = await dispatch(getPrintSelectionComponentData(data)).unwrap();
if (response?.data?.statusCode === 1) {
let DCPrintTemplate =
response?.data?.data?.[0]?.ComponentDetails?.find(
(item) => item.SetasDefault == 'Y' && item.PrintTypeName === 'Delivery Chalan'
);
console.log(DCPrintTemplate, response?.data?.data?.[0]?.ComponentDetails?.find(
(item) => item.SetasDefault == 'Y' && item.PrintTypeName === 'Delivery Chalan'
), 'DCPrintTemplate');
setPrintStyle(DCPrintTemplate?.StyleName);
setPrintTemplateDtl(DCPrintTemplate);
}
};
const openCompleteModal = (products) => {
setCompleteProducts(products);
setCompleteModalVisible(true);
};
const handleRemarksChange = (index, value) => {
setRemarks((prev) => ({ ...prev, [index]: value }));
};
const completeColumns = [
{
title: 'S.No',
dataIndex: 'slno',
key: 'slno',
align: 'center',
width: '60px',
render: (text, record, index) => index + 1,
},
{
title: 'Product Name',
dataIndex: 'ProdName',
key: 'ProdName',
width: 150,
},
{
title: 'Variant Name',
dataIndex: 'RequestVariantName',
key: 'RequestVariantName',
width: 150,
},
{
title: 'QTY',
children: [
{
title: 'Dispatched',
dataIndex: 'DispatchedQty',
key: 'DispatchedQty',
align: 'right',
width: 100,
},
{
title: 'Delivered',
dataIndex: 'ReceivedQty',
key: 'ReceivedQty',
align: 'right',
width: 100,
},
{
title: 'Accepted',
dataIndex: 'AcceptedQty',
key: 'AcceptedQty',
align: 'right',
width: 100,
},
// {
// title: 'Received',
// dataIndex: 'receivedQty',
// key: 'receivedQty',
// align: 'right',
// width: 100,
// },
],
},
// {
// title: 'Remarks',
// dataIndex: 'remarks',
// key: 'remarks',
// width: 200,
// render: (text, record, index) => (
// record.dispatchedQty !== record.acceptedQty ?
// <Input
// placeholder="Enter remarks"
// value={remarks[index] || ''}
// onChange={(e) => handleRemarksChange(index, e.target.value)}
// /> : null
// )
// }
];
const viewProductDetails = (products) => {
const productDetails =
isDispatchQueue === 'queue'
? products?.filter((prod) => prod?.PendingQty > 0)
: products;
setSelectedProducts(productDetails);
setViewModalVisible(true);
};
const openDispatchModal = (products, record) => {
const productsWithDispatchQty = products
?.filter((prod) => prod?.PendingQty > 0)
.map((product) => ({
...product,
requestId: record?.RequestId,
toLocationId: record?.FromLocationId,
}));
setDispatchProducts(productsWithDispatchQty);
setDispatchModalVisible(true);
};
const openAllocateModal = (product, index) => {
// Check if ProdVariantDetails is an array
if (!Array.isArray(product?.ProdVariantDetails)) {
console.error('ProdVariantDetails is not an array');
return;
}
// Check if at least one variant has StockDetails with length > 1
const hasValidStockDetails = product.ProdVariantDetails.some(
(variant) =>
Array.isArray(variant?.StockDetails) && variant.StockDetails.length > 0
);
if (!hasValidStockDetails) {
// console.error('No variant found with StockDetails length > 1');
setMessageData(
'This product cannot be allocated. No valid stock details found.'
);
setMessageType('error');
// Optionally show a user-friendly message
// alert('This product cannot be allocated. No valid stock details found.');
return;
}
// Proceed with setting state if validation passes
setCurrentProduct({
...product,
ProdVariantDetails: product?.ProdVariantDetails?.map((variant) => ({
...variant,
RequestId: product?.requestId,
})),
});
setCurrentProductIndex(index);
setAllocateModalVisible(true);
};
const handleStockAllocation = (value, balanceQty, inwardDtlId, requestId) => {
const numericValue = parseInt(value || 0, 10);
if (isNaN(numericValue) || numericValue < 0) return;
if (numericValue > balanceQty) {
setMessageType('error');
setMessageData(`Cannot exceed available qty of ${balanceQty}`);
return;
}
const productKey = `${currentProduct.ProdId}_${currentProductIndex}`;
const updatedAllocations = { ...allocatedStocks };
if (!updatedAllocations[productKey]) {
updatedAllocations[productKey] = {};
}
const stockKey = `${requestId}_${inwardDtlId}`;
updatedAllocations[productKey][stockKey] = numericValue;
const totalAllocated = Object.values(updatedAllocations[productKey]).reduce(
(sum, qty) => sum + qty,
0
);
if (totalAllocated > currentProduct.PendingQty) {
setMessageType('error');
setMessageData(
`Total allocated quantity cannot exceed pending quantity of ${currentProduct.PendingQty}`
);
updatedAllocations[productKey][stockKey] = 0;
return;
}
setAllocatedStocks(updatedAllocations);
};
const submitAllocation = () => {
const productKey = `${currentProduct.ProdId}_${currentProductIndex}`;
const allocations = allocatedStocks[productKey] || {};
const totalAllocated = Object.values(allocations).reduce(
(sum, qty) => sum + qty,
0
);
const updatedProducts = [...dispatchProducts];
updatedProducts[currentProductIndex].dispatchQty = totalAllocated;
setDispatchProducts(updatedProducts);
setAllocatedDispatch((prev) => {
let updated = [...prev];
Object.entries(allocations).forEach(([key, qty]) => {
const [, inwardDtlId] = key.split('_'); // take second part
if (!inwardDtlId) return;
const index = updated.findIndex(
(item) => item.InwardDtlId === inwardDtlId
);
if (index !== -1) {
// update existing
updated[index] = {
...updated[index],
dispatchQty: qty,
};
} else {
// push new
updated.push({
ProdId: currentProduct?.ProdId,
InwardDtlId: inwardDtlId,
CreatedBy: UserId,
dispatchQty: qty,
toLocationId: currentProduct?.toLocationId,
RequestId: currentProduct?.requestId,
});
}
});
return updated;
});
setAllocateModalVisible(false);
setCurrentProduct(null);
setCurrentProductIndex(null);
};
const getTotalAllocatedQty = () => {
if (!currentProduct) return 0;
const productKey = `${currentProduct.ProdId}_${currentProductIndex}`;
const allocations = allocatedStocks[productKey] || {};
return Object.values(allocations).reduce((sum, qty) => sum + qty, 0);
};
const handleDispatchSubmit = async () => {
try {
if (
allocatedDispatch?.filter(
(prod) => parseFloat(prod?.dispatchQty) || 0 > 0
)?.length === 0
) {
setMessageType('warning');
setMessageData('No products to dispatch');
return;
}
let data = {
RequestId: allocatedDispatch?.[0]?.RequestId,
AppId: AppId,
CompId: CompId,
FromLocationId: BranchId,
ToLocationId: allocatedDispatch?.[0]?.toLocationId,
DispatchStatus: '',
DispatchStockDetails: allocatedDispatch
?.filter((prod) => parseFloat(prod?.dispatchQty) || 0 > 0)
?.map((prod) => ({
ProdId: prod?.ProdId,
InwardDtlId: prod?.InwardDtlId,
DispatchedQty: parseFloat(prod?.dispatchQty) || 0,
CreatedBy: UserId,
})),
CreatedBy: UserId,
};
console.log(data, 'dispatchingdata');
const res = await dispatch(postDispatchStock(data))?.unwrap();
if (res?.data?.statusCode === 1) {
setMessageData(res?.data?.response);
setMessageType('success');
} else {
throw new Error(res?.data?.response);
}
console.log(res?.data, 'response');
await fetchData();
setDispatchModalVisible(false);
setAllocatedDispatch([]);
setAllocatedStocks({});
setCurrentProduct(null);
setDispatchProducts([]);
formRef?.current?.resetFields();
} catch (error) {
console.log(error);
setMessageType('error');
setMessageData('Failed to submit dispatch');
}
};
const viewColumns = [
{
title: 'SL.NO',
dataIndex: 'index',
key: 'index',
align: 'center',
width: '60px',
render: (text, record, index) => (
<span>{(prodDtlsPage - 1) * 10 + index + 1}</span>
),
},
{
title: 'Product Name',
dataIndex: 'ProdName',
key: 'ProdName',
},
{
title: 'Requested Variant',
dataIndex: 'RequestVariantName',
key: 'RequestVariantName',
},
{
title: isDispatchQueue ? 'Requested Qty' : 'Requested Qty',
dataIndex: isDispatchQueue === 'queue' ? 'RequestQty' : 'RequestedQty',
key: isDispatchQueue === 'queue' ? 'RequestQty' : 'RequestedQty',
align: 'right',
},
...(isDispatchQueue === 'queue'
? [
{
title: isDispatchQueue ? 'Pending Qty' : 'Pending Qty',
dataIndex:
isDispatchQueue === 'queue' ? 'PendingQty' : 'PendingQty',
key: isDispatchQueue === 'queue' ? 'PendingQty' : 'PendingQty',
align: 'right',
},
]
: []),
...(isDispatchQueue === 'queue'
? []
: [
{
title: 'Dispatched Qty',
dataIndex: 'DispatchedQty',
key: 'DispatchedQty',
align: 'right',
},
]),
];
const dispatchColumns = [
{
title: 'SL.No',
dataIndex: 'index',
key: 'index',
align: 'center',
width: '60px',
render: (text, record, index) => (
<span>{(prodDtlsPage - 1) * 10 + index + 1}</span>
),
},
{
title: 'Product Name',
dataIndex: 'ProdName',
key: 'ProdName',
width: '200px',
},
{
title: 'Requested Variant',
dataIndex: 'RequestVariantName',
key: 'RequestVariantName',
width: '200px',
},
{
title: 'Requested Qty',
dataIndex: 'RequestQty',
key: 'RequestQty',
align: 'right',
width: '130px',
},
{
title: 'Pending Qty',
dataIndex: 'PendingQty',
key: 'PendingQty',
align: 'right',
width: '130px',
},
{
title: 'Allocated Qty',
dataIndex: 'dispatchQty',
key: 'dispatchQty',
align: 'center',
width: '120px',
render: (text, record, index) => (
<span style={{ fontWeight: 'bold', color: '#1890ff' }}>
{text || 0}
</span>
),
},
{
title: 'Qty Allocation',
key: 'action',
align: 'center',
width: '120px',
render: (text, record, index) => {
if (record?.StockAvailable === 'Y') {
return (
<div
className="allocate-btn"
onClick={() => openAllocateModal(record, index)}
>
Allocate Qty
</div>
);
} else if (record?.StockAvailable === 'N') {
return (
<Form.Item
name={`DispatchQty${record?.requestId}${index}`}
rules={[
{
validator: (_, value) => {
if (!value) {
return Promise.resolve();
}
if (Number(value) > record.RequestQty) {
return Promise.reject(
new Error(
'Dispatch quantity cannot be greater than requested quantity'
)
);
}
if (Number(value) < 0) {
return Promise.reject(
new Error('Dispatch quantity cannot be negative')
);
}
return Promise.resolve();
},
},
]}
>
<Input
inputMode="decimal"
onInput={(e) => {
let cleanedValue = e.target.value.replace(/[^0-9.]/g, ''); // Remove invalid characters
const parts = cleanedValue.split('.');
if (cleanedValue.startsWith('.')) {
cleanedValue = '0' + cleanedValue;
}
e.target.value =
parts.length > 2
? `${parts[0]}.${parts.slice(1).join('')}`
: cleanedValue;
}}
min={0}
max={record.RequestQty}
value={text}
onChange={(e) =>
handleDispatchQtyChange(index, e.target.value, record)
}
/>
</Form.Item>
);
}
},
},
];
const handleDispatchQtyChange = (idx, value, record) => {
const dispatchingQty = value > record?.PendingQty ? '' : value;
if ((value || 0) > record?.PendingQty) {
setMessageData(
'Dispatch quantity cannot be greater than requested quantity'
);
setMessageType('error');
}
setDispatchProducts((prevProducts) => {
const updatedProducts = [...prevProducts];
updatedProducts[idx].dispatchQty = dispatchingQty;
return updatedProducts;
});
const inwardDtlId =
record?.ProdVariantDetails?.[0]?.StockDetails?.[0]?.InwardDtlId;
if (!inwardDtlId) return;
setAllocatedDispatch((prev) => {
const index = prev?.findIndex((item) => item.InwardDtlId === inwardDtlId);
// If exists → update
if (index !== -1) {
const updated = [...prev];
updated[index] = {
...updated[index],
dispatchQty: dispatchingQty,
};
return updated;
}
// Else → push new
return [
...prev,
{
ProdId: record?.ProdId,
InwardDtlId: inwardDtlId,
CreatedBy: UserId,
dispatchQty: dispatchingQty,
toLocationId: record?.toLocationId,
RequestId: record?.requestId,
},
];
});
formRef?.current?.setFieldsValue({
[`DispatchQty${record?.requestId}${idx}`]: dispatchingQty,
});
};
const onComplete = useCallback(() => {
setMessageType(null);
setMessageData(null);
}, []);
return (
<div className="userPageTable">
<Messages
messageType={messageType}
messageData={messageData}
onComplete={onComplete}
/>
<div className="userPageContent">
<div className="ProductMasterListSwitchBTN ">
<div
className={`${isDispatchQueue === 'queue' ? 'selected' : ''}`}
onClick={() => {
setDateRange([]);
setIsDispatchQueue('queue');
setpage(1);
setSearchedText('');
}}
>
Dispatch Queue
{count?.RequestCount > 0 && (
<span className="badgeForTab">{count?.RequestCount || 0}</span>
)}
</div>
<div
className={`${isDispatchQueue === 'transit' ? 'selected' : ''}`}
onClick={() => {
setDateRange([]);
setIsDispatchQueue('transit');
setpage(1);
setSearchedText('');
}}
>
In-Transit
{count?.PendingCount > 0 && (
<span className="badgeForTab">{count?.PendingCount || 0}</span>
)}
</div>
<div
className={`${isDispatchQueue === 'dispatched' ? 'selected' : ''}`}
onClick={() => {
setDateRange([]);
setIsDispatchQueue('dispatched');
setpage(1);
setSearchedText('');
}}
>
Completed
</div>
</div>
<div className="formAddNew">
{/* <div>
<FormHeader title={"Stock Dispatch"} />
</div> */}
<div className="searchAddDiv">
<Tooltip
title={`Search by ${isDispatchQueue === 'queue' ? 'Queue No or' : 'Request Id or Dispatch No or'} Branch Name`}
>
<div className="formSearch">
<Search
placeholder={`Search by ${isDispatchQueue === 'queue' ? 'Queue No or' : 'Request Id or Dispatch No or'} Branch Name`}
onSearch={onSearch}
onSearchChange={onSearchChange}
value={searchedText}
/>
</div>
</Tooltip>
<div className="dateRange">
<DatePicker.RangePicker
value={dateRange}
onChange={handleDateRangeChange}
placeholder={['Start Date', 'End Date']}
disabledDate={(current) => current && current > new Date()}
/>
</div>
</div>
</div>
<div className="reportTable stock-dispatch-table">
<Tables
columns={
isDispatchQueue === 'queue'
? queueColumns
: isDispatchQueue === 'transit'
? transitColumns
: dispatchedColumns
}
data={dispatchData}
dataSource={dispatchData}
onChange={handleChange}
// pagination={handlePageChange}
ownPagination={true}
pagination={{
current: page,
onChange: handlePageChange,
defaultPageSize: 10,
showSizeChanger: false,
hideOnSinglePage: true,
}}
/>
</div>
</div>
<DefaultModal
className={`product-dtls-modal`}
title="Product Details"
open={viewModalVisible}
handleCancel={() => {
setViewModalVisible(false);
setProdDtlsPage(1);
}}
footer={null}
width={600}
children={
<div className="dispatch-view-table">
<Tables
columns={viewColumns}
data={selectedProducts}
dataSource={selectedProducts}
ownPagination={true}
pagination={{
current: prodDtlsPage,
onChange: handleProdDtlsPageChange,
defaultPageSize: 10,
showSizeChanger: false,
hideOnSinglePage: true,
}}
/>
</div>
}
/>
<DefaultModal
className={`product-dtls-modal`}
title="Dispatch Products"
open={dispatchModalVisible}
handleCancel={() => {
setDispatchModalVisible(false);
setAllocatedStocks({});
setAllocatedDispatch([]);
setProdDtlsPage(1);
formRef?.current?.resetFields();
}}
handleSubmit={handleDispatchSubmit}
footer={true}
width={750}
children={
<>
<Form ref={formRef}>
{/* <div className="dispatch-form-fields">
<div className="subRowFlex">
<Form.Item
name="transportMode"
rules={[{ required: true, message: 'Please select mode of transport' }]}
>
<Select
placeholder="Mode of Transport"
value={transportMode}
onChange={setTransportMode}
options={[
{ value: 'truck', label: 'Truck' },
{ value: 'rail', label: 'Rail' },
{ value: 'courier', label: 'Courier' },
{ value: 'air', label: 'Air' },
{ value: 'ship', label: 'Ship' }
]}
/>
</Form.Item>
<Form.Item
name="vehicleNumber"
rules={[{ required: true, message: 'Please enter vehicle number' }]}
>
<Input
placeholder="Vehicle Number"
value={vehicleNumber}
onChange={(e) => setVehicleNumber(e.target.value)}
/>
</Form.Item>
</div>
<div className="subRowFlex">
<Form.Item
name="driverMobile"
rules={[
{ required: true, message: 'Please enter driver mobile number' },
{ pattern: /^[0-9]{10}$/, message: 'Please enter valid 10-digit mobile number' }
]}
>
<Input
placeholder="Driver Mobile Number"
value={driverMobile}
onChange={(e) => setDriverMobile(e.target.value)}
maxLength={10}
/>
</Form.Item>
<Form.Item
name="driverLicense"
rules={[{ required: true, message: 'Please enter driver license number' }]}
>
<Input
placeholder="Driver License Number"
value={driverLicense}
onChange={(e) => setDriverLicense(e.target.value)}
/>
</Form.Item>
</div>
</div> */}
<div className="dispatch-table">
<Tables
columns={dispatchColumns}
data={dispatchProducts}
dataSource={dispatchProducts}
ownPagination={true}
pagination={{
current: prodDtlsPage,
onChange: handleProdDtlsPageChange,
defaultPageSize: 10,
showSizeChanger: false,
hideOnSinglePage: true,
}}
/>
</div>
</Form>
<DefaultModal
className="allocate-qty-modal"
title={`Allocate Quantity - ${currentProduct?.ProdName}`}
open={allocateModalVisible}
handleCancel={() => {
setAllocateModalVisible(false);
setCurrentProduct(null);
setCurrentProductIndex(null);
}}
footer={false}
width={1000}
children={
currentProduct && (
<div className="allocate-qty-container">
<div className="qty-summary">
<div className="qty-item">
<span className="qty-label">Requested Qty:</span>
<span className="qty-value">
{currentProduct.RequestQty}
</span>
</div>
<div className="qty-item">
<span className="qty-label">Pending Qty:</span>
<span className="qty-value">
{currentProduct.PendingQty}
</span>
</div>
<div className="qty-item">
<span className="qty-label">Allocated:</span>
<span className="qty-value">
{getTotalAllocatedQty()} / {currentProduct.PendingQty}
</span>
{getTotalAllocatedQty() < currentProduct.RequestQty && (
<span className="qty-remaining">
(Remaining:{' '}
{currentProduct.PendingQty - getTotalAllocatedQty()}
)
</span>
)}
</div>
</div>
<div className="variants-collapse">
<Collapse
items={
currentProduct.ProdVariantDetails?.filter(
(variant) => variant?.StockDetails?.length > 0
)?.map((variant, variantIndex) => ({
key: String(variantIndex),
label: (
<span
style={{
display: 'flex',
gap: '10px',
alignItems: 'center',
justifyContent: 'space-between',
}}
>
<span>{variant.ProdVariantName}</span>
<span
style={{ fontWeight: 500, color: '#d46b08' }}
>
Total Available Qty: {variant.OverAllQty || 0}
</span>
</span>
),
children: (
<div>
{variant.StockDetails?.map(
(stock, stockIndex) => {
const productKey = `${currentProduct.ProdId}_${currentProductIndex}`;
const stockKey = `${variant?.RequestId}_${stock?.InwardDtlId}`;
const currentValue =
allocatedStocks[productKey]?.[stockKey] ||
0;
return (
<div
key={stockIndex}
style={{
marginBottom: '20px',
display: 'flex',
gap: '1rem',
alignItems: 'center',
flexWrap: 'wrap',
}}
>
<p>
<strong>Batch No:</strong>{' '}
{stock.BatchRef || 'N/A'}
</p>
<p>
<strong>Available Qty:</strong>{' '}
{stock.BalanceQty}
</p>
<p>
<strong>Expiry Date:</strong>{' '}
{stock.ExpiryDate || 'N/A'}
</p>
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '10px',
}}
className="stockqtyDiv"
>
<strong>Allocate Qty:</strong>
<Input
type="number"
min={0}
max={stock.BalanceQty}
placeholder="Enter qty"
style={{ width: 100 }}
value={currentValue}
onChange={(e) =>
handleStockAllocation(
e.target.value,
stock.BalanceQty,
stock?.InwardDtlId,
variant?.RequestId
)
}
/>
</div>
</div>
);
}
)}
</div>
),
})) || []
}
/>
</div>
<div
className="modal-footer"
style={{ marginTop: '20px', textAlign: 'right' }}
>
<Tooltip
title={
getTotalAllocatedQty() === 0
? 'Please allocate some quantity to continue'
: ''
}
>
<span>
<Buttons
buttonText="SUBMIT"
color="901D77"
icon={<ArrowRightOutlined />}
handleSubmit={submitAllocation}
disabled={getTotalAllocatedQty() === 0}
/>
</span>
</Tooltip>
</div>
</div>
)
}
/>
</>
}
/>
<DefaultModal
className={`product-dtls-modal`}
title="Delivered Details"
open={completeModalVisible}
handleCancel={() => {
setCompleteModalVisible(false);
setProdDtlsPage(1);
}}
footer={false}
width={800}
children={
<div className="complete-table">
<Tables
columns={completeColumns}
data={completeProducts}
dataSource={completeProducts}
ownPagination={true}
pagination={{
current: prodDtlsPage,
onChange: handleProdDtlsPageChange,
defaultPageSize: 10,
showSizeChanger: false,
hideOnSinglePage: true,
}}
/>
</div>
}
/>
{deliveryData?.length > 0 &&
<div
style={{ display: "none" }}
>
<DeliveryPrint
PrintingData1={[deliveryData]}
DCprinterTemplateStyle={printStyle}
DCPrintTemplateDtl={printTemplateDtl}
/>
</div>
}
</div >
);
};
export default Dispatch;