From aa4b734b253458c4d7fa73043d9fa1e9840b655f Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 28 Jan 2026 14:26:13 +0530 Subject: [PATCH] turbo add scroll fix jsx --- src/Pages/Product/TurboAddForm.jsx | 3831 +++++++++++++++++----------- 1 file changed, 2406 insertions(+), 1425 deletions(-) diff --git a/src/Pages/Product/TurboAddForm.jsx b/src/Pages/Product/TurboAddForm.jsx index 5033fc1..c3089dd 100644 --- a/src/Pages/Product/TurboAddForm.jsx +++ b/src/Pages/Product/TurboAddForm.jsx @@ -1,11 +1,14 @@ -import { useState, useRef, useEffect, useMemo, useCallback } from "react"; -import { Trash2, Plus, RotateCcw, Search, Save } from "lucide-react"; -import { useDispatch, useSelector } from "react-redux"; -import "../../Styles/Product/TurboAddForm.scss"; -import { getAdmin, taxSelector } from "../../Features/Tax/Tax.js"; -import { getSession } from "../../Services/Others.js"; +import { useState, useRef, useEffect, useMemo, useCallback } from 'react'; +import { Trash2, Plus, RotateCcw, Search, Save } from 'lucide-react'; +import { useDispatch, useSelector } from 'react-redux'; +import '../../Styles/Product/TurboAddForm.scss'; +import { getAdmin, taxSelector } from '../../Features/Tax/Tax.js'; +import { getSession } from '../../Services/Others.js'; import { isMobile } from 'react-device-detect'; -import { ApplicationPreferences, getCommonAppPreference } from "../../Features/BrachLogin/BranchLogin.js"; +import { + ApplicationPreferences, + getCommonAppPreference, +} from '../../Features/BrachLogin/BranchLogin.js'; import { bulkpostdata, getBrandData, @@ -23,32 +26,36 @@ import { productTypeDataSelector, putProductData, uomDataSelector, -} from "../../Features/ProductPage/ProductPage.js"; -import { Messages } from "../../Components/Notifications/Messages.jsx"; -import { changeSearchedData, getLayoutsearch, GlobalSearchData } from "../../Features/BookingScreen/BookingData/BookingData.js"; +} from '../../Features/ProductPage/ProductPage.js'; +import { Messages } from '../../Components/Notifications/Messages.jsx'; +import { + changeSearchedData, + getLayoutsearch, + GlobalSearchData, +} from '../../Features/BookingScreen/BookingData/BookingData.js'; import debounce from 'lodash.debounce'; -import { MdOutlineAppRegistration } from "react-icons/md"; -import { IoClose } from "react-icons/io5"; +import { MdOutlineAppRegistration } from 'react-icons/md'; +import { IoClose } from 'react-icons/io5'; -import { Form, Radio, Tooltip } from "antd"; -import { DatePicker, TimePicker } from "antd"; -import { DefaultModal } from "../../Components/Modal/DefaultModal.jsx"; -import FormHeader from "../PageComponents/FormHeader.jsx"; -import { DropDowns } from "../../Components/Forms/DropDown.jsx"; -import Buttons from "../../Components/Forms/Buttons.jsx"; +import { Form, Radio, Tooltip } from 'antd'; +import { DatePicker, TimePicker } from 'antd'; +import { DefaultModal } from '../../Components/Modal/DefaultModal.jsx'; +import FormHeader from '../PageComponents/FormHeader.jsx'; +import { DropDowns } from '../../Components/Forms/DropDown.jsx'; +import Buttons from '../../Components/Forms/Buttons.jsx'; import { ArrowRightOutlined } from '@ant-design/icons'; -import DefaultSettingPopover from "./Utils/DefaultSettingPopover.jsx"; -import CellSelect from "./Utils/CellSelect.jsx"; -import CellInput from "./Utils/CellInput.jsx"; -import dayjs from "dayjs"; -import EnhancedVoiceForm from "./Utils/EnhancedVoiceForm.jsx"; +import DefaultSettingPopover from './Utils/DefaultSettingPopover.jsx'; +import CellSelect from './Utils/CellSelect.jsx'; +import CellInput from './Utils/CellInput.jsx'; +import dayjs from 'dayjs'; +import EnhancedVoiceForm from './Utils/EnhancedVoiceForm.jsx'; import CropUpload from '../../Components/Forms/CropUpload.jsx'; -import { uploadImage } from "../../Features/upload/upload.js"; -import { FaRegEye } from "react-icons/fa"; -import { changeBreadCrumb } from "../../Features/AppPage/CenterPage.js"; -import { BiBarcodeReader } from "react-icons/bi"; -import BarcodeScanner from "../BookingScreen/Components/UtillComponents/BarcodeScanner.jsx"; -import BarcodeScannerTurbo from "../BookingScreen/Components/UtillComponents/BarcodeScannerTurbo.jsx"; +import { uploadImage } from '../../Features/upload/upload.js'; +import { FaRegEye } from 'react-icons/fa'; +import { changeBreadCrumb } from '../../Features/AppPage/CenterPage.js'; +import { BiBarcodeReader } from 'react-icons/bi'; +import BarcodeScanner from '../BookingScreen/Components/UtillComponents/BarcodeScanner.jsx'; +import BarcodeScannerTurbo from '../BookingScreen/Components/UtillComponents/BarcodeScannerTurbo.jsx'; const subDirectory = import.meta.env.ENV_BASE_URL; const items = [ @@ -69,25 +76,33 @@ const items = [ // --- Constants --- const FIELD_NAMES = { - productName: "Product Name", - uom: "UOM", - nou: "No. of. Units", - mrp: "MRP", - salePrice: "Sale Price", - category: "Category", - subCategory: "Sub-Category", - tax: "Tax", + productName: 'Product Name', + uom: 'UOM', + nou: 'No. of. Units', + mrp: 'MRP', + salePrice: 'Sale Price', + category: 'Category', + subCategory: 'Sub-Category', + tax: 'Tax', }; -const REQUIRED_FIELDS = ["productName", "uom", "nou", "mrp", "salePrice", "category", "tax"]; +const REQUIRED_FIELDS = [ + 'productName', + 'uom', + 'nou', + 'mrp', + 'salePrice', + 'category', + 'tax', +]; const TOTAL_COLS = 8; // --- Validation --- const createValidationRules = (products) => ({ productName: (v, row) => { - if (!v?.trim()) return "Product Name is required"; + if (!v?.trim()) return 'Product Name is required'; // Duplicate check: productName + uom + nou const isDuplicate = products?.some( - p => + (p) => p.id !== row.id && p.productName?.trim().toLowerCase() === v.trim().toLowerCase() && p.uom === row.uom && @@ -95,36 +110,42 @@ const createValidationRules = (products) => ({ p.category === row.category && p.subCategory === row.subCategory ); - if (isDuplicate) return "Product already exists"; - return ""; + if (isDuplicate) return 'Product already exists'; + return ''; }, - uom: v => !v ? "UOM is required" : "", - nou: v => { - if (!v || v.toString().trim() === "") return "No. of. Units is required"; + uom: (v) => (!v ? 'UOM is required' : ''), + nou: (v) => { + if (!v || v.toString().trim() === '') return 'No. of. Units is required'; const num = parseFloat(v); - return num < 0 ? "No. of. Units cannot be negative" : ""; + return num < 0 ? 'No. of. Units cannot be negative' : ''; }, - mrp: v => { - if (!v || v.toString().trim() === "") return "MRP is required"; + mrp: (v) => { + if (!v || v.toString().trim() === '') return 'MRP is required'; const num = parseFloat(v); - return num < 0 ? "MRP cannot be negative" : ""; + return num < 0 ? 'MRP cannot be negative' : ''; }, salePrice: (v, row) => { - if (!v || v.toString().trim() === "") return "Sale Price is required"; + if (!v || v.toString().trim() === '') return 'Sale Price is required'; const num = parseFloat(v); - if (num < 0) return "Sale Price cannot be negative"; - if (row?.mrp && num > parseFloat(row.mrp)) return "Sale Price cannot be greater than MRP"; - return ""; + if (num < 0) return 'Sale Price cannot be negative'; + if (row?.mrp && num > parseFloat(row.mrp)) + return 'Sale Price cannot be greater than MRP'; + return ''; }, discountLimit: (v, row) => { const num = parseFloat(v); - if (row.discountType === "P" && num >= 100) return "Percentage must be less than 100"; - if (row.discountType === "F" && num >= parseFloat(row.salePrice || 0)) return "Fixed must be less than Sale Price"; - return ""; + if (row.discountType === 'P' && num >= 100) + return 'Percentage must be less than 100'; + if (row.discountType === 'F' && num >= parseFloat(row.salePrice || 0)) + return 'Fixed must be less than Sale Price'; + return ''; }, - imei1: v => v && !/^\d{15}$/.test(v) ? "IMEI must be 15 digits" : "", - imei2: v => v && !/^\d{15}$/.test(v) ? "IMEI must be 15 digits" : "", - macId: v => v && !/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/.test(v) ? "Invalid MacId" : "", + imei1: (v) => (v && !/^\d{15}$/.test(v) ? 'IMEI must be 15 digits' : ''), + imei2: (v) => (v && !/^\d{15}$/.test(v) ? 'IMEI must be 15 digits' : ''), + macId: (v) => + v && !/^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/.test(v) + ? 'Invalid MacId' + : '', }); // --- Main Form --- @@ -133,22 +154,22 @@ const TurboAddForm = () => { // Redux selectors const taxData = useSelector(taxSelector); const scannerRef = useRef(); - console.log(taxData, "taxData") + console.log(taxData, 'taxData'); const UomData = useSelector(uomDataSelector); const productTypeData = useSelector(productTypeDataSelector); const ApplicationPreferenceData = useSelector(ApplicationPreferences); const categoryId = ApplicationPreferenceData?.find( - p => p?.PreferredCatName?.toLowerCase() === "product form fields" + (p) => p?.PreferredCatName?.toLowerCase() === 'product form fields' )?.PreferredCatId; const prodCategories = useSelector(prodCatDataSelector); const prodSubCategories = useSelector(prodSubCatDataSelector); const searchText = useSelector(GlobalSearchData); // Session data - const AppId = useMemo(() => getSession("AppId"), []); - const CompId = useMemo(() => getSession("CompId"), []); - const BranchId = useMemo(() => getSession("BranchId"), []); - const UserId = useMemo(() => getSession("UserId"), []); + const AppId = useMemo(() => getSession('AppId'), []); + const CompId = useMemo(() => getSession('CompId'), []); + const BranchId = useMemo(() => getSession('BranchId'), []); + const UserId = useMemo(() => getSession('UserId'), []); // State const [voiceData, setVoiceData] = useState(null); @@ -156,7 +177,7 @@ const TurboAddForm = () => { const [messageData, setMessageData] = useState(null); const [products, setProducts] = useState([]); const [selectedCell, setSelectedCell] = useState(null); - const [searchTerm, setSearchTerm] = useState(""); + const [searchTerm, setSearchTerm] = useState(''); const [errors, setErrors] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); const [fieldSetup, setFieldSetup] = useState(false); @@ -165,25 +186,25 @@ const TurboAddForm = () => { const [defaultCategories, setDefaultCategories] = useState([]); const [selectedDefaultCategory, setSelectedDefaultCategory] = useState(null); const [defaultSubCategories, setDefaultSubCategories] = useState([]); - const [selectedDefaultSubCategory, setSelectedDefaultSubCategory] = useState(null); + const [selectedDefaultSubCategory, setSelectedDefaultSubCategory] = + useState(null); const [defaultBrands, setDefaultBrands] = useState([]); const [selectedDefaultBrand, setSelectedDefaultBrand] = useState(null); const [tableFieldPreferences, setTableFieldPreferences] = useState([]); const [hsnDetails, setHsnDetails] = useState([]); - const [selectedHsn, setSelectedHsn] = useState(""); + const [selectedHsn, setSelectedHsn] = useState(''); const [selectedImage, setSelectedImage] = useState(null); - const [onlineImage, setOnlineImage] = useState(""); + const [onlineImage, setOnlineImage] = useState(''); const [imageModal, setImageModal] = useState(false); const [uploadImageModal, setUploadImageModal] = useState(false); const [onlineImageData, setOnlineImageData] = useState([]); const [imageModalRowIndex, setImageModalRowIndex] = useState(null); const [openScanner, setOpenScanner] = useState(false); - console.log(imageModalRowIndex, "onlineImage") - - const hasField = label => - tableFieldPreferences?.some(f => f.label === label && f.access === "Y"); + console.log(imageModalRowIndex, 'onlineImage'); + const hasField = (label) => + tableFieldPreferences?.some((f) => f.label === label && f.access === 'Y'); // Defaults const [defaults, setDefaults] = useState({ @@ -199,10 +220,17 @@ const TurboAddForm = () => { const debouncedProductLookup = useRef(); const debouncedHSNLookup = useRef(); const formRef = useRef(null); - const defaultsSetRef = useRef({ category: false, subCategory: false, tax: false, uom: false }); + const defaultsSetRef = useRef({ + category: false, + subCategory: false, + tax: false, + uom: false, + }); const tableRef = useRef(null); - const validationRules = useMemo(() => createValidationRules(products), [products]); - + const validationRules = useMemo( + () => createValidationRules(products), + [products] + ); // Memoized UOM preference const { @@ -212,51 +240,82 @@ const TurboAddForm = () => { qrAndTokenDtlsFields, priceDtlsFields, hsnAndMoreDtlsFields, - dateAndTimeDtlsFields + dateAndTimeDtlsFields, } = useMemo(() => { // UOM Preference const uomPref = ApplicationPreferenceData?.find( - p => p?.PreferredCatName === "Product Uom" + (p) => p?.PreferredCatName === 'Product Uom' )?.PreferenceCatDetails; // Field Preferences const fieldsPref = ApplicationPreferenceData?.find( - p => p?.PreferredCatName?.toLowerCase() === "product form fields" + (p) => p?.PreferredCatName?.toLowerCase() === 'product form fields' )?.PreferenceCatDetails; - const UomPreference = UomData?.filter(item => + const UomPreference = UomData?.filter((item) => uomPref?.some?.( - e => - e?.PreferredSubCatName?.toLowerCase() === item?.ConfigName?.toLowerCase() && - e?.PreferredStatus === "Y" + (e) => + e?.PreferredSubCatName?.toLowerCase() === + item?.ConfigName?.toLowerCase() && e?.PreferredStatus === 'Y' ) ); - const fieldPreferences = fieldsPref?.filter(e => e?.PreferredStatus === "Y"); + const fieldPreferences = fieldsPref?.filter( + (e) => e?.PreferredStatus === 'Y' + ); // Category mapping const categoryMap = { productDtls: [ - 'Sub Category', 'Brand', 'Product Image', 'EMI Allowed', 'WholeSale Price' + 'Sub Category', + 'Brand', + 'Product Image', + 'EMI Allowed', + 'WholeSale Price', ], qrAndTokenDtls: [ - 'Auto Generate QRCode', 'Stock Maintenance', 'Token Maintenance', 'Amount Per Piece' + 'Auto Generate QRCode', + 'Stock Maintenance', + 'Token Maintenance', + 'Amount Per Piece', ], priceDtls: [ - 'Product Type', 'Tax', 'Cess', 'Discount Type', 'Discount Limit', 'Add Variant' + 'Product Type', + 'Tax', + 'Cess', + 'Discount Type', + 'Discount Limit', + 'Add Variant', ], hsnAndMoreDtls: [ - 'HSN', 'Part Number', 'Rack', 'Batch Number', 'Model Number', 'IMEI 1', 'IMEI 2', 'Serial Number', 'MacId' + 'HSN', + 'Part Number', + 'Rack', + 'Batch Number', + 'Model Number', + 'IMEI 1', + 'IMEI 2', + 'Serial Number', + 'MacId', ], dateAndTimeDtls: [ - 'Manufacture Date', 'Expire Date', 'Expiry Notification Days', 'Available From', 'Available To' - ] + 'Manufacture Date', + 'Expire Date', + 'Expiry Notification Days', + 'Available From', + 'Available To', + ], }; // Build reverse map - const reverseMap = Object.entries(categoryMap).reduce((acc, [key, values]) => { - values.forEach(val => { acc[val] = key; }); - return acc; - }, {}); + const reverseMap = Object.entries(categoryMap).reduce( + (acc, [key, values]) => { + values.forEach((val) => { + acc[val] = key; + }); + return acc; + }, + {} + ); // Build result fields const result = { @@ -267,7 +326,7 @@ const TurboAddForm = () => { dateAndTimeDtlsFields: [], }; - fieldPreferences?.forEach(field => { + fieldPreferences?.forEach((field) => { const key = reverseMap[field?.PreferredSubCatName]; if (key) { result[key + 'Fields'].push(field.PreferredSubCatName); @@ -277,37 +336,58 @@ const TurboAddForm = () => { return { UomPreference, fieldPreferences, - ...result + ...result, }; }, [ApplicationPreferenceData, UomData]); // Memoized select options - const selectOptions = useMemo(() => ({ - uom: UomPreference?.map(uom => ({ label: uom?.ConfigName, value: uom?.ConfigId })) || [], - tax: taxData?.map(tax => ({ - label: `${tax?.TaxIdName} - ${tax?.TaxPercentage}%`, - value: tax?.TaxId, - name: tax?.TaxIdName, - percentage: tax?.TaxPercentage, - })) || [], - categories: prodCategories?.map(cat => ({ label: cat?.ConfigName, value: cat?.ConfigId })) || [], - subCategories: prodSubCategories?.map(subCat => ({ - label: subCat?.ConfigName, - value: subCat?.ConfigId, - })).filter(item => item.label) || [], - }), [UomPreference, taxData, prodCategories, prodSubCategories]); + const selectOptions = useMemo( + () => ({ + uom: + UomPreference?.map((uom) => ({ + label: uom?.ConfigName, + value: uom?.ConfigId, + })) || [], + tax: + taxData?.map((tax) => ({ + label: `${tax?.TaxIdName} - ${tax?.TaxPercentage}%`, + value: tax?.TaxId, + name: tax?.TaxIdName, + percentage: tax?.TaxPercentage, + })) || [], + categories: + prodCategories?.map((cat) => ({ + label: cat?.ConfigName, + value: cat?.ConfigId, + })) || [], + subCategories: + prodSubCategories + ?.map((subCat) => ({ + label: subCat?.ConfigName, + value: subCat?.ConfigId, + })) + .filter((item) => item.label) || [], + }), + [UomPreference, taxData, prodCategories, prodSubCategories] + ); // Filtered products for search const filteredProducts = useMemo(() => { if (!searchTerm.trim()) return products; const term = searchTerm.toLowerCase(); - return products.filter(p => - p?.productName?.toLowerCase()?.includes(term) || - p?.barcode?.toLowerCase()?.includes(term) + return products.filter( + (p) => + p?.productName?.toLowerCase()?.includes(term) || + p?.barcode?.toLowerCase()?.includes(term) ); }, [products, searchTerm]); - console.log(productTypeData?.filter(pt => pt?.ConfigName !== 'Others')?.map(pt => ({ label: pt.ConfigName, value: pt.ConfigId })), "productTypeData") + console.log( + productTypeData + ?.filter((pt) => pt?.ConfigName !== 'Others') + ?.map((pt) => ({ label: pt.ConfigName, value: pt.ConfigId })), + 'productTypeData' + ); // --- Effects: Data fetching and defaults --- useEffect(() => { @@ -320,10 +400,15 @@ const TurboAddForm = () => { useEffect(() => { const setGeneralDefaults = async () => { - setDefaultCategories(prodCategories?.map(cat => ({ label: cat?.ConfigName, value: cat?.ConfigId })) || []); + setDefaultCategories( + prodCategories?.map((cat) => ({ + label: cat?.ConfigName, + value: cat?.ConfigId, + })) || [] + ); if (prodCategories?.length > 0 && !defaultsSetRef.current.category) { const generalCat = prodCategories.find( - c => c?.ConfigName?.toLowerCase() === "general" + (c) => c?.ConfigName?.toLowerCase() === 'general' ); if (generalCat) { @@ -336,7 +421,7 @@ const TurboAddForm = () => { const subCategories = response?.data?.data || []; const generalSubCat = subCategories.find( - s => s?.ConfigName?.toLowerCase() === "general" + (s) => s?.ConfigName?.toLowerCase() === 'general' ); let availableBrands = []; @@ -347,31 +432,33 @@ const TurboAddForm = () => { availableBrands = brandResponse?.data?.data || []; } - setDefaults(prev => ({ + setDefaults((prev) => ({ ...prev, category: generalCat.ConfigId, subCategory: generalSubCat?.ConfigId || null, availableSubCategories: subCategories .filter( - subCat => + (subCat) => subCat.NumFId === parseInt(generalCat.ConfigId) && !!subCat?.ConfigName ) - .map(subCat => ({ + .map((subCat) => ({ label: subCat?.ConfigName, value: subCat.ConfigId, })), - availableBrands: availableBrands.filter( - brand => - brand.NumFId === parseInt(generalSubCat.ConfigId) && - !!brand?.ConfigName - ).map(brand => ({ - label: brand?.ConfigName, - value: brand.ConfigId, - })), + availableBrands: availableBrands + .filter( + (brand) => + brand.NumFId === parseInt(generalSubCat.ConfigId) && + !!brand?.ConfigName + ) + .map((brand) => ({ + label: brand?.ConfigName, + value: brand.ConfigId, + })), })); } catch (error) { - console.error("Failed to set defaults", error); + console.error('Failed to set defaults', error); } } } @@ -382,9 +469,11 @@ const TurboAddForm = () => { useEffect(() => { if (taxData?.length > 0 && !defaultsSetRef.current.tax) { - const nilTax = taxData.find(t => t?.TaxPercentage === 0 && t?.TaxIdName === "NIL"); + const nilTax = taxData.find( + (t) => t?.TaxPercentage === 0 && t?.TaxIdName === 'NIL' + ); if (nilTax) { - setDefaults(prev => ({ ...prev, tax: nilTax?.TaxId })); + setDefaults((prev) => ({ ...prev, tax: nilTax?.TaxId })); defaultsSetRef.current.tax = true; } } @@ -392,9 +481,9 @@ const TurboAddForm = () => { useEffect(() => { if (UomPreference?.length > 0 && !defaultsSetRef.current.uom) { - const pcsDt = UomPreference.find(u => u?.ConfigName === "PCS"); + const pcsDt = UomPreference.find((u) => u?.ConfigName === 'PCS'); if (pcsDt) { - setDefaults(prev => ({ ...prev, uom: pcsDt?.ConfigId })); + setDefaults((prev) => ({ ...prev, uom: pcsDt?.ConfigId })); defaultsSetRef.current.uom = true; } } @@ -402,19 +491,27 @@ const TurboAddForm = () => { useEffect(() => { getFieldSetup(); - }, [categoryId]) + }, [categoryId]); const getFieldSetup = async () => { try { - const response = await dispatch(getFieldSetupData({ AppId, CompId, BranchId, categoryId, Type: "GB" })).unwrap(); + const response = await dispatch( + getFieldSetupData({ AppId, CompId, BranchId, categoryId, Type: 'GB' }) + ).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 - })) || []); + 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, + })) || [] + ); } } catch (error) { console.error('Error fetching field setup:', error); @@ -422,432 +519,583 @@ const TurboAddForm = () => { }; // --- Validation --- - const validateField = useCallback((id, field, value, rowData = {}) => { - const validator = validationRules[field]; - const error = validator ? validator(value, rowData) : ""; - setErrors(prev => ({ ...prev, [`${id}-${field}`]: error })); - return !error; - }, [validationRules]); + const validateField = useCallback( + (id, field, value, rowData = {}) => { + const validator = validationRules[field]; + const error = validator ? validator(value, rowData) : ''; + setErrors((prev) => ({ ...prev, [`${id}-${field}`]: error })); + return !error; + }, + [validationRules] + ); - const validateRow = useCallback(rowData => { - const rowErrors = {}; - let isValid = true; - REQUIRED_FIELDS.forEach(field => { - const error = validationRules[field]?.(rowData[field], rowData) || ""; - if (error) { - rowErrors[`${rowData.id}-${field}`] = error; - isValid = false; - } - }); - setErrors(prev => ({ ...prev, ...rowErrors })); - return isValid; - }, [validationRules]); + const validateRow = useCallback( + (rowData) => { + const rowErrors = {}; + let isValid = true; + REQUIRED_FIELDS.forEach((field) => { + const error = validationRules[field]?.(rowData[field], rowData) || ''; + if (error) { + rowErrors[`${rowData.id}-${field}`] = error; + isValid = false; + } + }); + setErrors((prev) => ({ ...prev, ...rowErrors })); + return isValid; + }, + [validationRules] + ); // --- Default Setting Handler --- - const handleDefaultSetting = useCallback(async (field, selectedValue, applyToAll) => { - // Step 1: Always update raw default field immediately - setDefaults(prev => ({ ...prev, [field]: selectedValue })); + const handleDefaultSetting = useCallback( + async (field, selectedValue, applyToAll) => { + // Step 1: Always update raw default field immediately + setDefaults((prev) => ({ ...prev, [field]: selectedValue })); - let newDefaults = { ...defaults, [field]: selectedValue }; - let updatedProducts = products; + let newDefaults = { ...defaults, [field]: selectedValue }; + let updatedProducts = products; - // Base update when applyToAll is true - if (applyToAll && products?.length > 0) { - updatedProducts = products?.map(product => { - if (field === 'subCategory' && product.category !== selectedDefaultCategory) return product; - if (field === 'brand' && product.subCategory !== selectedDefaultSubCategory) return product; - const updatedProduct = { ...product, [field]: selectedValue }; + // Base update when applyToAll is true + if (applyToAll && products?.length > 0) { + updatedProducts = products?.map((product) => { + if ( + field === 'subCategory' && + product.category !== selectedDefaultCategory + ) + return product; + if ( + field === 'brand' && + product.subCategory !== selectedDefaultSubCategory + ) + return product; + const updatedProduct = { ...product, [field]: selectedValue }; - switch (field) { - case "uom": { - const u = selectOptions.uom.find(x => x.value === selectedValue); - updatedProduct.uomName = u?.label || ""; - break; - } - case "category": { - const c = selectOptions.categories.find(x => x.value === selectedValue); - updatedProduct.categoryName = c?.label || ""; - updatedProduct.subCategory = ""; - updatedProduct.subCategoryName = ""; - updatedProduct.brand = ""; - updatedProduct.brandName = ""; - break; - } - case "subCategory": { - const sc = product.availableSubCategories?.find(x => x.value === selectedValue); - updatedProduct.subCategoryName = sc?.label || ""; - updatedProduct.brand = ""; - updatedProduct.brandName = ""; - break; - } - case "brand": { - const b = product.availableBrands?.find(x => x.value === selectedValue); - updatedProduct.brandName = b?.label || ""; - break; - } - case "tax": { - const t = selectOptions.tax.find(x => x.value === selectedValue); - updatedProduct.taxName = t?.label || ""; - break; - } - default: - break; - } - return updatedProduct; - }); - } - - // Helper: fetch subcategories - const fetchSubCategories = async (categoryId) => { - try { - const response = await dispatch(getProdSubCatData({ ConfigId: categoryId })).unwrap(); - if (response?.status !== 200) return { availableSubCategories: [], generalSubCat: null }; - - const subCategories = response?.data?.data || []; - const availableSubCategories = subCategories - .filter(s => s.NumFId === parseInt(categoryId) && !!s?.ConfigName) - .map(s => ({ label: s.ConfigName, value: s.ConfigId })); - - const generalSubCat = availableSubCategories.find(s => s.label?.toLowerCase() === "general"); - return { availableSubCategories, generalSubCat }; - } catch (err) { - console.error("Error fetching subcategories:", err); - return { availableSubCategories: [], generalSubCat: null }; - } - }; - - // Helper: fetch brands - const fetchBrands = async (subCategoryId) => { - try { - const response = await dispatch(getBrandData({ ConfigId: subCategoryId })).unwrap(); - if (response?.status !== 200) return []; - - return response?.data?.data - ?.filter(b => b.NumFId === parseInt(subCategoryId) && !!b?.ConfigName) - ?.map(b => ({ label: b?.ConfigName, value: b.ConfigId })) || []; - } catch (err) { - console.error("Error fetching brands:", err); - return []; - } - }; - - // Handle CATEGORY change → fetch subcategories (+auto "General") - if (field === "category" && selectedValue) { - const { availableSubCategories, generalSubCat } = await fetchSubCategories(selectedValue); - let availableBrands = []; - - if (generalSubCat) { - availableBrands = await fetchBrands(generalSubCat.value); - } - - newDefaults = { - ...newDefaults, - availableSubCategories, - subCategory: generalSubCat?.value || null, - availableBrands, - brand: null - }; - - if (applyToAll) { - updatedProducts = updatedProducts.map(p => ({ - ...p, - availableSubCategories, - subCategory: generalSubCat?.value || "", - subCategoryName: generalSubCat?.label || "", - availableBrands, - brand: "", - brandName: "" - })); - } - handleDefaultCategoryChange(selectedValue) - } - - // Handle SUBCATEGORY change → fetch brands - if (field === "subCategory" && selectedValue) { - - const availableBrands = await fetchBrands(selectedValue); - - newDefaults = { - ...newDefaults, - availableBrands, - brand: null - }; - - if (applyToAll) { - updatedProducts = updatedProducts.map(p => { - if (field === 'subCategory' && p.category !== selectedDefaultCategory) return p; - return { - ...p, - availableBrands, - brand: "", - brandName: "" + switch (field) { + case 'uom': { + const u = selectOptions.uom.find( + (x) => x.value === selectedValue + ); + updatedProduct.uomName = u?.label || ''; + break; + } + case 'category': { + const c = selectOptions.categories.find( + (x) => x.value === selectedValue + ); + updatedProduct.categoryName = c?.label || ''; + updatedProduct.subCategory = ''; + updatedProduct.subCategoryName = ''; + updatedProduct.brand = ''; + updatedProduct.brandName = ''; + break; + } + case 'subCategory': { + const sc = product.availableSubCategories?.find( + (x) => x.value === selectedValue + ); + updatedProduct.subCategoryName = sc?.label || ''; + updatedProduct.brand = ''; + updatedProduct.brandName = ''; + break; + } + case 'brand': { + const b = product.availableBrands?.find( + (x) => x.value === selectedValue + ); + updatedProduct.brandName = b?.label || ''; + break; + } + case 'tax': { + const t = selectOptions.tax.find( + (x) => x.value === selectedValue + ); + updatedProduct.taxName = t?.label || ''; + break; + } + default: + break; } + return updatedProduct; }); } - handleDefaultSubCategoryChange(selectedValue) - } - if (field === "brand" && selectedValue) setSelectedDefaultBrand(selectedValue); + // Helper: fetch subcategories + const fetchSubCategories = async (categoryId) => { + try { + const response = await dispatch( + getProdSubCatData({ ConfigId: categoryId }) + ).unwrap(); + if (response?.status !== 200) + return { availableSubCategories: [], generalSubCat: null }; - // Final state updates - if (applyToAll) setProducts(updatedProducts); - setDefaults(newDefaults); + const subCategories = response?.data?.data || []; + const availableSubCategories = subCategories + .filter((s) => s.NumFId === parseInt(categoryId) && !!s?.ConfigName) + .map((s) => ({ label: s.ConfigName, value: s.ConfigId })); - setMessageType("success"); - setMessageData(`Default ${field.toUpperCase()} set successfully${applyToAll ? " and applied to all rows" : ""}`); - }, [products, selectOptions, dispatch, defaults]); + const generalSubCat = availableSubCategories.find( + (s) => s.label?.toLowerCase() === 'general' + ); + return { availableSubCategories, generalSubCat }; + } catch (err) { + console.error('Error fetching subcategories:', err); + return { availableSubCategories: [], generalSubCat: null }; + } + }; + + // Helper: fetch brands + const fetchBrands = async (subCategoryId) => { + try { + const response = await dispatch( + getBrandData({ ConfigId: subCategoryId }) + ).unwrap(); + if (response?.status !== 200) return []; + + return ( + response?.data?.data + ?.filter( + (b) => b.NumFId === parseInt(subCategoryId) && !!b?.ConfigName + ) + ?.map((b) => ({ label: b?.ConfigName, value: b.ConfigId })) || [] + ); + } catch (err) { + console.error('Error fetching brands:', err); + return []; + } + }; + + // Handle CATEGORY change → fetch subcategories (+auto "General") + if (field === 'category' && selectedValue) { + const { availableSubCategories, generalSubCat } = + await fetchSubCategories(selectedValue); + let availableBrands = []; + + if (generalSubCat) { + availableBrands = await fetchBrands(generalSubCat.value); + } + + newDefaults = { + ...newDefaults, + availableSubCategories, + subCategory: generalSubCat?.value || null, + availableBrands, + brand: null, + }; + + if (applyToAll) { + updatedProducts = updatedProducts.map((p) => ({ + ...p, + availableSubCategories, + subCategory: generalSubCat?.value || '', + subCategoryName: generalSubCat?.label || '', + availableBrands, + brand: '', + brandName: '', + })); + } + handleDefaultCategoryChange(selectedValue); + } + + // Handle SUBCATEGORY change → fetch brands + if (field === 'subCategory' && selectedValue) { + const availableBrands = await fetchBrands(selectedValue); + + newDefaults = { + ...newDefaults, + availableBrands, + brand: null, + }; + + if (applyToAll) { + updatedProducts = updatedProducts.map((p) => { + if ( + field === 'subCategory' && + p.category !== selectedDefaultCategory + ) + return p; + return { + ...p, + availableBrands, + brand: '', + brandName: '', + }; + }); + } + handleDefaultSubCategoryChange(selectedValue); + } + + if (field === 'brand' && selectedValue) + setSelectedDefaultBrand(selectedValue); + + // Final state updates + if (applyToAll) setProducts(updatedProducts); + setDefaults(newDefaults); + + setMessageType('success'); + setMessageData( + `Default ${field.toUpperCase()} set successfully${applyToAll ? ' and applied to all rows' : ''}` + ); + }, + [products, selectOptions, dispatch, defaults] + ); const handleDefaultCategoryChange = async (selectedCategory) => { setSelectedDefaultCategory(selectedCategory); setSelectedDefaultSubCategory(null); setSelectedDefaultBrand(null); - const subCategoryResponse = await dispatch(getProdSubCatData({ ConfigId: selectedCategory }))?.unwrap(); - setDefaultSubCategories(subCategoryResponse?.data?.data - ?.filter(sub => sub.NumFId === selectedCategory && !!sub.ConfigName) - ?.map(sub => ({ label: sub.ConfigName, value: sub.ConfigId }))); + const subCategoryResponse = await dispatch( + getProdSubCatData({ ConfigId: selectedCategory }) + )?.unwrap(); + setDefaultSubCategories( + subCategoryResponse?.data?.data + ?.filter((sub) => sub.NumFId === selectedCategory && !!sub.ConfigName) + ?.map((sub) => ({ label: sub.ConfigName, value: sub.ConfigId })) + ); }; const handleDefaultSubCategoryChange = async (selectedSubCategory) => { setSelectedDefaultSubCategory(selectedSubCategory); setSelectedDefaultBrand(null); - const brandResponse = await dispatch(getBrandData({ ConfigId: selectedSubCategory }))?.unwrap(); - setDefaultBrands(brandResponse?.data?.data - ?.filter(brand => brand.NumFId === selectedSubCategory && !!brand.ConfigName) - ?.map(brand => ({ label: brand.ConfigName, value: brand.ConfigId }))); + const brandResponse = await dispatch( + getBrandData({ ConfigId: selectedSubCategory }) + )?.unwrap(); + setDefaultBrands( + brandResponse?.data?.data + ?.filter( + (brand) => brand.NumFId === selectedSubCategory && !!brand.ConfigName + ) + ?.map((brand) => ({ label: brand.ConfigName, value: brand.ConfigId })) + ); }; // --- Handlers --- - const handleInputChange = useCallback((id, field, value) => { - setProducts(prev => - prev.map(p => { - if (p.id !== id) return p; - let updatedRow = { ...p, [field]: value }; - // Update label fields - if (field === "uom") { - const selectedUOM = selectOptions.uom.find(u => u.value === value); - updatedRow.uomName = selectedUOM?.label || ""; - } else if (field === "category") { - const selectedCategory = selectOptions.categories.find(c => c.value === value); - updatedRow.categoryName = selectedCategory?.label || ""; - } else if (field === "subCategory") { - const selectedSubCategory = p.availableSubCategories?.find(s => s.value === value); - updatedRow.subCategoryName = selectedSubCategory?.label || ""; - } else if (field === "tax") { - const selectedTax = selectOptions.tax.find(t => t.value === value); - updatedRow.taxName = selectedTax?.label || ""; - } else if (field === "mrp") { - updatedRow.salePrice = ""; - setErrors(prevErrors => { - const { [`${id}-salePrice`]: _, ...rest } = prevErrors; - return rest; - }); - } - - // Duplicate check for productName, uom, nou - if (["productName", "uom", "nou", "category", "subCategory"].includes(field)) { - const trimmedName = field === "productName" ? value?.trim().toLowerCase() : updatedRow.productName?.trim().toLowerCase(); - const uomValue = field === "uom" ? value : updatedRow.uom; - const nouValue = field === "nou" ? value : updatedRow.nou; - const categoryValue = field === "category" ? value : updatedRow.category; - const subCategoryValue = field === "subCategory" ? value : updatedRow.subCategory; - - const duplicate = products.some(prod => - prod.id !== id && - prod.productName?.trim().toLowerCase() === trimmedName && - prod.uom === uomValue && - prod.nou === nouValue && - prod.category === categoryValue && - prod.subCategory === subCategoryValue - ); - - if (duplicate && trimmedName) { - setErrors(prev => ({ - ...prev, - [`${id}-productName`]: "Product already exists" - })); - } else { - setErrors(prev => { - const { [`${id}-productName`]: _, ...rest } = prev; + const handleInputChange = useCallback( + (id, field, value) => { + setProducts((prev) => + prev.map((p) => { + if (p.id !== id) return p; + let updatedRow = { ...p, [field]: value }; + // Update label fields + if (field === 'uom') { + const selectedUOM = selectOptions.uom.find( + (u) => u.value === value + ); + updatedRow.uomName = selectedUOM?.label || ''; + } else if (field === 'category') { + const selectedCategory = selectOptions.categories.find( + (c) => c.value === value + ); + updatedRow.categoryName = selectedCategory?.label || ''; + } else if (field === 'subCategory') { + const selectedSubCategory = p.availableSubCategories?.find( + (s) => s.value === value + ); + updatedRow.subCategoryName = selectedSubCategory?.label || ''; + } else if (field === 'tax') { + const selectedTax = selectOptions.tax.find( + (t) => t.value === value + ); + updatedRow.taxName = selectedTax?.label || ''; + } else if (field === 'mrp') { + updatedRow.salePrice = ''; + setErrors((prevErrors) => { + const { [`${id}-salePrice`]: _, ...rest } = prevErrors; return rest; }); } - } - setTimeout(() => validateField(id, field, value, updatedRow), 0); - if (["category", "subCategory"].includes(field)) { - setTimeout(() => validateField(id, "productName", updatedRow.productName, updatedRow), 0); - } - return updatedRow; - }) - ); - }, [validateField, selectOptions.uom, selectOptions.categories, selectOptions.tax, products]); + // Duplicate check for productName, uom, nou + if ( + ['productName', 'uom', 'nou', 'category', 'subCategory'].includes( + field + ) + ) { + const trimmedName = + field === 'productName' + ? value?.trim().toLowerCase() + : updatedRow.productName?.trim().toLowerCase(); + const uomValue = field === 'uom' ? value : updatedRow.uom; + const nouValue = field === 'nou' ? value : updatedRow.nou; + const categoryValue = + field === 'category' ? value : updatedRow.category; + const subCategoryValue = + field === 'subCategory' ? value : updatedRow.subCategory; - const handleCategoryChange = useCallback(async (productId, categoryId) => { - const selectedCategory = selectOptions.categories.find(c => c.value === categoryId); + const duplicate = products.some( + (prod) => + prod.id !== id && + prod.productName?.trim().toLowerCase() === trimmedName && + prod.uom === uomValue && + prod.nou === nouValue && + prod.category === categoryValue && + prod.subCategory === subCategoryValue + ); - // Reset product fields - handleInputChange(productId, "category", categoryId); - handleInputChange(productId, "categoryName", selectedCategory?.label || ""); - handleInputChange(productId, "subCategory", ""); - handleInputChange(productId, "subCategoryName", ""); - handleInputChange(productId, "brand", ""); - handleInputChange(productId, "brandName", ""); - handleInputChange(productId, "availableBrands", []); + if (duplicate && trimmedName) { + setErrors((prev) => ({ + ...prev, + [`${id}-productName`]: 'Product already exists', + })); + } else { + setErrors((prev) => { + const { [`${id}-productName`]: _, ...rest } = prev; + return rest; + }); + } + } - if (!categoryId) return; + setTimeout(() => validateField(id, field, value, updatedRow), 0); + if (['category', 'subCategory'].includes(field)) { + setTimeout( + () => + validateField( + id, + 'productName', + updatedRow.productName, + updatedRow + ), + 0 + ); + } + return updatedRow; + }) + ); + }, + [ + validateField, + selectOptions.uom, + selectOptions.categories, + selectOptions.tax, + products, + ] + ); - try { - const response = await dispatch(getProdSubCatData({ ConfigId: categoryId })).unwrap(); - if (response?.status !== 200) return; - - const allSubCats = response?.data?.data || []; - const availableSubCategories = allSubCats - .filter(subCat => subCat.NumFId === parseInt(categoryId) && !!subCat?.ConfigName) - .map(subCat => ({ label: subCat.ConfigName, value: subCat.ConfigId })); - - let subCategoryId = ""; - let subCategoryName = ""; - let availableBrands = []; - - // ✅ If selected category is "general", try to auto-select "general" subCategory + fetch brands - const isGeneralCategory = prodCategories.some( - cat => cat.ConfigId === parseInt(categoryId) && cat.ConfigName?.toLowerCase() === "general" + const handleCategoryChange = useCallback( + async (productId, categoryId) => { + const selectedCategory = selectOptions.categories.find( + (c) => c.value === categoryId ); - if (isGeneralCategory) { - const generalSubCat = availableSubCategories.find( - subCat => subCat.label?.toLowerCase() === "general" + // Reset product fields + handleInputChange(productId, 'category', categoryId); + handleInputChange( + productId, + 'categoryName', + selectedCategory?.label || '' + ); + handleInputChange(productId, 'subCategory', ''); + handleInputChange(productId, 'subCategoryName', ''); + handleInputChange(productId, 'brand', ''); + handleInputChange(productId, 'brandName', ''); + handleInputChange(productId, 'availableBrands', []); + + if (!categoryId) return; + + try { + const response = await dispatch( + getProdSubCatData({ ConfigId: categoryId }) + ).unwrap(); + if (response?.status !== 200) return; + + const allSubCats = response?.data?.data || []; + const availableSubCategories = allSubCats + .filter( + (subCat) => + subCat.NumFId === parseInt(categoryId) && !!subCat?.ConfigName + ) + .map((subCat) => ({ + label: subCat.ConfigName, + value: subCat.ConfigId, + })); + + let subCategoryId = ''; + let subCategoryName = ''; + let availableBrands = []; + + // ✅ If selected category is "general", try to auto-select "general" subCategory + fetch brands + const isGeneralCategory = prodCategories.some( + (cat) => + cat.ConfigId === parseInt(categoryId) && + cat.ConfigName?.toLowerCase() === 'general' ); - if (generalSubCat) { - subCategoryId = generalSubCat.value; - subCategoryName = generalSubCat.label; + if (isGeneralCategory) { + const generalSubCat = availableSubCategories.find( + (subCat) => subCat.label?.toLowerCase() === 'general' + ); - try { - const brandResponse = await dispatch(getBrandData({ ConfigId: generalSubCat.value })).unwrap(); - availableBrands = - brandResponse?.data?.data - ?.filter(b => b.NumFId === parseInt(generalSubCat.value) && !!b?.ConfigName) - ?.map(b => ({ label: b.ConfigName, value: b.ConfigId })) || []; - } catch (err) { - console.error("Error fetching brands:", err); + if (generalSubCat) { + subCategoryId = generalSubCat.value; + subCategoryName = generalSubCat.label; + + try { + const brandResponse = await dispatch( + getBrandData({ ConfigId: generalSubCat.value }) + ).unwrap(); + availableBrands = + brandResponse?.data?.data + ?.filter( + (b) => + b.NumFId === parseInt(generalSubCat.value) && + !!b?.ConfigName + ) + ?.map((b) => ({ label: b.ConfigName, value: b.ConfigId })) || + []; + } catch (err) { + console.error('Error fetching brands:', err); + } } } - } - // ✅ Apply updates in one go (no async inside setProducts) - setProducts(prev => - prev.map(p => - p.id === productId - ? { - ...p, - availableSubCategories, - subCategory: subCategoryId, - subCategoryName, - availableBrands, - brand: "", - brandName: "", - } - : p + // ✅ Apply updates in one go (no async inside setProducts) + setProducts((prev) => + prev.map((p) => + p.id === productId + ? { + ...p, + availableSubCategories, + subCategory: subCategoryId, + subCategoryName, + availableBrands, + brand: '', + brandName: '', + } + : p + ) + ); + + handleInputChange(productId, 'subCategory', subCategoryId); + } catch (error) { + console.error('Error fetching subcategories:', error); + } + }, + [handleInputChange, dispatch, prodCategories, selectOptions.categories] + ); + + const handleSubCategoryChange = useCallback( + async (productId, subCategoryId) => { + const selectedSubCategory = selectOptions.subCategories.find( + (s) => s.value === subCategoryId + ); + handleInputChange(productId, 'subCategory', subCategoryId); + handleInputChange( + productId, + 'subCategoryName', + selectedSubCategory?.label || '' + ); + handleInputChange(productId, 'brand', ''); + handleInputChange(productId, 'brandName', ''); + if (subCategoryId) { + try { + const response = await dispatch( + getBrandData({ ConfigId: subCategoryId }) + ).unwrap(); + setProducts((prev) => { + return prev.map((p) => { + if (p.id === productId) { + return { + ...p, + availableBrands: response?.data?.data + ?.filter( + (brand) => + brand.NumFId === parseInt(subCategoryId) && + !!brand?.ConfigName + ) + ?.map((brand) => ({ + label: brand.ConfigName, + value: brand.ConfigId, + })), + brand: '', + brandName: '', + }; + } + return p; + }); + }); + } catch (error) {} + } + }, + [handleInputChange, selectOptions.subCategories] + ); + + // --- Row operations --- + const createNewRow = useCallback( + (overrides = {}) => { + const newId = Math.max(0, ...products.map((p) => p.id), 0) + 1; + const defaultUOM = selectOptions.uom.find( + (u) => u.value === defaults.uom + ); + const defaultCategory = selectOptions.categories.find( + (c) => c.value === defaults.category + ); + const defaultSubCategory = defaults.availableSubCategories?.find( + (s) => s.value === defaults.subCategory + ); + const defaultTax = selectOptions.tax.find( + (t) => t.value === defaults.tax + ); + + // Clean overrides: drop null/undefined so defaults are preserved + const cleanedOverrides = Object.fromEntries( + Object.entries(overrides).filter( + ([_, v]) => v !== null && v !== undefined && v !== '' ) ); - handleInputChange(productId, "subCategory", subCategoryId); - - } catch (error) { - console.error("Error fetching subcategories:", error); - } - }, [handleInputChange, dispatch, prodCategories, selectOptions.categories]); - - const handleSubCategoryChange = useCallback(async (productId, subCategoryId) => { - const selectedSubCategory = selectOptions.subCategories.find(s => s.value === subCategoryId); - handleInputChange(productId, "subCategory", subCategoryId); - handleInputChange(productId, "subCategoryName", selectedSubCategory?.label || ""); - handleInputChange(productId, "brand", ""); - handleInputChange(productId, "brandName", ""); - if (subCategoryId) { - try { - const response = await dispatch(getBrandData({ ConfigId: subCategoryId })).unwrap(); - setProducts(prev => { - return prev.map(p => { - if (p.id === productId) { - return { - ...p, - availableBrands: response?.data?.data - ?.filter(brand => brand.NumFId === parseInt(subCategoryId) && !!brand?.ConfigName) - ?.map(brand => ({ label: brand.ConfigName, value: brand.ConfigId })), - brand: "", - brandName: "", - }; - } - return p; - }); - }); - } catch (error) { - - } - } - }, [handleInputChange, selectOptions.subCategories]); - - // --- Row operations --- - const createNewRow = useCallback((overrides = {}) => { - const newId = Math.max(0, ...products.map(p => p.id), 0) + 1; - const defaultUOM = selectOptions.uom.find(u => u.value === defaults.uom); - const defaultCategory = selectOptions.categories.find(c => c.value === defaults.category); - const defaultSubCategory = defaults.availableSubCategories?.find(s => s.value === defaults.subCategory); - const defaultTax = selectOptions.tax.find(t => t.value === defaults.tax); - - // Clean overrides: drop null/undefined so defaults are preserved - const cleanedOverrides = Object.fromEntries( - Object.entries(overrides).filter(([_, v]) => v !== null && v !== undefined && v !== "") - ); - - - return { - id: newId, - productName: "", - uom: defaults.uom || "", - uomName: defaultUOM?.label || "", - nou: "1", - mrp: "", - salePrice: "", - category: defaults.category, - categoryName: defaultCategory?.label || "", - subCategory: defaults.subCategory, - subCategoryName: defaultSubCategory?.label || "", - tax: defaults.tax, - taxName: defaultTax?.label || "", - barcode: "", - availableSubCategories: defaults.availableSubCategories, - availableBrands: defaults.availableBrands, - emiAllowed: 'No', - autoGenerateQrcode: 'No', - stockMaintenance: 'No', - tokenMaintenance: 'No', - amountPerPiece: 'No', - autoGenerateOnePcQrcode: 'No', - productType: productTypeData?.find(pt => pt?.ConfigName === "Product")?.ConfigId, - cess: "", - discountType: "F", - discountLimit: "", - hsnCode: "", - partNumber: "", - batchNumber: "", - modelNumber: "", - rack: "", - imei1: "", - imei2: "", - serialNumber: "", - macId: "", - manufactureDate: null, - expireDate: null, - expiryNotificationDays: 0, - availableFrom: null, - availableTo: null, - productImage: "", - ...cleanedOverrides, - }; - }, [products, defaults, selectOptions.uom, selectOptions.categories, selectOptions.tax]); + return { + id: newId, + productName: '', + uom: defaults.uom || '', + uomName: defaultUOM?.label || '', + nou: '1', + mrp: '', + salePrice: '', + category: defaults.category, + categoryName: defaultCategory?.label || '', + subCategory: defaults.subCategory, + subCategoryName: defaultSubCategory?.label || '', + tax: defaults.tax, + taxName: defaultTax?.label || '', + barcode: '', + availableSubCategories: defaults.availableSubCategories, + availableBrands: defaults.availableBrands, + emiAllowed: 'No', + autoGenerateQrcode: 'No', + stockMaintenance: 'No', + tokenMaintenance: 'No', + amountPerPiece: 'No', + autoGenerateOnePcQrcode: 'No', + productType: productTypeData?.find((pt) => pt?.ConfigName === 'Product') + ?.ConfigId, + cess: '', + discountType: 'F', + discountLimit: '', + hsnCode: '', + partNumber: '', + batchNumber: '', + modelNumber: '', + rack: '', + imei1: '', + imei2: '', + serialNumber: '', + macId: '', + manufactureDate: null, + expireDate: null, + expiryNotificationDays: 0, + availableFrom: null, + availableTo: null, + productImage: '', + ...cleanedOverrides, + }; + }, + [ + products, + defaults, + selectOptions.uom, + selectOptions.categories, + selectOptions.tax, + ] + ); useEffect(() => { // Only add if products is empty and defaults are available @@ -860,51 +1108,68 @@ const TurboAddForm = () => { ) { setProducts([createNewRow()]); } - }, [defaults.uom, defaults.category, defaults.subCategory, defaults.tax, products?.length, createNewRow]); + }, [ + defaults.uom, + defaults.category, + defaults.subCategory, + defaults.tax, + products?.length, + createNewRow, + ]); const addNewRow = useCallback(() => { // Fields to check - const requiredFields = ["productName", "nou", "uom"]; + const requiredFields = ['productName', 'nou', 'uom']; // Find the first incomplete row - const incompleteIndex = products.findIndex(row => - requiredFields.some(field => row[field] === null || row[field] === undefined || row[field] === "") + const incompleteIndex = products.findIndex((row) => + requiredFields.some( + (field) => + row[field] === null || row[field] === undefined || row[field] === '' + ) ); if (incompleteIndex !== -1) { // Move the incomplete row to the top - setProducts(prev => { + setProducts((prev) => { const newProducts = [...prev]; const [incompleteRow] = newProducts.splice(incompleteIndex, 1); newProducts.unshift(incompleteRow); return newProducts; }); - setMessageType("error"); - setMessageData("Please fill all required fields in the highlighted row before adding a new row."); + setMessageType('error'); + setMessageData( + 'Please fill all required fields in the highlighted row before adding a new row.' + ); return; } - setProducts(prev => [createNewRow(), ...prev]); + setProducts((prev) => [createNewRow(), ...prev]); }, [products, createNewRow]); - const insertRowAbove = useCallback(targetIndex => { - const newRow = createNewRow(); - setProducts(prev => { - const newProducts = [...prev]; - newProducts.splice(targetIndex, 0, newRow); - return newProducts; - }); - }, [createNewRow]); + const insertRowAbove = useCallback( + (targetIndex) => { + const newRow = createNewRow(); + setProducts((prev) => { + const newProducts = [...prev]; + newProducts.splice(targetIndex, 0, newRow); + return newProducts; + }); + }, + [createNewRow] + ); - const deleteRow = useCallback(id => { - setProducts(prev => prev.filter(p => p.id !== id)); - setErrors(prev => { + const deleteRow = useCallback((id) => { + setProducts((prev) => prev.filter((p) => p.id !== id)); + setErrors((prev) => { const newErrors = { ...prev }; - Object.keys(newErrors).forEach(key => { if (key.startsWith(`${id}-`)) delete newErrors[key]; }); + Object.keys(newErrors).forEach((key) => { + if (key.startsWith(`${id}-`)) delete newErrors[key]; + }); return newErrors; }); }, []); const resetAll = useCallback(() => { setProducts([]); - setSearchTerm(""); + setSearchTerm(''); setSelectedCell(null); setErrors({}); }, []); @@ -912,38 +1177,40 @@ const TurboAddForm = () => { // --- Submit handler --- const handleSubmit = useCallback(async () => { if (products?.length === 0) { - setMessageType("error"); - setMessageData("Please add at least one product before submitting."); + setMessageType('error'); + setMessageData('Please add at least one product before submitting.'); return; } // Duplicate check: productName + uom + nou let duplicateError = {}; products.forEach((product, idx) => { - const isDuplicate = products?.some((p, i) => - i !== idx && - p.productName?.trim().toLowerCase() === product.productName?.trim().toLowerCase() && - p.uom === product.uom && - p.nou === product.nou && - p?.category === product?.category && - p?.subCategory === product?.subCategory + const isDuplicate = products?.some( + (p, i) => + i !== idx && + p.productName?.trim().toLowerCase() === + product.productName?.trim().toLowerCase() && + p.uom === product.uom && + p.nou === product.nou && + p?.category === product?.category && + p?.subCategory === product?.subCategory ); if (isDuplicate && product.productName?.trim()) { - duplicateError[`${product.id}-productName`] = "Product already exists"; + duplicateError[`${product.id}-productName`] = 'Product already exists'; } }); if (Object.keys(duplicateError)?.length > 0) { - setErrors(prev => ({ ...prev, ...duplicateError })); - setMessageType("error"); - setMessageData("Duplicate products found. Please fix before submitting."); + setErrors((prev) => ({ ...prev, ...duplicateError })); + setMessageType('error'); + setMessageData('Duplicate products found. Please fix before submitting.'); return; } setIsSubmitting(true); try { let hasErrors = false; const allErrors = {}; - products.forEach(product => { - REQUIRED_FIELDS.forEach(field => { - const error = validationRules[field]?.(product[field], product) || ""; + products.forEach((product) => { + REQUIRED_FIELDS.forEach((field) => { + const error = validationRules[field]?.(product[field], product) || ''; if (error) { allErrors[`${product.id}-${field}`] = error; hasErrors = true; @@ -953,71 +1220,85 @@ const TurboAddForm = () => { if (hasErrors) { setErrors(allErrors); - setMessageType("error"); - setMessageData("Please fix all validation errors before submitting."); + setMessageType('error'); + setMessageData('Please fix all validation errors before submitting.'); return; } - const postData = products.map(product => ({ + const postData = products.map((product) => ({ AppId: AppId || 0, - CompId: CompId || "", - BranchId: BranchId || "", + CompId: CompId || '', + BranchId: BranchId || '', CreatedBy: UserId || 0, - ProdName: product.productName?.trim() || "", - ProdVariantName: product.prodVariantName || "", - Size: parseFloat(product.nou) || "", - UOM: product.uomName || "", + ProdName: product.productName?.trim() || '', + ProdVariantName: product.prodVariantName || '', + Size: parseFloat(product.nou) || '', + UOM: product.uomName || '', MRP: parseFloat(product.mrp) || 0, WhSalePrice: parseFloat(product.whSalePrice) || 0, SellPrice: parseFloat(product.salePrice) || 0, - ProdCat: product.categoryName || "", - ProdSubCat: product.subCategoryName || "", - Brand: product.brandName || product.brand || "", - AutoGenerateQr: product.autoGenerateQrcode || "", - QRCode: product.barcode || product.qrcode || "", - StockAvailable: product.stockAvailable || "N", - TaxId: product.taxName || product.tax || "", - HSNCode: product.hsnCode || "", - PartNumber: product.partNumber || "", + ProdCat: product.categoryName || '', + ProdSubCat: product.subCategoryName || '', + Brand: product.brandName || product.brand || '', + AutoGenerateQr: product.autoGenerateQrcode || '', + QRCode: product.barcode || product.qrcode || '', + StockAvailable: product.stockAvailable || 'N', + TaxId: product.taxName || product.tax || '', + HSNCode: product.hsnCode || '', + PartNumber: product.partNumber || '', Rack: product.rack ? parseInt(product.rack) : 0, - ManufDate: product.manufactureDate || "", - ExpDate: product.expireDate || "", - AvailableFrom: product.availableFrom || "", - AvailableTo: product.availableTo || "", - ProdLogo: product.productImage || "", - OnePcsAvailable: product.onePcsAvailable || "No", - AutoGenerateSingleQr: product.autoGenerateOnePcQrcode || "N", - OnePcQR: product.onePcQrcode || "", - TokenAvailable: product.tokenAvailable || product.tokenMaintenance || "N", + ManufDate: product.manufactureDate || '', + ExpDate: product.expireDate || '', + AvailableFrom: product.availableFrom || '', + AvailableTo: product.availableTo || '', + ProdLogo: product.productImage || '', + OnePcsAvailable: product.onePcsAvailable || 'No', + AutoGenerateSingleQr: product.autoGenerateOnePcQrcode || 'N', + OnePcQR: product.onePcQrcode || '', + TokenAvailable: + product.tokenAvailable || product.tokenMaintenance || 'N', OpeningQty: product.openingQty ? parseFloat(product.openingQty) : 0, - QtyBasedPrice: product.qtyBasedPrice || "", - InwardDate: product.inwardDate || "", - SuppId: product.suppId || "", - Reference: product.reference || "", + QtyBasedPrice: product.qtyBasedPrice || '', + InwardDate: product.inwardDate || '', + SuppId: product.suppId || '', + Reference: product.reference || '', ReceivedQty: product.receivedQty ? parseFloat(product.receivedQty) : 0, AcceptedQty: product.acceptedQty ? parseFloat(product.acceptedQty) : 0, RejectedQty: product.rejectedQty ? parseFloat(product.rejectedQty) : 0, - RejectionReason: product.rejectionReason || "", + RejectionReason: product.rejectionReason || '', IssuedQty: product.issuedQty ? parseFloat(product.issuedQty) : 0, BalanceQty: product.balanceQty ? parseFloat(product.balanceQty) : 0, InwardPrice: product.inwardPrice ? parseFloat(product.inwardPrice) : 0, OfferPrice: product.offerPrice ? parseFloat(product.offerPrice) : 0, - SpecialPrice: product.specialPrice ? parseFloat(product.specialPrice) : 0, + SpecialPrice: product.specialPrice + ? parseFloat(product.specialPrice) + : 0, Cess: product.cess ? parseFloat(product.cess) : 0, })); await dispatch(bulkpostdata({ ProdDetails: postData })).unwrap(); - setMessageType("success"); - setMessageData(`Successfully added ${postData?.length > 1 ? `${postData?.length} products` : "1 product"}`); + setMessageType('success'); + setMessageData( + `Successfully added ${postData?.length > 1 ? `${postData?.length} products` : '1 product'}` + ); resetAll(); } catch (error) { - console.error("Bulk product submit error:", error); - setMessageType("error"); - setMessageData("Failed to add products. Please try again."); + console.error('Bulk product submit error:', error); + setMessageType('error'); + setMessageData('Failed to add products. Please try again.'); } finally { setIsSubmitting(false); } - }, [products, validationRules, AppId, CompId, resetAll, dispatch, UserId, BranchId]); + }, [ + products, + validationRules, + AppId, + CompId, + resetAll, + dispatch, + UserId, + BranchId, + ]); const buildColumnMapping = useCallback(() => { const columns = [ @@ -1039,7 +1320,7 @@ const TurboAddForm = () => { } if (hasField('WholeSale Price')) { - columns?.push({ id: 'whSalePrice', index: 'whSalePrice' }) + columns?.push({ id: 'whSalePrice', index: 'whSalePrice' }); } if (hasField('EMI Allowed')) { @@ -1063,7 +1344,10 @@ const TurboAddForm = () => { if (hasField('Amount Per Piece')) { columns.push({ id: 'amountPerPiece', index: 'amountPerPiece' }); columns.push({ id: 'perPieceAmount', index: 'perPieceAmount' }); - columns.push({ id: 'autoGenerateOnePcQrcode', index: 'autoGenerateOnePcQrcode' }); + columns.push({ + id: 'autoGenerateOnePcQrcode', + index: 'autoGenerateOnePcQrcode', + }); columns.push({ id: 'onePcQrcode', index: 'onePcQrcode' }); columns.push({ id: 'noOfPieceInside', index: 'noOfPieceInside' }); } @@ -1133,7 +1417,10 @@ const TurboAddForm = () => { } if (hasField('Expiry Notification Days')) { - columns.push({ id: 'expiryNotificationDays', index: 'expiryNotificationDays' }); + columns.push({ + id: 'expiryNotificationDays', + index: 'expiryNotificationDays', + }); } if (hasField('Available From')) { @@ -1148,110 +1435,146 @@ const TurboAddForm = () => { }, [hasField]); // Memoize the column mapping - const columnMapping = useMemo(() => buildColumnMapping(), [buildColumnMapping]); + const columnMapping = useMemo( + () => buildColumnMapping(), + [buildColumnMapping] + ); // Helper function to get current column position - const getCurrentColumnIndex = useCallback((cellId) => { - // Extract field name from cellId (e.g., "0-brand" -> "brand", "0-1" -> find by index 1) - const [rowStr, fieldStr] = cellId?.split('-'); + const getCurrentColumnIndex = useCallback( + (cellId) => { + // Extract field name from cellId (e.g., "0-brand" -> "brand", "0-1" -> find by index 1) + const [rowStr, fieldStr] = cellId?.split('-'); - if (isNaN(fieldStr)) { - // It's a field name like "brand" - return columnMapping.findIndex(col => col.id === fieldStr); - } else { - // It's a numeric index - const numericIndex = parseInt(fieldStr); - return columnMapping.findIndex(col => col.index === numericIndex); - } - }, [columnMapping]); + if (isNaN(fieldStr)) { + // It's a field name like "brand" + return columnMapping.findIndex((col) => col.id === fieldStr); + } else { + // It's a numeric index + const numericIndex = parseInt(fieldStr); + return columnMapping.findIndex((col) => col.index === numericIndex); + } + }, + [columnMapping] + ); // Helper function to build cell ID - const buildCellId = useCallback((rowIndex, columnIndex) => { - const column = columnMapping[columnIndex]; - if (!column) return null; + const buildCellId = useCallback( + (rowIndex, columnIndex) => { + const column = columnMapping[columnIndex]; + if (!column) return null; - return `${rowIndex}-${column.index}`; - }, [columnMapping]); + return `${rowIndex}-${column.index}`; + }, + [columnMapping] + ); // Updated handleKeyDown function - const handleKeyDown = useCallback(async (e, rowIndex, currentCellId) => { - const totalRows = products?.length; - const totalCols = columnMapping?.length; + const handleKeyDown = useCallback( + async (e, rowIndex, currentCellId) => { + const totalRows = products?.length; + const totalCols = columnMapping?.length; - // Get current column index - const currentColIndex = getCurrentColumnIndex(currentCellId); + // Get current column index + const currentColIndex = getCurrentColumnIndex(currentCellId); - const moveCell = (r, c) => { - const cellId = buildCellId(r, c); - if (cellId) { - setSelectedCell(cellId); - } - }; - - switch (e.key) { - case "Tab": - e.preventDefault(); - if (currentColIndex < totalCols - 1) { - moveCell(rowIndex, currentColIndex + 1); - } else if (rowIndex < totalRows - 1) { - moveCell(rowIndex + 1, 0); - } else { - addNewRow(); - setTimeout(() => moveCell(totalRows, 0), 0); + const moveCell = (r, c) => { + const cellId = buildCellId(r, c); + if (cellId) { + setSelectedCell(cellId); } - break; + }; - case "Enter": - e.preventDefault(); - const prodName = products?.find((p, idx) => idx === rowIndex)?.productName; - if (prodName) { - const qrResponse = await dispatch(getQrcodeData({ QRCode: prodName })).unwrap(); - if (qrResponse?.data?.statusCode === 1 && qrResponse?.data?.data?.filter((item) => item?.AppId === AppId && item?.CompId === CompId && item?.BranchId === BranchId)?.length > 0) { - setMessageType("error"); - setMessageData("Barcode already exists"); - return; + switch (e.key) { + case 'Tab': + e.preventDefault(); + if (currentColIndex < totalCols - 1) { + moveCell(rowIndex, currentColIndex + 1); + } else if (rowIndex < totalRows - 1) { + moveCell(rowIndex + 1, 0); + } else { + addNewRow(); + setTimeout(() => moveCell(totalRows, 0), 0); } - } - if (rowIndex > 0) { - moveCell(rowIndex - 1, currentColIndex); - } else { - addNewRow(); - setSelectedCell(null); - setTimeout(() => moveCell(rowIndex, currentColIndex), 0); - } - break; + break; - case "ArrowUp": - if (rowIndex > 0) { - moveCell(rowIndex - 1, currentColIndex); - } else { - addNewRow(); - setSelectedCell(null); - setTimeout(() => moveCell(rowIndex, currentColIndex), 0); - } - break; - case "ArrowDown": - if (rowIndex < totalRows - 1) { - moveCell(rowIndex + 1, currentColIndex); - } - break; + case 'Enter': + e.preventDefault(); + const prodName = products?.find( + (p, idx) => idx === rowIndex + )?.productName; + if (prodName) { + const qrResponse = await dispatch( + getQrcodeData({ QRCode: prodName }) + ).unwrap(); + if ( + qrResponse?.data?.statusCode === 1 && + qrResponse?.data?.data?.filter( + (item) => + item?.AppId === AppId && + item?.CompId === CompId && + item?.BranchId === BranchId + )?.length > 0 + ) { + setMessageType('error'); + setMessageData('Barcode already exists'); + return; + } + } + if (rowIndex > 0) { + moveCell(rowIndex - 1, currentColIndex); + } else { + addNewRow(); + setSelectedCell(null); + setTimeout(() => moveCell(rowIndex, currentColIndex), 0); + } + break; - // case "ArrowLeft": - // if (currentColIndex > 0) { - // moveCell(rowIndex, currentColIndex - 1); - // } - // break; + case 'ArrowUp': + if (rowIndex > 0) { + moveCell(rowIndex - 1, currentColIndex); + } else { + addNewRow(); + setSelectedCell(null); + setTimeout(() => moveCell(rowIndex, currentColIndex), 0); + } + break; + case 'ArrowDown': + if (rowIndex < totalRows - 1) { + moveCell(rowIndex + 1, currentColIndex); + } + break; - // case "ArrowRight": - // if (currentColIndex < totalCols - 1) { - // moveCell(rowIndex, currentColIndex + 1); - // } - // break; + // case "ArrowLeft": + // if (currentColIndex > 0) { + // moveCell(rowIndex, currentColIndex - 1); + // } + // break; - default: - break; - } - }, [products?.length, addNewRow, columnMapping, getCurrentColumnIndex, buildCellId, dispatch, AppId, CompId, BranchId, setMessageType, setMessageData]); + // case "ArrowRight": + // if (currentColIndex < totalCols - 1) { + // moveCell(rowIndex, currentColIndex + 1); + // } + // break; + + default: + break; + } + }, + [ + products?.length, + addNewRow, + columnMapping, + getCurrentColumnIndex, + buildCellId, + dispatch, + AppId, + CompId, + BranchId, + setMessageType, + setMessageData, + ] + ); // --- Focus management --- useEffect(() => { @@ -1262,65 +1585,79 @@ const TurboAddForm = () => { }, [selectedCell]); useEffect(() => { - debouncedProductLookup.current = debounce(async (enteredValue, rowIndex) => { - // Use refs or stable values for dispatch, AppId, etc. - if (enteredValue && enteredValue?.length >= 6) { - try { - const response = await dispatch( - getLayoutsearch({ - AppId, - CompId, - BranchId, - ProdName: enteredValue, - }) - ).unwrap(); - - const found = response?.data?.data?.[0]; - if (response?.data?.statusCode === 1 && found?.ProdName && found?.QrBasedSearch === null && found?.SearchType === 'N') { - setProducts(prev => - prev.map((p, idx) => { - const index = rowIndex === 0 && !isMobile ? rowIndex + 1 : rowIndex; - return idx === index - ? { - ...p, - productName: found.ProdName, - barcode: enteredValue, - productImage: found.ImageUrl, - } - : p + debouncedProductLookup.current = debounce( + async (enteredValue, rowIndex) => { + // Use refs or stable values for dispatch, AppId, etc. + if (enteredValue && enteredValue?.length >= 6) { + try { + const response = await dispatch( + getLayoutsearch({ + AppId, + CompId, + BranchId, + ProdName: enteredValue, }) - ); - // setRowToSubmit(rowIndex); - } else if (response?.data?.statusCode === 1 && found?.ProdName && found?.QrBasedSearch === 'Y' && found?.SearchType === 'Y') { - setProducts(prev => + ).unwrap(); + + const found = response?.data?.data?.[0]; + if ( + response?.data?.statusCode === 1 && + found?.ProdName && + found?.QrBasedSearch === null && + found?.SearchType === 'N' + ) { + setProducts((prev) => + prev.map((p, idx) => { + const index = + rowIndex === 0 && !isMobile ? rowIndex + 1 : rowIndex; + return idx === index + ? { + ...p, + productName: found.ProdName, + barcode: enteredValue, + productImage: found.ImageUrl, + } + : p; + }) + ); + // setRowToSubmit(rowIndex); + } else if ( + response?.data?.statusCode === 1 && + found?.ProdName && + found?.QrBasedSearch === 'Y' && + found?.SearchType === 'Y' + ) { + setProducts((prev) => + prev.map((p, idx) => + idx === rowIndex ? { ...p, productName: '' } : p + ) + ); + setMessageType('error'); + setMessageData('Product Already Exists'); + } + // setTimeout(() => validateField(id, field, value, updatedRow), 0); + // else { + // setProducts(prev => + // prev.map((p, idx) => + // idx === rowIndex ? { ...p, productName: "" } : p + // ) + // ); + // setMessageType("error"); + // setMessageData("Product Details not found"); + // } + } catch { + setProducts((prev) => prev.map((p, idx) => - idx === rowIndex ? { ...p, productName: "" } : p + idx === rowIndex ? { ...p, productName: '' } : p ) ); - setMessageType("error"); - setMessageData("Product Already Exists"); + setMessageType('error'); + setMessageData('Product Details not found'); } - // setTimeout(() => validateField(id, field, value, updatedRow), 0); - // else { - // setProducts(prev => - // prev.map((p, idx) => - // idx === rowIndex ? { ...p, productName: "" } : p - // ) - // ); - // setMessageType("error"); - // setMessageData("Product Details not found"); - // } - } catch { - setProducts(prev => - prev.map((p, idx) => - idx === rowIndex ? { ...p, productName: "" } : p - ) - ); - setMessageType("error"); - setMessageData("Product Details not found"); } - } - }, 300); + }, + 300 + ); try { dispatch(changeBreadCrumb({ items: items })); } catch (err) { @@ -1338,7 +1675,7 @@ const TurboAddForm = () => { const baseData = { productName: voiceData.product, - nou: voiceData.qty || "1", + nou: voiceData.qty || '1', uom: matchedUom?.value, uomName: matchedUom?.label, mrp: voiceData.price, @@ -1346,15 +1683,17 @@ const TurboAddForm = () => { }; // Check for duplicate - const duplicateExists = products.some(row => - row.productName?.trim().toLowerCase() === baseData.productName?.trim().toLowerCase() && - row.nou === baseData.nou && - row.uom === baseData.uom + const duplicateExists = products.some( + (row) => + row.productName?.trim().toLowerCase() === + baseData.productName?.trim().toLowerCase() && + row.nou === baseData.nou && + row.uom === baseData.uom ); if (duplicateExists) { - setMessageType("error"); - setMessageData("Product Already exists"); + setMessageType('error'); + setMessageData('Product Already exists'); setVoiceData(null); return; } @@ -1362,13 +1701,16 @@ const TurboAddForm = () => { setProducts((prev) => { // Find the first row with an empty product name (if any) const firstEmptyRowIndex = prev.findIndex( - (row) => !row.productName || row.productName.trim() === "" + (row) => !row.productName || row.productName.trim() === '' ); if (firstEmptyRowIndex !== -1) { // Update existing row const updatedProducts = [...prev]; - const rowToUpdate = { ...updatedProducts[firstEmptyRowIndex], ...baseData }; + const rowToUpdate = { + ...updatedProducts[firstEmptyRowIndex], + ...baseData, + }; // Move updated row to top updatedProducts.splice(firstEmptyRowIndex, 1); @@ -1385,13 +1727,17 @@ const TurboAddForm = () => { }, [voiceData, createNewRow, selectOptions.uom, products]); useEffect(() => { - if (searchText !== "" && searchText !== null && searchText?.length !== 0 && searchText !== undefined) { + if ( + searchText !== '' && + searchText !== null && + searchText?.length !== 0 && + searchText !== undefined + ) { handleQrScan(searchText); } - }, [searchText]) - - console.log(products, 'productsproducts') + }, [searchText]); + console.log(products, 'productsproducts'); useEffect(() => { debouncedHSNLookup.current = debounce(async (value) => { @@ -1413,7 +1759,10 @@ const TurboAddForm = () => { }, [dispatch]); // --- Error helper --- - const getError = useCallback((id, field) => errors[`${id}-${field}`], [errors]); + const getError = useCallback( + (id, field) => errors[`${id}-${field}`], + [errors] + ); const handleFieldSetup = () => { setFieldSetup(true); @@ -1421,18 +1770,18 @@ const TurboAddForm = () => { const handleQrScan = async (value) => { const barcodeExists = products.some( - p => p.barcode && p.barcode.toLowerCase() === value?.toLowerCase() + (p) => p.barcode && p.barcode.toLowerCase() === value?.toLowerCase() ); if (barcodeExists) { - setMessageType("error"); - setMessageData("Barcode Already Exists"); + setMessageType('error'); + setMessageData('Barcode Already Exists'); stopScannerFromParent(); return; } const firstEmptyRowIndex = products?.findIndex( - (row) => !row.productName || row.productName.trim() === "" + (row) => !row.productName || row.productName.trim() === '' ); if (firstEmptyRowIndex === -1) { @@ -1447,32 +1796,31 @@ const TurboAddForm = () => { }; const handleFieldSetupSubmit = async () => { - const postData = { - "AppId": AppId, - "CompId": CompId, - "BranchId": BranchId, - "Type": "GB", - "FormType": "Product", - "TypeId": categoryId, - "ConfigDtl": selectedFields?.map((field) => ({ - "ConfigId": field, - "Access": 'Y', + AppId: AppId, + CompId: CompId, + BranchId: BranchId, + Type: 'GB', + FormType: 'Product', + TypeId: categoryId, + ConfigDtl: selectedFields?.map((field) => ({ + ConfigId: field, + Access: 'Y', })), - "CreatedBy": UserId - } + CreatedBy: UserId, + }; const response = await dispatch(postFieldSetup(postData))?.unwrap(); if (response?.data?.statusCode === 1) { - setMessageType("success"); + setMessageType('success'); setMessageData(response?.data?.response); await getFieldSetup(); } else { - setMessageType("error"); - setMessageData("Failed to set up fields"); + setMessageType('error'); + setMessageData('Failed to set up fields'); } - } + }; const handleFieldSelect = (value) => { setSelectedFields((prev) => { @@ -1488,7 +1836,7 @@ const TurboAddForm = () => { }; const handleHSNChange = (id, value) => { - handleInputChange(id, "hsnCode", value); + handleInputChange(id, 'hsnCode', value); setSelectedHsn(value); debouncedHSNLookup.current(value); }; @@ -1517,7 +1865,7 @@ const TurboAddForm = () => { if (uploadImgData?.data?.status) { setOnlineImage(uploadImgData?.data?.image); } - setProducts(prev => + setProducts((prev) => prev.map((p, idx) => idx === imageModalRowIndex ? { ...p, productImage: uploadImgData?.data?.image } @@ -1536,24 +1884,34 @@ const TurboAddForm = () => { scannerRef.current.stopScanner(); } } catch (err) { - console.warn("Scanner not running, skip stop:", err); + console.warn('Scanner not running, skip stop:', err); } setOpenScanner(false); }, []); - const handleQrData = useCallback((value, err) => { - if (err && err?.error) { - setMessageData(err?.error); - setMessageType("error"); - } - dispatch(changeSearchedData(value)); - stopScannerFromParent(); - setOpenScanner(false); - }, [dispatch, stopScannerFromParent]); + const handleQrData = useCallback( + (value, err) => { + if (err && err?.error) { + setMessageData(err?.error); + setMessageType('error'); + } + dispatch(changeSearchedData(value)); + stopScannerFromParent(); + setOpenScanner(false); + }, + [dispatch, stopScannerFromParent] + ); // --- Render --- return (
- { setMessageData(null); setMessageType(null); }} /> + { + setMessageData(null); + setMessageType(null); + }} + />
{/* Toolbar */}
@@ -1565,7 +1923,7 @@ const TurboAddForm = () => { type="text" placeholder="Search products..." value={searchTerm} - onChange={e => setSearchTerm(e.target.value)} + onChange={(e) => setSearchTerm(e.target.value)} className="search-input" />
@@ -1580,32 +1938,60 @@ const TurboAddForm = () => { )} -
+
+ +
- - -
{/* Table */} -
- +
+
- - - + + - {hasField('WholeSale Price') && } + {hasField('WholeSale Price') && ( + + )} - {hasField('Sub Category') && } - {(hasField('Sub Category') && hasField('Brand')) && } + {hasField('Sub Category') && ( + + )} + {hasField('Sub Category') && hasField('Brand') && ( + + )} {hasField('EMI Allowed') && } - {hasField('Auto Generate QRCode') && } + {hasField('Auto Generate QRCode') && ( + + )} {hasField('Auto Generate QRCode') && } {hasField('Stock Maintenance') && } {hasField('Stock Maintenance') && } {hasField('Token Maintenance') && } - {hasField('Amount Per Piece') && + {hasField('Amount Per Piece') && ( <> @@ -1666,18 +2072,20 @@ const TurboAddForm = () => { - } + )} {hasField('Product Type') && } - {hasField('Tax') && } + {hasField('Tax') && ( + + )} {hasField('Cess') && } {hasField('Discount Type') && } {hasField('Discount Limit') && } @@ -1692,7 +2100,9 @@ const TurboAddForm = () => { {hasField('MacId') && } {hasField('Manufacture Date') && } {hasField('Expire Date') && } - {hasField('Expiry Notification Days') && } + {hasField('Expiry Notification Days') && ( + + )} {hasField('Available From') && } {hasField('Available To') && } {hasField('Product Image') && } @@ -1713,21 +2123,29 @@ const TurboAddForm = () => { ) : ( )} */} {filteredProducts?.map((product, rowIndex) => ( - product[field] === null || product[field] === undefined || product[field] === "" + ['productName', 'nou', 'uom'].some( + (field) => + product[field] === null || + product[field] === undefined || + product[field] === '' ) - ? "incomplete-row" - : "" + ? 'incomplete-row' + : '' } > {/* Row Number */} - {/* Product Name */} - {/* UOM */} - {/* No. of Units */} @@ -1817,24 +2255,42 @@ const TurboAddForm = () => { handleInputChange(product.id, "nou", e.target.value)} - onKeyDown={e => { + onChange={(e) => + handleInputChange(product.id, 'nou', e.target.value) + } + onKeyDown={(e) => { if ( !( - (e.key >= "0" && e.key <= "9") || - ["Backspace", "Tab", "ArrowLeft", "ArrowRight", "Delete", "Home", "End", "Enter", "Escape", "Period", "."].includes(e.key) + (e.key >= '0' && e.key <= '9') || + [ + 'Backspace', + 'Tab', + 'ArrowLeft', + 'ArrowRight', + 'Delete', + 'Home', + 'End', + 'Enter', + 'Escape', + 'Period', + '.', + ].includes(e.key) ) ) { e.preventDefault(); } - handleKeyDown(e, rowIndex, `${rowIndex}-2`) + handleKeyDown(e, rowIndex, `${rowIndex}-2`); }} onFocus={() => setSelectedCell(`${rowIndex}-2`)} dataCell={`${rowIndex}-2`} placeholder="0" - hasError={!!getError(product.id, "nou")} + hasError={!!getError(product.id, 'nou')} /> - {getError(product.id, "nou") && {getError(product.id, "nou")}} + {getError(product.id, 'nou') && ( + + {getError(product.id, 'nou')} + + )} {/* MRP */} @@ -1843,12 +2299,26 @@ const TurboAddForm = () => { handleInputChange(product.id, "mrp", e.target.value)} - onKeyDown={e => { + onChange={(e) => + handleInputChange(product.id, 'mrp', e.target.value) + } + onKeyDown={(e) => { if ( !( - (e.key >= "0" && e.key <= "9") || - ["Backspace", "Tab", "ArrowLeft", "ArrowRight", "Delete", "Home", "End", "Enter", "Escape", "Period", "."].includes(e.key) + (e.key >= '0' && e.key <= '9') || + [ + 'Backspace', + 'Tab', + 'ArrowLeft', + 'ArrowRight', + 'Delete', + 'Home', + 'End', + 'Enter', + 'Escape', + 'Period', + '.', + ].includes(e.key) ) ) { e.preventDefault(); @@ -1859,9 +2329,13 @@ const TurboAddForm = () => { onFocus={() => setSelectedCell(`${rowIndex}-3`)} placeholder="₹ 0.00" step="0.01" - hasError={!!getError(product.id, "mrp")} + hasError={!!getError(product.id, 'mrp')} /> - {getError(product.id, "mrp") && {getError(product.id, "mrp")}} + {getError(product.id, 'mrp') && ( + + {getError(product.id, 'mrp')} + + )} {/* Sale Price */} @@ -1870,12 +2344,30 @@ const TurboAddForm = () => { handleInputChange(product.id, "salePrice", e.target.value)} - onKeyDown={e => { + onChange={(e) => + handleInputChange( + product.id, + 'salePrice', + e.target.value + ) + } + onKeyDown={(e) => { if ( !( - (e.key >= "0" && e.key <= "9") || - ["Backspace", "Tab", "ArrowLeft", "ArrowRight", "Delete", "Home", "End", "Enter", "Escape", "Period", "."].includes(e.key) + (e.key >= '0' && e.key <= '9') || + [ + 'Backspace', + 'Tab', + 'ArrowLeft', + 'ArrowRight', + 'Delete', + 'Home', + 'End', + 'Enter', + 'Escape', + 'Period', + '.', + ].includes(e.key) ) ) { e.preventDefault(); @@ -1886,576 +2378,1042 @@ const TurboAddForm = () => { onFocus={() => setSelectedCell(`${rowIndex}-4`)} placeholder="₹ 0.00" step="0.01" - hasError={!!getError(product.id, "salePrice")} + hasError={!!getError(product.id, 'salePrice')} /> - {getError(product.id, "salePrice") && {getError(product.id, "salePrice")}} + {getError(product.id, 'salePrice') && ( + + {getError(product.id, 'salePrice')} + + )} - {hasField('WholeSale Price') && ()} - {/* Category */} - - {/* Sub-Category */} - {hasField('Sub Category') && } - {(hasField('Brand') && hasField('Sub Category')) && } - {/* EMI Allowed */} - {hasField('EMI Allowed') && } - {/* Auto Generate QRCode */} - {hasField('Auto Generate QRCode') && <> + {hasField('WholeSale Price') && ( - {/* Scanner/AddQrCode */} - {product.autoGenerateQrcode === "No" ? ( - - ) : ()} - } - {hasField('Stock Maintenance') && <> - {/* Stock Maintenance */} - - {/* Low Stock Count */} - {product.stockMaintenance === "Yes" ? ( - - ) : ( - - )} - } - {/* Token Maintenance */} - {hasField('Token Maintenance') && } - {/* Amount Per Piece */} - {hasField('Amount Per Piece') && <> - - {/* Per Piece Amount, Auto Generate One Piece Qrcode, Scanner/AddOnePcQrCode, No. of Piece Inside */} - {product.amountPerPiece === "Yes" ? ( - <> - - - {product.autoGenerateOnePcQrcode === "No" ? ( - - ) : ( - - )} - - - ) : ( - <> - - - - - - )} - } - {/* Product Type */} - {hasField('Product Type') && } - {/* Tax */} - {hasField('Tax') && } - {/* Cess */} - {hasField('Cess') && } - - {/* Percentage/Fixed */} - {hasField('Discount Type') && } - - {/* Discount Limit */} - {hasField('Discount Limit') && + )} + {/* Category */} + } + + {/* Sub-Category */} + {hasField('Sub Category') && ( + + )} + {hasField('Brand') && hasField('Sub Category') && ( + + )} + {/* EMI Allowed */} + {hasField('EMI Allowed') && ( + + )} + {/* Auto Generate QRCode */} + {hasField('Auto Generate QRCode') && ( + <> + + {/* Scanner/AddQrCode */} + {product.autoGenerateQrcode === 'No' ? ( + + ) : ( + + )} + + )} + {hasField('Stock Maintenance') && ( + <> + {/* Stock Maintenance */} + + {/* Low Stock Count */} + {product.stockMaintenance === 'Yes' ? ( + + ) : ( + + )} + + )} + {/* Token Maintenance */} + {hasField('Token Maintenance') && ( + + )} + {/* Amount Per Piece */} + {hasField('Amount Per Piece') && ( + <> + + {/* Per Piece Amount, Auto Generate One Piece Qrcode, Scanner/AddOnePcQrCode, No. of Piece Inside */} + {product.amountPerPiece === 'Yes' ? ( + <> + + + {product.autoGenerateOnePcQrcode === 'No' ? ( + + ) : ( + + )} + + + ) : ( + <> + + + + + + )} + + )} + {/* Product Type */} + {hasField('Product Type') && ( + + )} + {/* Tax */} + {hasField('Tax') && ( + + )} + {/* Cess */} + {hasField('Cess') && ( + + )} + + {/* Percentage/Fixed */} + {hasField('Discount Type') && ( + + )} + + {/* Discount Limit */} + {hasField('Discount Limit') && ( + + )} {/* HSN Code */} - {hasField('HSN') && } + {hasField('HSN') && ( + + )} {/* Part Number */} - {hasField('Part Number') && } + {hasField('Part Number') && ( + + )} {/* Rack */} - {hasField('Rack') && } + {hasField('Rack') && ( + + )} {/* Batch Number */} - {hasField('Batch Number') && } + {hasField('Batch Number') && ( + + )} {/* Model Number */} - {hasField('Model Number') && } - {/* IMEI 1 */} - {hasField('IMEI 1') && } + + )} + {/* IMEI 1 */} + {hasField('IMEI 1') && ( + + )} {/* IMEI 2 */} - {hasField('IMEI 2') && } + {hasField('IMEI 2') && ( + + )} {/* Serial Number */} - {hasField('Serial Number') && } + {hasField('Serial Number') && ( + + )} {/* MacId */} - {hasField('MacId') && } + {hasField('MacId') && ( + + )} {/* Manufacture Date */} - {hasField('Manufacture Date') && } + {hasField('Manufacture Date') && ( + + )} {/* Expire Date */} - {hasField('Expire Date') && } + onChange={(_, dateString) => + handleInputChange( + product.id, + 'expireDate', + dateString || null + ) + } + size="small" + style={{ width: 120 }} + format="YYYY-MM-DD" + placeholder="Expire" + allowClear + inputReadOnly + disabledDate={(current) => { + // Disable dates before manufactureDate (if set) + if (product.manufactureDate) { + return ( + current && + current.isBefore( + dayjs(product.manufactureDate), + 'day' + ) + ); + } + return false; + }} + /> + + )} {/* Expiry Notification Days */} - {hasField('Expiry Notification Days') && } + placeholder="0" + min={0} + onFocus={() => + setSelectedCell(`${rowIndex}-expiryNotificationDays`) + } + onKeyDown={(e) => { + if ( + !( + (e.key >= '0' && e.key <= '9') || + [ + 'Backspace', + 'Tab', + 'ArrowLeft', + 'ArrowRight', + 'Delete', + 'Home', + 'End', + 'Enter', + 'Escape', + ].includes(e.key) + ) + ) { + e.preventDefault(); + } + }} + /> + + )} {/* Available From */} - {hasField('Available From') && } + {hasField('Available From') && ( + + )} {/* Available To */} - {hasField('Available To') && } + {hasField('Available To') && ( + + )} {/* Product Image */} {/* {hasField('Product Image') && } */} - {hasField('Product Image') && - - } + )} {/* Delete */} -
#Product Name * + # + Product Name * + UOM * { No. of Units * MRP * Sale Price *Wholesale/Special PriceWholesale/Special Price Category * { onSubmit={handleDefaultSetting} /> - Sub-Category - 0 && selectedDefaultCategory ? `Selected Category : ${defaultCategories?.find(cat => cat.value === selectedDefaultCategory)?.label}` : null} - /> - Brand - 0 && selectedDefaultSubCategory ? `Selected Sub-Category : ${defaultSubCategories?.find(subCat => subCat.value === selectedDefaultSubCategory)?.label}` : null} - currentDefault={selectedDefaultBrand} - popOverHeading="Set Default Brand" - field="brand" - options={defaultBrands} - onSubmit={handleDefaultSetting} - checkBoxHeading={'Apply to all selected sub-category rows'} - /> - + Sub-Category + 0 && selectedDefaultCategory + ? `Selected Category : ${defaultCategories?.find((cat) => cat.value === selectedDefaultCategory)?.label}` + : null + } + /> + + Brand + 0 && + selectedDefaultSubCategory + ? `Selected Sub-Category : ${defaultSubCategories?.find((subCat) => subCat.value === selectedDefaultSubCategory)?.label}` + : null + } + currentDefault={selectedDefaultBrand} + popOverHeading="Set Default Brand" + field="brand" + options={defaultBrands} + onSubmit={handleDefaultSetting} + checkBoxHeading={ + 'Apply to all selected sub-category rows' + } + /> + EMI AllowedAuto Generate QRCodeAuto Generate QRCodeScanner/AddQrCodeStock MaintenanceLow Stock CountToken MaintenanceAmount Per Piece Per Piece AmountScanner/AddOnePcQrCode No. of Piece InsideProduct Type - Tax % * - - + Tax % * + + CessDiscount TypeDiscount LimitMacIdManufacture DateExpire DateExpiry Notification DaysExpiry Notification DaysAvailable FromAvailable ToProduct Image
insertRowAbove(rowIndex)} title="Click to add row above"> + insertRowAbove(rowIndex)} + title="Click to add row above" + > {rowIndex + 1} + {/*
{
{ + onChange={(e) => { const newValue = e.target.value?.trim(); const barcodeExists = products.some( - p => p.barcode && p.barcode.toLowerCase() === newValue?.toLowerCase() + (p) => + p.barcode && + p.barcode.toLowerCase() === + newValue?.toLowerCase() ); if (barcodeExists) { - handleInputChange(product.id, "productName", ""); // clear field - setMessageType("error"); - setMessageData("Barcode Already Exists"); + handleInputChange(product.id, 'productName', ''); // clear field + setMessageType('error'); + setMessageData('Barcode Already Exists'); return; } - handleInputChange(product.id, "productName", e.target.value) - debouncedProductLookup.current(e.target.value, rowIndex); + handleInputChange( + product.id, + 'productName', + e.target.value + ); + debouncedProductLookup.current( + e.target.value, + rowIndex + ); }} onFocus={() => setSelectedCell(`${rowIndex}-0`)} - onKeyDown={e => handleKeyDown(e, rowIndex, `${rowIndex}-0`)} + onKeyDown={(e) => + handleKeyDown(e, rowIndex, `${rowIndex}-0`) + } dataCell={`${rowIndex}-0`} dataBarcode={true} placeholder="Enter product name..." - hasError={!!getError(product.id, "productName")} + hasError={!!getError(product.id, 'productName')} /> - -
- {getError(product.id, "productName") && ( - {getError(product.id, "productName")} + {getError(product.id, 'productName') && ( + + {getError(product.id, 'productName')} + )}
+
handleInputChange(product.id, "uom", e.target.value)} - onKeyDown={e => handleKeyDown(e, rowIndex, `${rowIndex}-1`)} + onChange={(e) => + handleInputChange(product.id, 'uom', e.target.value) + } + onKeyDown={(e) => + handleKeyDown(e, rowIndex, `${rowIndex}-1`) + } dataCell={`${rowIndex}-1`} options={selectOptions.uom} - hasError={!!getError(product.id, "uom")} + hasError={!!getError(product.id, 'uom')} placeholder="Select UOM" onClick={() => setSelectedCell(`${rowIndex}-1`)} /> - {getError(product.id, "uom") && {getError(product.id, "uom")}} + {getError(product.id, 'uom') && ( + + {getError(product.id, 'uom')} + + )}
- { - if ( - !( - (e.key >= "0" && e.key <= "9") || - ["Backspace", "Tab", "ArrowLeft", "ArrowRight", "Delete", "Home", "End", "Enter", "Escape", "Period", "."].includes(e.key) - ) - ) { - e.preventDefault(); - } - }} - value={product.whSalePrice} - onChange={e => handleInputChange(product.id, "whSalePrice", e.target.value)} - placeholder="Wholesale/Special Price" - onFocus={() => setSelectedCell(`${rowIndex}-whSalePrice`)} - /> - -
- handleCategoryChange(product.id, e.target.value)} - onKeyDown={e => handleKeyDown(e, rowIndex, `${rowIndex}-5`)} - dataCell={`${rowIndex}-5`} - onClick={() => setSelectedCell(`${rowIndex}-5`)} - options={selectOptions.categories} - hasError={!!getError(product.id, "category")} - placeholder="Select Category" - /> - {getError(product.id, "category") && {getError(product.id, "category")}} -
-
- handleSubCategoryChange(product.id, e.target.value)} - onKeyDown={e => handleKeyDown(e, rowIndex, `${rowIndex}-6`)} - dataCell={`${rowIndex}-6`} - onClick={() => setSelectedCell(`${rowIndex}-6`)} - options={product.availableSubCategories} - hasError={!!getError(product.id, "subCategory")} - placeholder="Select Sub-Category" - /> - - handleInputChange(product.id, "brand", e.target.value)} - options={product.availableBrands || []} // define your brand options - placeholder="Select Brand" - onClick={() => setSelectedCell(`${rowIndex}-brand`)} - /> - - handleInputChange(product.id, "emiAllowed", e.target.value)} - options={[ - { label: "Yes", value: "Yes" }, - { label: "No", value: "No" } - ]} - optionType="button" - buttonStyle="solid" - /> - - { - const val = e.target.value; - handleInputChange(product.id, "autoGenerateQrcode", val); - handleInputChange(product.id, "qrcode", ""); - }} - options={[ - { label: "Yes", value: "Yes" }, - { label: "No", value: "No" } - ]} - optionType="button" - buttonStyle="solid" - /> - - handleInputChange(product.id, "qrcode", e.target.value)} - placeholder="Scan/Add QR" - onFocus={() => setSelectedCell(`${rowIndex}-qrcode`)} - /> - - - { - const val = e.target.value; - handleInputChange(product.id, "stockMaintenance", val); - if (val === "Yes") { - handleInputChange(product.id, "lowStockCount", 0); - } else { - handleInputChange(product.id, "lowStockCount", undefined); - } - }} - options={[ - { label: "Yes", value: "Yes" }, - { label: "No", value: "No" } - ]} - optionType="button" - buttonStyle="solid" - /> - - { - if ( - !( - (e.key >= "0" && e.key <= "9") || - ["Backspace", "Tab", "ArrowLeft", "ArrowRight", "Delete", "Home", "End", "Enter", "Escape", "Period", "."].includes(e.key) - ) - ) { - e.preventDefault(); - } - }} - value={product?.lowStockCount} - onChange={e => handleInputChange(product.id, "lowStockCount", e.target.value)} - placeholder="0" - onFocus={() => setSelectedCell(`${rowIndex}-lowStockCount`)} - /> - - - handleInputChange(product.id, "tokenMaintenance", e.target.value)} - options={[ - { label: "Yes", value: "Yes" }, - { label: "No", value: "No" } - ]} - optionType="button" - buttonStyle="solid" - /> - - { - const val = e.target.value; - handleInputChange(product.id, "amountPerPiece", val); - if (val === "Yes") { - handleInputChange(product.id, "perPieceAmount", 0); - } else { - handleInputChange(product.id, "perPieceAmount", undefined); - handleInputChange(product.id, "autoGenerateOnePcQrcode", 'No'); - handleInputChange(product.id, "onePcQrcode", ""); - handleInputChange(product.id, "noOfPieceInside", "") - } - }} - options={[ - { label: "Yes", value: "Yes" }, - { label: "No", value: "No" } - ]} - optionType="button" - buttonStyle="solid" - /> - - { - if ( - !( - (e.key >= "0" && e.key <= "9") || - ["Backspace", "Tab", "ArrowLeft", "ArrowRight", "Delete", "Home", "End", "Enter", "Escape", "Period", "."].includes(e.key) - ) - ) { - e.preventDefault(); - } - }} - value={product.perPieceAmount} - onChange={e => handleInputChange(product.id, "perPieceAmount", e.target.value)} - placeholder="Amount" - onFocus={() => setSelectedCell(`${rowIndex}-perPieceAmount`)} - /> - - { - const val = e.target.value; - handleInputChange(product.id, "autoGenerateOnePcQrcode", val); - handleInputChange(product.id, "onePcQrcode", ""); - }} - options={[ - { label: "Yes", value: "Yes" }, - { label: "No", value: "No" } - ]} - optionType="button" - buttonStyle="solid" - /> - - handleInputChange(product.id, "onePcQrcode", e.target.value)} - placeholder="Scan/Add OnePc QR" - onFocus={() => setSelectedCell(`${rowIndex}-onePcQrcode`)} - /> - - - { - if ( - !( - (e.key >= "0" && e.key <= "9") || - ["Backspace", "Tab", "ArrowLeft", "ArrowRight", "Delete", "Home", "End", "Enter", "Escape", "Period", "."].includes(e.key) - ) - ) { - e.preventDefault(); - } - }} - value={product.noOfPieceInside} - onChange={e => handleInputChange(product.id, "noOfPieceInside", e.target.value)} - placeholder="No. of Piece" - onFocus={() => setSelectedCell(`${rowIndex}-noOfPieceInside`)} - /> - ---- - handleInputChange(product.id, "productType", e.target.value)} - options={productTypeData?.filter(pt => pt?.ConfigName !== 'Others')?.map(pt => ({ label: pt.ConfigName, value: pt.ConfigId }))} - placeholder="Select Type" - onClick={() => setSelectedCell(`${rowIndex}-productType`)} - /> - -
- handleInputChange(product.id, "tax", e.target.value)} - onKeyDown={e => handleKeyDown(e, rowIndex, 7)} - dataCell={`${rowIndex}-7`} - onClick={() => setSelectedCell(`${rowIndex}-7`)} - options={selectOptions.tax} - hasError={!!getError(product.id, "tax")} - placeholder="Select Tax" - /> - {getError(product.id, "tax") && {getError(product.id, "tax")}} -
-
- { - if ( - !( - (e.key >= "0" && e.key <= "9") || - ["Backspace", "Tab", "ArrowLeft", "ArrowRight", "Delete", "Home", "End", "Enter", "Escape", "Period", "."].includes(e.key) - ) - ) { - e.preventDefault(); - } - }} - value={product.cess} - onChange={e => handleInputChange(product.id, "cess", e.target.value)} - placeholder="Cess" - onFocus={() => setSelectedCell(`${rowIndex}-cess`)} - /> - - handleInputChange(product.id, "discountType", e.target.value)} - options={[ - { label: "Percentage", value: "P" }, - { label: "Fixed", value: "F" } - ]} - optionType="button" - buttonStyle="solid" - /> - -
{ + onKeyDown={(e) => { if ( !( - (e.key >= "0" && e.key <= "9") || - ["Backspace", "Tab", "ArrowLeft", "ArrowRight", "Delete", "Home", "End", "Enter", "Escape", "Period", "."].includes(e.key) + (e.key >= '0' && e.key <= '9') || + [ + 'Backspace', + 'Tab', + 'ArrowLeft', + 'ArrowRight', + 'Delete', + 'Home', + 'End', + 'Enter', + 'Escape', + 'Period', + '.', + ].includes(e.key) ) ) { e.preventDefault(); } }} - value={product.discountLimit} - onChange={e => handleInputChange(product.id, "discountLimit", e.target.value)} - placeholder="Discount Limit" - onFocus={() => setSelectedCell(`${rowIndex}-discountLimit`)} - hasError={!!getError(product.id, "discountLimit")} + value={product.whSalePrice} + onChange={(e) => + handleInputChange( + product.id, + 'whSalePrice', + e.target.value + ) + } + placeholder="Wholesale/Special Price" + onFocus={() => + setSelectedCell(`${rowIndex}-whSalePrice`) + } /> - {getError(product.id, "discountLimit") && {getError(product.id, "discountLimit")}} +
+
+ + handleCategoryChange(product.id, e.target.value) + } + onKeyDown={(e) => + handleKeyDown(e, rowIndex, `${rowIndex}-5`) + } + dataCell={`${rowIndex}-5`} + onClick={() => setSelectedCell(`${rowIndex}-5`)} + options={selectOptions.categories} + hasError={!!getError(product.id, 'category')} + placeholder="Select Category" + /> + {getError(product.id, 'category') && ( + + {getError(product.id, 'category')} + + )}
-
+ + handleSubCategoryChange(product.id, e.target.value) + } + onKeyDown={(e) => + handleKeyDown(e, rowIndex, `${rowIndex}-6`) + } + dataCell={`${rowIndex}-6`} + onClick={() => setSelectedCell(`${rowIndex}-6`)} + options={product.availableSubCategories} + hasError={!!getError(product.id, 'subCategory')} + placeholder="Select Sub-Category" + /> + + + handleInputChange(product.id, 'brand', e.target.value) + } + options={product.availableBrands || []} // define your brand options + placeholder="Select Brand" + onClick={() => setSelectedCell(`${rowIndex}-brand`)} + /> + + + handleInputChange( + product.id, + 'emiAllowed', + e.target.value + ) + } + options={[ + { label: 'Yes', value: 'Yes' }, + { label: 'No', value: 'No' }, + ]} + optionType="button" + buttonStyle="solid" + /> + + { + const val = e.target.value; + handleInputChange( + product.id, + 'autoGenerateQrcode', + val + ); + handleInputChange(product.id, 'qrcode', ''); + }} + options={[ + { label: 'Yes', value: 'Yes' }, + { label: 'No', value: 'No' }, + ]} + optionType="button" + buttonStyle="solid" + /> + + + handleInputChange( + product.id, + 'qrcode', + e.target.value + ) + } + placeholder="Scan/Add QR" + onFocus={() => + setSelectedCell(`${rowIndex}-qrcode`) + } + /> + + - + + { + const val = e.target.value; + handleInputChange( + product.id, + 'stockMaintenance', + val + ); + if (val === 'Yes') { + handleInputChange(product.id, 'lowStockCount', 0); + } else { + handleInputChange( + product.id, + 'lowStockCount', + undefined + ); + } + }} + options={[ + { label: 'Yes', value: 'Yes' }, + { label: 'No', value: 'No' }, + ]} + optionType="button" + buttonStyle="solid" + /> + + { + if ( + !( + (e.key >= '0' && e.key <= '9') || + [ + 'Backspace', + 'Tab', + 'ArrowLeft', + 'ArrowRight', + 'Delete', + 'Home', + 'End', + 'Enter', + 'Escape', + 'Period', + '.', + ].includes(e.key) + ) + ) { + e.preventDefault(); + } + }} + value={product?.lowStockCount} + onChange={(e) => + handleInputChange( + product.id, + 'lowStockCount', + e.target.value + ) + } + placeholder="0" + onFocus={() => + setSelectedCell(`${rowIndex}-lowStockCount`) + } + /> + + - + + + handleInputChange( + product.id, + 'tokenMaintenance', + e.target.value + ) + } + options={[ + { label: 'Yes', value: 'Yes' }, + { label: 'No', value: 'No' }, + ]} + optionType="button" + buttonStyle="solid" + /> + + { + const val = e.target.value; + handleInputChange( + product.id, + 'amountPerPiece', + val + ); + if (val === 'Yes') { + handleInputChange( + product.id, + 'perPieceAmount', + 0 + ); + } else { + handleInputChange( + product.id, + 'perPieceAmount', + undefined + ); + handleInputChange( + product.id, + 'autoGenerateOnePcQrcode', + 'No' + ); + handleInputChange(product.id, 'onePcQrcode', ''); + handleInputChange( + product.id, + 'noOfPieceInside', + '' + ); + } + }} + options={[ + { label: 'Yes', value: 'Yes' }, + { label: 'No', value: 'No' }, + ]} + optionType="button" + buttonStyle="solid" + /> + + { + if ( + !( + (e.key >= '0' && e.key <= '9') || + [ + 'Backspace', + 'Tab', + 'ArrowLeft', + 'ArrowRight', + 'Delete', + 'Home', + 'End', + 'Enter', + 'Escape', + 'Period', + '.', + ].includes(e.key) + ) + ) { + e.preventDefault(); + } + }} + value={product.perPieceAmount} + onChange={(e) => + handleInputChange( + product.id, + 'perPieceAmount', + e.target.value + ) + } + placeholder="Amount" + onFocus={() => + setSelectedCell(`${rowIndex}-perPieceAmount`) + } + /> + + { + const val = e.target.value; + handleInputChange( + product.id, + 'autoGenerateOnePcQrcode', + val + ); + handleInputChange( + product.id, + 'onePcQrcode', + '' + ); + }} + options={[ + { label: 'Yes', value: 'Yes' }, + { label: 'No', value: 'No' }, + ]} + optionType="button" + buttonStyle="solid" + /> + + + handleInputChange( + product.id, + 'onePcQrcode', + e.target.value + ) + } + placeholder="Scan/Add OnePc QR" + onFocus={() => + setSelectedCell(`${rowIndex}-onePcQrcode`) + } + /> + + - + + { + if ( + !( + (e.key >= '0' && e.key <= '9') || + [ + 'Backspace', + 'Tab', + 'ArrowLeft', + 'ArrowRight', + 'Delete', + 'Home', + 'End', + 'Enter', + 'Escape', + 'Period', + '.', + ].includes(e.key) + ) + ) { + e.preventDefault(); + } + }} + value={product.noOfPieceInside} + onChange={(e) => + handleInputChange( + product.id, + 'noOfPieceInside', + e.target.value + ) + } + placeholder="No. of Piece" + onFocus={() => + setSelectedCell(`${rowIndex}-noOfPieceInside`) + } + /> + + - + + - + + - + + - + + + handleInputChange( + product.id, + 'productType', + e.target.value + ) + } + options={productTypeData + ?.filter((pt) => pt?.ConfigName !== 'Others') + ?.map((pt) => ({ + label: pt.ConfigName, + value: pt.ConfigId, + }))} + placeholder="Select Type" + onClick={() => + setSelectedCell(`${rowIndex}-productType`) + } + /> + +
+ + handleInputChange(product.id, 'tax', e.target.value) + } + onKeyDown={(e) => handleKeyDown(e, rowIndex, 7)} + dataCell={`${rowIndex}-7`} + onClick={() => setSelectedCell(`${rowIndex}-7`)} + options={selectOptions.tax} + hasError={!!getError(product.id, 'tax')} + placeholder="Select Tax" + /> + {getError(product.id, 'tax') && ( + + {getError(product.id, 'tax')} + + )} +
+
+ { + if ( + !( + (e.key >= '0' && e.key <= '9') || + [ + 'Backspace', + 'Tab', + 'ArrowLeft', + 'ArrowRight', + 'Delete', + 'Home', + 'End', + 'Enter', + 'Escape', + 'Period', + '.', + ].includes(e.key) + ) + ) { + e.preventDefault(); + } + }} + value={product.cess} + onChange={(e) => + handleInputChange(product.id, 'cess', e.target.value) + } + placeholder="Cess" + onFocus={() => setSelectedCell(`${rowIndex}-cess`)} + /> + + + handleInputChange( + product.id, + 'discountType', + e.target.value + ) + } + options={[ + { label: 'Percentage', value: 'P' }, + { label: 'Fixed', value: 'F' }, + ]} + optionType="button" + buttonStyle="solid" + /> + +
+ { + if ( + !( + (e.key >= '0' && e.key <= '9') || + [ + 'Backspace', + 'Tab', + 'ArrowLeft', + 'ArrowRight', + 'Delete', + 'Home', + 'End', + 'Enter', + 'Escape', + 'Period', + '.', + ].includes(e.key) + ) + ) { + e.preventDefault(); + } + }} + value={product.discountLimit} + onChange={(e) => + handleInputChange( + product.id, + 'discountLimit', + e.target.value + ) + } + placeholder="Discount Limit" + onFocus={() => + setSelectedCell(`${rowIndex}-discountLimit`) + } + hasError={!!getError(product.id, 'discountLimit')} + /> + {getError(product.id, 'discountLimit') && ( + + {getError(product.id, 'discountLimit')} + + )} +
+
- handleHSNChange(product.id, e.target.value)} - placeholder="HSN Code" - onFocus={() => setSelectedCell(`${rowIndex}-hsnCode`)} - list={`hsnList-${product.id}`} - /> - {hsnDetails?.length > 0 && ( - - {hsnDetails - .filter(item => { - const search = (selectedHsn || "").toLowerCase(); - return ( - item.HSN_CD?.toLowerCase()?.includes(search) || - item.Description?.toLowerCase()?.includes(search) - ); - }) - .map((item, index) => ( - - )} - + + handleHSNChange(product.id, e.target.value) + } + placeholder="HSN Code" + onFocus={() => setSelectedCell(`${rowIndex}-hsnCode`)} + list={`hsnList-${product.id}`} + /> + {hsnDetails?.length > 0 && ( + + {hsnDetails + .filter((item) => { + const search = (selectedHsn || '').toLowerCase(); + return ( + item.HSN_CD?.toLowerCase()?.includes(search) || + item.Description?.toLowerCase()?.includes( + search + ) + ); + }) + .map((item, index) => ( + + )} + - handleInputChange(product.id, "partNumber", e.target.value)} - placeholder="Part Number" - onFocus={() => setSelectedCell(`${rowIndex}-partNumber`)} - /> - + + handleInputChange( + product.id, + 'partNumber', + e.target.value + ) + } + placeholder="Part Number" + onFocus={() => + setSelectedCell(`${rowIndex}-partNumber`) + } + /> + - handleInputChange(product.id, "rack", e.target.value)} - placeholder="Rack" - onFocus={() => setSelectedCell(`${rowIndex}-rack`)} - /> - + + handleInputChange(product.id, 'rack', e.target.value) + } + placeholder="Rack" + onFocus={() => setSelectedCell(`${rowIndex}-rack`)} + /> + - handleInputChange(product.id, "batchNumber", e.target.value)} - placeholder="Batch Number" - onFocus={() => setSelectedCell(`${rowIndex}-batchNumber`)} - /> - + + handleInputChange( + product.id, + 'batchNumber', + e.target.value + ) + } + placeholder="Batch Number" + onFocus={() => + setSelectedCell(`${rowIndex}-batchNumber`) + } + /> + - handleInputChange(product.id, "modelNumber", e.target.value)} - placeholder="Model Number" - onFocus={() => setSelectedCell(`${rowIndex}-modelNumber`)} - /> - -
+ {hasField('Model Number') && ( +
handleInputChange(product.id, "imei1", e.target.value)} - placeholder="IMEI 1" - onFocus={() => setSelectedCell(`${rowIndex}-imei1`)} - hasError={!!getError(product.id, "imei1")} + value={product.modelNumber} + onChange={(e) => + handleInputChange( + product.id, + 'modelNumber', + e.target.value + ) + } + placeholder="Model Number" + onFocus={() => + setSelectedCell(`${rowIndex}-modelNumber`) + } /> - {getError(product.id, "imei1") && {getError(product.id, "imei1")}} - - +
+ + handleInputChange( + product.id, + 'imei1', + e.target.value + ) + } + placeholder="IMEI 1" + onFocus={() => setSelectedCell(`${rowIndex}-imei1`)} + hasError={!!getError(product.id, 'imei1')} + /> + {getError(product.id, 'imei1') && ( + + {getError(product.id, 'imei1')} + + )} +
+
-
- handleInputChange(product.id, "imei2", e.target.value)} - placeholder="IMEI 2" - onFocus={() => setSelectedCell(`${rowIndex}-imei2`)} - hasError={!!getError(product.id, "imei2")} - /> - {getError(product.id, "imei2") && {getError(product.id, "imei2")}} - -
-
+
+ + handleInputChange( + product.id, + 'imei2', + e.target.value + ) + } + placeholder="IMEI 2" + onFocus={() => setSelectedCell(`${rowIndex}-imei2`)} + hasError={!!getError(product.id, 'imei2')} + /> + {getError(product.id, 'imei2') && ( + + {getError(product.id, 'imei2')} + + )} +
+
- handleInputChange(product.id, "serialNumber", e.target.value)} - placeholder="Serial Number" - onFocus={() => setSelectedCell(`${rowIndex}-serialNumber`)} - /> - + + handleInputChange( + product.id, + 'serialNumber', + e.target.value + ) + } + placeholder="Serial Number" + onFocus={() => + setSelectedCell(`${rowIndex}-serialNumber`) + } + /> + -
- handleInputChange(product.id, "macId", e.target.value)} - placeholder="MacId" - onFocus={() => setSelectedCell(`${rowIndex}-macId`)} - hasError={!!getError(product.id, "macId")} - /> - {getError(product.id, "macId") && {getError(product.id, "macId")}} -
-
+
+ + handleInputChange( + product.id, + 'macId', + e.target.value + ) + } + placeholder="MacId" + onFocus={() => setSelectedCell(`${rowIndex}-macId`)} + hasError={!!getError(product.id, 'macId')} + /> + {getError(product.id, 'macId') && ( + + {getError(product.id, 'macId')} + + )} +
+
- { - handleInputChange(product.id, "manufactureDate", dateString || null); - // If manufacture date changes, clear expire date - handleInputChange(product.id, "expireDate", null); - }} - size="small" - style={{ width: 120 }} - format="YYYY-MM-DD" - placeholder="Manufacture" - allowClear - inputReadOnly - /> - + { + handleInputChange( + product.id, + 'manufactureDate', + dateString || null + ); + // If manufacture date changes, clear expire date + handleInputChange(product.id, 'expireDate', null); + }} + size="small" + style={{ width: 120 }} + format="YYYY-MM-DD" + placeholder="Manufacture" + allowClear + inputReadOnly + /> + - handleInputChange(product.id, "expireDate", dateString || null)} - size="small" - style={{ width: 120 }} - format="YYYY-MM-DD" - placeholder="Expire" - allowClear - inputReadOnly - disabledDate={current => { - // Disable dates before manufactureDate (if set) - if (product.manufactureDate) { - return current && current.isBefore(dayjs(product.manufactureDate), "day"); + {hasField('Expire Date') && ( + + - - handleInputChange(product.id, "expiryNotificationDays", e.target.value)} - placeholder="0" - min={0} - onFocus={() => setSelectedCell(`${rowIndex}-expiryNotificationDays`)} - onKeyDown={e => { - if ( - !( - (e.key >= "0" && e.key <= "9") || - ["Backspace", "Tab", "ArrowLeft", "ArrowRight", "Delete", "Home", "End", "Enter", "Escape"].includes(e.key) + {hasField('Expiry Notification Days') && ( + + + handleInputChange( + product.id, + 'expiryNotificationDays', + e.target.value ) - ) { - e.preventDefault(); } - }} - /> - - { - handleInputChange(product.id, "availableFrom", timeString || null); - handleInputChange(product.id, "availableTo", null); - }} - size="small" - style={{ width: 110 }} - format="hh:mm a" - use12Hours - placeholder="From" - allowClear - inputReadOnly - /> - + { + handleInputChange( + product.id, + 'availableFrom', + timeString || null + ); + handleInputChange(product.id, 'availableTo', null); + }} + size="small" + style={{ width: 110 }} + format="hh:mm a" + use12Hours + placeholder="From" + allowClear + inputReadOnly + /> + - handleInputChange(product.id, "availableTo", timeString || null)} - size="small" - style={{ width: 110 }} - format="hh:mm a" - use12Hours - placeholder="To" - allowClear - inputReadOnly - /> - + + handleInputChange( + product.id, + 'availableTo', + timeString || null + ) + } + size="small" + style={{ width: 110 }} + format="hh:mm a" + use12Hours + placeholder="To" + allowClear + inputReadOnly + /> + @@ -2506,25 +3464,27 @@ const TurboAddForm = () => { + {hasField('Product Image') && ( +
{ setImageModalRowIndex(rowIndex); setUploadImageModal(true); setOnlineImage(null); setSelectedImage(null); - }} /> - + }} + />
+