Optimizationstockform

This commit is contained in:
Tamilselvan 2026-02-06 14:27:37 +05:30
parent cc39168b03
commit 63d682e151
8 changed files with 2147 additions and 3145 deletions

View File

@ -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 (
<div
className={`pozo-menu pozo-menu-${mode}`}

View File

@ -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 (
<Tooltip title="Add All Products" placement="top">
<button type="button" className="addallProductTBN" onClick={handleAddAll}>
Add All
</button>
</Tooltip>
);
};
export default AddAllProductsButton;

View File

@ -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 */}
<Tooltip title={tooltipTitle} placement="left">
<IoSettingsOutline
style={{ cursor: 'pointer', fontSize: '20px' }}
onClick={handleIconClick}
/>
</Tooltip>
{/* MODAL */}
<DefaultModal
title="Select Additional Fields"
open={open}
footer
buttonText="Apply"
destroyOnClose
handleSubmit={handleApply}
handleCancel={handleCancel}
>
<div style={{ padding: '20px' }}>
{tableFieldPreferences.map((option) => (
<div key={option.value} className="Customized_QuickAdd">
<label>
<input
type="checkbox"
checked={selectedFields.includes(option.value)}
onChange={(e) => {
const checked = e.target.checked;
setSelectedFields((prev) =>
checked
? [...prev, option.value]
: prev.filter((v) => v !== option.value)
);
setTempSelectedColumns((prev) =>
checked
? [...prev, option.label]
: prev.filter((v) => v !== option.label)
);
}}
style={{ marginRight: '8px' }}
/>
{option.label}
</label>
</div>
))}
</div>
</DefaultModal>
</>
);
};
export default SettingsIconWithModal;

File diff suppressed because it is too large Load Diff

View File

@ -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,
@ -232,8 +233,8 @@ 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();
@ -294,7 +295,7 @@ const StockList = () => {
setpage(1);
return Response?.data?.data;
} else {
return [];
}
}
@ -1163,7 +1164,7 @@ const StockList = () => {
? countData?.length
: 0
: data?.[0]?.TotalCount;
console.log(debouncedSearchText,countData,data,"kkkk",countOfProduct)
console.log(debouncedSearchText, countData, data, "kkkk", countOfProduct)
return (
<div className="userPageTable">
<div className="userPageContent">

View File

@ -0,0 +1,16 @@
import { useRef, useCallback } from 'react';
export const useDebounce = (fn, delay = 300) => {
const timer = useRef(null);
return useCallback(
(...args) => {
if (timer.current) clearTimeout(timer.current);
timer.current = setTimeout(() => {
fn(...args);
}, delay);
},
[fn, delay]
);
};

View File

@ -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 (
<div
style={{
border: '1px solid #d1d1d1',
borderRadius: '4px',
width: '100%',
overflow: 'hidden',
}}
>
{/* Header */}
<div ref={headerRef} style={{ overflowX: 'hidden', overflowY: 'hidden' }}>
<table
style={{
borderCollapse: 'collapse',
width: '100%',
tableLayout: 'auto',
}}
>
<thead style={{ background: '#2c88ee', color: '#fff' }}>
<tr className="proReceiptTD">
{columns.map((col) => (
<th
key={col.key || col.dataIndex}
style={{
padding: '10px 6px',
textAlign: col.align || 'left',
fontWeight: 600,
fontSize: '13px',
borderRight: '1px solid rgba(255,255,255,0.3)',
boxSizing: 'border-box',
width: col.width || 'auto',
minWidth: col.width || 80,
whiteSpace: 'nowrap',
}}
>
{col.title}
</th>
))}
</tr>
</thead>
</table>
</div>
{/* Body */}
<div
ref={parentRef}
onScroll={handleBodyScroll}
style={{
height,
overflowY: 'auto',
overflowX: 'auto',
position: 'relative',
scrollbarWidth: 'thin',
}}
>
<div style={{ height: `${totalSize}px`, position: 'relative' }}>
{virtualItems.map((virtualRow) => {
const record = dataSource[virtualRow.index];
const key = record?.[rowKey] || virtualRow.index;
return (
<div
className="UppertablePR"
key={key}
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: `${rowHeight}px`,
transform: `translateY(${virtualRow.start}px)`,
}}
>
<table
style={{
borderCollapse: 'collapse',
width: '100%',
tableLayout: 'auto',
height: '100%',
}}
>
<tbody>
<tr
className="proReceiptTD"
style={{
backgroundColor:
virtualRow.index % 2 === 0 ? '#e2e2e2' : '#fff',
borderBottom: '1px solid #f0f0f0',
}}
>
{columns.map((col) => (
<td
className="tdProductrecipt"
key={col.key || col.dataIndex}
style={{
padding: '6px',
borderRight: '1px solid #f0f0f0',
fontSize: '12px',
fontWeight: 500,
boxSizing: 'border-box',
width: col.width || 'auto',
minWidth: col.width || 80,
textAlign: col.align || 'left',
verticalAlign: 'middle',
}}
>
{col.render
? col.render(
record?.[col.dataIndex],
record,
virtualRow.index
)
: record?.[col.dataIndex]}
</td>
))}
</tr>
</tbody>
</table>
</div>
);
})}
</div>
</div>
{dataSource.length === 0 && (
<div
style={{
textAlign: 'center',
padding: '20px',
color: '#999',
fontSize: '14px',
}}
>
No data
</div>
)}
</div>
);
};
export default VirtualizedTable;

View File

@ -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;
}
}
}
.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;
}
}