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 (
| # | -Product Name * | -+ | # | ++ Product Name * + | +
UOM *
| No. of Units * |
MRP * |
Sale Price * |
- {hasField('WholeSale Price') && Wholesale/Special Price | }
+ {hasField('WholeSale Price') && (
+ Wholesale/Special Price |
+ )}
Category *
|
- {hasField('Sub Category') &&
- Sub-Category
- | }
- {(hasField('Sub Category') && hasField('Brand')) && Brand
- | }
+ {hasField('Sub Category') && (
+
+ Sub-Category
+ |
+ )}
+ {hasField('Sub Category') && hasField('Brand') && (
+
+ Brand
+ |
+ )}
{hasField('EMI Allowed') && EMI Allowed | }
- {hasField('Auto Generate QRCode') && Auto Generate QRCode | }
+ {hasField('Auto Generate QRCode') && (
+ Auto Generate QRCode |
+ )}
{hasField('Auto Generate QRCode') && Scanner/AddQrCode | }
{hasField('Stock Maintenance') && Stock Maintenance | }
{hasField('Stock Maintenance') && Low Stock Count | }
{hasField('Token Maintenance') && Token Maintenance | }
- {hasField('Amount Per Piece') &&
+ {hasField('Amount Per Piece') && (
<>
Amount Per Piece |
Per Piece Amount |
@@ -1666,18 +2072,20 @@ const TurboAddForm = () => {
Scanner/AddOnePcQrCode |
No. of Piece Inside |
>
- }
+ )}
{hasField('Product Type') && Product Type | }
- {hasField('Tax') &&
- Tax % *
- | }
+ {hasField('Tax') && (
+
+ Tax % *
+ |
+ )}
{hasField('Cess') && Cess | }
{hasField('Discount Type') && Discount Type | }
{hasField('Discount Limit') && Discount Limit | }
@@ -1692,7 +2100,9 @@ const TurboAddForm = () => {
{hasField('MacId') && MacId | }
{hasField('Manufacture Date') && Manufacture Date | }
{hasField('Expire Date') && Expire Date | }
- {hasField('Expiry Notification Days') && Expiry Notification Days | }
+ {hasField('Expiry Notification Days') && (
+ Expiry Notification Days |
+ )}
{hasField('Available From') && Available From | }
{hasField('Available To') && Available To | }
{hasField('Product Image') && Product Image | }
@@ -1713,21 +2123,29 @@ const TurboAddForm = () => {
) : (
)} */}
{filteredProducts?.map((product, rowIndex) => (
- insertRowAbove(rowIndex)} title="Click to add row above">
+ | insertRowAbove(rowIndex)}
+ title="Click to add row above"
+ >
{rowIndex + 1}
|
{/* Product Name */}
-
+ |
{/* |
{/* UOM */}
-
+ |
|
{/* No. of Units */}
@@ -1817,24 +2255,42 @@ const TurboAddForm = () => {
- | )}
- {/* 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') && (
+
+ {hasField('Model Number') && (
+
| }
+
+ |
+ )}
{/* 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') &&
- |
+ )}
{/* Expiry Notification Days */}
- {hasField('Expiry Notification Days') &&
+ | }
+ 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;
+ }}
+ />
+
- |
+ )}
{/* Available From */}
- {hasField('Available From') &&
+ | }
+ 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();
+ }
+ }}
+ />
+
- | }
+ {hasField('Available From') && (
+
+ |
+ )}
{/* Available To */}
- {hasField('Available To') &&
- | }
+ {hasField('Available To') && (
+
+ |
+ )}
{/* Product Image */}
{/* {hasField('Product Image') &&
@@ -2506,25 +3464,27 @@ const TurboAddForm = () => {
| } */}
- {hasField('Product Image') &&
-
+ {hasField('Product Image') && (
+ |
|
- }
+ )}
{/* Delete */}
-
+ |
|
- Total Products: {products?.length}
- Showing: {filteredProducts?.length} of {products?.length}
+
+ Total Products: {products?.length}
+
+
+ Showing: {filteredProducts?.length} of{' '}
+ {products?.length}
+
• Click row numbers to insert
@@ -2567,7 +3532,7 @@ const TurboAddForm = () => {
>
{/*
@@ -2591,11 +3556,16 @@ const TurboAddForm = () => {
label="Select fields"
// valueData={selectedFields}
onChangeFunction={handleFieldSelect}
- options={tableFieldPreferences?.filter(p => !selectedFields?.some(s => s === p.value))} />
+ options={tableFieldPreferences?.filter(
+ (p) => !selectedFields?.some((s) => s === p.value)
+ )}
+ />
{selectedFields
- ?.map((id) => tableFieldPreferences?.find((p) => p.value === id))
+ ?.map((id) =>
+ tableFieldPreferences?.find((p) => p.value === id)
+ )
.filter(Boolean)
.map((field) => (
@@ -2669,19 +3639,30 @@ const TurboAddForm = () => {
key={products?.[imageModalRowIndex]?.id}
onlineImage={onlineImage}
ImageLink={products?.[imageModalRowIndex]?.productImage}
- updateImageUrl={url => {
- setProducts(prev =>
+ updateImageUrl={(url) => {
+ setProducts((prev) =>
prev.map((p, idx) =>
idx === imageModalRowIndex ? { ...p, productImage: url } : p
)
);
}}
/>
- |
|---|