3581 lines
102 KiB
JavaScript
3581 lines
102 KiB
JavaScript
import React, { useState, useRef, useEffect, useContext } from 'react';
|
|
import { useDispatch, useSelector } from 'react-redux';
|
|
import ExcelJS from 'exceljs';
|
|
import { Table, Form, Input, Tooltip, Modal, Select } from 'antd';
|
|
import {
|
|
emptyExcelData,
|
|
excelDataSelector,
|
|
excelFileSelector,
|
|
excelFileErrorSelector,
|
|
fileInputRefSelector,
|
|
uploadExcel,
|
|
} from '../../Features/ExcelUploadPage/ExcelUploadPage.js';
|
|
import {
|
|
getProductData,
|
|
onlineimages,
|
|
postFieldSetup,
|
|
getFieldSetupData,
|
|
} from '../../Features/ProductPage/ProductPage.js';
|
|
import Imageupload from '../../Components/Forms/Upload.jsx';
|
|
import { CiExport } from 'react-icons/ci';
|
|
|
|
import { uploadImage } from '../../Features/upload/upload.js';
|
|
import { TbWorldSearch } from 'react-icons/tb';
|
|
import { DefaultModal } from '../../Components/Modal/DefaultModal.jsx';
|
|
import '../../Styles/Product/excel.scss';
|
|
import { AiFillDelete } from 'react-icons/ai';
|
|
import Excelupldimg from '../../Images/Excel.png';
|
|
import { FiDelete, FiDownload } from 'react-icons/fi';
|
|
import { getSession } from '../../Services/Others.js';
|
|
import { ArrowRightOutlined, CloseCircleOutlined } from '@ant-design/icons';
|
|
import stringSimilarity from 'string-similarity';
|
|
import {
|
|
getCommonAppPreference,
|
|
ApplicationPreferences,
|
|
getApplicationDetails,
|
|
} from '../../Features/BrachLogin/BranchLogin.js';
|
|
import { MdOutlineAppRegistration } from 'react-icons/md';
|
|
import FormHeader from '../PageComponents/FormHeader.jsx';
|
|
import { DropDowns } from '../../Components/Forms/DropDown.jsx';
|
|
import Buttons from '../../Components/Forms/Buttons.jsx';
|
|
import { IoClose } from 'react-icons/io5';
|
|
import { Messages } from '../../Components/Notifications/Messages.jsx';
|
|
import useWhyDidYouUpdate from '../../Services/findrerender.js';
|
|
import { downloadFile } from '../../utils/downloadFile.js';
|
|
|
|
const getApplicationSampleData = (appName) => {
|
|
const sampleDataMap = {
|
|
Restaurant: {
|
|
productName: 'Chicken Biryani',
|
|
quantity: 1,
|
|
uom: 'PLT',
|
|
salesPrice: 250,
|
|
mrp: 280,
|
|
category: 'Non-Vegetarian',
|
|
subCategory: 'Biryani',
|
|
brand: 'Chef Special',
|
|
variantName: 'Large',
|
|
tax: 'CGST+SGST - 5%',
|
|
hsn: '21069030',
|
|
additionalSamples: [
|
|
{
|
|
productName: 'Masala Dosa',
|
|
quantity: 1,
|
|
uom: 'PLT',
|
|
salesPrice: 120,
|
|
category: 'Vegetarian',
|
|
subCategory: 'South Indian',
|
|
},
|
|
{
|
|
productName: 'Paneer Butter Masala',
|
|
quantity: 1,
|
|
uom: 'PLT',
|
|
salesPrice: 200,
|
|
category: 'Vegetarian',
|
|
subCategory: 'North Indian',
|
|
},
|
|
],
|
|
},
|
|
Bakery: {
|
|
productName: 'Chocolate Cake',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 500,
|
|
mrp: 550,
|
|
category: 'Cakes',
|
|
subCategory: 'Birthday Cakes',
|
|
brand: 'Fresh Bakes',
|
|
variantName: '1 Kg',
|
|
tax: 'CGST+SGST - 5%',
|
|
hsn: '19053100',
|
|
additionalSamples: [
|
|
{
|
|
productName: 'Croissant',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 45,
|
|
category: 'Pastries',
|
|
subCategory: 'French Pastries',
|
|
},
|
|
{
|
|
productName: 'Whole Wheat Bread',
|
|
quantity: 1,
|
|
uom: 'LOAF',
|
|
salesPrice: 35,
|
|
category: 'Breads',
|
|
subCategory: 'Wheat Breads',
|
|
},
|
|
],
|
|
},
|
|
'Electrical, Electronics and Computers': {
|
|
productName: 'LED Bulb 9W',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 120,
|
|
mrp: 150,
|
|
category: 'Lighting',
|
|
subCategory: 'LED Bulbs',
|
|
brand: 'Philips',
|
|
variantName: 'Cool White',
|
|
tax: 'CGST+SGST - 18%',
|
|
hsn: '85395000',
|
|
additionalSamples: [
|
|
{
|
|
productName: 'Mobile Charger',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 299,
|
|
category: 'Mobile Accessories',
|
|
subCategory: 'Chargers',
|
|
},
|
|
{
|
|
productName: 'Extension Board 4 Socket',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 350,
|
|
category: 'Electrical Accessories',
|
|
subCategory: 'Power Strips',
|
|
},
|
|
],
|
|
},
|
|
'Mobile Phone and Accessories': {
|
|
productName: 'Smartphone Screen Guard',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 199,
|
|
mrp: 299,
|
|
category: 'Screen Protection',
|
|
subCategory: 'Tempered Glass',
|
|
brand: 'TechGuard',
|
|
variantName: 'iPhone 14',
|
|
tax: 'CGST+SGST - 18%',
|
|
hsn: '70071900',
|
|
additionalSamples: [
|
|
{
|
|
productName: 'Phone Case',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 149,
|
|
category: 'Protection',
|
|
subCategory: 'Back Covers',
|
|
},
|
|
{
|
|
productName: 'Wireless Earbuds',
|
|
quantity: 1,
|
|
uom: 'PAIR',
|
|
salesPrice: 1599,
|
|
category: 'Audio',
|
|
subCategory: 'Bluetooth Earphones',
|
|
},
|
|
],
|
|
},
|
|
Grocery: {
|
|
productName: 'Basmati Rice',
|
|
quantity: 1,
|
|
uom: 'KG',
|
|
salesPrice: 85,
|
|
mrp: 90,
|
|
category: 'Food Grains',
|
|
subCategory: 'Rice',
|
|
brand: 'India Gate',
|
|
variantName: '1 Kg Pack',
|
|
tax: 'NIL - 0%',
|
|
hsn: '10063020',
|
|
additionalSamples: [
|
|
{
|
|
productName: 'Toor Dal',
|
|
quantity: 1,
|
|
uom: 'KG',
|
|
salesPrice: 120,
|
|
category: 'Pulses',
|
|
subCategory: 'Arhar Dal',
|
|
},
|
|
{
|
|
productName: 'Refined Oil',
|
|
quantity: 1,
|
|
uom: 'LITER',
|
|
salesPrice: 110,
|
|
category: 'Cooking Oil',
|
|
subCategory: 'Sunflower Oil',
|
|
},
|
|
],
|
|
},
|
|
'Departmental Stores': {
|
|
productName: 'Shampoo',
|
|
quantity: 1,
|
|
uom: 'BTL',
|
|
salesPrice: 180,
|
|
mrp: 200,
|
|
category: 'Personal Care',
|
|
subCategory: 'Hair Care',
|
|
brand: 'Head & Shoulders',
|
|
variantName: '200ml',
|
|
tax: 'CGST+SGST - 18%',
|
|
hsn: '33051000',
|
|
additionalSamples: [
|
|
{
|
|
productName: 'Toothpaste',
|
|
quantity: 1,
|
|
uom: 'TUBE',
|
|
salesPrice: 45,
|
|
category: 'Oral Care',
|
|
subCategory: 'Toothpaste',
|
|
},
|
|
{
|
|
productName: 'Laundry Detergent',
|
|
quantity: 1,
|
|
uom: 'KG',
|
|
salesPrice: 85,
|
|
category: 'Household',
|
|
subCategory: 'Washing Powder',
|
|
},
|
|
],
|
|
},
|
|
Jewellery: {
|
|
productName: 'Gold Ring',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 25000,
|
|
mrp: 26000,
|
|
category: 'Gold Jewellery',
|
|
subCategory: 'Rings',
|
|
brand: 'Tanishq',
|
|
variantName: '22K Gold',
|
|
tax: 'CGST+SGST - 3%',
|
|
hsn: '71131900',
|
|
additionalSamples: [
|
|
{
|
|
productName: 'Silver Earrings',
|
|
quantity: 1,
|
|
uom: 'PAIR',
|
|
salesPrice: 1200,
|
|
category: 'Silver Jewellery',
|
|
subCategory: 'Earrings',
|
|
},
|
|
{
|
|
productName: 'Diamond Pendant',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 15000,
|
|
category: 'Diamond Jewellery',
|
|
subCategory: 'Pendants',
|
|
},
|
|
],
|
|
},
|
|
'Lifestyle and Fashion': {
|
|
productName: 'Cotton T-Shirt',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 499,
|
|
mrp: 599,
|
|
category: 'Apparel',
|
|
subCategory: 'T-Shirts',
|
|
brand: 'Nike',
|
|
variantName: 'Medium',
|
|
tax: 'CGST+SGST - 5%',
|
|
hsn: '61091000',
|
|
additionalSamples: [
|
|
{
|
|
productName: 'Jeans',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 1299,
|
|
category: 'Bottoms',
|
|
subCategory: 'Denim',
|
|
},
|
|
{
|
|
productName: 'Sneakers',
|
|
quantity: 1,
|
|
uom: 'PAIR',
|
|
salesPrice: 2499,
|
|
category: 'Footwear',
|
|
subCategory: 'Casual Shoes',
|
|
},
|
|
],
|
|
},
|
|
'Salon, Spa and Beauty Parlour': {
|
|
productName: 'Hair Cut Service',
|
|
quantity: 1,
|
|
uom: 'SERVICE',
|
|
salesPrice: 300,
|
|
mrp: 350,
|
|
category: 'Hair Services',
|
|
subCategory: 'Cutting',
|
|
brand: 'Premium Salon',
|
|
variantName: 'Regular Cut',
|
|
tax: 'CGST+SGST - 18%',
|
|
hsn: '99820000',
|
|
additionalSamples: [
|
|
{
|
|
productName: 'Facial Treatment',
|
|
quantity: 1,
|
|
uom: 'SERVICE',
|
|
salesPrice: 800,
|
|
category: 'Skin Care',
|
|
subCategory: 'Facial',
|
|
},
|
|
{
|
|
productName: 'Hair Color',
|
|
quantity: 1,
|
|
uom: 'SERVICE',
|
|
salesPrice: 1200,
|
|
category: 'Hair Services',
|
|
subCategory: 'Coloring',
|
|
},
|
|
],
|
|
},
|
|
Stationery: {
|
|
productName: 'A4 Paper Pack',
|
|
quantity: 1,
|
|
uom: 'PACK',
|
|
salesPrice: 280,
|
|
mrp: 300,
|
|
category: 'Paper Products',
|
|
subCategory: 'Copy Paper',
|
|
brand: 'JK Copier',
|
|
variantName: '500 Sheets',
|
|
tax: 'CGST+SGST - 12%',
|
|
hsn: '48025510',
|
|
additionalSamples: [
|
|
{
|
|
productName: 'Ball Pen',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 15,
|
|
category: 'Writing Instruments',
|
|
subCategory: 'Pens',
|
|
},
|
|
{
|
|
productName: 'Notebook',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 45,
|
|
category: 'Books & Notebooks',
|
|
subCategory: 'Exercise Books',
|
|
},
|
|
],
|
|
},
|
|
Pharmacy: {
|
|
productName: 'Paracetamol 500mg',
|
|
quantity: 1,
|
|
uom: 'STRIP',
|
|
salesPrice: 12,
|
|
mrp: 15,
|
|
category: 'Medicines',
|
|
subCategory: 'Fever & Pain',
|
|
brand: 'Crocin',
|
|
variantName: '10 Tablets',
|
|
tax: 'CGST+SGST - 12%',
|
|
hsn: '30049099',
|
|
additionalSamples: [
|
|
{
|
|
productName: 'Hand Sanitizer',
|
|
quantity: 1,
|
|
uom: 'BTL',
|
|
salesPrice: 65,
|
|
category: 'Healthcare',
|
|
subCategory: 'Sanitizers',
|
|
},
|
|
{
|
|
productName: 'Bandage Roll',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 25,
|
|
category: 'Medical Supplies',
|
|
subCategory: 'First Aid',
|
|
},
|
|
],
|
|
},
|
|
Furniture: {
|
|
productName: 'Wooden Dining Table',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 15000,
|
|
mrp: 18000,
|
|
category: 'Dining Furniture',
|
|
subCategory: 'Dining Tables',
|
|
brand: 'Royal Oak',
|
|
variantName: '6 Seater',
|
|
tax: 'CGST+SGST - 12%',
|
|
hsn: '94036090',
|
|
additionalSamples: [
|
|
{
|
|
productName: 'Office Chair',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 5500,
|
|
category: 'Office Furniture',
|
|
subCategory: 'Chairs',
|
|
},
|
|
{
|
|
productName: 'Queen Size Bed',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 12000,
|
|
category: 'Bedroom Furniture',
|
|
subCategory: 'Beds',
|
|
},
|
|
],
|
|
},
|
|
Fruits: {
|
|
productName: 'Fresh Apples',
|
|
quantity: 1,
|
|
uom: 'KG',
|
|
salesPrice: 180,
|
|
mrp: 200,
|
|
category: 'Fresh Fruits',
|
|
subCategory: 'Seasonal Fruits',
|
|
brand: 'Farm Fresh',
|
|
variantName: 'Kashmiri Apple',
|
|
tax: 'NIL - 0%',
|
|
hsn: '08081000',
|
|
additionalSamples: [
|
|
{
|
|
productName: 'Bananas',
|
|
quantity: 1,
|
|
uom: 'DOZEN',
|
|
salesPrice: 60,
|
|
category: 'Fresh Fruits',
|
|
subCategory: 'Tropical Fruits',
|
|
},
|
|
{
|
|
productName: 'Orange Juice',
|
|
quantity: 1,
|
|
uom: 'LITER',
|
|
salesPrice: 120,
|
|
category: 'Fruit Juices',
|
|
subCategory: 'Fresh Juice',
|
|
},
|
|
],
|
|
},
|
|
Textiles: {
|
|
productName: 'Cotton Fabric',
|
|
quantity: 1,
|
|
uom: 'METER',
|
|
salesPrice: 85,
|
|
mrp: 100,
|
|
category: 'Fabric',
|
|
subCategory: 'Cotton Fabric',
|
|
brand: 'Raymond',
|
|
variantName: 'Plain White',
|
|
tax: 'CGST+SGST - 5%',
|
|
hsn: '52081900',
|
|
additionalSamples: [
|
|
{
|
|
productName: 'Silk Saree',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 2500,
|
|
category: 'Ready Made',
|
|
subCategory: 'Sarees',
|
|
},
|
|
{
|
|
productName: 'Woolen Shawl',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 800,
|
|
category: 'Accessories',
|
|
subCategory: 'Shawls',
|
|
},
|
|
],
|
|
},
|
|
'Computer Sales Shop': {
|
|
productName: 'Gaming Laptop',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 55000,
|
|
mrp: 60000,
|
|
category: 'Laptops',
|
|
subCategory: 'Gaming Laptops',
|
|
brand: 'ASUS',
|
|
variantName: 'ROG Strix G15',
|
|
tax: 'CGST+SGST - 18%',
|
|
hsn: '84713000',
|
|
additionalSamples: [
|
|
{
|
|
productName: 'Wireless Mouse',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 1200,
|
|
category: 'Computer Accessories',
|
|
subCategory: 'Mouse',
|
|
},
|
|
{
|
|
productName: 'SSD 500GB',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 4500,
|
|
category: 'Storage',
|
|
subCategory: 'Solid State Drive',
|
|
},
|
|
],
|
|
},
|
|
};
|
|
|
|
// Return sample data for the specified app, or generic data if not found
|
|
return (
|
|
sampleDataMap[appName] || {
|
|
productName: 'Sample Product Name',
|
|
quantity: 1,
|
|
uom: 'PCS',
|
|
salesPrice: 100,
|
|
mrp: 100,
|
|
category: 'Sample Category',
|
|
subCategory: 'Sample Sub Category',
|
|
brand: 'Sample Brand',
|
|
variantName: 'Sample Variant Name',
|
|
tax: 'NIL - 0%',
|
|
hsn: '12345678',
|
|
additionalSamples: [],
|
|
}
|
|
);
|
|
};
|
|
|
|
const allowedExcelTypes = [
|
|
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
|
|
'application/vnd.ms-excel', // .xls
|
|
];
|
|
|
|
const ProductExcel = ({
|
|
handleSubmit,
|
|
CategoryId,
|
|
CategoryNames,
|
|
SubcatId,
|
|
SubCatData,
|
|
AllBrandId,
|
|
Allbranddata,
|
|
Allbranddatanumfid,
|
|
Subcatnumfid,
|
|
Unit,
|
|
TaxDatas,
|
|
SuplierNames,
|
|
CategoryName1,
|
|
Catconfigid,
|
|
}) => {
|
|
useWhyDidYouUpdate('ProductExcel', {
|
|
handleSubmit,
|
|
CategoryId,
|
|
CategoryNames,
|
|
SubcatId,
|
|
SubCatData,
|
|
AllBrandId,
|
|
Allbranddata,
|
|
Allbranddatanumfid,
|
|
Subcatnumfid,
|
|
Unit,
|
|
TaxDatas,
|
|
SuplierNames,
|
|
CategoryName1,
|
|
Catconfigid,
|
|
});
|
|
const dispatch = useDispatch();
|
|
const formRef = useRef(null);
|
|
const CompId = getSession('CompId');
|
|
const BranchId = getSession('BranchId');
|
|
const AppId = getSession('AppId');
|
|
const UserId = getSession('UserId');
|
|
|
|
const fileInputRef = useRef(fileInputRefSelector);
|
|
const excelData = useSelector(excelDataSelector);
|
|
const excelFile = useSelector(excelFileSelector);
|
|
const excelFileError = useSelector(excelFileErrorSelector);
|
|
const ApplicationPreferenceData = useSelector(ApplicationPreferences);
|
|
const productBulkUploadCatId = ApplicationPreferenceData?.find(
|
|
(p) => p?.PreferredCatName?.toLowerCase() === 'product bulk upload'
|
|
)?.PreferredCatId;
|
|
|
|
const [appName, setAppName] = useState(null);
|
|
const [messageType, setMessageType] = useState(null);
|
|
const [messageData, setMessageData] = useState(null);
|
|
const [selectedFields, setSelectedFields] = useState([]);
|
|
const [tableFieldPreferences, setTableFieldPreferences] = useState([]);
|
|
const [worksheet, setWorksheet] = useState(null);
|
|
const [worksheet1, setWorksheet1] = useState(null);
|
|
const [editdelete, seteditdelete] = useState('');
|
|
const [fieldSetup, setFieldSetup] = useState(false);
|
|
const [selectedImageIndex, setSelectedImageIndex] = useState(null);
|
|
const [selectedProductName, setSelectedProductName] = useState(null);
|
|
const [imagedata, setimagedata] = useState();
|
|
const [selectedImage, setSelectedImage] = useState(null);
|
|
const [imageopen, setimageOpen] = useState();
|
|
const [onlineImage, setOnlineImage] = useState(null);
|
|
const [loading, setLoading] = useState(false);
|
|
const [message, setMessage] = useState(false);
|
|
const [Datas, setDatas] = useState();
|
|
const [currentPage, setCurrentPage] = useState(1);
|
|
const [MappingOpen, setMappingOpen] = useState(false);
|
|
const [fieldMapping, setFieldMapping] = useState({});
|
|
const [headers, setHeaders] = useState([]);
|
|
const [UploadedRawData, setUploadedRawData] = useState([]);
|
|
const [ExcelData, setExcelData] = useState([]);
|
|
const [originalUploadedRawData, setOriginalUploadedRawData] = useState([]);
|
|
const [FieldValues, setFieldValues] = useState([]);
|
|
const [OrginalData, setOrginalData] = useState([]);
|
|
const handleTableChange = (pagination, filters, sorter) => {
|
|
setCurrentPage(pagination.current);
|
|
};
|
|
|
|
console.log(tableFieldPreferences, 'tableFieldPreferences');
|
|
|
|
useEffect(() => {
|
|
if (excelData && excelData?.length > 0) {
|
|
let ExecelToJsConvertion = [];
|
|
let slicedData = excelData?.slice(1);
|
|
let filteredData = slicedData?.filter((item) => {
|
|
const values = Object.values(item || {});
|
|
const lastVal = values[values.length - 1];
|
|
return typeof lastVal === 'string'
|
|
? lastVal.trim() !== 'sample row'
|
|
: true;
|
|
});
|
|
filteredData?.map((item, index) => {
|
|
ExecelToJsConvertion?.push({
|
|
key: index,
|
|
ProductName: item?.['0'],
|
|
Quantity: item?.['1'],
|
|
UOM: item?.['2'],
|
|
SellPrice: item?.['3'],
|
|
Category: item?.['4'],
|
|
SubCategory: item?.['5'],
|
|
Brand: item?.['6'],
|
|
ProductVariantName: item?.['7'] ? item?.['7'] : 'Variant 1',
|
|
MRP: item?.['8'],
|
|
WhSalePrice: item?.['9'],
|
|
StockAvailable: item?.['10'] ? item?.['10'] : 'No',
|
|
TokenAvailable: item?.['11'] ? item?.['11'] : 'No',
|
|
ProductType: item?.['12'],
|
|
Tax: item?.['13'],
|
|
AutoGenerateQrcode: item?.['14'],
|
|
AddQrCode: item?.['15'],
|
|
OnePeiceAvailable: item?.['16'] ? item?.['16'] : 'No',
|
|
OnePeicePrice: item?.['17'],
|
|
AutoGenerateOnePieceQrcode: item?.['18'] ? item?.['18'] : 'No',
|
|
AutoGenerateOnePieceQrcodeNumber: item?.['19'],
|
|
CESS: item?.['20'],
|
|
HSN: item?.['21'],
|
|
PartNumber: item?.['22'],
|
|
Rack: item?.['23'],
|
|
ManufactureDate: item?.['24'],
|
|
ExpireDate: item?.['25'],
|
|
AvailableFrom: item?.['26'] ? item?.['26'] : '',
|
|
AvailableTo: item?.['27'] ? item?.['27'] : '',
|
|
Productimage: '',
|
|
});
|
|
});
|
|
|
|
setDatas(ExecelToJsConvertion);
|
|
// setExcelData(ExecelToJsConvertion);
|
|
// setOriginalUploadedRawData(ExecelToJsConvertion);
|
|
const style = document.createElement('style');
|
|
style.type = 'text/css';
|
|
style.innerHTML = `
|
|
.viewerExcelupload::-webkit-scrollbar {
|
|
display: block !important;
|
|
}
|
|
`;
|
|
document.head.appendChild(style);
|
|
|
|
return () => {
|
|
document.head.removeChild(style);
|
|
};
|
|
}
|
|
}, [excelData]);
|
|
|
|
useEffect(() => {
|
|
const fetchData = async () => {
|
|
if (AppId) {
|
|
const response = await dispatch(
|
|
getApplicationDetails({ AppId })
|
|
)?.unwrap();
|
|
setAppName(response?.data?.data?.[0]?.AppName);
|
|
}
|
|
};
|
|
fetchData();
|
|
}, [AppId]);
|
|
|
|
useEffect(() => {
|
|
// Call handleSubmit when Datas is updated
|
|
if (Datas) {
|
|
handleSubmit(Datas);
|
|
}
|
|
}, [Datas, handleSubmit]);
|
|
|
|
// Automatically update preview data on mapping change
|
|
const mandatoryFields = [
|
|
'ProductName',
|
|
'Quantity',
|
|
'UOM',
|
|
'SellPrice',
|
|
'Category',
|
|
];
|
|
useEffect(() => {
|
|
if (UploadedRawData.length > 0) {
|
|
const updatedMappedData = UploadedRawData.map((row, index) => {
|
|
const newRow = { key: index };
|
|
newRow['isModified'] = row?.isModified;
|
|
|
|
// ✅ Process mandatory fields (retain value even if unmapped)
|
|
mandatoryFields.forEach((field) => {
|
|
const mappedHeader = fieldMapping[field];
|
|
|
|
if (mappedHeader && row[mappedHeader] !== undefined) {
|
|
newRow[field] = row[mappedHeader];
|
|
} else {
|
|
// Fallback: try original headers (case-insensitive, whitespace-insensitive)
|
|
const fallbackHeader = headers.find(
|
|
(header) =>
|
|
header?.toLowerCase().replace(/\s+/g, '') ===
|
|
field.toLowerCase().replace(/\s+/g, '')
|
|
);
|
|
|
|
if (fallbackHeader && row[fallbackHeader] !== undefined) {
|
|
newRow[field] = row[fallbackHeader];
|
|
} else {
|
|
// Preserve existing value if present
|
|
newRow[field] = ExcelData?.[index]?.[field] ?? '';
|
|
}
|
|
}
|
|
});
|
|
|
|
// ✅ Process non-mandatory mapped fields
|
|
Object.entries(fieldMapping).forEach(([field, header]) => {
|
|
if (!mandatoryFields.includes(field)) {
|
|
newRow[field] = headers.includes(header) ? (row[header] ?? '') : '';
|
|
}
|
|
});
|
|
|
|
// ✅ Clean up unmapped columns
|
|
headers.forEach((header) => {
|
|
const isMapped = Object.values(fieldMapping).includes(header);
|
|
const mappedField = Object.keys(fieldMapping).find(
|
|
(key) => fieldMapping[key] === header
|
|
);
|
|
const isMandatory = mandatoryFields.includes(mappedField);
|
|
|
|
if (!isMapped && !(header in newRow)) {
|
|
newRow[header] = isMandatory ? (row[header] ?? '') : '';
|
|
}
|
|
});
|
|
return newRow;
|
|
});
|
|
|
|
setExcelData(updatedMappedData);
|
|
setDatas(updatedMappedData);
|
|
}
|
|
}, [fieldMapping, UploadedRawData, headers]);
|
|
|
|
useEffect(() => {
|
|
if (AppId && BranchId && CompId) {
|
|
handleExportBulkdata();
|
|
}
|
|
}, [AppId, BranchId, CompId]);
|
|
|
|
useEffect(() => {
|
|
getFieldSetup();
|
|
}, [productBulkUploadCatId]);
|
|
|
|
const getFieldSetup = async () => {
|
|
try {
|
|
const response = await dispatch(
|
|
getFieldSetupData({
|
|
AppId,
|
|
CompId,
|
|
BranchId,
|
|
categoryId: productBulkUploadCatId,
|
|
Type: 'EB',
|
|
})
|
|
).unwrap();
|
|
if (response?.data?.statusCode === 1) {
|
|
console.log(response?.data?.data?.[0]?.ConfigDtl, 'Field Setup Data');
|
|
setSelectedFields(
|
|
response?.data?.data?.[0]?.ConfigDtl?.filter(
|
|
(c) => c.ConfigId && c.Access === 'Y'
|
|
)?.map((c) => c.ConfigId) || []
|
|
);
|
|
setTableFieldPreferences(
|
|
response?.data?.data?.[0]?.ConfigDtl?.map((c) => ({
|
|
value: c.ConfigId,
|
|
label: c.ConfigName,
|
|
access: c.Access,
|
|
})) || []
|
|
);
|
|
setFieldValues(response?.data?.data?.[0]?.ConfigDtl);
|
|
} else {
|
|
setMessageType('error');
|
|
setMessageData('Failed to fetch field setup');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error fetching field setup:', error);
|
|
}
|
|
};
|
|
|
|
const handleFileUpload = (e) => {
|
|
const file = e.target.files[0];
|
|
const reader = new FileReader();
|
|
|
|
if (file) {
|
|
const isAllowed = allowedExcelTypes?.includes(file.type);
|
|
if (!isAllowed) {
|
|
Modal.error({
|
|
title: 'Invalid File Type',
|
|
content: 'Only Excel files (.xls, .xlsx) are allowed.',
|
|
});
|
|
handleCancelData();
|
|
} else {
|
|
reader.readAsDataURL(file);
|
|
uploadAndProcessExcel(file);
|
|
}
|
|
} else {
|
|
dispatch(emptyExcelData());
|
|
}
|
|
};
|
|
|
|
const handleClick = () => {
|
|
// Programmatically trigger the file input click event
|
|
fileInputRef.current.click();
|
|
};
|
|
const getFieldValue = (product, field) => {
|
|
const aliasField = FIELD_ALIAS[field];
|
|
return normalize(product[field] ?? product[aliasField]);
|
|
};
|
|
const normalize = (value) => {
|
|
// Empty / invalid values
|
|
if (
|
|
value === undefined ||
|
|
value === null ||
|
|
value === '' ||
|
|
value === 'NaN-NaN-NaN'
|
|
) {
|
|
return '';
|
|
}
|
|
|
|
// Y / N flags
|
|
if (value === 'Y') return true;
|
|
if (value === 'N') return false;
|
|
|
|
// Numbers (handles "150", 150, "0")
|
|
if (
|
|
typeof value === 'number' ||
|
|
(typeof value === 'string' &&
|
|
value.trim() !== '' &&
|
|
!Number.isNaN(Number(value)))
|
|
) {
|
|
return Number(value);
|
|
}
|
|
|
|
// Default string
|
|
return String(value).trim();
|
|
};
|
|
const hashProduct = (product) =>
|
|
COMPARE_FIELDS.map((field) => getFieldValue(product, field)).join('§');
|
|
function findModifiedProducts(originalData, updatedData) {
|
|
if (!originalData?.length || !updatedData?.length) return [];
|
|
|
|
const originalHashMap = new Map();
|
|
|
|
for (const product of originalData) {
|
|
if (!product.ProdId) continue;
|
|
originalHashMap.set(product.ProdId, hashProduct(product));
|
|
}
|
|
|
|
// Step 2: Compare updated products
|
|
const modifiedProducts = [];
|
|
|
|
for (const product of updatedData) {
|
|
if (!product.ProdId) continue;
|
|
|
|
const originalHash = originalHashMap.get(product.ProdId);
|
|
if (!originalHash) continue; // new product, not modified
|
|
|
|
const updatedHash = hashProduct(product);
|
|
|
|
if (originalHash !== updatedHash) {
|
|
modifiedProducts.push(product);
|
|
}
|
|
}
|
|
|
|
return modifiedProducts;
|
|
}
|
|
const FIELD_ALIAS = {
|
|
'Product Variant Name': 'Product Varient Name',
|
|
};
|
|
|
|
const uploadAndProcessExcel = (file) => {
|
|
const reader = new FileReader();
|
|
|
|
reader.onload = async (e) => {
|
|
const buffer = e.target.result;
|
|
|
|
const workbook = new ExcelJS.Workbook();
|
|
await workbook.xlsx.load(buffer);
|
|
|
|
const worksheet = workbook.worksheets[0];
|
|
|
|
// 🔥 convert to same format as xlsx (array of arrays)
|
|
const jsonData = [];
|
|
worksheet.eachRow((row) => {
|
|
const rowValues = row.values.slice(1); // remove first empty index
|
|
jsonData.push(rowValues);
|
|
});
|
|
|
|
const nonEmptyRows = jsonData.filter((row) =>
|
|
row.some((cell) => cell !== '' && cell !== null && cell !== undefined)
|
|
);
|
|
|
|
const header = nonEmptyRows[0];
|
|
const rows = nonEmptyRows.slice(1);
|
|
|
|
const dateFields = [
|
|
'Available From',
|
|
'Available To',
|
|
'Manufacture Date',
|
|
'Expire Date',
|
|
'Stock Date',
|
|
];
|
|
|
|
function convertToDisplayFormat(dateString) {
|
|
if (!dateString) return '';
|
|
|
|
const date = new Date(dateString);
|
|
if (isNaN(date)) return dateString;
|
|
|
|
const day = date.getDate().toString().padStart(2, '0');
|
|
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
const year = date.getFullYear().toString();
|
|
return `${day}-${month}-${year}`;
|
|
}
|
|
|
|
const formattedData = rows.map((row) => {
|
|
let rowData = header.reduce((acc, col, columnIndex) => {
|
|
if (dateFields?.includes(col)) {
|
|
acc[col] = convertToDisplayFormat(row[columnIndex]);
|
|
} else {
|
|
acc[col] = row[columnIndex];
|
|
}
|
|
return acc;
|
|
}, {});
|
|
return rowData;
|
|
});
|
|
|
|
if (jsonData.length > 0) {
|
|
const extractedHeaders = Object.keys(formattedData[0] || {});
|
|
|
|
const filteredData = formattedData.filter(
|
|
(item) => !Object.values(item)?.includes('sample row')
|
|
);
|
|
|
|
const updatedData = filteredData.map((item) => ({
|
|
...item,
|
|
'Product Varient Name':
|
|
item['Product Varient Name'] === undefined
|
|
? 'Variant 1'
|
|
: item['Product Varient Name'],
|
|
'Available To':
|
|
item['Available To'] == 'NaN-NaN-NaN' ? '' : item['Available To'],
|
|
ProdId: item['ProdId'] || null,
|
|
}));
|
|
|
|
const modifiedProducts = findModifiedProducts(OrginalData, updatedData);
|
|
const modifiedIds = new Set(modifiedProducts.map((p) => p.ProdId));
|
|
const originalIds = new Set(OrginalData?.map((p) => p.ProdId) || []);
|
|
|
|
const markedData = updatedData
|
|
.map((item) => ({
|
|
...item,
|
|
isModified:
|
|
modifiedIds.has(item.ProdId) || !originalIds.has(item.ProdId),
|
|
}))
|
|
.sort((a, b) => b.isModified - a.isModified);
|
|
|
|
setUploadedRawData(markedData);
|
|
|
|
setHeaders(extractedHeaders);
|
|
autoMapFields(extractedHeaders);
|
|
|
|
dispatch(uploadExcel({ file, jsonData: nonEmptyRows }));
|
|
|
|
setWorksheet(worksheet);
|
|
setDatas([]);
|
|
setExcelData([]);
|
|
}
|
|
|
|
// ⚠️ keep your original flow (no break)
|
|
dispatch(uploadExcel({ file, jsonData: nonEmptyRows }));
|
|
setWorksheet(worksheet);
|
|
setWorksheet1(worksheet1);
|
|
setDatas(formattedData);
|
|
};
|
|
|
|
reader.readAsArrayBuffer(file);
|
|
};
|
|
|
|
useEffect(() => {
|
|
imagecall();
|
|
addOnlineImage();
|
|
Fielddatas();
|
|
}, []);
|
|
|
|
const imagecall = async (ProdName) => {
|
|
if (selectedProductName !== null) {
|
|
let response = await dispatch(onlineimages(ProdName)).unwrap();
|
|
setimagedata(response?.data?.data);
|
|
}
|
|
};
|
|
|
|
const addOnlineImage = (item, index) => {
|
|
item?.ProductName && setimageOpen(true);
|
|
imagecall(item?.ProductName);
|
|
setSelectedProductName(item?.ProductName);
|
|
setSelectedImageIndex(index);
|
|
};
|
|
|
|
const updateImageUrl = (url, index, key) => {
|
|
const newData1 = [...Datas];
|
|
newData1[key] = { ...newData1[key], Productimage: url };
|
|
|
|
setDatas(newData1);
|
|
setExcelData(newData1);
|
|
};
|
|
|
|
const handleimage = () => {
|
|
setimageOpen(false);
|
|
setSelectedImageIndex(null);
|
|
};
|
|
|
|
const submitimage = async () => {
|
|
const pageNo = currentPage * 10 + selectedImageIndex - 10;
|
|
|
|
if (selectedImageIndex != null && selectedImageIndex != undefined) {
|
|
try {
|
|
if (selectedImage?.image !== undefined) {
|
|
const response = await fetch(selectedImage.image);
|
|
const datas = await response.blob();
|
|
const metadata = {
|
|
type: 'image/jpeg',
|
|
};
|
|
|
|
const file = new File([datas], 'image.jpg', metadata);
|
|
|
|
const uploadImgData = await dispatch(uploadImage(file)).unwrap();
|
|
|
|
if (uploadImgData?.data?.status == 1) {
|
|
let tempData = Datas;
|
|
const selectedIndex = tempData.findIndex(
|
|
(_, index) => index === pageNo
|
|
);
|
|
|
|
if (selectedIndex !== -1) {
|
|
tempData[selectedIndex].Productimage = uploadImgData?.data?.image;
|
|
}
|
|
setDatas(tempData);
|
|
setExcelData(tempData);
|
|
}
|
|
}
|
|
} finally {
|
|
handleimage();
|
|
}
|
|
}
|
|
};
|
|
|
|
const fieldOrder = [
|
|
'Product Name',
|
|
'Quantity',
|
|
'UOM',
|
|
'Sales Price',
|
|
'MRP',
|
|
'WholeSale Price',
|
|
'Category',
|
|
'Sub Category',
|
|
'Brand',
|
|
'Product Variant Name',
|
|
'Stock Available',
|
|
'Token Available',
|
|
'Product Type',
|
|
'Tax',
|
|
'Auto Generate QRCode',
|
|
'Add Qr Code',
|
|
'One Piece Available',
|
|
'OnePiece Price',
|
|
'Auto Generate One Piece Qrcode',
|
|
'Auto Generate One Piece Qrcode Number',
|
|
'CESS',
|
|
'HSN',
|
|
'Part Number',
|
|
'Rack',
|
|
'Manufacture Date',
|
|
'Expire Date',
|
|
'Available From',
|
|
'Available To',
|
|
];
|
|
|
|
const sortPreferredFields = (fields) => {
|
|
const fieldMap = {};
|
|
fields.forEach((field) => {
|
|
fieldMap[field.ConfigName] = field;
|
|
});
|
|
|
|
const sorted = [];
|
|
|
|
fieldOrder.forEach((name) => {
|
|
if (fieldMap[name] && fieldMap[name].Access === 'Y') {
|
|
sorted.push(fieldMap[name]);
|
|
}
|
|
});
|
|
|
|
return sorted;
|
|
};
|
|
|
|
// Usage inside your Fielddatas
|
|
const Fielddatas = async () => {
|
|
const res = await dispatch(getCommonAppPreference(AppId)).unwrap();
|
|
|
|
const productbulkUploadCatId =
|
|
res?.data?.data?.[0]?.PreferenceDetails?.find(
|
|
(p) => p?.PreferredCatName?.toLowerCase() === 'product bulk upload'
|
|
)?.PreferredCatId;
|
|
|
|
const response = await dispatch(
|
|
getFieldSetupData({
|
|
AppId,
|
|
CompId,
|
|
BranchId,
|
|
categoryId: productbulkUploadCatId,
|
|
Type: 'EB',
|
|
})
|
|
).unwrap();
|
|
|
|
const productBulkUploadFields = response?.data?.data?.[0]?.ConfigDtl || [];
|
|
const sortedFields = sortPreferredFields(productBulkUploadFields);
|
|
|
|
setFieldValues(sortedFields);
|
|
};
|
|
|
|
const handleDownload = async () => {
|
|
const workbook = new ExcelJS.Workbook();
|
|
const worksheet = workbook.addWorksheet('Sheet1');
|
|
const worksheet1 = workbook.addWorksheet('Sheet2');
|
|
|
|
const headerStyle = {
|
|
font: { bold: true },
|
|
border: {
|
|
top: { style: 'thin' },
|
|
left: { style: 'thin' },
|
|
bottom: { style: 'thin' },
|
|
right: { style: 'thin' },
|
|
},
|
|
alignment: { horizontal: 'center', vertical: 'middle' },
|
|
};
|
|
|
|
// Define all possible field configurations
|
|
const sampleData = getApplicationSampleData(appName);
|
|
console.log(sampleData, 'sampleData');
|
|
const fieldConfigs = {
|
|
ProdId: {
|
|
header: 'ProdId',
|
|
key: 'ProdId',
|
|
width: 15,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: null,
|
|
sampleValue: '',
|
|
headerColor: 'D3D3D3',
|
|
locked: true,
|
|
},
|
|
ProdName: {
|
|
header: 'Product Name',
|
|
key: 'ProdName',
|
|
width: 20,
|
|
Height: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: null,
|
|
sampleValue: sampleData?.productName || 'Sample Product Name',
|
|
headerColor: 'FF2929',
|
|
},
|
|
Size: {
|
|
header: 'Quantity',
|
|
key: 'Size',
|
|
width: 10,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: {
|
|
type: 'custom',
|
|
allowBlank: true,
|
|
formulae: ['ISNUMBER({COLUMN}{ROW})'],
|
|
showErrorMessage: true,
|
|
errorTitle: 'Validation Error',
|
|
error: 'Please enter a Number.',
|
|
},
|
|
sampleValue: sampleData?.quantity || 1,
|
|
headerColor: 'FF2929',
|
|
},
|
|
UOM: {
|
|
header: 'UOM',
|
|
key: 'UOM',
|
|
width: 10,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: {
|
|
type: 'list',
|
|
allowBlank: true,
|
|
formulae: [
|
|
'"' +
|
|
(typeof Unit !== 'undefined' ? Unit.join(',') : 'KGS,PCS,LITER') +
|
|
'"',
|
|
],
|
|
},
|
|
sampleValue: sampleData?.uom || 'KGS',
|
|
headerColor: 'FF2929',
|
|
},
|
|
SellPrice: {
|
|
header: 'Sales Price',
|
|
key: 'SellPrice',
|
|
width: 15,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: {
|
|
type: 'custom',
|
|
allowBlank: true,
|
|
formulae: ['ISNUMBER({COLUMN}{ROW})'],
|
|
showErrorMessage: true,
|
|
errorTitle: 'Validation Error',
|
|
error: 'Please enter a Number.',
|
|
},
|
|
sampleValue: sampleData?.salesPrice || 100,
|
|
headerColor: 'FF2929',
|
|
},
|
|
ProdCat: {
|
|
header: 'Category',
|
|
key: 'ProdCat',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: {
|
|
type: 'list',
|
|
allowBlank: true,
|
|
formulae: [`Sheet2!$B$2:$B$${(CategoryNames?.length || 0) + 1}`],
|
|
},
|
|
sampleValue: sampleData?.category || 'Sample Category',
|
|
headerColor: 'FF2929',
|
|
},
|
|
ProdSubCat: {
|
|
header: 'Sub Category',
|
|
key: 'ProdSubCat',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: null, // set dynamically per row below
|
|
sampleValue: sampleData?.subCategory || 'Sample Sub Category',
|
|
headerColor: '52c41a',
|
|
},
|
|
Brand: {
|
|
header: 'Brand',
|
|
key: 'Brand',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: null, // set dynamically per row below
|
|
sampleValue: sampleData?.brand || 'Sample Brand',
|
|
headerColor: '52c41a',
|
|
},
|
|
ProdVarientName: {
|
|
header: 'Product Variant Name',
|
|
key: 'ProdVarientName',
|
|
width: 20,
|
|
Height: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: null,
|
|
sampleValue: sampleData?.variantName || 'Sample Variant Name',
|
|
headerColor: '52c41a',
|
|
},
|
|
MRP: {
|
|
header: 'MRP',
|
|
key: 'MRP',
|
|
width: 10,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: {
|
|
type: 'custom',
|
|
allowBlank: true,
|
|
formulae: ['ISNUMBER({COLUMN}{ROW})'],
|
|
showErrorMessage: true,
|
|
errorTitle: 'Validation Error',
|
|
error: 'Please enter a Number.',
|
|
},
|
|
sampleValue: sampleData?.mrp || 100,
|
|
headerColor: '52c41a',
|
|
},
|
|
WhSalePrice: {
|
|
header: 'WholeSale Price',
|
|
key: 'WhSalePrice',
|
|
width: 15,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: {
|
|
type: 'custom',
|
|
allowBlank: true,
|
|
formulae: ['ISNUMBER({COLUMN}{ROW})'],
|
|
showErrorMessage: true,
|
|
errorTitle: 'Validation Error',
|
|
error: 'Please enter a Number.',
|
|
},
|
|
sampleValue: 100,
|
|
headerColor: '52c41a',
|
|
},
|
|
|
|
StockAvailable: {
|
|
header: 'Stock Available',
|
|
key: 'StockAvailable',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: {
|
|
type: 'list',
|
|
allowBlank: true,
|
|
formulae: ['"Yes,No"'],
|
|
},
|
|
sampleValue: 'Choose Yes or No',
|
|
headerColor: '52c41a',
|
|
},
|
|
OfferPrice: {
|
|
header: 'Token Available',
|
|
key: 'OfferPrice',
|
|
width: 15,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: {
|
|
type: 'list',
|
|
allowBlank: true,
|
|
formulae: ['"Yes,No"'],
|
|
},
|
|
sampleValue: 'Choose Yes or No',
|
|
headerColor: '52c41a',
|
|
},
|
|
Supplier: {
|
|
header: 'Product Type',
|
|
key: 'Supplier',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: {
|
|
type: 'list',
|
|
allowBlank: true,
|
|
formulae: ['"Product"'],
|
|
},
|
|
sampleValue: 'Product',
|
|
headerColor: '52c41a',
|
|
},
|
|
TaxId: {
|
|
header: 'Tax',
|
|
key: 'TaxId',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: null, // set via Sheet2 range reference below
|
|
sampleValue: sampleData?.tax || 'NIL - 0%',
|
|
headerColor: '52c41a',
|
|
},
|
|
AutoGenerateQr: {
|
|
header: 'Auto Generate Qrcode',
|
|
key: 'AutoGenerateQr',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: {
|
|
type: 'list',
|
|
allowBlank: true,
|
|
formulae: ['"Yes,No"'],
|
|
},
|
|
sampleValue: 'Choose Yes or No',
|
|
headerColor: '52c41a',
|
|
},
|
|
AddQrCode: {
|
|
header: 'Add Qr Code',
|
|
key: 'AddQrCode',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: null,
|
|
sampleValue: 'Sample Qr eg:12345',
|
|
headerColor: '52c41a',
|
|
},
|
|
SpecialPrice: {
|
|
header: 'One Piece Available',
|
|
key: 'SpecialPrice',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: {
|
|
type: 'list',
|
|
allowBlank: true,
|
|
formulae: ['"Yes,No"'],
|
|
},
|
|
sampleValue: 'Choose Yes or No',
|
|
headerColor: '52c41a',
|
|
},
|
|
OnePiecePrice: {
|
|
header: 'OnePiece Price',
|
|
key: 'OnePiecePrice',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: {
|
|
type: 'custom',
|
|
allowBlank: true,
|
|
formulae: ['ISNUMBER({COLUMN}{ROW})'],
|
|
showErrorMessage: true,
|
|
errorTitle: 'Validation Error',
|
|
error: 'Please enter a Number.',
|
|
},
|
|
sampleValue: 'OnePiecePrice',
|
|
headerColor: '52c41a',
|
|
},
|
|
AutoGenOnePieceQr: {
|
|
header: 'Auto Generate One Piece Qrcode',
|
|
key: 'AutoGenOnePieceQr',
|
|
width: 30,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: {
|
|
type: 'list',
|
|
allowBlank: true,
|
|
formulae: ['"Yes,No"'],
|
|
},
|
|
sampleValue: 'Choose Yes or No',
|
|
headerColor: '52c41a',
|
|
},
|
|
AutoGenOnePieceQrNum: {
|
|
header: 'Auto Generate One Piece Qrcode Number',
|
|
key: 'AutoGenOnePieceQrNum',
|
|
width: 40,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: null,
|
|
sampleValue: 'Qr Number eg:ABC123',
|
|
headerColor: '52c41a',
|
|
},
|
|
CESS: {
|
|
header: 'CESS',
|
|
key: 'CESS',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: null,
|
|
sampleValue: 'Cess eg:0.5',
|
|
headerColor: '52c41a',
|
|
},
|
|
HSNCode: {
|
|
header: 'HSN',
|
|
key: 'HSNCode',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: null,
|
|
sampleValue: 'Hsn Number eg:100190',
|
|
headerColor: '52c41a',
|
|
},
|
|
PartNumber: {
|
|
header: 'Part Number',
|
|
key: 'PartNumber',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: null,
|
|
sampleValue: 'Part Number eg:1234-5678',
|
|
headerColor: '52c41a',
|
|
},
|
|
Rack: {
|
|
header: 'Rack',
|
|
key: 'Rack',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: null,
|
|
sampleValue: 'Rack Number eg:2200302',
|
|
headerColor: '52c41a',
|
|
},
|
|
ManufDate: {
|
|
header: 'Manufacture Date',
|
|
key: 'ManufDate DATETIME',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: null,
|
|
sampleValue: new Date(),
|
|
headerColor: '52c41a',
|
|
},
|
|
ExpDate: {
|
|
header: 'Expire Date',
|
|
key: 'ExpDate DATETIME',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
validation: null,
|
|
sampleValue: new Date(),
|
|
headerColor: '52c41a',
|
|
},
|
|
AvailableFrom: {
|
|
header: 'Available From',
|
|
key: 'AvailableFrom',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
numFmt: '@',
|
|
},
|
|
validation: null,
|
|
sampleValue: '08:00:00',
|
|
headerColor: '52c41a',
|
|
isTimeField: true,
|
|
},
|
|
AvailableTo: {
|
|
header: 'Available To',
|
|
key: 'AvailableTo',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
numFmt: '@',
|
|
},
|
|
validation: null,
|
|
sampleValue: '22:00:00',
|
|
headerColor: '52c41a',
|
|
isTimeField: true,
|
|
},
|
|
};
|
|
|
|
function getExcelColumnLetter(colIndex) {
|
|
let temp = '';
|
|
let letter = '';
|
|
while (colIndex > 0) {
|
|
temp = (colIndex - 1) % 26;
|
|
letter = String.fromCharCode(temp + 65) + letter;
|
|
colIndex = Math.floor((colIndex - temp - 1) / 26);
|
|
}
|
|
return letter;
|
|
}
|
|
|
|
const mandatoryFields = ['ProdName', 'Size', 'UOM', 'SellPrice', 'ProdCat'];
|
|
|
|
const apiToInternalFieldMap = {
|
|
'Product Name': 'ProdName',
|
|
Quantity: 'Size',
|
|
UOM: 'UOM',
|
|
'Sales Price': 'SellPrice',
|
|
MRP: 'MRP',
|
|
'WholeSale Price': 'WhSalePrice',
|
|
Category: 'ProdCat',
|
|
'Sub Category': 'ProdSubCat',
|
|
Brand: 'Brand',
|
|
'Product Variant Name': 'ProdVarientName',
|
|
'Token Available': 'OfferPrice',
|
|
'Stock Available': 'StockAvailable',
|
|
Tax: 'TaxId',
|
|
'Auto Generate QRCode': 'AutoGenerateQr',
|
|
'Add Qr Code': 'AddQrCode',
|
|
'One Piece Available': 'SpecialPrice',
|
|
'OnePiece Price': 'OnePiecePrice',
|
|
'Auto Generate One Piece Qrcode': 'AutoGenOnePieceQr',
|
|
'Auto Generate One Piece Qrcode Number': 'AutoGenOnePieceQrNum',
|
|
CESS: 'CESS',
|
|
HSN: 'HSNCode',
|
|
'Part Number': 'PartNumber',
|
|
Rack: 'Rack',
|
|
'Manufacture Date': 'ManufDate',
|
|
'Expire Date': 'ExpDate',
|
|
'Available From': 'AvailableFrom',
|
|
'Available To': 'AvailableTo',
|
|
'Product Type': 'Supplier',
|
|
};
|
|
|
|
// Define dependent relationships
|
|
const dependentFieldMap = {
|
|
'Auto Generate QRCode': 'Add Qr Code',
|
|
'One Piece Available': 'OnePiece Price',
|
|
'Auto Generate One Piece Qrcode': 'Auto Generate One Piece Qrcode Number',
|
|
};
|
|
|
|
// Step 1: Sort preferred fields
|
|
const sortedPreferredFields = sortPreferredFields(FieldValues); // Your existing sort function
|
|
|
|
// Step 2: Convert API names to internal keys while skipping dependent fields initially
|
|
const preferredFieldKeys = [];
|
|
const preferredFieldSet = new Set();
|
|
|
|
sortedPreferredFields.forEach((field) => {
|
|
const fieldName = field.ConfigName;
|
|
const status = field.Access;
|
|
|
|
// Skip dependent fields for now
|
|
if (Object.values(dependentFieldMap).includes(fieldName)) return;
|
|
|
|
if (status === 'Y') {
|
|
const internalKey = apiToInternalFieldMap[fieldName];
|
|
if (internalKey) {
|
|
preferredFieldKeys.push(internalKey);
|
|
preferredFieldSet.add(fieldName);
|
|
}
|
|
|
|
// If this field has a dependent field, and it's enabled, add dependent too
|
|
const dependentFieldName = dependentFieldMap[fieldName];
|
|
if (dependentFieldName) {
|
|
const dependentInternalKey =
|
|
apiToInternalFieldMap[dependentFieldName];
|
|
if (dependentInternalKey) {
|
|
preferredFieldKeys.push(dependentInternalKey);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
// Step 3: Merge Mandatory + Preferred (no duplicates)
|
|
const finalFieldKeys = [
|
|
...mandatoryFields,
|
|
...preferredFieldKeys.filter((key) => !mandatoryFields.includes(key)),
|
|
];
|
|
|
|
// Step 4: Map to ExcelJS column configs
|
|
const selectedColumns = finalFieldKeys
|
|
.map((fieldKey) => {
|
|
const config = fieldConfigs[fieldKey];
|
|
if (!config) {
|
|
console.warn(`Field configuration not found for: ${fieldKey}`);
|
|
return null;
|
|
}
|
|
return config;
|
|
})
|
|
.filter(Boolean);
|
|
|
|
selectedColumns.push({
|
|
header: '__isSampleRow',
|
|
key: '__isSampleRow',
|
|
width: 10, // width can be any value
|
|
style: { font: { size: 1 }, alignment: { horizontal: 'left' } },
|
|
sampleValue: 'sample row', // Mark sample row
|
|
headerColor: 'FFFFFF',
|
|
});
|
|
|
|
worksheet.columns = selectedColumns;
|
|
|
|
worksheet.getColumn(selectedColumns.length).hidden = true;
|
|
|
|
selectedColumns.forEach((config, index) => {
|
|
const columnLetter = getExcelColumnLetter(index + 1); // ✅ FIXED
|
|
const headerCell = worksheet.getCell(`${columnLetter}1`);
|
|
headerCell.style = {
|
|
...config.style,
|
|
fill: {
|
|
type: 'pattern',
|
|
pattern: 'solid',
|
|
fgColor: { argb: config.headerColor },
|
|
},
|
|
};
|
|
});
|
|
|
|
// Add sample data in row 2 — time fields forced to text format
|
|
selectedColumns.forEach((config, index) => {
|
|
const columnLetter = getExcelColumnLetter(index + 1);
|
|
const cell = worksheet.getCell(`${columnLetter}2`);
|
|
if (config.isTimeField) {
|
|
cell.numFmt = '@';
|
|
cell.value = String(config.sampleValue);
|
|
} else {
|
|
cell.value = config.sampleValue;
|
|
}
|
|
});
|
|
|
|
// Force text format on all data rows for time fields
|
|
selectedColumns.forEach((config, index) => {
|
|
if (config.isTimeField) {
|
|
const columnLetter = getExcelColumnLetter(index + 1);
|
|
for (let r = 2; r <= 1002; r++) {
|
|
worksheet.getCell(`${columnLetter}${r}`).numFmt = '@';
|
|
}
|
|
}
|
|
});
|
|
|
|
// Add validation to columns
|
|
|
|
selectedColumns.forEach((config, colIndex) => {
|
|
if (config.validation) {
|
|
const colNumber = colIndex + 1; // Excel is 1-based
|
|
const columnLetter = getExcelColumnLetter(colNumber); // ✅ FIXED
|
|
|
|
for (let rowNumber = 2; rowNumber <= 1001; rowNumber++) {
|
|
const cell = worksheet.getCell(rowNumber, colNumber); // ✅ Use (row, col) instead of letter
|
|
let validation = { ...config.validation };
|
|
|
|
if (validation.formulae) {
|
|
validation.formulae = validation.formulae.map((formula) =>
|
|
formula
|
|
.replace('{COLUMN}', columnLetter)
|
|
.replace('{ROW}', rowNumber.toString())
|
|
);
|
|
}
|
|
|
|
cell.dataValidation = validation;
|
|
}
|
|
}
|
|
});
|
|
|
|
// Add empty rows for data entry
|
|
for (let i = 1; i <= 1000; i++) {
|
|
worksheet.addRow({});
|
|
}
|
|
|
|
// Explicitly unlock all data-entry cells (rows 3+) for every column
|
|
for (let rowNum = 3; rowNum <= worksheet.rowCount; rowNum++) {
|
|
for (let colNum = 1; colNum <= selectedColumns.length; colNum++) {
|
|
const config = selectedColumns[colNum - 1];
|
|
worksheet.getCell(rowNum, colNum).protection = {
|
|
locked: config?.locked || false,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Lock header row (row 1) and sample row (row 2)
|
|
for (let colNum = 1; colNum <= selectedColumns.length; colNum++) {
|
|
worksheet.getCell(1, colNum).protection = { locked: true };
|
|
worksheet.getCell(2, colNum).protection = { locked: true };
|
|
}
|
|
|
|
// Protect the worksheet
|
|
await worksheet.protect('', {
|
|
selectLockedCells: true, // Allow selecting locked cells (header/sample)
|
|
selectUnlockedCells: true, // Allow selecting unlocked cells (data rows)
|
|
formatCells: false,
|
|
formatColumns: false,
|
|
formatRows: false,
|
|
insertColumns: false,
|
|
insertRows: false,
|
|
deleteColumns: false,
|
|
deleteRows: false,
|
|
});
|
|
|
|
// Configure Sheet2 with all original columns and functionality
|
|
worksheet1.columns = [
|
|
{
|
|
header: 'Category Id',
|
|
key: 'CategoryId',
|
|
width: 15,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
},
|
|
{
|
|
header: 'Category Name',
|
|
key: 'Category',
|
|
width: 30,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
},
|
|
{
|
|
header: 'SUBCAT DATAS OF PARTICULAR CATEGORY',
|
|
key: 'SDPC',
|
|
width: 180,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
},
|
|
{
|
|
header: 'SUBCAT ID',
|
|
key: 'SubcayID',
|
|
width: 10,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
},
|
|
{
|
|
header: 'Sub Category name',
|
|
key: 'SubCategory',
|
|
width: 25,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
},
|
|
{
|
|
header: '',
|
|
key: '',
|
|
width: 5,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
},
|
|
{
|
|
header: 'SUBCAT NUMFID',
|
|
key: 'SubcatnumfId',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
},
|
|
{
|
|
header: '',
|
|
key: 'smallIcon',
|
|
width: 5,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
},
|
|
{
|
|
header: 'Brand ID',
|
|
key: 'BrandId',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
},
|
|
{
|
|
header: 'Brand Name',
|
|
key: 'Brandname',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
},
|
|
{
|
|
header: 'Brand NumfId',
|
|
key: 'BrandnumfId',
|
|
width: 20,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
},
|
|
{
|
|
header: '',
|
|
key: 'smallIcon',
|
|
width: 5,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
},
|
|
{
|
|
header: 'Brand Details',
|
|
key: 'Branddetails',
|
|
width: 150,
|
|
style: {
|
|
...headerStyle,
|
|
font: { bold: true, color: { argb: '#000000' } },
|
|
},
|
|
},
|
|
];
|
|
|
|
// Add data to Sheet2
|
|
for (let i = 1; i <= 100; i++) {
|
|
worksheet1.addRow({
|
|
CategoryId: '',
|
|
Category: '',
|
|
SDPC: '',
|
|
SubcayID: '',
|
|
SubCategory: '',
|
|
SubcatnumfId: '',
|
|
BrandId: '',
|
|
Brandname: '',
|
|
BrandnumfId: '',
|
|
Branddetails: '',
|
|
});
|
|
}
|
|
|
|
// All the original dropdown and validation logic for Sheet2
|
|
const dropdownOptions = {
|
|
Option1: ['Yes', 'No'],
|
|
Option2: ['Product'],
|
|
Option3: ['ValueX', 'ValueY', 'ValueZ'],
|
|
};
|
|
|
|
const selectedOptionA = 'Option1';
|
|
const selectedOptionB = 'Option2';
|
|
|
|
// Create category and brand data maps (original logic)
|
|
const categoryDataMap = {};
|
|
if (typeof CategoryId !== 'undefined') {
|
|
CategoryId.forEach((categoryId, index) => {
|
|
categoryDataMap[categoryId] =
|
|
typeof SubCatData !== 'undefined' ? SubCatData[index] : '';
|
|
});
|
|
}
|
|
|
|
const BrandDataMap = {};
|
|
if (typeof AllBrandId !== 'undefined') {
|
|
AllBrandId.forEach((AllBrandId, index) => {
|
|
BrandDataMap[AllBrandId] =
|
|
typeof SubCatData !== 'undefined' ? SubCatData[index] : '';
|
|
});
|
|
}
|
|
|
|
// Apply all original Sheet2 validations and logic
|
|
if (typeof CategoryId !== 'undefined') {
|
|
worksheet1
|
|
.getColumn('A')
|
|
.eachCell({ includeEmpty: true }, function (cell, rowNumber) {
|
|
if (rowNumber > 1) {
|
|
const dynamicValidValues = '"' + CategoryId.join(',') + '"';
|
|
const defaultCategoryId = CategoryId[rowNumber - 2];
|
|
cell.value = defaultCategoryId;
|
|
|
|
cell.dataValidation = {
|
|
type: 'list',
|
|
allowBlank: true,
|
|
formulae: [dynamicValidValues],
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
if (typeof CategoryNames !== 'undefined') {
|
|
worksheet1
|
|
.getColumn('B')
|
|
.eachCell({ includeEmpty: true }, function (cell, rowNumber) {
|
|
if (rowNumber > 1) {
|
|
const dynamicValidValues = '"' + CategoryNames.join(',') + '"';
|
|
const defaultCategoryName = CategoryNames[rowNumber - 2];
|
|
cell.value = defaultCategoryName;
|
|
|
|
cell.dataValidation = {
|
|
type: 'list',
|
|
allowBlank: true,
|
|
formulae: [dynamicValidValues],
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
if (typeof SubcatId !== 'undefined') {
|
|
worksheet1
|
|
.getColumn('D')
|
|
.eachCell({ includeEmpty: true }, function (cell, rowNumber) {
|
|
if (rowNumber > 1) {
|
|
const dynamicValidValues = '"' + SubcatId.join(',') + '"';
|
|
const defaultCategorysubName = SubcatId[rowNumber - 2];
|
|
cell.value = defaultCategorysubName;
|
|
|
|
cell.dataValidation = {
|
|
type: 'list',
|
|
allowBlank: true,
|
|
formulae: [dynamicValidValues],
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
if (typeof SubCatData !== 'undefined') {
|
|
worksheet1
|
|
.getColumn('E')
|
|
.eachCell({ includeEmpty: true }, function (cell, rowNumber) {
|
|
if (rowNumber > 1) {
|
|
const dynamicValidValues = '"' + SubCatData.join(',') + '"';
|
|
const defaultCategorysubName = SubCatData[rowNumber - 2];
|
|
cell.value = defaultCategorysubName;
|
|
|
|
cell.dataValidation = {
|
|
type: 'list',
|
|
allowBlank: true,
|
|
formulae: [dynamicValidValues],
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
if (typeof Subcatnumfid !== 'undefined') {
|
|
worksheet1
|
|
.getColumn('G')
|
|
.eachCell({ includeEmpty: true }, function (cell, rowNumber) {
|
|
if (rowNumber > 1) {
|
|
const dynamicValidValues = '"' + Subcatnumfid.join(',') + '"';
|
|
const defaultCategorysubName = Subcatnumfid[rowNumber - 2];
|
|
cell.value = defaultCategorysubName;
|
|
|
|
cell.dataValidation = {
|
|
type: 'list',
|
|
allowBlank: true,
|
|
formulae: [dynamicValidValues],
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
if (typeof AllBrandId !== 'undefined') {
|
|
worksheet1
|
|
.getColumn('I')
|
|
.eachCell({ includeEmpty: true }, function (cell, rowNumber) {
|
|
if (rowNumber > 1) {
|
|
const dynamicValidValues = '"' + AllBrandId.join(',') + '"';
|
|
const defaultCategorysubName = AllBrandId[rowNumber - 2];
|
|
cell.value = defaultCategorysubName;
|
|
|
|
cell.dataValidation = {
|
|
type: 'list',
|
|
allowBlank: true,
|
|
formulae: [dynamicValidValues],
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
if (typeof Allbranddata !== 'undefined') {
|
|
worksheet1
|
|
.getColumn('J')
|
|
.eachCell({ includeEmpty: true }, function (cell, rowNumber) {
|
|
if (rowNumber > 1) {
|
|
const dynamicValidValues = '"' + Allbranddata.join(',') + '"';
|
|
const defaultCategorysubName = Allbranddata[rowNumber - 2];
|
|
cell.value = defaultCategorysubName;
|
|
|
|
cell.dataValidation = {
|
|
type: 'list',
|
|
allowBlank: true,
|
|
formulae: [dynamicValidValues],
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
if (typeof Allbranddatanumfid !== 'undefined') {
|
|
worksheet1
|
|
.getColumn('K')
|
|
.eachCell({ includeEmpty: true }, function (cell, rowNumber) {
|
|
if (rowNumber > 1) {
|
|
const dynamicValidValues = '"' + Allbranddatanumfid.join(',') + '"';
|
|
const defaultCategorysubName = Allbranddatanumfid[rowNumber - 2];
|
|
cell.value = defaultCategorysubName;
|
|
|
|
cell.dataValidation = {
|
|
type: 'list',
|
|
allowBlank: true,
|
|
formulae: [dynamicValidValues],
|
|
};
|
|
}
|
|
});
|
|
}
|
|
|
|
// Write TaxDatas into Sheet2 column N for range-based dropdown (avoids 255-char limit)
|
|
if (typeof TaxDatas !== 'undefined' && TaxDatas?.length > 0) {
|
|
TaxDatas.forEach((taxItem, idx) => {
|
|
worksheet1.getCell(`N${idx + 2}`).value = taxItem
|
|
.replace(/,/g, '')
|
|
.trim();
|
|
});
|
|
}
|
|
|
|
// Apply Tax validation using Sheet2 column N range
|
|
const taxColIndex = selectedColumns.findIndex((c) => c.key === 'TaxId') + 1;
|
|
if (taxColIndex > 0 && TaxDatas?.length > 0) {
|
|
for (let rowNumber = 3; rowNumber <= 1002; rowNumber++) {
|
|
worksheet.getCell(rowNumber, taxColIndex).dataValidation = {
|
|
type: 'list',
|
|
allowBlank: true,
|
|
formulae: [`Sheet2!$N$2:$N$${TaxDatas.length + 1}`],
|
|
showErrorMessage: false,
|
|
};
|
|
}
|
|
}
|
|
|
|
// ── Dynamic SubCategory dropdown per Category ──────────────────────
|
|
// Build a map: categoryName → [subcat names]
|
|
const catSubcatMap = {};
|
|
if (CategoryNames && SubCatData && Subcatnumfid) {
|
|
CategoryNames.forEach((catName, catIdx) => {
|
|
const catId = CategoryId?.[catIdx];
|
|
const subcats = SubCatData.filter(
|
|
(_, sIdx) => Subcatnumfid[sIdx] == catId
|
|
);
|
|
catSubcatMap[catName] = subcats;
|
|
});
|
|
}
|
|
|
|
// Write each category's subcats into Sheet2 starting at column O (col 15)
|
|
// Row 1 = category name header, rows 2+ = subcat names
|
|
const catSubcatStartCol = 15; // column O
|
|
const catNameToColLetter = {};
|
|
|
|
if (CategoryNames) {
|
|
CategoryNames.forEach((catName, catIdx) => {
|
|
const colIndex = catSubcatStartCol + catIdx;
|
|
const colLetter = getExcelColumnLetter(colIndex);
|
|
catNameToColLetter[catName] = colLetter;
|
|
|
|
// Write header (category name)
|
|
worksheet1.getCell(`${colLetter}1`).value = catName;
|
|
|
|
// Write subcats for this category
|
|
const subcats = catSubcatMap[catName] || [];
|
|
subcats.forEach((subcat, sIdx) => {
|
|
worksheet1.getCell(`${colLetter}${sIdx + 2}`).value = subcat;
|
|
});
|
|
|
|
// Define a named range for this category's subcats
|
|
const safeName = catName.replace(/[^A-Za-z0-9_]/g, '_');
|
|
const rangeRef =
|
|
subcats.length > 0
|
|
? `Sheet2!$${colLetter}$2:$${colLetter}$${subcats.length + 1}`
|
|
: `Sheet2!$${colLetter}$2:$${colLetter}$2`;
|
|
|
|
workbook.definedNames.add(rangeRef, safeName);
|
|
});
|
|
}
|
|
|
|
// Find which column index ProdCat and ProdSubCat are in Sheet1
|
|
const catColIndex =
|
|
selectedColumns.findIndex((c) => c.key === 'ProdCat') + 1;
|
|
const subCatColIndex =
|
|
selectedColumns.findIndex((c) => c.key === 'ProdSubCat') + 1;
|
|
|
|
if (catColIndex > 0 && subCatColIndex > 0) {
|
|
const catColLetter = getExcelColumnLetter(catColIndex);
|
|
|
|
for (let rowNumber = 3; rowNumber <= 1002; rowNumber++) {
|
|
const cell = worksheet.getCell(rowNumber, subCatColIndex);
|
|
cell.dataValidation = {
|
|
type: 'list',
|
|
allowBlank: true,
|
|
formulae: [
|
|
`INDIRECT(SUBSTITUTE(${catColLetter}${rowNumber}," ","_"))`,
|
|
],
|
|
showErrorMessage: false,
|
|
};
|
|
}
|
|
}
|
|
|
|
// ── Dynamic Brand dropdown per SubCategory ────────────────────────
|
|
// Build a map: subcatName → [brand names] using Allbranddatanumfid (parent subcat ID)
|
|
const subcatBrandMap = {};
|
|
if (SubCatData && Allbranddata && Allbranddatanumfid) {
|
|
SubCatData.forEach((subcatName, sIdx) => {
|
|
const subcatId = SubcatId?.[sIdx];
|
|
const brands = Allbranddata.filter(
|
|
(_, bIdx) => Allbranddatanumfid[bIdx] == subcatId
|
|
);
|
|
subcatBrandMap[subcatName] = brands;
|
|
});
|
|
}
|
|
|
|
// Write each subcat's brands into Sheet2 starting after the category columns
|
|
const brandStartCol = catSubcatStartCol + (CategoryNames?.length || 0) + 1;
|
|
|
|
if (SubCatData) {
|
|
SubCatData.forEach((subcatName, sIdx) => {
|
|
const colIndex = brandStartCol + sIdx;
|
|
const colLetter = getExcelColumnLetter(colIndex);
|
|
|
|
worksheet1.getCell(`${colLetter}1`).value = `B_${subcatName}`;
|
|
|
|
const brands = subcatBrandMap[subcatName] || [];
|
|
brands.forEach((brand, bIdx) => {
|
|
worksheet1.getCell(`${colLetter}${bIdx + 2}`).value = brand;
|
|
});
|
|
|
|
const safeName = `B_${subcatName.replace(/[^A-Za-z0-9_]/g, '_')}`;
|
|
const rangeRef =
|
|
brands.length > 0
|
|
? `Sheet2!$${colLetter}$2:$${colLetter}$${brands.length + 1}`
|
|
: `Sheet2!$${colLetter}$2:$${colLetter}$2`;
|
|
workbook.definedNames.add(rangeRef, safeName);
|
|
});
|
|
}
|
|
|
|
// Apply INDIRECT brand validation based on selected SubCategory
|
|
const brandColIndex =
|
|
selectedColumns.findIndex((c) => c.key === 'Brand') + 1;
|
|
if (subCatColIndex > 0 && brandColIndex > 0) {
|
|
const subCatColLetter = getExcelColumnLetter(subCatColIndex);
|
|
for (let rowNumber = 3; rowNumber <= 1002; rowNumber++) {
|
|
const cell = worksheet.getCell(rowNumber, brandColIndex);
|
|
cell.dataValidation = {
|
|
type: 'list',
|
|
allowBlank: true,
|
|
formulae: [
|
|
`INDIRECT("B_"&SUBSTITUTE(${subCatColLetter}${rowNumber}," ","_"))`,
|
|
],
|
|
showErrorMessage: false,
|
|
};
|
|
}
|
|
}
|
|
// ─────────────────────────────────────────────────────────────────────
|
|
|
|
// Original complex logic for column C (SUBCAT DATAS)
|
|
worksheet1
|
|
.getColumn('C')
|
|
.eachCell({ includeEmpty: true }, function (cell, rowNumber) {
|
|
if (rowNumber > 1) {
|
|
const categoryId = worksheet1.getCell(`A${rowNumber}`).value;
|
|
if (categoryId !== null) {
|
|
const uniqueSubCategoryData = new Set();
|
|
const matchedData = [];
|
|
|
|
worksheet1
|
|
.getColumn('G')
|
|
.eachCell(
|
|
{ includeEmpty: true },
|
|
function (subCatCell, subCatRowNumber) {
|
|
if (subCatRowNumber > 1 && subCatCell.value === categoryId) {
|
|
const subCategoryName = worksheet1.getCell(
|
|
`E${subCatRowNumber}`
|
|
).value;
|
|
const subCatId = worksheet1.getCell(
|
|
`D${subCatRowNumber}`
|
|
).value;
|
|
|
|
const fullSubCategoryName = `${subCatId} - ${subCategoryName}`;
|
|
|
|
uniqueSubCategoryData.add({
|
|
subCategoryId: subCatId,
|
|
subCategoryName: subCategoryName,
|
|
});
|
|
|
|
const subCatData = categoryDataMap[categoryId]
|
|
? categoryDataMap[categoryId].subCatData
|
|
: null;
|
|
|
|
const data = {
|
|
subCategoryId: subCatId,
|
|
subCategoryName: subCategoryName,
|
|
subCatData: subCatData,
|
|
};
|
|
matchedData.push(data);
|
|
}
|
|
}
|
|
);
|
|
|
|
cell.value = JSON.stringify(Array.from(uniqueSubCategoryData));
|
|
}
|
|
}
|
|
});
|
|
|
|
// Original complex logic for column M (Brand Details)
|
|
worksheet1
|
|
.getColumn('M')
|
|
.eachCell({ includeEmpty: true }, function (cell, rowNumber) {
|
|
if (rowNumber > 1) {
|
|
const SubcatId = worksheet1.getCell(`D${rowNumber}`).value;
|
|
if (SubcatId !== null) {
|
|
const uniqueSubCategoryData = new Set();
|
|
const matchedData = [];
|
|
|
|
worksheet1
|
|
.getColumn('K')
|
|
.eachCell(
|
|
{ includeEmpty: true },
|
|
function (BrandCell, BrandRowNumber) {
|
|
if (BrandRowNumber > 1 && BrandCell.value === SubcatId) {
|
|
const BrandName = worksheet1.getCell(
|
|
`J${BrandRowNumber}`
|
|
).value;
|
|
const BrandId = worksheet1.getCell(
|
|
`I${BrandRowNumber}`
|
|
).value;
|
|
|
|
const fullSubCategoryName = `${BrandId} - ${BrandName}`;
|
|
|
|
uniqueSubCategoryData.add({
|
|
BrandId: BrandId,
|
|
BrandName: BrandName,
|
|
});
|
|
|
|
const BrandData = BrandDataMap[SubcatId]
|
|
? BrandDataMap[SubcatId].BrandData
|
|
: null;
|
|
|
|
const data = {
|
|
BrandId: BrandId,
|
|
BrandName: BrandName,
|
|
BrandData: BrandData,
|
|
};
|
|
matchedData.push(data);
|
|
}
|
|
}
|
|
);
|
|
|
|
cell.value = JSON.stringify(Array.from(uniqueSubCategoryData));
|
|
}
|
|
}
|
|
});
|
|
|
|
const buffer = await workbook.xlsx.writeBuffer();
|
|
|
|
downloadFile(
|
|
buffer,
|
|
"Product Data.xlsx",
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
(success) => {
|
|
if (success) {
|
|
console.log("File downloaded and opened successfully!");
|
|
} else {
|
|
console.log("Failed to download/open file.");
|
|
}
|
|
}
|
|
);
|
|
|
|
};
|
|
|
|
const handleFileSubmit = (e) => {
|
|
e.preventDefault();
|
|
if (excelFile === null) {
|
|
excelFileError('Please select an Excel file.');
|
|
} else {
|
|
handleSubmit(Datas);
|
|
}
|
|
};
|
|
|
|
const handleFieldSetup = () => {
|
|
setFieldSetup(true);
|
|
};
|
|
|
|
const handleFieldSetupSubmit = async () => {
|
|
const postData = {
|
|
AppId: AppId,
|
|
CompId: CompId,
|
|
BranchId: BranchId,
|
|
Type: 'EB',
|
|
FormType: 'Product',
|
|
TypeId: productBulkUploadCatId,
|
|
ConfigDtl: selectedFields?.map((field) => ({
|
|
ConfigId: field,
|
|
Access: 'Y',
|
|
})),
|
|
CreatedBy: UserId,
|
|
};
|
|
|
|
const response = await dispatch(postFieldSetup(postData))?.unwrap();
|
|
|
|
if (response?.data?.statusCode === 1) {
|
|
setMessageType('success');
|
|
setMessageData(response?.data?.response);
|
|
setFieldSetup(false);
|
|
await getFieldSetup();
|
|
} else {
|
|
setMessageType('error');
|
|
setMessageData('Failed to set up fields');
|
|
}
|
|
};
|
|
|
|
const handleFieldSelect = (value) => {
|
|
setSelectedFields((prev) => {
|
|
if (prev.includes(value)) {
|
|
return prev;
|
|
}
|
|
return [value, ...prev];
|
|
});
|
|
};
|
|
|
|
const handleFieldRemove = (id) => {
|
|
setSelectedFields((prev) => prev.filter((field) => field !== id));
|
|
};
|
|
|
|
const handleCancelData = () => {
|
|
dispatch(emptyExcelData());
|
|
if (fileInputRef.current) {
|
|
fileInputRef.current.value = '';
|
|
}
|
|
};
|
|
|
|
const columns = [
|
|
{
|
|
title: 'Product Name',
|
|
dataIndex: 'ProductName',
|
|
key: 'ProductName',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Qty',
|
|
dataIndex: 'Quantity',
|
|
key: 'Quantity',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'UOM',
|
|
dataIndex: 'UOM',
|
|
key: 'UOM',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Sales Price',
|
|
dataIndex: 'SellPrice',
|
|
key: 'SellPrice',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Category',
|
|
dataIndex: 'Category',
|
|
key: 'Category',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Sub Category',
|
|
dataIndex: 'SubCategory',
|
|
key: 'SubCategory',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Brand',
|
|
dataIndex: 'Brand',
|
|
key: 'Brand',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Variant Name',
|
|
dataIndex: 'ProductVariantName',
|
|
key: 'ProductVariantName',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'MRP',
|
|
dataIndex: 'MRP',
|
|
key: 'MRP',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'WholeSale Price',
|
|
dataIndex: 'WhSalePrice',
|
|
key: 'WhSalePrice',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Auto Qrcode',
|
|
dataIndex: 'AutoGenerateQrcode',
|
|
key: 'AutoGenerateQrcode',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Add QrCode',
|
|
dataIndex: 'AddQrCode',
|
|
key: 'AddQrCode',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Stock Available',
|
|
dataIndex: 'StockAvailable',
|
|
key: 'StockAvailable',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Token Available',
|
|
dataIndex: 'TokenAvailable',
|
|
key: 'TokenAvailable',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'One Piece Available',
|
|
dataIndex: 'OnePieceAvailable',
|
|
key: 'OnePieceAvailable',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'One Piece Price',
|
|
dataIndex: 'OnePiecePrice',
|
|
key: 'OnePiecePrice',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Auto Gen One Piece Qr',
|
|
dataIndex: 'AutoGenerateOnePieceQrcode',
|
|
key: 'AutoGenerateOnePieceQrcode',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Auto Gen One Piece Qr No',
|
|
dataIndex: 'AutoGenerateOnePieceQrcodeNumber',
|
|
key: 'AutoGenerateOnePieceQrcodeNumber',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Product Type',
|
|
dataIndex: 'ProductType',
|
|
key: 'ProductType',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Tax',
|
|
dataIndex: 'Tax',
|
|
key: 'Tax',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Cess',
|
|
dataIndex: 'CESS',
|
|
key: 'CESS',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'HSN',
|
|
dataIndex: 'HSN',
|
|
key: 'HSN',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Part Number',
|
|
dataIndex: 'PartNumber',
|
|
key: 'PartNumber',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Rack',
|
|
dataIndex: 'Rack',
|
|
key: 'Rack',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Mfg Date',
|
|
dataIndex: 'ManufactureDate',
|
|
key: 'ManufactureDate',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Exp Date',
|
|
dataIndex: 'ExpireDate',
|
|
key: 'ExpireDate',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Avl From',
|
|
dataIndex: 'AvailableFrom',
|
|
key: 'AvailableFrom',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Avl To',
|
|
dataIndex: 'AvailableTo',
|
|
key: 'AvailableTo',
|
|
editable: true,
|
|
},
|
|
{
|
|
title: 'Image',
|
|
dataIndex: 'Productimage',
|
|
key: 'Productimage',
|
|
render: (ProdImage, record, index) => {
|
|
const Img = ProdImage;
|
|
return (
|
|
<>
|
|
{Img ? (
|
|
<img
|
|
src={Img}
|
|
alt={record.Productimage}
|
|
style={{ maxWidth: '50px', maxHeight: '50px' }}
|
|
/>
|
|
) : (
|
|
<>
|
|
<div className="imgUpldr" style={{ fontSize: '15px' }}>
|
|
<Imageupload
|
|
singleImage={true}
|
|
updateImageUrl={(url) =>
|
|
updateImageUrl(url, index, record.key)
|
|
}
|
|
// ImageLink={record.Productimage || ""}
|
|
ImageLink={
|
|
onlineImage
|
|
? onlineImage
|
|
: record.Productimage
|
|
? record.Productimage
|
|
: ''
|
|
}
|
|
listType="picture-card"
|
|
// onlineImage={onlineImage?onlineImage:""}
|
|
// ImageLink={record.Productimage }
|
|
// updateImageUrl={updateImageUrl}
|
|
// singleImage={true}
|
|
recordMaintainKey={record.key}
|
|
/>
|
|
|
|
<Tooltip title="Online Search">
|
|
<TbWorldSearch
|
|
style={{ fontSize: '30px' }}
|
|
onClick={() => addOnlineImage(record, index)}
|
|
/>
|
|
</Tooltip>
|
|
</div>
|
|
</>
|
|
)}
|
|
</>
|
|
);
|
|
},
|
|
},
|
|
];
|
|
|
|
const column = columns?.map((col) => {
|
|
if (!col.editable) {
|
|
return col;
|
|
}
|
|
|
|
let onClickHandler = null;
|
|
|
|
if (editdelete === 'Delete') {
|
|
onClickHandler = (record) => removeFromTable(record);
|
|
}
|
|
|
|
return {
|
|
...col,
|
|
onCell: (record) => ({
|
|
record,
|
|
editable: col.editable,
|
|
dataIndex: col.dataIndex,
|
|
title: col.title,
|
|
handleSave,
|
|
onClick: () => onClickHandler(record),
|
|
}),
|
|
};
|
|
});
|
|
|
|
const EditableContext = React.createContext(null);
|
|
|
|
const EditableRow = ({ index, dataIndex, ...props }) => {
|
|
const [form] = Form.useForm();
|
|
return (
|
|
<Form form={form} component={false}>
|
|
<EditableContext.Provider value={form}>
|
|
<tr {...props} />
|
|
</EditableContext.Provider>
|
|
</Form>
|
|
);
|
|
};
|
|
|
|
const EditableCell = ({
|
|
title,
|
|
editable,
|
|
children,
|
|
dataIndex,
|
|
record,
|
|
handleSave,
|
|
index,
|
|
...restProps
|
|
}) => {
|
|
const [editing, setEditing] = useState(false);
|
|
const inputRef = useRef(null);
|
|
const form = useContext(EditableContext);
|
|
|
|
useEffect(() => {
|
|
if (editing) {
|
|
inputRef?.current?.focus();
|
|
}
|
|
}, [editing]);
|
|
|
|
const toggleEdit = () => {
|
|
setEditing(!editing);
|
|
form.setFieldsValue({
|
|
[dataIndex]: record[dataIndex],
|
|
});
|
|
};
|
|
|
|
const save = async () => {
|
|
try {
|
|
const values = await form.validateFields();
|
|
toggleEdit();
|
|
handleSave({
|
|
...record,
|
|
...values,
|
|
});
|
|
} catch (errInfo) {}
|
|
};
|
|
|
|
let childNode = children;
|
|
|
|
if (editable) {
|
|
childNode = editing ? (
|
|
<Form.Item
|
|
style={{
|
|
margin: 0,
|
|
}}
|
|
name={dataIndex}
|
|
>
|
|
<Input ref={inputRef} onPressEnter={save} onBlur={save} />
|
|
</Form.Item>
|
|
) : (
|
|
<div
|
|
className="editable-cell-value-wrap"
|
|
style={{
|
|
textAlign: 'right',
|
|
}}
|
|
onClick={toggleEdit}
|
|
>
|
|
{children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return <td {...restProps}>{childNode}</td>;
|
|
};
|
|
|
|
const components = {
|
|
body: {
|
|
row: EditableRow,
|
|
cell: EditableCell,
|
|
},
|
|
};
|
|
|
|
const handleSave = (row) => {
|
|
const newDatas = [...Datas];
|
|
|
|
const item = newDatas.filter((item) => item.key === row.key);
|
|
|
|
// const item = newDatas[index];
|
|
const UpdatedData = {
|
|
ProductName: row?.ProductName,
|
|
ProductVariantName: row?.ProductVariantName
|
|
? row?.ProductVariantName
|
|
: 'Variant 1',
|
|
Quantity: row?.Quantity,
|
|
UOM: row?.UOM,
|
|
MRP: row?.MRP,
|
|
isModified: true,
|
|
WhSalePrice: row?.WhSalePrice,
|
|
SellPrice: row?.SellPrice,
|
|
Category: row?.Category,
|
|
SubCategory: row?.SubCategory,
|
|
Brand: row?.Brand,
|
|
AutoGenerateQrcode: row?.AutoGenerateQrcode,
|
|
AddQrCode: row?.AddQrCode,
|
|
StockAvailable: row?.StockAvailable,
|
|
// NumberOfPieceInside:row?.NumberOfPieceInside,
|
|
TokenAvailable: row?.TokenAvailable,
|
|
OnePieceAvailable: row?.OnePieceAvailable,
|
|
OnePiecePrice: row?.OnePiecePrice,
|
|
AutoGenerateOnePieceQrcode: row?.AutoGenerateOnePieceQrcode,
|
|
AutoGenerateOnePieceQrcodeNumber: row?.AutoGenerateOnePieceQrcodeNumber,
|
|
ProductType: row?.ProductType,
|
|
Tax: row?.Tax,
|
|
CESS: row?.CESS,
|
|
HSN: row?.HSN,
|
|
PartNumber: row?.PartNumber,
|
|
Rack: row?.Rack,
|
|
ManufactureDate: row?.ManufactureDate,
|
|
ExpireDate: row?.ExpireDate,
|
|
AvailableFrom: row?.AvailableFrom,
|
|
AvailableTo: row?.AvailableTo,
|
|
Productimage: row?.Productimage,
|
|
key: row?.key,
|
|
};
|
|
newDatas.splice(item?.[0]?.key, 1, {
|
|
...item?.[0],
|
|
...UpdatedData,
|
|
});
|
|
setDatas(newDatas);
|
|
setExcelData(newDatas);
|
|
};
|
|
|
|
const handlechange22 = (index) => {
|
|
if (editdelete === 'Delete') {
|
|
seteditdelete('');
|
|
} else {
|
|
seteditdelete('Delete');
|
|
}
|
|
};
|
|
|
|
const removeFromTable = (record) => {
|
|
const indexToRemove = Datas.findIndex((item) => item.key === record.key);
|
|
|
|
if (indexToRemove !== -1) {
|
|
const updatedDataSource = [...Datas];
|
|
updatedDataSource.splice(indexToRemove, 1);
|
|
setDatas(updatedDataSource);
|
|
setExcelData(updatedDataSource);
|
|
}
|
|
};
|
|
|
|
const handleExportData = async () => {
|
|
setLoading(true);
|
|
let response = await dispatch(
|
|
getProductData({ CompId: CompId, BranchId: BranchId, AppId: AppId })
|
|
).unwrap();
|
|
if (response?.data?.statusCode == 1 && response?.data?.data?.length > 0) {
|
|
const formattedData = response?.data?.data?.map((item) => {
|
|
return {
|
|
ProdId: item.ProdId,
|
|
'Product Name': item.ProdName,
|
|
'Product Variant Name': item.ProdVarientName,
|
|
Quantity: item.Size,
|
|
UOM: item.UomName,
|
|
MRP: item.MRP,
|
|
'WholeSale Price': item.WhSalePrice,
|
|
'Sales Price': item.SellPrice,
|
|
Category: item.CategoryName,
|
|
'Sub Category': item.SubCategoryName,
|
|
Brand: item.BrandName,
|
|
'Auto Generate Qrcode': item.AutoGenerateQr
|
|
? item.AutoGenerateQr
|
|
: 'N',
|
|
'Add Qr Code': item.QRCode,
|
|
'Stock Available': item.StockAvailable ? item.StockAvailable : 'N',
|
|
'Token Available': item.TokenAvailable ? item.TokenAvailable : 'N',
|
|
'One Piece Available': item.OnePcsAvailable
|
|
? item.OnePcsAvailable
|
|
: 'N',
|
|
'One Piece Price': item.OnePcsPrice,
|
|
'Auto Generate One Piece Qrcode': item.AutoGenerateSingleQr
|
|
? item.AutoGenerateSingleQr
|
|
: 'N',
|
|
'Auto Generate One Piece Qrcode Number': item.OnePcQR,
|
|
'Product Type': item.ProdTypeName ? item.ProdTypeName : 'Product',
|
|
Tax: `${item.TaxName || 'NIL'} - ${item.TaxPercentage || 0}%`,
|
|
CESS: item.CESS,
|
|
HSN: item.HSNCode,
|
|
'Part Number': item.PartNumber,
|
|
Rack: item.Rack,
|
|
'Manufacture Date': null,
|
|
'Expire Date': null,
|
|
'Available From':
|
|
item.AvailableFrom != '00:00:00' ? item.AvailableFrom : null,
|
|
'Available To':
|
|
item.AvailableTo != '00:00:00' ? item.AvailableTo : null,
|
|
};
|
|
});
|
|
|
|
const workbook = new ExcelJS.Workbook();
|
|
const worksheet = workbook.addWorksheet('Products');
|
|
|
|
worksheet.columns = Object.keys(formattedData[0]).map((key) => ({
|
|
header: key,
|
|
key: key,
|
|
width: 20,
|
|
}));
|
|
|
|
formattedData.forEach((row) => worksheet.addRow(row));
|
|
|
|
worksheet.eachRow((row, rowNumber) => {
|
|
row.eachCell((cell, colNumber) => {
|
|
cell.protection = { locked: colNumber === 1 };
|
|
});
|
|
});
|
|
|
|
await worksheet.protect('', {
|
|
selectLockedCells: true,
|
|
selectUnlockedCells: true,
|
|
});
|
|
|
|
// const buffer = await workbook.xlsx.writeBuffer();
|
|
// const blob = new Blob([buffer], {
|
|
// type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
// });
|
|
// const downloadLink = document.createElement('a');
|
|
// downloadLink.href = window.URL.createObjectURL(blob);
|
|
// downloadLink.download = 'Product_Data.xlsx';
|
|
// downloadLink.click();
|
|
const buffer = await workbook.xlsx.writeBuffer();
|
|
|
|
downloadFile(
|
|
buffer,
|
|
"Product_Data.xlsx",
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
|
|
);
|
|
setLoading(false);
|
|
} else {
|
|
setLoading(false);
|
|
setMessage(true);
|
|
}
|
|
};
|
|
|
|
const handleExportBulkdata = async () => {
|
|
let response = await dispatch(
|
|
getProductData({ CompId: CompId, BranchId: BranchId, AppId: AppId })
|
|
).unwrap();
|
|
if (response?.data?.statusCode == 1 && response?.data?.data?.length > 0) {
|
|
const formattedData = response?.data?.data?.map((item) => {
|
|
return {
|
|
ProdId: item.ProdId,
|
|
'Product Name': item.ProdName,
|
|
'Product Variant Name': item.ProdVarientName,
|
|
Quantity: item.Size,
|
|
UOM: item.UomName,
|
|
MRP: item.MRP,
|
|
'WholeSale Price': item.WhSalePrice,
|
|
'Sales Price': item.SellPrice,
|
|
Category: item.CategoryName,
|
|
'Sub Category': item.SubCategoryName,
|
|
Brand: item.BrandName,
|
|
'Auto Generate Qrcode': item.AutoGenerateQr
|
|
? item.AutoGenerateQr
|
|
: 'N',
|
|
'Add Qr Code': item.QRCode,
|
|
'Stock Available': item.StockAvailable ? item.StockAvailable : 'N',
|
|
'Token Available': item.TokenAvailable ? item.TokenAvailable : 'N',
|
|
'One Piece Available': item.OnePcsAvailable
|
|
? item.OnePcsAvailable
|
|
: 'N',
|
|
'One Piece Price': item.OnePcsPrice,
|
|
'Auto Generate One Piece Qrcode': item.AutoGenerateSingleQr
|
|
? item.AutoGenerateSingleQr
|
|
: 'N',
|
|
'Auto Generate One Piece Qrcode Number': item.OnePcQR,
|
|
'Product Type': item.ProdTypeName ? item.ProdTypeName : 'Product',
|
|
Tax: `${item.TaxName || 'NIL'} - ${item.TaxPercentage || 0}%`,
|
|
CESS: item.CESS,
|
|
HSN: item.HSNCode,
|
|
'Part Number': item.PartNumber,
|
|
Rack: item.Rack,
|
|
'Manufacture Date': null,
|
|
'Expire Date': null,
|
|
'Available From':
|
|
item.AvailableFrom != '00:00:00' ? item.AvailableFrom : null,
|
|
'Available To':
|
|
item.AvailableTo != '00:00:00' ? item.AvailableTo : null,
|
|
};
|
|
});
|
|
|
|
setOrginalData(formattedData);
|
|
}
|
|
};
|
|
|
|
const requiredFields = [
|
|
'ProductName',
|
|
'ProductVariantName',
|
|
'Quantity',
|
|
'UOM',
|
|
'MRP',
|
|
'SellPrice',
|
|
'WhSalePrice',
|
|
'Category',
|
|
'SubCategory',
|
|
'Brand',
|
|
'StockAvailable',
|
|
'TokenAvailable',
|
|
'ProductType',
|
|
'Tax',
|
|
'AutoGenerateQrcode',
|
|
'AddQrCode',
|
|
'OnePieceAvailable',
|
|
'OnePiecePrice',
|
|
'AutoGenerateOnePieceQrcode',
|
|
'AutoGenerateOnePieceQrcodeNumber',
|
|
'CESS',
|
|
'HSN',
|
|
'PartNumber',
|
|
'Rack',
|
|
'ManufactureDate',
|
|
'ExpireDate',
|
|
'AvailableFrom',
|
|
'AvailableTo',
|
|
];
|
|
|
|
const enhancedColumns = column.map((col) => {
|
|
if (col.dataIndex === 'Productimage') return col;
|
|
|
|
// Get all used fields *except* the current column (to allow remap)
|
|
const usedFields = Object.entries(fieldMapping)
|
|
.filter(([key]) => key !== col.dataIndex)
|
|
.map(([, value]) => value);
|
|
|
|
// Fields left for dropdown (excluding already selected ones)
|
|
const availableHeaders = headers?.filter(
|
|
(h) => !usedFields?.includes(h) && h !== '__isSampleRow'
|
|
);
|
|
|
|
const isMapped = !!fieldMapping[col.dataIndex];
|
|
|
|
return {
|
|
...col,
|
|
title: (
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
height: '60%',
|
|
}}
|
|
>
|
|
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
|
<span
|
|
style={{
|
|
whiteSpace: 'nowrap',
|
|
fontWeight: '500',
|
|
fontFamily: 'Poppins',
|
|
}}
|
|
>
|
|
{col.title}
|
|
</span>
|
|
{!isMapped && (
|
|
<span style={{ color: '#f7a0a0', fontSize: '11px' }}>
|
|
Not mapped
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
<div style={{ position: 'relative', width: '80%', marginTop: 5 }}>
|
|
<Select
|
|
// allowClear
|
|
style={{ width: '100%' }}
|
|
placeholder="Select"
|
|
value={fieldMapping[col.dataIndex] || undefined}
|
|
onChange={(selectedField) => {
|
|
setFieldMapping((prev) => {
|
|
const updated = { ...prev };
|
|
|
|
// Remove the same field from other mappings
|
|
Object.entries(updated).forEach(([key, val]) => {
|
|
if (val === selectedField) delete updated[key];
|
|
});
|
|
|
|
if (selectedField) {
|
|
updated[col.dataIndex] = selectedField;
|
|
} else {
|
|
delete updated[col.dataIndex];
|
|
}
|
|
|
|
return updated;
|
|
});
|
|
}}
|
|
>
|
|
{availableHeaders.map((header) => (
|
|
<Select.Option key={header} value={header}>
|
|
{header}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
|
|
{fieldMapping[col.dataIndex] && (
|
|
<CloseCircleOutlined
|
|
onClick={() => {
|
|
setFieldMapping((prev) => {
|
|
const updated = { ...prev };
|
|
delete updated[col.dataIndex];
|
|
return updated;
|
|
});
|
|
}}
|
|
className="SelectCancelicon"
|
|
style={{
|
|
position: 'absolute',
|
|
right: '-17px',
|
|
top: '90%',
|
|
transform: 'translateY(-50%)',
|
|
color: 'red',
|
|
fontSize: 14,
|
|
cursor: 'pointer',
|
|
zIndex: 1,
|
|
}}
|
|
/>
|
|
)}
|
|
</div>
|
|
</div>
|
|
),
|
|
render: (text, record, index) => {
|
|
const mappedField = fieldMapping[col.dataIndex];
|
|
|
|
if (
|
|
mappedField &&
|
|
originalUploadedRawData?.[index]?.[mappedField] !== undefined
|
|
) {
|
|
return originalUploadedRawData[index][mappedField];
|
|
}
|
|
|
|
return record[col.dataIndex] !== undefined ? record[col.dataIndex] : '';
|
|
},
|
|
onCell: (record) => ({
|
|
record,
|
|
editable: col.editable,
|
|
dataIndex: col.dataIndex,
|
|
title: col.title,
|
|
handleSave,
|
|
onClick: () => {
|
|
if (editdelete === 'Delete') {
|
|
removeFromTable(record);
|
|
}
|
|
},
|
|
}),
|
|
};
|
|
});
|
|
|
|
const aliasMap = {
|
|
ProductName: ['product name', 'product', 'item', 'item name', 'prodname'],
|
|
ProductVariantName: [
|
|
'variant',
|
|
'product variant',
|
|
'variant name',
|
|
'productvariant',
|
|
],
|
|
Quantity: ['qty', 'quantity', 'qty size', 'quantitysize', 'qnt'],
|
|
UOM: ['uom', 'unit', 'unit of measure'],
|
|
MRP: ['mrp', 'mrp price', 'm.r.p'],
|
|
SellPrice: ['retail', 'retail price', 'sales price', 'selling price'],
|
|
WhSalePrice: ['wholesale', 'wholesale price', 'wholesaleprice'],
|
|
Category: ['category', 'product category', 'cat'],
|
|
SubCategory: ['subcategory', 'sub category', 'sub cat', 'sub-cat'],
|
|
Tax: ['tax', 'tax percent', 'gst', 'vat'],
|
|
CESS: ['cess'],
|
|
HSN: ['hsn', 'hsn code'],
|
|
Brand: ['brand', 'brand name'],
|
|
// Add more as needed...
|
|
};
|
|
|
|
const autoMapFields = (headers) => {
|
|
const updatedMapping = {};
|
|
const cleanedHeaders = headers.map((h) =>
|
|
h?.toLowerCase().replace(/\s+/g, '')
|
|
);
|
|
|
|
requiredFields.forEach((requiredField) => {
|
|
const cleanedField = requiredField?.toLowerCase();
|
|
|
|
// 1. Try exact match (cleaned)
|
|
const match = stringSimilarity.findBestMatch(
|
|
cleanedField,
|
|
cleanedHeaders
|
|
);
|
|
const bestMatchIndex = match.bestMatchIndex;
|
|
const bestMatchScore = match.bestMatch.rating;
|
|
|
|
if (bestMatchScore > 0.6) {
|
|
updatedMapping[requiredField] = headers[bestMatchIndex];
|
|
return;
|
|
}
|
|
|
|
// 2. Try alias match
|
|
const aliases = aliasMap[requiredField] || [];
|
|
const matchedHeader = headers.find((header) =>
|
|
aliases.some(
|
|
(alias) =>
|
|
header?.toLowerCase().replace(/\s+/g, '') ===
|
|
alias.replace(/\s+/g, '')
|
|
)
|
|
);
|
|
|
|
if (matchedHeader) {
|
|
updatedMapping[requiredField] = matchedHeader;
|
|
}
|
|
});
|
|
|
|
setFieldMapping(updatedMapping);
|
|
};
|
|
|
|
const COMPARE_FIELDS = [
|
|
'Product Name',
|
|
'Quantity',
|
|
'MRP',
|
|
'Sales Price',
|
|
'WholeSale Price',
|
|
'Category',
|
|
'Sub Category',
|
|
'Brand',
|
|
'Stock Available',
|
|
];
|
|
|
|
return (
|
|
<div>
|
|
<Messages
|
|
messageType={messageType}
|
|
messageData={messageData}
|
|
onComplete={() => {
|
|
setMessageData(null);
|
|
setMessageType(null);
|
|
}}
|
|
/>
|
|
|
|
<div className="form">
|
|
<form
|
|
className="form-group"
|
|
autoComplete="off"
|
|
onSubmit={handleFileSubmit}
|
|
>
|
|
<div className="download-linkMain">
|
|
<div className="HeadingUploadExl">
|
|
<p
|
|
style={{
|
|
fontSize: '16px',
|
|
fontWeight: '500',
|
|
whiteSpace: 'nowrap',
|
|
}}
|
|
>
|
|
Upload Your Excel
|
|
</p>
|
|
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
flexDirection: 'row',
|
|
gap: '0.5rem',
|
|
}}
|
|
>
|
|
<Tooltip title="Field Setup">
|
|
<div
|
|
className="btn btn--info"
|
|
style={{
|
|
background: '#00694aff',
|
|
color: '#fff',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
padding: '5px 12px',
|
|
borderRadius: '4px',
|
|
}}
|
|
onClick={handleFieldSetup}
|
|
>
|
|
<MdOutlineAppRegistration size={19} />
|
|
</div>
|
|
</Tooltip>
|
|
<div
|
|
className="export-excel-div"
|
|
onClick={() => handleExportData()}
|
|
>
|
|
<div>
|
|
<CiExport size={20} strokeWidth={1} />
|
|
</div>
|
|
<div style={{ fontSize: '14px', letterSpacing: '0.2px' }}>
|
|
Export Excel
|
|
</div>
|
|
</div>
|
|
|
|
{message && (
|
|
<div style={{ color: '#ff4d4f' }}>No Data Found</div>
|
|
)}
|
|
</div>
|
|
|
|
{excelData?.length > 0 && excelData && (
|
|
<div>
|
|
<Tooltip title="Delete">
|
|
<AiFillDelete
|
|
onClick={handlechange22}
|
|
style={{
|
|
fontSize: '23px',
|
|
color: editdelete ? '#52c41a' : '#1292ee',
|
|
}}
|
|
/>
|
|
</Tooltip>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div
|
|
className="download-link"
|
|
style={{
|
|
display: 'flex',
|
|
justifyContent:
|
|
excelData?.length > 0 ? 'space-between' : 'flex-end',
|
|
gap: '1rem',
|
|
}}
|
|
>
|
|
{excelData?.length > 0 && (
|
|
<div className="SheetIndiacte">
|
|
<div>
|
|
<p className="mdsheet"></p>
|
|
<span>Modified</span>
|
|
</div>
|
|
<div>
|
|
<p className="mdsheet2"></p>
|
|
<span>Not Modified</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<Tooltip title={'Format Sheet'} placement="top">
|
|
<button onClick={handleDownload}>
|
|
<FiDownload style={{ fontSize: '20px' }} />
|
|
<span>Download Sample Sheet</span>
|
|
</button>
|
|
</Tooltip>
|
|
</div>
|
|
</div>
|
|
|
|
{(!excelData || excelData?.length === 0) && (
|
|
<>
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
border: '1px dashed #000',
|
|
width: 'max-content',
|
|
padding: '10px',
|
|
borderRadius: '6px',
|
|
}}
|
|
>
|
|
<div style={{ position: 'relative', display: 'inline-block' }}>
|
|
{/* <Imageupload size="2x" /> */}
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
flexDirection: 'column',
|
|
alignItems: 'center',
|
|
}}
|
|
>
|
|
<img
|
|
src={Excelupldimg}
|
|
width={'80px'}
|
|
style={{ cursor: 'pointer', margin: '0 0 1rem 0' }}
|
|
onClick={handleClick}
|
|
/>
|
|
<input
|
|
type="file"
|
|
className="form-control"
|
|
onChange={handleFileUpload}
|
|
ref={fileInputRef}
|
|
style={{
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
width: '100%',
|
|
height: '100%',
|
|
opacity: 0,
|
|
cursor: 'pointer',
|
|
}}
|
|
/>
|
|
<a style={{ color: '#000', fontSize: '12px' }}>
|
|
{' '}
|
|
Upload Sheet{' '}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
|
|
{excelFileError && (
|
|
<div className="text-danger" style={{ marginTop: '5px' }}>
|
|
{excelFileError}
|
|
</div>
|
|
)}
|
|
</form>
|
|
</div>
|
|
<div
|
|
className="viewerExcelupload"
|
|
style={{ maxHeight: '90vh', overflowX: 'auto' }}
|
|
>
|
|
{excelData && excelData?.length > 0 ? (
|
|
<Table
|
|
columns={enhancedColumns}
|
|
components={components}
|
|
bordered
|
|
dataSource={ExcelData}
|
|
className="tableExcelUpload"
|
|
onChange={handleTableChange}
|
|
rowClassName={(record) =>
|
|
record.isModified ? 'modified-row' : 'old-row'
|
|
}
|
|
></Table>
|
|
) : (
|
|
<p></p>
|
|
)}
|
|
</div>
|
|
<div
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
width: '100%',
|
|
gap: '2rem',
|
|
margin: '1rem 0rem',
|
|
}}
|
|
>
|
|
{excelData && excelData?.length > 0 && (
|
|
<div
|
|
className="cancel-link"
|
|
onClick={handleCancelData}
|
|
style={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
gap: '.5rem',
|
|
padding: '0.3rem 0.4rem',
|
|
borderRadius: '6px',
|
|
backgroundColor: 'ghostwhite',
|
|
}}
|
|
>
|
|
<FiDelete style={{ fontSize: '20px', color: '#1292EE' }} />
|
|
<a style={{ color: '#000' }}> Remove File</a>
|
|
</div>
|
|
)}
|
|
</div>
|
|
<DefaultModal
|
|
open={fieldSetup}
|
|
handleCancel={() => setFieldSetup(false)}
|
|
footer={false}
|
|
children={
|
|
<div className="field-setup-container">
|
|
<div className="field-setup-header">
|
|
<FormHeader
|
|
title={'Select the fields you want to include in the table'}
|
|
/>
|
|
</div>
|
|
<div className="field-setup-content">
|
|
<Form
|
|
onFinish={handleFieldSetupSubmit}
|
|
ref={formRef}
|
|
style={{ marginTop: '1rem' }}
|
|
>
|
|
<Form.Item name="fields">
|
|
<DropDowns
|
|
label="Select fields"
|
|
// valueData={selectedFields}
|
|
onChangeFunction={handleFieldSelect}
|
|
options={tableFieldPreferences?.filter(
|
|
(p) => !selectedFields?.some((s) => s === p.value)
|
|
)}
|
|
/>
|
|
</Form.Item>
|
|
<div className="field-setup-selected-fields">
|
|
{selectedFields
|
|
?.map((id) =>
|
|
tableFieldPreferences?.find((p) => p.value === id)
|
|
)
|
|
.filter(Boolean)
|
|
.map((field) => (
|
|
<div className="selected-field" key={field.value}>
|
|
<div>{field.label}</div>
|
|
<div
|
|
className="close-icon"
|
|
onClick={() => handleFieldRemove(field.value)}
|
|
>
|
|
<IoClose size={15} />
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div style={{ display: 'flex', justifyContent: 'flex-end' }}>
|
|
<Buttons
|
|
buttonText="SUBMIT"
|
|
color="901D77"
|
|
icon={<ArrowRightOutlined />}
|
|
htmlType={true}
|
|
/>
|
|
</div>
|
|
</Form>
|
|
</div>
|
|
</div>
|
|
}
|
|
/>
|
|
|
|
<div className="catalogueproductImg">
|
|
<DefaultModal
|
|
title="ONLINE IMAGE UPLOADER"
|
|
width={800}
|
|
open={imageopen}
|
|
footer={true}
|
|
buttonText="Submit"
|
|
children={
|
|
<Form className="">
|
|
<div className="productonlineImg">
|
|
{imagedata?.map((item, index) => (
|
|
<div
|
|
className={`singleimg ${
|
|
selectedImage === item ? 'selected' : ''
|
|
}`}
|
|
key={index}
|
|
onClick={() => setSelectedImage(item)}
|
|
>
|
|
<img
|
|
src={item.image}
|
|
alt="no image"
|
|
style={{ height: '100%', width: '100%' }}
|
|
/>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</Form>
|
|
}
|
|
handleSubmit={submitimage}
|
|
handleCancel={handleimage}
|
|
></DefaultModal>
|
|
</div>
|
|
|
|
<Modal
|
|
title="Map Your Fields"
|
|
open={MappingOpen}
|
|
onCancel={() => setMappingOpen(false)}
|
|
>
|
|
{requiredFields.map((field) => {
|
|
const usedHeaders = Object.entries(fieldMapping)
|
|
.filter(([key]) => key !== field)
|
|
.map(([, value]) => value);
|
|
|
|
const availableHeaders = headers?.filter(
|
|
(header) => !usedHeaders?.includes(header)
|
|
);
|
|
|
|
return (
|
|
<div key={field} style={{ marginBottom: 10 }}>
|
|
<label>{field}</label>
|
|
<Select
|
|
allowClear
|
|
style={{ width: '100%' }}
|
|
placeholder="Select column"
|
|
value={fieldMapping[field] || undefined}
|
|
clearIcon={
|
|
<CloseCircleOutlined style={{ color: 'red', fontSize: 20 }} />
|
|
}
|
|
onChange={(value) => {
|
|
setFieldMapping((prev) => {
|
|
const updated = { ...prev };
|
|
if (!value) {
|
|
delete updated[field];
|
|
} else {
|
|
updated[field] = value;
|
|
}
|
|
return updated;
|
|
});
|
|
}}
|
|
>
|
|
{availableHeaders.map((header) => (
|
|
<Select.Option key={header} value={header}>
|
|
{header}
|
|
</Select.Option>
|
|
))}
|
|
</Select>
|
|
</div>
|
|
);
|
|
})}
|
|
</Modal>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ProductExcel;
|