Merge pull request 'temp/direct-sales-feature' (#134) from temp/direct-sales-feature into main
Reviewed-on: Pozomind/pozo-retail-app#134
This commit is contained in:
commit
8fc0e75122
|
|
@ -60,7 +60,7 @@ export const getTopSellingProduct = createAsyncThunk(
|
|||
}
|
||||
);
|
||||
export const PostPedalOCR = createAsyncThunk(
|
||||
'Stock/PostPedalOCR',
|
||||
'Stock/PostPedalOCR',
|
||||
async (fileOrFormData) => {
|
||||
let formdata;
|
||||
if (fileOrFormData instanceof FormData) {
|
||||
|
|
@ -244,6 +244,22 @@ export const getProductDataPageNo = createAsyncThunk(
|
|||
}
|
||||
);
|
||||
|
||||
export const getDirectSaleProducts = createAsyncThunk(
|
||||
'product/getDirectSaleProducts',
|
||||
async (data) => {
|
||||
const { CompId, BranchId, AppId, page } = data
|
||||
if (
|
||||
CompId != null && CompId != undefined &&
|
||||
BranchId != null && BranchId != undefined &&
|
||||
AppId != null && AppId != undefined
|
||||
) {
|
||||
return await axiosRetailInstanceData.get(
|
||||
`/product?CompId=${CompId}&BranchId=${BranchId}&AppId=${AppId}&SalesType=DS&PageNumber=${page}`
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const getAllprodIds = createAsyncThunk(
|
||||
'product/getAllprodIds',
|
||||
async (ProdData) => {
|
||||
|
|
@ -273,6 +289,13 @@ export const getAllprodIds = createAsyncThunk(
|
|||
}
|
||||
);
|
||||
|
||||
export const getAllProductShortCodes = createAsyncThunk('product/getAllProductShortCodes', async (data) => {
|
||||
const { AppId, CompId, BranchId } = data;
|
||||
if (AppId != null && AppId && BranchId != null && BranchId && AppId && AppId != null) {
|
||||
return await axiosRetailInstanceData.get(`/Product/ProdShortCodes?CompId=${CompId}&BranchId=${BranchId}&AppId=${AppId}`);
|
||||
}
|
||||
});
|
||||
|
||||
export const getProductDataSearch = createAsyncThunk(
|
||||
'product/getProductDataSearch',
|
||||
async (ProdData) => {
|
||||
|
|
@ -291,6 +314,22 @@ export const getProductDataSearch = createAsyncThunk(
|
|||
}
|
||||
);
|
||||
|
||||
export const getDirectSaleSearchedProduct = createAsyncThunk(
|
||||
'product/getDirectSaleSearchedProduct',
|
||||
async (data) => {
|
||||
const { CompId, BranchId, AppId, prodName } = data;
|
||||
if (
|
||||
CompId != null && CompId != undefined &&
|
||||
BranchId != null && BranchId != undefined &&
|
||||
AppId != undefined && AppId != null
|
||||
) {
|
||||
return await axiosRetailInstanceData.get(
|
||||
`/ProductMasterSearch?CompId=${CompId}&BranchId=${BranchId}&AppId=${AppId}&prodName=${prodName}&SalesType=DS`
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const getBrandData = createAsyncThunk(
|
||||
'product/getBrandData',
|
||||
async ({ ConfigId }) => {
|
||||
|
|
@ -571,24 +610,24 @@ export const postRackData = createAsyncThunk(
|
|||
async (postData) => {
|
||||
return await axiosRetailInstanceData.post(`/RackMaster`, postData);
|
||||
}
|
||||
);
|
||||
);
|
||||
export const putRackData = createAsyncThunk(
|
||||
'putRackData/RackMaster',
|
||||
async (postData) => {
|
||||
return await axiosRetailInstanceData.put(`/RackMaster`, postData);
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
export const getRackData = createAsyncThunk(
|
||||
'getRackData/RackMaster',
|
||||
async ({ AppId, CompId, BranchId}) => {
|
||||
async ({ AppId, CompId, BranchId }) => {
|
||||
if (
|
||||
AppId != null &&
|
||||
AppId != undefined &&
|
||||
CompId != null &&
|
||||
CompId != undefined &&
|
||||
BranchId != null &&
|
||||
BranchId != undefined
|
||||
BranchId != undefined
|
||||
) {
|
||||
return await axiosRetailInstanceData.get(
|
||||
`/RackMaster?AppId=${AppId}&CompId=${CompId}&BranchId=${BranchId}`
|
||||
|
|
@ -599,12 +638,12 @@ export const getRackData = createAsyncThunk(
|
|||
|
||||
export const deleteRackData = createAsyncThunk(
|
||||
'deleteRackData/RackMaster',
|
||||
async ({ activeStatus, updatedBy, rackId}) => {
|
||||
async ({ activeStatus, updatedBy, rackId }) => {
|
||||
if (
|
||||
updatedBy != null &&
|
||||
updatedBy != undefined &&
|
||||
activeStatus != null &&
|
||||
activeStatus != undefined
|
||||
activeStatus != undefined
|
||||
) {
|
||||
return await axiosRetailInstanceData.delete(
|
||||
`/RackMaster?activeStatus=${activeStatus}&updatedBy=${updatedBy}&rackId=${rackId}`
|
||||
|
|
|
|||
|
|
@ -26,21 +26,29 @@ export const getProductData = createAsyncThunk(
|
|||
|
||||
export const getInvoiceImageData = createAsyncThunk(
|
||||
'Stock/getInvoiceImageData',
|
||||
async (file) => {
|
||||
async ({ file, AppId, CompId, BranchId }) => {
|
||||
if (!file) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('file', file); // 👈 actual file here
|
||||
formData.append('file', file); // file
|
||||
formData.append('AppId', AppId); // 👈 add AppId
|
||||
formData.append('CompId', CompId); // 👈 add CompId
|
||||
formData.append('BranchId', BranchId); // 👈 add BranchId
|
||||
|
||||
const response = await axiosRetailOcrData.post('/invoice', formData, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
const response = await axiosRetailOcrData.post(
|
||||
'/invoice',
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
return response.data;
|
||||
}
|
||||
);
|
||||
|
||||
export const getPurchaseTypeData = createAsyncThunk(
|
||||
'product/getPurchaseTypeData',
|
||||
async () => {
|
||||
|
|
|
|||
|
|
@ -358,6 +358,7 @@ const StandardTablePayment = () => {
|
|||
const [creditCustomerOpen, setcreditCustomerOpen] = useState(false);
|
||||
const [Splitpayment, setSplitpayment] = useState(false);
|
||||
const [customerPreviesOrders, setCustomerPreviesOrders] = useState(false);
|
||||
const paymentGatewayRef = React.useRef(null);
|
||||
const [PrintOrderDetails, setPrintOrderDetails] = useState([]);
|
||||
const [PaymentUpiOptions, setPaymentUpiOptions] = useState([]);
|
||||
const [PaymentDeviceCard, setPaymentDeviceCard] = useState([]);
|
||||
|
|
@ -4125,6 +4126,7 @@ const StandardTablePayment = () => {
|
|||
children={
|
||||
<>
|
||||
<PaymentGatewayEmbedded
|
||||
ref={paymentGatewayRef}
|
||||
orderId={businessUPIOrderId}
|
||||
setBusinessUPIOrderId={setBusinessUPIOrderId}
|
||||
businessUPILink={businessUPILink}
|
||||
|
|
@ -4142,6 +4144,7 @@ const StandardTablePayment = () => {
|
|||
open={showCancelConfirm}
|
||||
title="Cancel Payment"
|
||||
onOk={() => {
|
||||
paymentGatewayRef.current?.clearPolling();
|
||||
setBusinessUPI(false);
|
||||
setShowCancelConfirm(false);
|
||||
ClearAllGlobalStateDatas();
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
Switch,
|
||||
Tooltip,
|
||||
Popconfirm,
|
||||
Tabs,
|
||||
} from 'antd';
|
||||
import { TiDelete } from 'react-icons/ti';
|
||||
import { CiCalculator1 } from 'react-icons/ci';
|
||||
|
|
@ -171,6 +172,7 @@ const FeaturesFunctionalities = (props) => {
|
|||
const [WeightScale, setWeightScale] = useState(props.WeightScale);
|
||||
const [WeightScale2, setWeightScale2] = useState(true);
|
||||
|
||||
const [activeTab, setActiveTab] = useState('quickAdd');
|
||||
const [CusDetails, setCusDetails] = useState(false);
|
||||
const [zipCodeData, setZipCodeData] = useState(false);
|
||||
const [messageType, setMessageType] = useState(null);
|
||||
|
|
@ -3904,17 +3906,27 @@ const FeaturesFunctionalities = (props) => {
|
|||
<div>
|
||||
<DefaultModal
|
||||
open={props.QuickAdd}
|
||||
width={800}
|
||||
width={activeTab === 'quickAdd' ? 800 : 1000}
|
||||
footer={false}
|
||||
children={
|
||||
// cardData Mohan 18-4-2025
|
||||
<ProductForm
|
||||
formType="add"
|
||||
handleCancelData={props.handleQuickAddCancel}
|
||||
ProductSearch={props.ProductSearch}
|
||||
ProductSearchType={props.ProductSearchType}
|
||||
cardData={props.cardData}
|
||||
/>
|
||||
<>
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
onChange={setActiveTab}
|
||||
items={[
|
||||
{ key: 'quickAdd', label: 'Quick Add' },
|
||||
{ key: 'directSale', label: 'Direct Sale' }
|
||||
]}
|
||||
/>
|
||||
<ProductForm
|
||||
formType="add"
|
||||
activeTab={activeTab}
|
||||
handleCancelData={props.handleQuickAddCancel}
|
||||
ProductSearch={props.ProductSearch}
|
||||
ProductSearchType={props.ProductSearchType}
|
||||
cardData={props.cardData}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
handleCancel={props.handleQuickAddCancel}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -1280,6 +1280,7 @@ function BSReprint({ setOpenModel = () => { } }) {
|
|||
OverallDisc,
|
||||
OrderPaymentDtl,
|
||||
} = orderDtls;
|
||||
|
||||
const updated = productDetails?.map((item) => ({
|
||||
...item,
|
||||
Offer: item?.OfferAmt,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useEffect, useRef, useState, useImperativeHandle, forwardRef } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { getPaymentStatusForBusinessUPI } from '../../../../Features/BookingScreen/BookingData/BookingData';
|
||||
|
||||
const PaymentGatewayEmbedded = ({
|
||||
const PaymentGatewayEmbedded = forwardRef(({
|
||||
orderId,
|
||||
businessUPILink,
|
||||
setBusinessUPI,
|
||||
|
|
@ -12,12 +12,21 @@ const PaymentGatewayEmbedded = ({
|
|||
ClearAllGlobalStateDatas = () => {},
|
||||
CustomerDisplay = () => {},
|
||||
Type=null
|
||||
}) => {
|
||||
}, ref) => {
|
||||
const dispatch = useDispatch();
|
||||
const intervalRef = useRef(null);
|
||||
const [timedOut, setTimedOut] = useState(false);
|
||||
const [showTimeout, setShowTimeout] = useState(false);
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
clearPolling: () => {
|
||||
if (intervalRef.current) {
|
||||
clearInterval(intervalRef.current);
|
||||
intervalRef.current = null;
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
if (paymentSucess || timedOut) return;
|
||||
|
||||
|
|
@ -174,6 +183,6 @@ const PaymentGatewayEmbedded = ({
|
|||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default PaymentGatewayEmbedded;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,901 @@
|
|||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Table, Input, Select, Button } from 'antd';
|
||||
import { PlusOutlined, DeleteOutlined, SaveOutlined, PrinterOutlined } from '@ant-design/icons';
|
||||
import { Tables } from '../../../../Components/Tables/Table.jsx';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { postProductData, getDirectSaleProducts, getAllProductShortCodes, deleteProductData, getDirectSaleSearchedProduct } from '../../../../Features/ProductPage/ProductPage.js';
|
||||
import { getSession, printDiv } from '../../../../Services/Others.js';
|
||||
import { Messages } from '../../../../Components/Notifications/Messages.jsx';
|
||||
import { FaStarOfLife } from "react-icons/fa6";
|
||||
import { IoMdRefresh } from "react-icons/io";
|
||||
import StickerPrintModel from '../../../Product/StickerPrintModal.jsx';
|
||||
import { getPageStyle, style } from '../../../Product/Printstyles.js';
|
||||
import { getBarcodeSessionsIDs, getBarcodeTemplate } from '../../../../Features/Barcode/Barcode.js';
|
||||
import { generateBarcode, generateQRCode, generateQRCodeCopy } from '../../../../Services/utils.js';
|
||||
import StickerPrintTemplates from '../../../Product/StickerTemplates.jsx';
|
||||
import useBarcodeGenerator from './useBarcodeQRGenerator.js';
|
||||
|
||||
const DirectSale = ({ UomData = [], TaxData = [], ProductTypeData = [], ProdCatData = [], ProdSubCatData = [], SupplierData = [], activeTab = null }) => {
|
||||
|
||||
const formRef = useRef();
|
||||
const tableRef = useRef();
|
||||
const searchInputRef = useRef();
|
||||
const dispatch = useDispatch();
|
||||
const [messageType, setMessageType] = useState(null);
|
||||
const [messageData, setMessageData] = useState(null);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const CompId = getSession('CompId');
|
||||
const BranchId = getSession('BranchId');
|
||||
const AppId = getSession('AppId');
|
||||
const UserId = getSession('UserId');
|
||||
|
||||
const [dataSource, setDataSource] = useState([]);
|
||||
const [editingKey, setEditingKey] = useState(null);
|
||||
|
||||
const [copies, setCopies] = useState(null);
|
||||
const [dropdownValue, setDropdownValue] = useState(null);
|
||||
const [multiProduct, setMultiProduct] = useState(false);
|
||||
const [printReady, setPrintReady] = useState(false);
|
||||
const [printProductName, setPrintProductName] = useState(false);
|
||||
const [productShortCodes, setAllProductShortCodes] = useState([]);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
console.log(searchText, "searchText")
|
||||
const [stickerPrintModalOpen, setStickerPrintModalOpen] = useState(false);
|
||||
const [barcodeTemplateDetails, setBarcodeTemplateDetails] = useState([]);
|
||||
const [filteredProducts, setFilteredProducts] = useState([]);
|
||||
const [labelPrintData, setLabelPrintData] = useState(null);
|
||||
const [prodId, setProdId] = useState(null);
|
||||
const [nickName, setNickName] = useState(null);
|
||||
const [crossData, setCrossData] = useState(null);
|
||||
const [triggerState, setTriggerState] = useState(1);
|
||||
const [templateOptions, setTemplateOptions] = useState({
|
||||
mrp: false,
|
||||
barcode: false,
|
||||
color: false,
|
||||
eDate: false,
|
||||
mDate: false,
|
||||
sellingPrice: false,
|
||||
productName: false,
|
||||
value: '',
|
||||
secondValue: '',
|
||||
qrcode: false,
|
||||
codeType: '',
|
||||
});
|
||||
|
||||
console.log((searchText != null && searchText != ''), "filteredProducts")
|
||||
|
||||
const { qrBase64, barcodeBase64 } = useBarcodeGenerator(prodId);
|
||||
|
||||
const handleAdd = () => {
|
||||
setDataSource([{
|
||||
key: dataSource.length + 1,
|
||||
productName: '',
|
||||
shortCode: '',
|
||||
sellingPrice: '',
|
||||
uom: null,
|
||||
tax: null,
|
||||
prodId: null,
|
||||
size: null,
|
||||
uomName: null,
|
||||
activeStatus: 'A'
|
||||
}, ...dataSource]);
|
||||
};
|
||||
|
||||
const handleDelete = async (record) => {
|
||||
if (record?.prodId) {
|
||||
const deleteData = {
|
||||
ProdId: record.prodId,
|
||||
ActiveStatus: record.activeStatus === 'A' ? 'D' : 'A',
|
||||
UpdatedBy: UserId,
|
||||
};
|
||||
const response = await dispatch(deleteProductData(deleteData))?.unwrap();
|
||||
if (response?.data?.statusCode === 1) {
|
||||
setMessageData(record.activeStatus === 'A' ? 'Product De-Activated Successfully' : 'Product Activated Successfully');
|
||||
setMessageType('success');
|
||||
await fetchDirectSaleProducts();
|
||||
} else {
|
||||
setMessageData(response?.data?.response || 'Failed to update status');
|
||||
setMessageType('error');
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
// const { key } = record;
|
||||
// setDataSource(dataSource.filter(item => item.key !== key));
|
||||
setMessageData('Atleast one empty row should be in table.');
|
||||
setMessageType('warning');
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const handleChange = useCallback((key, field, value) => {
|
||||
if (searchText) {
|
||||
setFilteredProducts(prev =>
|
||||
prev.map(item =>
|
||||
item.key === key ? { ...item, [field]: value } : item
|
||||
)
|
||||
);
|
||||
} else {
|
||||
setDataSource(prev =>
|
||||
prev.map(item =>
|
||||
item.key === key ? { ...item, [field]: value } : item
|
||||
)
|
||||
);
|
||||
}
|
||||
}, [searchText]);
|
||||
|
||||
const handleSave = async (record) => {
|
||||
|
||||
if (!record.productName || !record.sellingPrice || !record.uom || !record.tax || !record?.shortCode) {
|
||||
setMessageData('Please fill all the required fields!');
|
||||
setMessageType('warning');
|
||||
return;
|
||||
}
|
||||
|
||||
const exists = isShortCodeDuplicate(record?.shortCode, record?.prodId);
|
||||
|
||||
if (exists) {
|
||||
setMessageData('Short code already exists for another product');
|
||||
setMessageType('error');
|
||||
return;
|
||||
}
|
||||
|
||||
const postData = {
|
||||
ProdName: record.productName,
|
||||
Size: '1',
|
||||
UOM: record.uom,
|
||||
MRP: record.sellingPrice,
|
||||
SellPrice: record.sellingPrice,
|
||||
TaxId: record.tax,
|
||||
CompId: CompId,
|
||||
BranchId: BranchId,
|
||||
AppId: AppId,
|
||||
ProdType: ProductTypeData?.find(item => item.ConfigName === 'Product')?.ConfigId,
|
||||
ProdCat: ProdCatData?.find(item => item.ConfigName === 'General')?.ConfigId,
|
||||
ProdSubCat: ProdSubCatData?.find(item => item.ConfigName === 'General')?.ConfigId,
|
||||
QtyBasedPrice: 'N',
|
||||
ProdQtywisePriceDetails: [],
|
||||
StockAvailable: 'N',
|
||||
TokenAvailable: 'N',
|
||||
InwardDate: new Date().toJSON(),
|
||||
SuppId: SupplierData?.find(item => item.SuppName?.toLowerCase() === 'self')?.SuppId,
|
||||
AutoGenerateQr: 'N',
|
||||
QRCode: null,
|
||||
CreatedBy: UserId,
|
||||
ProdLogo: '',
|
||||
Cess: 0,
|
||||
ProdShortCode: record?.shortCode
|
||||
};
|
||||
|
||||
try {
|
||||
if (record?.prodId) {
|
||||
console.log('Updating Product')
|
||||
} else {
|
||||
const response = await dispatch(postProductData(postData)).unwrap();
|
||||
if (response?.data?.statusCode === 1) {
|
||||
const prodId = response?.data?.ProductDetails?.[0]?.ProdId;
|
||||
const size = response?.data?.ProductDetails?.[0]?.Size;
|
||||
const uomName = response?.data?.ProductDetails?.[0]?.UomName;
|
||||
const totalProducts = response?.data?.ProductDetails?.[0]?.TotalCount;
|
||||
setSearchText(currentSearchText => {
|
||||
if (currentSearchText != null && currentSearchText !== '') {
|
||||
setCurrentPage(1);
|
||||
setTriggerState(prev => prev + 1);
|
||||
if (searchInputRef.current) {
|
||||
searchInputRef.current.value = '';
|
||||
}
|
||||
return '';
|
||||
} else {
|
||||
setDataSource(prev => prev.map(item =>
|
||||
item.key === record.key ? { ...item, prodId, size, uomName, totalProducts } : { ...item, totalProducts }
|
||||
));
|
||||
}
|
||||
return currentSearchText;
|
||||
});
|
||||
setMessageData('Product saved successfully!');
|
||||
setMessageType('success');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error saving product:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePrint = async (record) => {
|
||||
const exists = isShortCodeDuplicate(record?.shortCode, record?.prodId);
|
||||
if (exists) {
|
||||
setMessageData('Short code already exists for another product. Cannot print.');
|
||||
setMessageType('error');
|
||||
return;
|
||||
}
|
||||
setStickerPrintModalOpen(true);
|
||||
setLabelPrintData({ ...record, MRP: record?.sellingPrice || 0, SellPrice: record?.sellingPrice || 0 });
|
||||
setProdId(record?.shortCode + '' + record?.sellingPrice)
|
||||
}
|
||||
|
||||
const columns = useMemo(() => [
|
||||
{
|
||||
title: 'SL.No',
|
||||
width: 50,
|
||||
align: 'center',
|
||||
render: (_, __, index) => (currentPage - 1) * 10 + index + 1
|
||||
},
|
||||
{
|
||||
title: () => <div style={{ textWrap: 'nowrap', display: "flex", gap: "4px " }}>Product Name
|
||||
<span>
|
||||
<FaStarOfLife size={10} color='red' className='requiredIcons' />
|
||||
</span>
|
||||
</div>,
|
||||
dataIndex: 'productName',
|
||||
width: 150,
|
||||
render: (text, record) => (
|
||||
record.prodId && editingKey !== record.key ?
|
||||
<div>{text}</div> :
|
||||
<Input
|
||||
value={text}
|
||||
onChange={(e) => handleChange(record.key, 'productName', e.target.value)}
|
||||
placeholder="Enter product name"
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: () => <div style={{ textWrap: 'nowrap', display: "flex", gap: "4px " }}>Short Code
|
||||
<span>
|
||||
<FaStarOfLife size={10} color='red' className='requiredIcons' />
|
||||
</span>
|
||||
</div>,
|
||||
dataIndex: 'shortCode',
|
||||
width: 100,
|
||||
render: (text, record) => (
|
||||
record.prodId && editingKey !== record.key ?
|
||||
<div>{text}</div> :
|
||||
<Input
|
||||
value={text}
|
||||
onInput={(e) => {
|
||||
const originalValue = e.target.value;
|
||||
const hasNonAlpha = /[^a-zA-Z]/.test(originalValue);
|
||||
if (hasNonAlpha) {
|
||||
setMessageData('Enter Alphabet Characters only');
|
||||
setMessageType('warning');
|
||||
}
|
||||
}}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value.replace(/[^a-zA-Z]/g, '').toUpperCase();
|
||||
const exists = isShortCodeDuplicate(value, record?.prodId);
|
||||
if (exists && value) {
|
||||
setMessageData('Short code already exists for another product');
|
||||
setMessageType('error');
|
||||
}
|
||||
handleChange(record.key, 'shortCode', value);
|
||||
}}
|
||||
placeholder="Short Code"
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: () => <div style={{ textWrap: 'nowrap', display: "flex", gap: "4px " }}>Selling Price
|
||||
<span>
|
||||
<FaStarOfLife size={10} color='red' className='requiredIcons' />
|
||||
</span>
|
||||
</div>,
|
||||
dataIndex: 'sellingPrice',
|
||||
width: 120,
|
||||
render: (text, record) => (
|
||||
record.prodId && editingKey !== record.key ?
|
||||
<div>{text}</div> :
|
||||
<Input
|
||||
value={text}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value.replace(/[^0-9.]/g, '');
|
||||
const parts = value.split('.');
|
||||
const formatted = parts.length > 2 ? `${parts[0]}.${parts.slice(1).join('')}` : value;
|
||||
handleChange(record.key, 'sellingPrice', formatted);
|
||||
}}
|
||||
placeholder="0.00"
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: () => <div style={{ textWrap: 'nowrap', display: "flex", gap: "4px " }}>UOM
|
||||
<span>
|
||||
<FaStarOfLife size={10} color='red' className='requiredIcons' />
|
||||
</span>
|
||||
</div>,
|
||||
dataIndex: 'uom',
|
||||
width: 60,
|
||||
render: (text, record) => (
|
||||
record.prodId && editingKey !== record.key ?
|
||||
<div>{UomData?.find(item => item.ConfigId === text)?.ConfigName || '-'}</div> :
|
||||
<Select
|
||||
disabled={record?.prodId}
|
||||
className='uom-select'
|
||||
value={text}
|
||||
onChange={(value) => handleChange(record.key, 'uom', value)}
|
||||
placeholder="UOM"
|
||||
style={{ width: '100%' }}
|
||||
options={UomData?.map(item => ({
|
||||
value: item.ConfigId,
|
||||
label: item.ConfigName
|
||||
}))}
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: () => <div style={{ textWrap: 'nowrap', display: "flex", gap: "4px " }}>Tax
|
||||
<span>
|
||||
<FaStarOfLife size={10} color='red' className='requiredIcons' />
|
||||
</span>
|
||||
</div>,
|
||||
dataIndex: 'tax',
|
||||
width: 70,
|
||||
render: (text, record) => {
|
||||
if (record.prodId && editingKey !== record.key) {
|
||||
const tax = TaxData?.find(item => item.TaxId === text);
|
||||
return <div>{tax ? tax?.TaxIdName + ' - ' + `${tax?.TaxPercentage}%` : '-'}</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Select
|
||||
className='tax-select'
|
||||
value={text}
|
||||
disabled={record?.prodId}
|
||||
onChange={(value) => handleChange(record.key, 'tax', value)}
|
||||
placeholder="Tax"
|
||||
style={{ width: '100%' }}
|
||||
options={TaxData?.map(item => ({
|
||||
value: item.TaxId,
|
||||
label: `${item.TaxIdName} - ${item.TaxPercentage}%`
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Save / Print',
|
||||
width: 90,
|
||||
align: 'center',
|
||||
render: (_, record) => {
|
||||
const existingProduct = record?.prodId;
|
||||
|
||||
return (
|
||||
<div className='directsaleBTN' style={{ display: 'flex', gap: '8px', justifyContent: 'center' }}>
|
||||
{!existingProduct && <Button
|
||||
icon={<SaveOutlined />}
|
||||
size="small"
|
||||
disabled={existingProduct}
|
||||
className='save-btn'
|
||||
onClick={() => handleSave(record)}
|
||||
>
|
||||
Save
|
||||
{/* {!existingProduct ? 'Save' : 'Update'} */}
|
||||
</Button>}
|
||||
{existingProduct && <Button
|
||||
icon={<PrinterOutlined />}
|
||||
size="small"
|
||||
disabled={!existingProduct}
|
||||
onClick={() => handlePrint(record)}
|
||||
>
|
||||
Print
|
||||
</Button>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Action',
|
||||
width: 60,
|
||||
align: "center",
|
||||
render: (_, record) => {
|
||||
if (record?.activeStatus === 'A') {
|
||||
return <DeleteOutlined
|
||||
onClick={() => handleDelete(record)}
|
||||
style={{ color: 'red', cursor: 'pointer', fontSize: '18px' }}
|
||||
/>
|
||||
} else {
|
||||
return <IoMdRefresh
|
||||
style={{ color: 'blue', cursor: 'pointer', fontSize: '18px' }}
|
||||
onClick={() => handleDelete(record)}
|
||||
/>
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
], [
|
||||
searchText,
|
||||
editingKey,
|
||||
productShortCodes,
|
||||
UomData,
|
||||
TaxData
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab === 'directSale' && AppId && CompId && BranchId) {
|
||||
fetchDirectSaleProducts();
|
||||
fetchAllProductShortCodes();
|
||||
}
|
||||
}, [activeTab, AppId, BranchId, CompId, currentPage, triggerState]);
|
||||
|
||||
useEffect(() => {
|
||||
if (dataSource.every(row => row.prodId)) {
|
||||
handleAdd();
|
||||
}
|
||||
}, [dataSource]);
|
||||
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (searchText) {
|
||||
fetchSearchedProducts();
|
||||
} else {
|
||||
setFilteredProducts([{
|
||||
key: 1,
|
||||
productName: '',
|
||||
shortCode: '',
|
||||
sellingPrice: '',
|
||||
uom: null,
|
||||
tax: null,
|
||||
prodId: null,
|
||||
size: null,
|
||||
uomName: null,
|
||||
activeStatus: 'A'
|
||||
}]);
|
||||
}
|
||||
}, 500);
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [searchText, AppId, BranchId, CompId]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (event) => {
|
||||
if (tableRef.current && !tableRef.current.contains(event.target) && !event.target.closest('.ant-select-dropdown')) {
|
||||
setEditingKey(null);
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (activeTab === 'directSale' && AppId && CompId && BranchId && stickerPrintModalOpen) {
|
||||
getBarcodeSessionData();
|
||||
fetchBarcodeTemplateDetails();
|
||||
}
|
||||
}, [activeTab, AppId, BranchId, CompId, stickerPrintModalOpen]);
|
||||
|
||||
const fetchSearchedProducts = async () => {
|
||||
try {
|
||||
const data = {
|
||||
AppId: AppId,
|
||||
CompId: CompId,
|
||||
BranchId: BranchId,
|
||||
prodName: searchText
|
||||
}
|
||||
const response = await dispatch(getDirectSaleSearchedProduct(data))?.unwrap();
|
||||
if (response?.data?.data?.length > 0 && response?.data?.statusCode === 1) {
|
||||
const products = response?.data?.data?.map((item, index) => ({
|
||||
key: index + 1,
|
||||
productName: item.ProdName,
|
||||
shortCode: item.ProdShortCode,
|
||||
sellingPrice: item.SellPrice,
|
||||
uom: item.UOM,
|
||||
tax: item.TaxId,
|
||||
prodId: item.ProdId,
|
||||
activeStatus: item?.ActiveStatus,
|
||||
size: item.Size || 1,
|
||||
uomName: item.UomName || 'PCS',
|
||||
totalProducts: item?.TotalCount
|
||||
}))
|
||||
setFilteredProducts([{
|
||||
key: products.length + 1,
|
||||
productName: '',
|
||||
shortCode: '',
|
||||
sellingPrice: '',
|
||||
uom: null,
|
||||
tax: null,
|
||||
prodId: null,
|
||||
size: null,
|
||||
uomName: null,
|
||||
activeStatus: 'A'
|
||||
}, ...products]);
|
||||
} else {
|
||||
setFilteredProducts([{
|
||||
key: 1,
|
||||
productName: '',
|
||||
shortCode: '',
|
||||
sellingPrice: '',
|
||||
uom: null,
|
||||
tax: null,
|
||||
prodId: null,
|
||||
size: null,
|
||||
uomName: null,
|
||||
activeStatus: 'A'
|
||||
}]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error?.message)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const isShortCodeDuplicate = useCallback((code, prodId) => {
|
||||
return productShortCodes.some(
|
||||
item =>
|
||||
item.ProdShortCode.toUpperCase() === code.toUpperCase() &&
|
||||
item.ProdId !== prodId
|
||||
);
|
||||
}, [productShortCodes]);
|
||||
|
||||
const fetchBarcodeTemplateDetails = async () => {
|
||||
const response = await dispatch(
|
||||
getBarcodeTemplate({ compId: CompId, appId: AppId, branchId: BranchId })
|
||||
).unwrap();
|
||||
if (response?.data?.statusCode === 1 && response?.data?.data?.length > 0) {
|
||||
const { TemplateDetails } = response?.data?.data?.[0];
|
||||
setBarcodeTemplateDetails(TemplateDetails);
|
||||
} else {
|
||||
setBarcodeTemplateDetails([]);
|
||||
}
|
||||
};
|
||||
|
||||
const getBarcodeSessionData = async () => {
|
||||
try {
|
||||
let response = await dispatch(getBarcodeSessionsIDs()).unwrap();
|
||||
if (response?.data?.statusCode === 1 && response?.data?.data?.length > 0) {
|
||||
setCrossData(
|
||||
response?.data?.data?.filter(
|
||||
(item) => !item.ConfigName?.includes('100X13 (55MM Printable Gold)')
|
||||
) || []
|
||||
);
|
||||
} else {
|
||||
setCrossData([]);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching barcode session data:', error);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchDirectSaleProducts = async () => {
|
||||
try {
|
||||
const data = {
|
||||
AppId: AppId,
|
||||
CompId: CompId,
|
||||
BranchId: BranchId,
|
||||
page: currentPage
|
||||
}
|
||||
|
||||
const response = await dispatch(getDirectSaleProducts(data))?.unwrap();
|
||||
if (response?.data?.statusCode === 1 && response?.data?.data?.length > 0) {
|
||||
const products = response?.data?.data?.map((item, index) => ({
|
||||
key: index + 1,
|
||||
productName: item.ProdName,
|
||||
shortCode: item.ProdShortCode,
|
||||
sellingPrice: item.SellPrice,
|
||||
uom: item.UOM,
|
||||
tax: item.TaxId,
|
||||
prodId: item.ProdId,
|
||||
activeStatus: item?.ActiveStatus,
|
||||
size: item.Size || 1,
|
||||
uomName: item.UomName || 'PCS',
|
||||
totalProducts: item?.TotalCount
|
||||
}));
|
||||
setDataSource([{
|
||||
key: products.length + 1,
|
||||
productName: '',
|
||||
shortCode: '',
|
||||
sellingPrice: '',
|
||||
uom: null,
|
||||
tax: null,
|
||||
prodId: null,
|
||||
size: null,
|
||||
uomName: null,
|
||||
activeStatus: 'A'
|
||||
}, ...products]);
|
||||
} else {
|
||||
setDataSource([])
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching direct sale products:', error);
|
||||
}
|
||||
}
|
||||
|
||||
const fetchAllProductShortCodes = async () => {
|
||||
try {
|
||||
const data = {
|
||||
AppId: AppId,
|
||||
CompId: CompId,
|
||||
BranchId: BranchId,
|
||||
}
|
||||
|
||||
const response = await dispatch(getAllProductShortCodes(data))?.unwrap();
|
||||
if (response?.data?.statusCode === 1 && response?.data?.data?.length > 0) {
|
||||
setAllProductShortCodes(response?.data?.data);
|
||||
} else {
|
||||
setAllProductShortCodes([]);
|
||||
}
|
||||
|
||||
console.log(response);
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error fetching product short codes:', error);
|
||||
}
|
||||
}
|
||||
|
||||
const handlePageChange = (current) => {
|
||||
setCurrentPage(current);
|
||||
};
|
||||
|
||||
const onFinish = async () => {
|
||||
const template = barcodeTemplateDetails?.find(
|
||||
(find) => find.SessionName === dropdownValue
|
||||
);
|
||||
const templateAvailable = template === undefined ? false : true;
|
||||
|
||||
if (templateAvailable) {
|
||||
const checkComponent = (name) =>
|
||||
template?.OptionDetails?.some((some) => some.OptionName === name);
|
||||
const mrp = checkComponent('MRP');
|
||||
const barcode = checkComponent('Barcode');
|
||||
const color = checkComponent('Color');
|
||||
const eDate = checkComponent('E Date');
|
||||
const mDate = checkComponent('M Date');
|
||||
const sellingPrice = checkComponent('Selling Price');
|
||||
const productName = checkComponent('Product Name');
|
||||
const qrcode = checkComponent('Qrcode');
|
||||
const position = template.Position !== '' ? template.Position : '';
|
||||
const positionString =
|
||||
template.PositionString !== '' ? template.PositionString : '';
|
||||
|
||||
setTemplateOptions({
|
||||
mrp: mrp,
|
||||
barcode: barcode,
|
||||
color: color,
|
||||
eDate: eDate,
|
||||
mDate: mDate,
|
||||
sellingPrice: sellingPrice,
|
||||
productName: productName,
|
||||
value: position,
|
||||
secondValue: positionString,
|
||||
qrcode: qrcode,
|
||||
codeType: template?.CodeType,
|
||||
});
|
||||
}
|
||||
|
||||
setPrintReady(true);
|
||||
|
||||
setTimeout(async () => {
|
||||
switch (dropdownValue) {
|
||||
case '15X15 6cross': // COMPLETE
|
||||
await printDiv(
|
||||
templateAvailable ? 'label-15X156cross' : 'StickerPrints0',
|
||||
style + getPageStyle(92)
|
||||
);
|
||||
handleModalClose();
|
||||
break;
|
||||
case '22X35 3cross': // Need to check
|
||||
await printDiv(
|
||||
templateAvailable ? '22X353cross' : 'StickerPrints1',
|
||||
style + getPageStyle(105)
|
||||
);
|
||||
handleModalClose();
|
||||
break;
|
||||
case '25X25 4cross': // COMPLETE
|
||||
await printDiv(
|
||||
templateAvailable ? '25X254cross' : 'StickerPrints2',
|
||||
style + getPageStyle(100)
|
||||
);
|
||||
handleModalClose();
|
||||
break;
|
||||
case '25X50 2cross': //Need to check
|
||||
await printDiv(
|
||||
templateAvailable ? '25X502cross' : 'StickerPrints3',
|
||||
style + getPageStyle(100)
|
||||
);
|
||||
handleModalClose();
|
||||
break;
|
||||
case '50X30 Single': // COMPLETE
|
||||
await printDiv(
|
||||
templateAvailable ? '50X30Single' : 'StickerPrints4',
|
||||
style
|
||||
);
|
||||
handleModalClose();
|
||||
break;
|
||||
case '50X25 Single': // COMPLETE
|
||||
await printDiv(
|
||||
templateAvailable ? '50X25Single' : 'StickerPrints5',
|
||||
style
|
||||
);
|
||||
handleModalClose();
|
||||
break;
|
||||
case '100X13 (55MM Printable)': //Need to check
|
||||
await printDiv(
|
||||
templateAvailable ? '100X13(55MMPrintable)' : 'StickerPrints6',
|
||||
style
|
||||
);
|
||||
handleModalClose();
|
||||
break;
|
||||
case '100X15 (70MM Printable)': // COMPLETE
|
||||
await printDiv(
|
||||
templateAvailable ? '100X15(70MMPrintable)' : 'StickerPrints7',
|
||||
style
|
||||
);
|
||||
handleModalClose();
|
||||
break;
|
||||
case '100X150': // COMPLETE
|
||||
await printDiv(
|
||||
templateAvailable ? '100X150' : 'StickerPrints8',
|
||||
style
|
||||
);
|
||||
handleModalClose();
|
||||
setPrintReady(false);
|
||||
break;
|
||||
default:
|
||||
alert('! Select correct cross ');
|
||||
setPrintReady(false);
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const CoresOnChange = (params) => {
|
||||
setDropdownValue(params);
|
||||
formRef.current?.setFieldsValue({ Cross: params });
|
||||
const template = barcodeTemplateDetails?.find(
|
||||
(find) => find.SessionName === params
|
||||
);
|
||||
const checkComponent = (name) =>
|
||||
template?.OptionDetails?.some((some) => some.OptionName === name);
|
||||
const productName = checkComponent('Product Name');
|
||||
setPrintProductName(productName);
|
||||
};
|
||||
|
||||
const CopiesOnChange = (params) => {
|
||||
setCopies(params);
|
||||
formRef.current?.setFieldsValue({ Copies: params });
|
||||
};
|
||||
|
||||
const handleModalClose = () => {
|
||||
setCopies(null)
|
||||
setDropdownValue('')
|
||||
setStickerPrintModalOpen(false);
|
||||
setLabelPrintData(null);
|
||||
setNickName(null);
|
||||
setProdId(null)
|
||||
setMultiProduct(false);
|
||||
formRef?.current?.resetFields();
|
||||
setPrintReady(false);
|
||||
};
|
||||
|
||||
const imageTagBarcodeAndQR = useCallback(
|
||||
(w = 'auto', fs = '6px', codeData = null) => {
|
||||
const currentCode = codeData || prodId;
|
||||
return (
|
||||
(templateOptions.barcode || templateOptions.qrcode) && (
|
||||
<>
|
||||
<img
|
||||
style={{ width: w }}
|
||||
src={
|
||||
templateOptions.codeType === 'B'
|
||||
? !multiProduct
|
||||
? barcodeBase64
|
||||
: generateBarcode(currentCode)
|
||||
: !multiProduct
|
||||
? qrBase64
|
||||
: generateQRCode(QrandbarcodeDatas, currentCode)
|
||||
}
|
||||
alt="Barcode"
|
||||
/>
|
||||
{templateOptions.codeType !== 'B' && (
|
||||
<p style={{ margin: '0', padding: '0', fontSize: fs }}>
|
||||
{currentCode}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
);
|
||||
},
|
||||
[templateOptions, multiProduct, barcodeBase64, qrBase64, prodId]
|
||||
);
|
||||
|
||||
const onComplete = useCallback(() => {
|
||||
setMessageType(null);
|
||||
setMessageData(null);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className='direct-sale-container'>
|
||||
<Messages
|
||||
messageType={messageType}
|
||||
messageData={messageData}
|
||||
onComplete={onComplete}
|
||||
/>
|
||||
<div className="bsreprintmodaltable-search">
|
||||
<input
|
||||
ref={searchInputRef}
|
||||
type="text"
|
||||
placeholder="Search Product / Short Code..."
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ marginBottom: '10px', color: '#666', fontSize: '14px', fontStyle: 'italic' }}>
|
||||
Note: Click on a row to modify the product details
|
||||
</div>
|
||||
<div className='DirectSaleTable' ref={tableRef}>
|
||||
<Tables
|
||||
rowKey="prodId"
|
||||
data={(searchText != null && searchText != '') ? filteredProducts : dataSource}
|
||||
dataSource={(searchText != null && searchText != '') ? filteredProducts : dataSource}
|
||||
columns={columns}
|
||||
onRow={(record) => ({
|
||||
onClick: () => record.prodId && setEditingKey(record.key)
|
||||
})}
|
||||
ownPagination={true}
|
||||
pagination={{
|
||||
current: currentPage,
|
||||
onChange: handlePageChange,
|
||||
total: (searchText != null && searchText != '') ? filteredProducts?.length : ((dataSource[dataSource.length - 1]?.totalProducts || 0) + 1),
|
||||
pageSize: 11,
|
||||
showSizeChanger: false,
|
||||
hideOnSinglePage: true,
|
||||
}}
|
||||
bordered
|
||||
/>
|
||||
</div>
|
||||
|
||||
{stickerPrintModalOpen && <StickerPrintModel
|
||||
open={stickerPrintModalOpen}
|
||||
handleCancel={handleModalClose}
|
||||
formRef={formRef}
|
||||
onFinish={onFinish}
|
||||
MultiCode={multiProduct}
|
||||
activeTab={null}
|
||||
setActiveTab={() => { }}
|
||||
selectedRecords={null}
|
||||
coresdata={crossData}
|
||||
dropdownValue={dropdownValue}
|
||||
CoresOnChange={CoresOnChange}
|
||||
barcodeTemplateDetails={barcodeTemplateDetails}
|
||||
detail={labelPrintData}
|
||||
proName={labelPrintData?.productName}
|
||||
nickName={nickName}
|
||||
ProdId={labelPrintData?.shortCode + '' + labelPrintData?.sellingPrice}
|
||||
size={labelPrintData?.size}
|
||||
// valueStyleColumn={valueStyleColumn}
|
||||
// secondValueStyle={secondValueStyle}
|
||||
// valueStyleRow={valueStyleRow}
|
||||
CopiesOnChange={CopiesOnChange}
|
||||
printProductName={printProductName}
|
||||
totalCopies={null}
|
||||
setstockWiseQrcode={null}
|
||||
setProductWiseQrcode={null}
|
||||
setNickName={setNickName}
|
||||
templateOptions={templateOptions}
|
||||
/>}
|
||||
|
||||
{printReady && (
|
||||
<StickerPrintTemplates
|
||||
selectedRecords={null}
|
||||
copies={copies}
|
||||
nickName={nickName}
|
||||
proName={labelPrintData?.productName}
|
||||
ProdId={labelPrintData?.shortCode + '' + labelPrintData?.sellingPrice}
|
||||
size={labelPrintData?.size + ' ' + labelPrintData?.uomName}
|
||||
detail={labelPrintData}
|
||||
templateOptions={templateOptions}
|
||||
base64Image={qrBase64}
|
||||
QrandbarcodeDatas={null}
|
||||
MultiCode={multiProduct}
|
||||
generateQRCodeCopy={(code) =>
|
||||
generateQRCodeCopy(null, code)
|
||||
}
|
||||
generateCodeImage={(code, codeType = 'Q') => generateCodeImage(code, codeType, null)}
|
||||
imageTagBarcodeAndQR={imageTagBarcodeAndQR}
|
||||
dropdownValue={dropdownValue}
|
||||
barcodeTemplateDetails={barcodeTemplateDetails}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DirectSale;
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import QRCode from 'qrcode';
|
||||
import JsBarcode from 'jsbarcode';
|
||||
|
||||
const useBarcodeGenerator = (code) => {
|
||||
const [qrBase64, setQrBase64] = useState(null);
|
||||
const [barcodeBase64, setBarcodeBase64] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!code) {
|
||||
setQrBase64(null);
|
||||
return;
|
||||
}
|
||||
|
||||
const generateQR = async () => {
|
||||
try {
|
||||
const url = await QRCode.toDataURL(code);
|
||||
setQrBase64(url);
|
||||
} catch (error) {
|
||||
console.error('QR generation error:', error);
|
||||
}
|
||||
};
|
||||
|
||||
generateQR();
|
||||
}, [code]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!code) {
|
||||
setBarcodeBase64(null);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const svg = document.createElementNS(
|
||||
'http://www.w3.org/2000/svg',
|
||||
'svg'
|
||||
);
|
||||
|
||||
JsBarcode(svg, code, {
|
||||
format: 'CODE128',
|
||||
displayValue: true,
|
||||
lineColor: '#000',
|
||||
width: 2,
|
||||
height: 50,
|
||||
});
|
||||
|
||||
const svgData = new XMLSerializer().serializeToString(svg);
|
||||
const svgBlob = new Blob([svgData], {
|
||||
type: 'image/svg+xml;charset=utf-8',
|
||||
});
|
||||
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
setBarcodeBase64(reader.result);
|
||||
};
|
||||
|
||||
reader.readAsDataURL(svgBlob);
|
||||
} catch (error) {
|
||||
console.error('Barcode generation error:', error);
|
||||
}
|
||||
}, [code]);
|
||||
|
||||
return {
|
||||
qrBase64,
|
||||
barcodeBase64
|
||||
};
|
||||
};
|
||||
|
||||
export default useBarcodeGenerator;
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -88,7 +88,7 @@ const StickerPrintModel = ({
|
|||
if (coresdata?.length > 0 && open) {
|
||||
CoresOnChange(coresdata?.[0].ConfigName);
|
||||
}
|
||||
}, [open]);
|
||||
}, [open, coresdata]);
|
||||
return (
|
||||
<DefaultModal
|
||||
width={1000}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import { Tables } from '../../Components/Tables/Table.jsx';
|
|||
import { InputField } from '../../Components/Forms/InputField.jsx';
|
||||
import TextAreaInput from '../../Components/Forms/TextArea.jsx';
|
||||
import { uploadImage } from '../../Features/upload/upload.js';
|
||||
import { getSession } from '../../Services/Others.js';
|
||||
|
||||
const { Option } = Select;
|
||||
const { Column } = Table;
|
||||
|
|
@ -490,6 +491,11 @@ const InvoiceImageExtractorModal = ({
|
|||
uomData = [],
|
||||
setVisible = () => { },
|
||||
}) => {
|
||||
|
||||
const AppId = getSession('AppId');
|
||||
const CompId = getSession('CompId');
|
||||
const BranchId = getSession('BranchId');
|
||||
|
||||
const extractedTableRef = useRef(null);
|
||||
const [form] = Form.useForm();
|
||||
const [ocrLoading, setOcrLoading] = useState(false);
|
||||
|
|
@ -503,7 +509,7 @@ const InvoiceImageExtractorModal = ({
|
|||
const [productTableData, setProductTableData] = useState([]);
|
||||
const [TotalAmtData, SetTotalAmtData] = useState(0);
|
||||
const [SupplierData, setSupplierData] = useState([]);
|
||||
console.log(form?.getFieldsValue(), 'SupplierData', SupplierData);
|
||||
console.log(form?.getFieldsValue(), 'productTableData', productTableData);
|
||||
const [showPreviewModal, setShowPreviewModal] = useState(false);
|
||||
const [processedImages, setProcessedImages] = useState(new Set());
|
||||
const [uploadMoreVisible, setUploadMoreVisible] = useState(false);
|
||||
|
|
@ -716,8 +722,14 @@ const InvoiceImageExtractorModal = ({
|
|||
setProcessingStep('Processing image…');
|
||||
|
||||
try {
|
||||
const data = {
|
||||
file: fileToProcess,
|
||||
AppId,
|
||||
CompId,
|
||||
BranchId
|
||||
}
|
||||
const response = await dispatch(
|
||||
getInvoiceImageData(fileToProcess)
|
||||
getInvoiceImageData(data)
|
||||
).unwrap();
|
||||
if (response?.data?.length > 0 && response?.statusCode === 1) {
|
||||
console.log('OCR API Response:', response);
|
||||
|
|
@ -762,19 +774,38 @@ const InvoiceImageExtractorModal = ({
|
|||
|
||||
if (data.products?.length) {
|
||||
const mergedProducts = [...productTableData];
|
||||
|
||||
data.products.forEach((p) => {
|
||||
const exists = mergedProducts.some(
|
||||
(x) => x.description === p.description && x.rate === p.rate
|
||||
);
|
||||
if (!exists) mergedProducts.push(p);
|
||||
});
|
||||
setProductTableData(
|
||||
mergedProducts?.map((item, index) => ({
|
||||
|
||||
// Sort Y first, then N
|
||||
const sortedData = [...mergedProducts]
|
||||
.sort((a, b) => {
|
||||
if (a?.type === b?.type) return 0;
|
||||
return a?.type === 'N' ? -1 : 1;
|
||||
})
|
||||
.map((item, index) => ({
|
||||
...item,
|
||||
srNo: index + 1,
|
||||
image: item?.image ? item?.image : (uploadImgData?.data?.status ? uploadImgData?.data?.image : null),
|
||||
}))
|
||||
);
|
||||
image: item?.image
|
||||
? item?.image
|
||||
: uploadImgData?.data?.status
|
||||
? uploadImgData?.data?.image
|
||||
: null,
|
||||
}));
|
||||
|
||||
setProductTableData(sortedData);
|
||||
// setProductTableData(
|
||||
// mergedProducts?.map((item, index) => ({
|
||||
// ...item,
|
||||
// srNo: index + 1,
|
||||
// image: item?.image ? item?.image : (uploadImgData?.data?.status ? uploadImgData?.data?.image : null),
|
||||
// }))
|
||||
// );
|
||||
SetTotalAmtData(
|
||||
mergedProducts.reduce(
|
||||
(sum, item) =>
|
||||
|
|
@ -1012,6 +1043,7 @@ const InvoiceImageExtractorModal = ({
|
|||
cursor: 'pointer',
|
||||
color: '#1292ee',
|
||||
marginBottom: '10px',
|
||||
fontFamily: 'Poppins'
|
||||
}}
|
||||
onClick={() => setIsEditMode(true)}
|
||||
>
|
||||
|
|
@ -1151,9 +1183,21 @@ const InvoiceImageExtractorModal = ({
|
|||
<div style={{ margin: '10px 0', fontSize: '14px', color: '#666' }}>
|
||||
<strong>Note:</strong> Click on a row to edit the data
|
||||
</div>
|
||||
<div className="product-legend">
|
||||
<div className="legend-item">
|
||||
<span className="legend-box green"></span>
|
||||
<span>Product exists in list</span>
|
||||
</div>
|
||||
|
||||
<div className="legend-item">
|
||||
<span className="legend-box red"></span>
|
||||
<span>Product not found in list</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="extractor-table" ref={extractedTableRef}>
|
||||
<Tables
|
||||
rowClassName="product-extractor-table"
|
||||
rowClassName={(record) => `product-extractor-table ${record.type === 'N' ? 'not-added' : 'added'}`}
|
||||
columns={columns}
|
||||
data={productTableData}
|
||||
onRow={(record) => ({
|
||||
|
|
|
|||
|
|
@ -629,6 +629,7 @@ const StockForm = ({ formType }) => {
|
|||
const unmatchedProducts = matchedProducts.filter(
|
||||
(item) => !item.matchFound
|
||||
);
|
||||
|
||||
if (unmatchedProducts?.length > 0) {
|
||||
setLoadingText('Mapping unmatched products...');
|
||||
const response = await dispatch(
|
||||
|
|
@ -705,7 +706,7 @@ const StockForm = ({ formType }) => {
|
|||
}));
|
||||
|
||||
const bulkResponse = await dispatch(
|
||||
bulkpostdata({ ProdDetails: newProductsData })
|
||||
bulkpostdata({ UploadType: 'Purchase', ProdDetails: newProductsData })
|
||||
).unwrap();
|
||||
|
||||
if (bulkResponse?.data?.statusCode === 1) {
|
||||
|
|
|
|||
|
|
@ -133,6 +133,11 @@
|
|||
.extractor-table {
|
||||
overflow: auto;
|
||||
scrollbar-width: thin;
|
||||
|
||||
}
|
||||
|
||||
.extractor-table-red .ant-table-thead {
|
||||
background-color: #ef4444;
|
||||
}
|
||||
|
||||
.product-extractor-table {
|
||||
|
|
@ -160,6 +165,48 @@
|
|||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
&.not-added {
|
||||
background-color: #ff4d4f;
|
||||
|
||||
&:hover {
|
||||
background-color: #ff4d4f !important;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
&.added {
|
||||
background-color: #52c41a;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.product-legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
font-family: 'Poppins';
|
||||
margin-bottom: 10px;
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.legend-box {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 2px;
|
||||
|
||||
&.green {
|
||||
background-color: #52c41a;
|
||||
}
|
||||
|
||||
&.red {
|
||||
background-color: #ff4d4f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// .supplierInfo .ant-select-selector
|
||||
|
|
|
|||
Loading…
Reference in New Issue