diff --git a/src/Components/Menu/PozoMenu.jsx b/src/Components/Menu/PozoMenu.jsx index b9d861e..fa56b0d 100644 --- a/src/Components/Menu/PozoMenu.jsx +++ b/src/Components/Menu/PozoMenu.jsx @@ -1,5 +1,4 @@ -import React, { useState, useRef, useEffect } from 'react'; -import { AppstoreOutlined, MailOutlined, SettingOutlined, } from '@ant-design/icons'; +import { useState, useRef, useEffect } from 'react'; import { FaCaretRight } from "react-icons/fa"; import './PozoMenu.scss'; @@ -8,7 +7,6 @@ const PozoMenu = ({ items = [], mode = "vertical", style = {}, onClick, collapse const [openKeys, setOpenKeys] = useState([]); const [selectedKey, setSelectedKey] = useState(''); const [dropdownPositions, setDropdownPositions] = useState({}); - console.log(dropdownPositions, "dropdownPositions") const [isMobile, setIsMobile] = useState(window.innerWidth <= 768); const menuRef = useRef(); @@ -115,7 +113,7 @@ const PozoMenu = ({ items = [], mode = "vertical", style = {}, onClick, collapse // Image 2: No space on right, space on left - open to LEFT position.left = rect.left - dropdownWidth - gap + scrollX; position.direction = 'left'; - console.log('✅ Opening LEFT - no space on right'); + } else { // Edge case: Limited space on both sides if (spaceRight >= spaceLeft) { @@ -245,7 +243,7 @@ const PozoMenu = ({ items = [], mode = "vertical", style = {}, onClick, collapse // Calculate position and add to dropdown positions const position = calculatePosition(event.currentTarget, parentKeys.length); - console.log('🎯 Setting position for', itemKey, ':', position); + setDropdownPositions(prev => { // Remove positions for closed items @@ -254,7 +252,6 @@ const PozoMenu = ({ items = [], mode = "vertical", style = {}, onClick, collapse if (prev[key]) newPos[key] = prev[key]; }); newPos[itemKey] = position; - console.log('🎯 Updated dropdown positions:', newPos); return newPos; }); @@ -276,11 +273,6 @@ const PozoMenu = ({ items = [], mode = "vertical", style = {}, onClick, collapse }); } - console.log('Menu item clicked:', { - key: item.key, - keyPath: [...parentKeys, item.key], - label: item.label - }); } }; @@ -415,8 +407,6 @@ const PozoMenu = ({ items = [], mode = "vertical", style = {}, onClick, collapse }, [items, openKeys]); - console.log(submenuRefs, "menuRef") - return (
{ - console.log(ProdData, 'ProdData'); if ( ProdData?.CompId != null && - ProdData?.CompId != undefined && ProdData?.BranchId != null && - ProdData?.BranchId != undefined && - ProdData?.AppId != null && - ProdData?.AppId != undefined && - ProdData?.OrderFromDate && - ProdData?.OrderToDate + ProdData?.AppId != null ) { const params = new URLSearchParams({ compId: ProdData.CompId, branchId: ProdData.BranchId, appId: ProdData.AppId, - fromDate: ProdData.OrderFromDate, - toDate: ProdData.OrderToDate, }); + // ✅ Add dates only if present + if (ProdData?.OrderFromDate) { + params.append('fromDate', ProdData.OrderFromDate); + } + + if (ProdData?.OrderToDate) { + params.append('toDate', ProdData.OrderToDate); + } + + // ✅ Search if (ProdData?.search) { params.append('searchText', ProdData.search); } + + // ✅ Pagination if (ProdData?.PageNumber) { params.append('pageNumber', ProdData.PageNumber); } - return await axiosRetailInstanceData.get( + return axiosRetailInstanceData.get( `/Purchase?${params.toString()}` ); } + + // optional safety + return Promise.reject('Missing required parameters'); } ); + export const putStockData = createAsyncThunk( 'Stock/postStockData', async (putData) => { diff --git a/src/Pages/StockMaster/AddAllProductGrid.jsx b/src/Pages/StockMaster/AddAllProductGrid.jsx new file mode 100644 index 0000000..a8f8da1 --- /dev/null +++ b/src/Pages/StockMaster/AddAllProductGrid.jsx @@ -0,0 +1,151 @@ +import { Tooltip } from 'antd'; +import { v4 as uuidv4 } from 'uuid'; + +const AddAllProductsButton = ({ + productData = [], + purchaseData = [], + setPurchaseData, + form, + setIsLoading, + setLoadingText, + batchSize = 100, + SelectedPurcTaxType, +}) => { + // This function just returns a prepared product object, no state changes + + const ProductDropDownChange = async ( + VariantName, + ProdId, + option, + productsList, + extractedProduct = false, + extractedItem + ) => { + const ProductData1 = (productsList || productData)?.find( + (p) => p.ProdId === ProdId && p.ProdVariantName === VariantName + ); + if (!ProductData1) return null; + + const DefaultVariant = ProductData1?.ProdVariantPriceDetails?.find( + (item) => item?.DefaultVariant === 'Y' && item?.ReceivedQty === 0 + )?.DefaultVariant; + + const qty = extractedProduct + ? parseFloat(extractedItem?.parsedData?.qty) || 0 + : 0; + const acceptedQty = extractedProduct + ? parseFloat(extractedItem?.parsedData?.qty) || 0 + : 0; + const rate = extractedProduct + ? parseFloat(extractedItem?.parsedData?.rate) || 0 + : 0; + + return { + DefaultVariant, + ProdId: ProductData1.ProdId, + MRP: ProductData1.MRP, + ProdName: ProductData1.ProdName, + UomName: ProductData1.UomName, + SellPrice: ProductData1.SellPrice, + StockAvailable: ProductData1.StockAvailable, + OnePcsAvailable: ProductData1.OnePcsAvailable, + OnePcsPrice: ProductData1.OnePcsPrice, + NoOfPcs: ProductData1.NoOfPcs, + TaxId: ProductData1.TaxId, + TaxPercentage: ProductData1.TaxPercentage, + ProdVariantName: ProductData1.ProdVariantName, + PurchaseTax: parseFloat(extractedItem?.parsedData?.tax) || 0, + BalanceQty: qty, + InwardPrice: rate, + PurcDisc: 0, + Amount: isNaN(rate) || isNaN(acceptedQty) ? 0 : rate * acceptedQty, + WhSalePrice: 0, + ReceivedQty: qty, + AcceptedQty: acceptedQty, + RejectedQty: qty - acceptedQty, + OfferPrice: 0, + SpecialPrice: 0, + FreeItem: 0, + TaxAmt: 0, + TaxType: SelectedPurcTaxType || 0, + PurcDiscType: 'P', + refImage: extractedItem?.parsedData?.image, + PurchaseHSNCode: extractedItem?.parsedData?.hsn || '', + localId: uuidv4(), + }; + }; + + const handleAddAll = async () => { + try { + setIsLoading(true); + setLoadingText('Adding products...'); + + // allow UI paint + await new Promise((r) => requestAnimationFrame(r)); + + const allValidProducts = []; + const allFormValues = {}; + + for (let i = 0; i < productData.length; i += batchSize) { + const batch = productData.slice(i, i + batchSize); + + setLoadingText( + `Processing ${Math.min(i + batchSize, productData.length)} / ${ + productData.length + }` + ); + + const batchProducts = await Promise.all( + batch.map((product) => + ProductDropDownChange( + product.ProdVariantName, + product.ProdId, + { + label: `${product.ProdName} (${product.Size} ${product.UomName})${ + product.BrandName ? ` - ${product.BrandName}` : '' + }`, + }, + productData + ) + ) + ); + + const valid = batchProducts.filter(Boolean); + allValidProducts.push(...valid); + + valid.forEach((p) => { + const id = p.localId; + allFormValues[`SellPrice${id}`] = p.SellPrice; + allFormValues[`PurchaseTax${id}`] = p.PurchaseTax; + allFormValues[`BalanceQty${id}`] = p.BalanceQty; + allFormValues[`ReceivedQty${id}`] = p.ReceivedQty; + allFormValues[`AcceptedQty${id}`] = p.AcceptedQty; + allFormValues[`RejectedQty${id}`] = p.RejectedQty; + allFormValues[`InwardPrice${id}`] = p.InwardPrice; + allFormValues[`Amount${id}`] = p.Amount; + }); + + await new Promise((r) => setTimeout(r, 0)); + } + + // ✅ ONE state update + setPurchaseData((prev) => [...allValidProducts, ...prev]); + + // ✅ ONE form update + form?.setFieldsValue(allFormValues); + } finally { + setIsLoading(false); + setLoadingText(''); + } + }; + + return ( + + + + ); +}; + +export default AddAllProductsButton; diff --git a/src/Pages/StockMaster/SettingsIconWithModal.jsx b/src/Pages/StockMaster/SettingsIconWithModal.jsx new file mode 100644 index 0000000..2b673c9 --- /dev/null +++ b/src/Pages/StockMaster/SettingsIconWithModal.jsx @@ -0,0 +1,99 @@ +import React, { useState } from 'react'; +import { Tooltip } from '@mui/material'; +import { IoSettingsOutline } from 'react-icons/io5'; +import { DefaultModal } from '../../Components/Modal/DefaultModal.jsx'; + + +const SettingsIconWithModal = ({ + tooltipTitle = 'Add Fields', + tableFieldPreferences = [], + selectedAdditionalColumn = [], + onSubmit, +}) => { + const [open, setOpen] = useState(false); + const [selectedFields, setSelectedFields] = useState([]); + const [tempSelectedColumns, setTempSelectedColumns] = useState([]); + + const handleIconClick = () => { + // map labels → values + const mappedFields = selectedAdditionalColumn + .map( + (col) => + tableFieldPreferences.find((pref) => pref.label === col)?.value + ) + .filter(Boolean); + + setSelectedFields(mappedFields); + setTempSelectedColumns([...selectedAdditionalColumn]); + setOpen(true); + }; + + const handleCancel = () => { + setOpen(false); + setTempSelectedColumns([]); + }; + + const handleApply = () => { + onSubmit({ + selectedFields, + tempSelectedColumns, + closeModal: () => setOpen(false), + resetTemp: () => setTempSelectedColumns([]), + }); + }; + + return ( + <> + {/* ⚙️ ICON ONLY */} + + + + + {/* MODAL */} + +
+ {tableFieldPreferences.map((option) => ( +
+ +
+ ))} +
+
+ + ); +}; + +export default SettingsIconWithModal; diff --git a/src/Pages/StockMaster/StockList.jsx b/src/Pages/StockMaster/StockList.jsx index ad84da5..28e9c20 100644 --- a/src/Pages/StockMaster/StockList.jsx +++ b/src/Pages/StockMaster/StockList.jsx @@ -2,7 +2,7 @@ import { useState, useEffect, useRef, useCallback } from 'react'; import { useNavigate, useLocation } from 'react-router-dom'; import { useDispatch, useSelector } from 'react-redux'; import { PlusOutlined, EyeOutlined, PrinterFilled } from '@ant-design/icons'; -import { Image, Pagination, Space, Table } from 'antd'; +import { Image, Pagination, Space, Table, Tooltip } from 'antd'; import { changeBreadCrumb, getEmpAccess, @@ -59,6 +59,7 @@ import StickerPrintTemplates from '../Product/StickerTemplates.jsx'; import QRCodeCopiesModal from '../Product/QRCodeCopiesModal.jsx'; import { DatePicker } from 'antd'; import { + formatDateForAPI, generateBarcode, generateCodeImage, generateQRCode, @@ -142,7 +143,6 @@ const StockList = () => { const [selectedRecords, setSelectedRecords] = useState([]); const [activeTab, setActiveTab] = useState('stock'); const [MultiCode, setMultiCode] = useState(true); - const [TotalCount, setTotalCount] = useState(null); const [dateStrings, setDateStrings] = useState([ dayjs().format('YYYY-MM-DD'), dayjs().format('YYYY-MM-DD'), @@ -233,16 +233,15 @@ const StockList = () => { AppId: AppId, BranchId: BranchId, CompId: CompId, - OrderFromDate: dates?.[0] ?? '', - OrderToDate: dates?.[1] ?? '', + OrderFromDate: formatDateForAPI(dates?.[0]) ?? '', + OrderToDate: formatDateForAPI(dates?.[1]) ?? '', PageNumber: page, }) ).unwrap(); if (Response?.data?.statusCode === 1) { - setTotalCount(Response?.data?.TotalCount); return Response?.data?.data; } else { - setTotalCount(0); + return []; } }; @@ -287,17 +286,16 @@ const StockList = () => { AppId: AppId, BranchId: BranchId, CompId: CompId, - OrderFromDate: dateStrings?.[0] ?? '', - OrderToDate: dateStrings?.[1] ?? '', + // OrderFromDate: dateStrings?.[0] ?? '', + // OrderToDate: dateStrings?.[1] ?? '', search: SearchText, }) ).unwrap(); if (Response?.data?.statusCode === 1) { - setTotalCount(Response?.data?.TotalCount); setpage(1); return Response?.data?.data; } else { - setTotalCount(0); + return []; } } @@ -703,9 +701,9 @@ const StockList = () => { dispatch( getSupplierData({ AppId: AppId, BranchId: BranchId, CompId: CompId }) ); - dispatch( - getPurchaseData({ AppId: AppId, BranchId: BranchId, CompId: CompId }) - ); + // dispatch( + // getPurchaseData({ AppId: AppId, BranchId: BranchId, CompId: CompId }) + // ); let ProdData = { AppId: AppId, @@ -894,25 +892,25 @@ const StockList = () => { PaymentAmount: PaymentAmount.toFixed(2), InvoiceDate: groupInfo.InwardDate ? moment(groupInfo.InwardDate, 'YYYY-MM-DD').format( - 'YYYY-MM-DDTHH:mm:ss' - ) + 'YYYY-MM-DDTHH:mm:ss' + ) : null, SuppInvoiceNo: groupInfo.SupplierInvoiceNumber, SuppInvoiceDate: groupInfo.SupplierInvoiceDate ? moment(groupInfo.SupplierInvoiceDate, 'YYYY-MM-DD').format( - 'YYYY-MM-DDTHH:mm:ss' - ) + 'YYYY-MM-DDTHH:mm:ss' + ) : null, DeliveryChallanNo: groupInfo.DeliveryChallanNo, DeliveryInvoiceDate: groupInfo.DeliveryInvoiceDate ? moment(groupInfo.DeliveryInvoiceDate, 'YYYY-MM-DD').format( - 'YYYY-MM-DDTHH:mm:ss' - ) + 'YYYY-MM-DDTHH:mm:ss' + ) : null, DueDate: groupInfo.DueDate ? moment(groupInfo.DueDate, 'YYYY-MM-DD').format( - 'YYYY-MM-DDTHH:mm:ss' - ) + 'YYYY-MM-DDTHH:mm:ss' + ) : null, InvoiceAmount: Totalamount.toFixed(2), TaxAmount: Totaltax.toFixed(2), @@ -930,13 +928,13 @@ const StockList = () => { FreeQty: item.FreeQty, ManufDate: item.ManufactureDate ? moment(item.ManufactureDate, 'YYYY-MM-DD').format( - 'YYYY-MM-DDTHH:mm:ss' - ) + 'YYYY-MM-DDTHH:mm:ss' + ) : null, ExpDate: item.ExpireDate ? moment(item.ExpireDate, 'YYYY-MM-DD').format( - 'YYYY-MM-DDTHH:mm:ss' - ) + 'YYYY-MM-DDTHH:mm:ss' + ) : null, BatchNo: item.BatchNo, ModelNo: item.ModelNo, @@ -989,14 +987,10 @@ const StockList = () => { const productName = checkComponent('Product Name'); setPrintProductName(productName); }; - // const ProductName = Productdata ?.map((e)=>e.ProdName+' '(+e.Size+' '+e.UomName)) const ProductName = Productdata?.map( (e) => `${e.ProdName} (${e.Size} ${e.UomName})` ); - // const ProductName = Varientdata?.map((e)=>e.ProdName) - // const ProductName = Productdata?.map((e)=>e.ProdName) - // const ProductSize = Productdata ?.map((e)=>e.Size) - // const ProductUomName = Productdata ?.map((e)=>e.UomName) + const SupplierNames = SupplierData?.map((e) => e.SuppName); @@ -1012,33 +1006,8 @@ const StockList = () => { const InwardDtlIds = Varientdata?.map((e) => e.InwardDtlId); const StockProductId = Varientdata?.map((e) => e.ProdId); - // let proddetail = StockData?.map((e) => { - // return { - // ...e, - // ProdDetails: e.ProdDetails?.map((d) => ({ - // ...d, - // InvoiceNo: e.InvoiceNo, - // InvoiceDate: e.InvoiceDate, - // InvoiceAmt: e.InvoiceAmount - // })) - // }; - // }); - // let subObjectsArray = []; - // // Iterate over mainObjects and concatenate subObjects arrays - // proddetail.forEach(obj => { - // subObjectsArray = subObjectsArray.concat(obj.ProdDetails); - // }); - // function safeRound(amountStr) { - // if (amountStr == null) return allowDecimal ? '0.00' : '0'; - // const cleaned = String(amountStr).replace(/[^0-9.-]+/g, ''); - // const num = Number(cleaned); - - // if (isNaN(num)) return allowDecimal ? '0.00' : '0'; - - // return allowDecimal ? num.toFixed(2) : Math.round(num).toString(); - // } const onFinish = async () => { const template = barcodeTemplateDetails?.find( @@ -1194,7 +1163,8 @@ const StockList = () => { ? countData?.length ? countData?.length : 0 - : TotalCount; + : data?.[0]?.TotalCount; + console.log(debouncedSearchText, countData, data, "kkkk", countOfProduct) return (
@@ -1210,14 +1180,15 @@ const StockList = () => {
-
- -
+ > +
+ +
+
{ data={filteredData} dataSource={filteredData} onChange={handleChange} - // pagination={handlePageChange} + // pagination={handlePageChange} />{' '} {countOfProduct > 10 && ( { + const timer = useRef(null); + + return useCallback( + (...args) => { + if (timer.current) clearTimeout(timer.current); + + timer.current = setTimeout(() => { + fn(...args); + }, delay); + }, + [fn, delay] + ); +}; diff --git a/src/Pages/StockMaster/VirtualizedTable.jsx b/src/Pages/StockMaster/VirtualizedTable.jsx new file mode 100644 index 0000000..b2b610f --- /dev/null +++ b/src/Pages/StockMaster/VirtualizedTable.jsx @@ -0,0 +1,171 @@ +import { useRef } from 'react'; +import { useVirtualizer } from '@tanstack/react-virtual'; + +const VirtualizedTable = ({ + dataSource = [], + columns = [], + rowHeight = 54, + height = '50vh', + overscan = 10, + rowKey = 'id', +}) => { + const parentRef = useRef(); + const headerRef = useRef(); + + const rowVirtualizer = useVirtualizer({ + count: dataSource.length, + getScrollElement: () => parentRef.current, + estimateSize: () => rowHeight, + overscan, + }); + + const handleBodyScroll = (e) => { + if (headerRef.current) { + headerRef.current.scrollLeft = e.target.scrollLeft; + } + }; + + const virtualItems = rowVirtualizer.getVirtualItems(); + const totalSize = rowVirtualizer.getTotalSize(); + + return ( +
+ {/* Header */} +
+ + + + {columns.map((col) => ( + + ))} + + +
+ {col.title} +
+
+ + {/* Body */} +
+
+ {virtualItems.map((virtualRow) => { + const record = dataSource[virtualRow.index]; + const key = record?.[rowKey] || virtualRow.index; + + return ( +
+ + + + {columns.map((col) => ( + + ))} + + +
+ {col.render + ? col.render( + record?.[col.dataIndex], + record, + virtualRow.index + ) + : record?.[col.dataIndex]} +
+
+ ); + })} +
+
+ + {dataSource.length === 0 && ( +
+ No data +
+ )} +
+ ); +}; + +export default VirtualizedTable; diff --git a/src/Styles/Stock/StockMaster.scss b/src/Styles/Stock/StockMaster.scss index 8274819..28b0852 100644 --- a/src/Styles/Stock/StockMaster.scss +++ b/src/Styles/Stock/StockMaster.scss @@ -36,7 +36,6 @@ } .Product-table { - table { border-spacing: unset; } @@ -88,7 +87,7 @@ width: 180px !important; } - >div:nth-child(2) { + > div:nth-child(2) { p { display: none !important; } @@ -146,7 +145,7 @@ max-width: 130px !important; } - >td:nth-child(3) { + > td:nth-child(3) { .ant-input { width: max-content !important; max-width: 70px !important; @@ -154,7 +153,7 @@ } } - >td:nth-child(5) { + > td:nth-child(5) { .ant-input { width: max-content !important; max-width: 100px !important; @@ -325,12 +324,12 @@ } .purchase-status { - >div:nth-child(1) { + > div:nth-child(1) { display: flex; align-items: center; gap: 10px; - >p { + > p { padding-bottom: 0 !important; } } @@ -448,7 +447,7 @@ .invoice-date { .ant-form-item-control-input-content { - >label { + > label { font-family: 'Poppins'; font-weight: 500; } @@ -474,7 +473,7 @@ gap: 1rem; margin-bottom: 10px; - >button { + > button { font-family: 'Poppins'; background-color: #ef4444; color: #fff; @@ -482,8 +481,8 @@ align-items: center; .ant-btn-icon { - >span { - >svg { + > span { + > svg { width: 13px; height: 13px; } @@ -566,8 +565,6 @@ width: 100%; } - - .stock-input { // width: 100vw; width: 100%; @@ -608,8 +605,6 @@ width: 100%; } - - .stock-input { width: 100vw; } @@ -781,13 +776,12 @@ } } - .addallProductTBN { border: none; outline: none; padding: 8px 16px; width: max-content; - font-family: "Poppins"; + font-family: 'Poppins'; font-weight: 500; font-size: 14px; cursor: pointer; @@ -822,9 +816,26 @@ } .imeiSNTable { - .ant-input { padding: 4px 4px !important; text-align: center; } -} \ No newline at end of file +} + + +.proReceiptTD { + .ant-input { + width: 100% !important; + height: 35px !important; + padding: 2px !important; + text-align: center; + } +} + +@media (max-width: 500px) { + .tdProductrecipt, + .proReceiptTD { + font-size: 12px !important; + } + +}