Merge pull request 'expired libary change' (#298) from Mohan into main

Reviewed-on: Pozomind/pozo-retail-app#298
This commit is contained in:
mohan 2026-03-23 16:09:33 +05:30
commit 1ac1570823
20 changed files with 9355 additions and 17509 deletions

9194
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -11,8 +11,7 @@
"format": "prettier --write ." "format": "prettier --write ."
}, },
"dependencies": { "dependencies": {
"@ant-design/plots": "^1.2.6", "@ant-design/plots": "^2.6.8",
"@autocomplete/material-ui": "0.0.17",
"@capacitor-community/speech-recognition": "^7.0.1", "@capacitor-community/speech-recognition": "^7.0.1",
"@capacitor-mlkit/barcode-scanning": "^8.0.1", "@capacitor-mlkit/barcode-scanning": "^8.0.1",
"@capacitor/app": "^8.0.1", "@capacitor/app": "^8.0.1",
@ -21,53 +20,49 @@
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@emotion/react": "^11.11.4", "@emotion/react": "^11.11.4",
"@emotion/styled": "^11.11.5", "@emotion/styled": "^11.11.5",
"@material-ui/lab": "^4.0.0-alpha.61",
"@mui/material": "^5.15.18", "@mui/material": "^5.15.18",
"@react-google-maps/api": "^2.20.8",
"@reduxjs/toolkit": "^1.9.5", "@reduxjs/toolkit": "^1.9.5",
"@tanstack/react-query": "^5.90.11", "@tanstack/react-query": "^5.90.11",
"@tanstack/react-query-devtools": "^5.91.1", "@tanstack/react-query-devtools": "^5.91.1",
"@tanstack/react-virtual": "^3.13.18", "@tanstack/react-virtual": "^3.13.18",
"@types/node": "^25.5.0",
"@zxing/library": "^0.21.3", "@zxing/library": "^0.21.3",
"antd": "^5.17.4", "antd": "^5.17.4",
"antd-img-crop": "^4.22.0", "antd-img-crop": "^4.22.0",
"aos": "^2.3.4", "aos": "^2.3.4",
"axios": "^1.7.2", "apexcharts": "^5.10.4",
"axios": "^1.13.6",
"browser-image-compression": "^2.0.2", "browser-image-compression": "^2.0.2",
"classnames": "^2.5.1", "classnames": "^2.5.1",
"cropperjs": "^1.6.2", "cropperjs": "^1.6.2",
"crypto-js": "^4.1.1", "crypto-js": "^4.1.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"devtools-detect": "^4.0.2", "devtools-detect": "^4.0.2",
"exceljs": "^4.3.0", "exceljs": "^4.4.0",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"google-maps-react": "^2.0.6",
"highcharts": "^11.4.3",
"highcharts-react-official": "^3.2.1",
"history": "^5.3.0", "history": "^5.3.0",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
"html2pdf": "^0.0.11", "html2pdf": "^0.0.11",
"html2pdf.js": "^0.10.3",
"html5-qrcode": "^2.3.8", "html5-qrcode": "^2.3.8",
"jquery": "^3.7.1", "jquery": "^3.7.1",
"js-cookie": "^3.0.5", "js-cookie": "^3.0.5",
"jsbarcode": "^3.11.6", "jsbarcode": "^3.11.6",
"jspdf": "^2.5.1", "jspdf": "^4.2.1",
"lucide": "^0.577.0", "lucide": "^0.577.0",
"lucide-react": "^0.536.0", "lucide-react": "^0.536.0",
"material-ui": "^0.20.2",
"moment": "^2.30.1", "moment": "^2.30.1",
"mui-autocomplete": "^2.0.1",
"number-to-words": "^1.2.4", "number-to-words": "^1.2.4",
"pdf-lib": "^1.17.1", "pdf-lib": "^1.17.1",
"qr-scanner": "^1.4.2", "qr-scanner": "^1.4.2",
"qrcode": "^1.5.3", "qrcode": "^1.5.3",
"qrcode.react": "^4.2.0", "qrcode.react": "^4.2.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-apexcharts": "^2.1.0",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",
"react-color": "^2.19.3", "react-color": "^2.19.3",
"react-countup": "^6.5.3", "react-countup": "^6.5.3",
"react-device-detect": "^2.2.3", "react-device-detect": "^2.2.3",
"react-devtools": "^5.2.0",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-icons": "^4.12.0", "react-icons": "^4.12.0",
"react-image-crop": "^11.0.10", "react-image-crop": "^11.0.10",
@ -82,25 +77,23 @@
"recharts": "^3.5.0", "recharts": "^3.5.0",
"string-similarity": "^4.0.4", "string-similarity": "^4.0.4",
"tesseract.js": "^6.0.0", "tesseract.js": "^6.0.0",
"universal-cookie": "^6.1.3", "universal-cookie": "^8.0.1",
"webfontloader": "^1.6.28", "webfontloader": "^1.6.28",
"xlsx": "^0.18.5",
"zustand": "^5.0.8" "zustand": "^5.0.8"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.3.3", "@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.0", "@vitejs/plugin-react": "^6.0.1",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-react": "^7.34.2", "eslint-plugin-react": "^7.34.2",
"eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7", "eslint-plugin-react-refresh": "^0.4.7",
"prettier": "^3.5.3", "prettier": "^3.5.3",
"rollup-plugin-obfuscator": "^1.1.0", "rollup-plugin-obfuscator": "^1.1.0",
"rollup-plugin-terser": "^7.0.2",
"sass": "^1.77.2", "sass": "^1.77.2",
"terser": "^5.31.3", "terser": "^5.31.3",
"vite": "^4.5.3", "vite": "^8.0.0",
"vite-plugin-obfuscator": "^1.0.5", "vite-plugin-obfuscator": "^1.0.5",
"vite-plugin-remove-console": "^2.2.0" "vite-plugin-remove-console": "^2.2.0"
} }

View File

@ -1,5 +1,10 @@
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Map, Marker, GoogleApiWrapper } from 'google-maps-react'; import { GoogleMap, LoadScript, Marker } from '@react-google-maps/api';
const containerStyle = {
width: '100%',
height: '100%',
};
class MapView extends Component { class MapView extends Component {
constructor(props) { constructor(props) {
@ -9,81 +14,73 @@ class MapView extends Component {
currentLocation: props.prevlca ? props.prevlca : false, currentLocation: props.prevlca ? props.prevlca : false,
}; };
} }
async componentDidMount() { async componentDidMount() {
if (Object.keys(this.state.fields.location)?.length === 0) { if (Object.keys(this.state.fields.location)?.length === 0) {
const { lat, lng } = await this.getcurrentLocation(); const { lat, lng } = await this.getcurrentLocation();
this.setState((prev) => ({ this.setState((prev) => ({
fields: { fields: {
...prev.fields, ...prev.fields,
location: { location: { lat, lng },
lat,
lng,
},
},
currentLocation: {
lat,
lng,
}, },
currentLocation: { lat, lng },
})); }));
} }
} }
getcurrentLocation() { getcurrentLocation() {
if (navigator && navigator.geolocation) { if (navigator && navigator.geolocation) {
return new Promise((resolve, reject) => { return new Promise((resolve) => {
navigator.geolocation.getCurrentPosition((pos) => { navigator.geolocation.getCurrentPosition((pos) => {
const coords = pos.coords;
resolve({ resolve({
lat: coords.latitude, lat: pos.coords.latitude,
lng: coords.longitude, lng: pos.coords.longitude,
}); });
}); });
}); });
} }
return { return { lat: 0, lng: 0 };
lat: 0.0,
lng: 0,
};
} }
addMarker = (location, map) => {
handleMapClick = (e) => {
const lat = e.latLng.lat();
const lng = e.latLng.lng();
const location = { lat, lng };
this.setState({ this.setState({
fields: { location: { lat: location.lat(), lng: location.lng() } }, fields: { location },
}); });
map.panTo(location);
this.props.onMarkerClick(location); if (this.props.onMarkerClick) {
this.props.onMarkerClick(location);
}
}; };
render() { render() {
const { lat = 0, lng = 0 } = this.state.fields.location; const { lat = 0, lng = 0 } = this.state.fields.location;
// var bounds = new this.props.google.maps.LatLngBounds();
return ( return (
<div> <LoadScript googleMapsApiKey="AIzaSyB6w_WDy6psJ5HPX15Me1-o6CkS5jTYWnE">
<Map <GoogleMap
google={this.props.google} mapContainerStyle={containerStyle}
style={{
width: '100%',
height: '100%',
}}
initialCenter={{ lat: lat || 0, lng: lng || 0 }}
center={{ lat: lat || 0, lng: lng || 0 }} center={{ lat: lat || 0, lng: lng || 0 }}
zoom={14} zoom={14}
mapTypeId="roadmap" onClick={this.handleMapClick}
onClick={(t, map, c) => {
this.addMarker(c.latLng, map);
}}
// bounds={bounds}
> >
<Marker {lat && lng && (
tooltip={true} <Marker
name="Your Position" position={{ lat, lng }}
position={this.state.fields.location} onClick={() =>
onClick={this.props.onMarkerClick} this?.props?.onMarkerClick &&
/> this?.props?.onMarkerClick({ lat, lng })
</Map> }
</div> />
)}
</GoogleMap>
</LoadScript>
); );
} }
} }
export default GoogleApiWrapper({
apiKey: 'AIzaSyB6w_WDy6psJ5HPX15Me1-o6CkS5jTYWnE', export default MapView;
})(MapView);

View File

@ -1,97 +1,97 @@
import React, { useState } from 'react'; // import React, { useState } from 'react';
import { Modal, Button } from 'antd'; // import { Modal, Button } from 'antd';
import { ImportOutlined, ExportOutlined } from '@ant-design/icons'; // import { ImportOutlined, ExportOutlined } from '@ant-design/icons';
import * as XLSX from 'xlsx'; // import * as XLSX from 'xlsx';
import { saveAs } from 'file-saver'; // import { saveAs } from 'file-saver';
const [excelData, setExcelData] = useState(null); // const [excelData, setExcelData] = useState(null);
const [excelFile, setExcelFile] = useState(null); // const [excelFile, setExcelFile] = useState(null);
const [excelFileError, setExcelFileError] = useState(null); // const [excelFileError, setExcelFileError] = useState(null);
const handleFile = (e) => { // const handleFile = (e) => {
let selectedFile = e.target.files[0]; // let selectedFile = e.target.files[0];
if (selectedFile) { // if (selectedFile) {
if (selectedFile && fileType.includes(selectedFile.type)) { // if (selectedFile && fileType.includes(selectedFile.type)) {
let reader = new FileReader(); // let reader = new FileReader();
reader.readAsArrayBuffer(selectedFile); // reader.readAsArrayBuffer(selectedFile);
reader.onload = (e) => { // reader.onload = (e) => {
setExcelFileError(null); // setExcelFileError(null);
setExcelFile(e.target.result); // setExcelFile(e.target.result);
}; // };
} else { // } else {
setExcelFileError('Please select only excel file types'); // setExcelFileError('Please select only excel file types');
setExcelFile(null); // setExcelFile(null);
} // }
} else { // } else {
console.log('Please select a file'); // console.log('Please select a file');
} // }
}; // };
const handleSubmit = (e) => { // const handleSubmit = (e) => {
e.preventDefault(); // e.preventDefault();
if (excelFile !== null) { // if (excelFile !== null) {
const workbook = XLSX.read(excelFile, { type: 'buffer' }); // const workbook = XLSX.read(excelFile, { type: 'buffer' });
const worksheetName = workbook.SheetNames[0]; // const worksheetName = workbook.SheetNames[0];
const worksheet = workbook.Sheets[worksheetName]; // const worksheet = workbook.Sheets[worksheetName];
const data = XLSX.utils.sheet_to_json(worksheet); // const data = XLSX.utils.sheet_to_json(worksheet);
setExcelData(data); // setExcelData(data);
} else { // } else {
setExcelData(null); // setExcelData(null);
} // }
}; // };
const handleExport = () => { // const handleExport = () => {
if (excelData !== null) { // if (excelData !== null) {
const worksheet = XLSX.utils.json_to_sheet(excelData); // const worksheet = XLSX.utils.json_to_sheet(excelData);
const workbook = XLSX.utils.book_new(); // const workbook = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1'); // XLSX.utils.book_append_sheet(workbook, worksheet, 'Sheet1');
const excelBuffer = XLSX.write(workbook, { // const excelBuffer = XLSX.write(workbook, {
bookType: 'xlsx', // bookType: 'xlsx',
type: 'array', // type: 'array',
}); // });
const excelData = new Blob([excelBuffer], { // const excelData = new Blob([excelBuffer], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
}); // });
saveAs(excelData, 'exported_data.xlsx'); // saveAs(excelData, 'exported_data.xlsx');
} // }
}; // };
const Modals = ({ open, title, handleCancel, children, footer }) => { // const Modals = ({ open, title, handleCancel, children, footer }) => {
return ( // return (
<Modal // <Modal
visible={open} // visible={open}
title={title} // title={title}
onCancel={handleCancel} // onCancel={handleCancel}
footer={ // footer={
footer // footer
? [ // ? [
<Button key="submit" type="primary" onClick={handleSubmit}> // <Button key="submit" type="primary" onClick={handleSubmit}>
Submit // Submit
</Button>, // </Button>,
<Button key="cancel" onClick={handleCancel}> // <Button key="cancel" onClick={handleCancel}>
Cancel // Cancel
</Button>, // </Button>,
] // ]
: footer // : footer
} // }
> // >
{children} // {children}
<div> // <div>
<h4>Import Excel</h4> // <h4>Import Excel</h4>
<input type="file" onChange={handleFile} /> // <input type="file" onChange={handleFile} />
{excelFileError && <div className="error">{excelFileError}</div>} // {excelFileError && <div className="error">{excelFileError}</div>}
<Button type="primary" icon={<ImportOutlined />} onClick={handleSubmit}> // <Button type="primary" icon={<ImportOutlined />} onClick={handleSubmit}>
Import // Import
</Button> // </Button>
</div> // </div>
<div> // <div>
<h4>Export Excel</h4> // <h4>Export Excel</h4>
<Button type="primary" icon={<ExportOutlined />} onClick={handleExport}> // <Button type="primary" icon={<ExportOutlined />} onClick={handleExport}>
Export // Export
</Button> // </Button>
</div> // </div>
</Modal> // </Modal>
); // );
}; // };
export default Modals; // export default Modals;

View File

@ -65,6 +65,7 @@ export const MobileMultiPdfPrint = async ({
}) => { }) => {
try { try {
const style = await PrintStyleFunction(printerTemplateStyle, 'Sales'); const style = await PrintStyleFunction(printerTemplateStyle, 'Sales');
const stylesMap = { const stylesMap = {
'Style 1': 'PrintStyle1', 'Style 1': 'PrintStyle1',
'Style 2': 'PrintStyle2', 'Style 2': 'PrintStyle2',
@ -88,78 +89,61 @@ export const MobileMultiPdfPrint = async ({
const selectedStyle = stylesMap[printerTemplateStyle] || 'RePrint'; const selectedStyle = stylesMap[printerTemplateStyle] || 'RePrint';
const Printsize = printDatas?.PageSize || 'A4'; const Printsize = printDatas?.PageSize || 'A4';
// Create or clear the container
let container = document.getElementById('print-merged');
if (!container) {
container = document.createElement('div');
container.id = 'print-merged';
container.style.display = 'none';
document.body.appendChild(container);
} else {
container.innerHTML = '';
}
// Process in batches for large files
const BATCH_SIZE = 5;
const totalOrders = PrintOrderDetails.length; const totalOrders = PrintOrderDetails.length;
let processedOrders = 0;
let allBlobs = []; let allBlobs = [];
for (let i = 0; i < totalOrders; i += BATCH_SIZE) { // 🔥 LOOP EACH INVOICE (MAIN FIX)
const batch = PrintOrderDetails.slice(i, i + BATCH_SIZE); for (let i = 0; i < totalOrders; i++) {
const element = document.getElementById(
`${selectedStyle}-${i}`
);
// Clear container for each batch if (!element) {
container.innerHTML = ''; console.warn('⚠️ Element not found:', `${selectedStyle}-${i}`);
continue;
// Process each order in the current batch
batch.forEach((_, index) => {
const globalIndex = i + index;
const element = document.getElementById(
`${selectedStyle}-${globalIndex}`
);
if (element) {
const cloned = element.cloneNode(true);
// Add page break between orders (except the last one)
if (globalIndex < totalOrders - 1) {
const pageBreak = document.createElement('div');
pageBreak.style.pageBreakAfter = 'always';
container.appendChild(cloned);
container.appendChild(pageBreak);
} else {
container.appendChild(cloned);
}
} else {
console.warn(
'⚠️ Element not found:',
`${selectedStyle}-${globalIndex}`
);
}
});
// Generate PDF for this batch
try {
const pdfBlob = await pdfDiv('print-merged', style);
allBlobs.push(pdfBlob);
processedOrders += batch.length;
const progress = Math.round((processedOrders / totalOrders) * 100);
onProgress(progress);
} catch (error) {
console.error('Error generating PDF batch:', error);
throw new Error(
`Failed to generate PDF for batch starting at index ${i}`
);
} }
// 🔥 TEMP WRAPPER (IMPORTANT)
const wrapper = document.createElement('div');
wrapper.id = `temp-${i}`;
wrapper.style.width = '794px';
wrapper.style.background = '#fff';
wrapper.innerHTML = `
${style}
${element.innerHTML}
`;
document.body.appendChild(wrapper);
try {
// 👉 ONE invoice ONE PDF
const blob = await pdfDiv(wrapper.id);
allBlobs.push(blob);
} catch (err) {
console.error('PDF error:', err);
}
document.body.removeChild(wrapper);
// 🔥 Progress update
const progress = Math.round(((i + 1) / totalOrders) * 100);
onProgress(progress);
// 🔥 small delay for stability (important for large data)
await new Promise((r) => setTimeout(r, 5));
} }
// Merge all PDF blobs into one // 🔥 MERGE ALL PDFs
const combinedPdfBlob = await mergePdfBlobs(allBlobs); const combinedPdfBlob = await mergePdfBlobs(allBlobs);
const base64Pdf = await blobToBase64(combinedPdfBlob); const base64Pdf = await blobToBase64(combinedPdfBlob);
// Handle different print actions // 🔥 DOWNLOAD
if (PrintComing === 'download') { if (PrintComing === 'download') {
const blobUrl = createSecureBlobUrl(combinedPdfBlob); const blobUrl = createSecureBlobUrl(combinedPdfBlob);
const link = document.createElement('a'); const link = document.createElement('a');
link.href = blobUrl; link.href = blobUrl;
link.download = 'Receipts.pdf'; link.download = 'Receipts.pdf';
@ -171,72 +155,45 @@ export const MobileMultiPdfPrint = async ({
revokeSecureBlobUrl(blobUrl); revokeSecureBlobUrl(blobUrl);
}, 100); }, 100);
} }
// Return blob if requested (after download completes) // 🔥 RETURN BLOB
if (returnBlob && PrintComing === 'download') { if (returnBlob && PrintComing === 'download') {
setPrintOrderDetails([]); setPrintOrderDetails([]);
if (container) {
document.body.removeChild(container);
}
return { success: true, pdfBlob: combinedPdfBlob }; return { success: true, pdfBlob: combinedPdfBlob };
} }
// if (PrintComing === "whatsapp") { // 🔥 WHATSAPP
// const orderIds = PrintOrderDetails.map((row) => row?.OrderId).filter(Boolean);
// const documentUrl = `${MainHomeUrl}viewbill?code=${orderIds}`;
// const waUrl =
// "https://web.whatsapp.com/send?text=" +
// encodeURIComponent("Here are your receipts 👉 " + documentUrl);
// window.open(waUrl, "_blank");
// }
if (PrintComing === 'whatsapp') { if (PrintComing === 'whatsapp') {
const orderIdsArray = PrintOrderDetails.map((row) => row?.OrderId).filter( const orderIdsArray = PrintOrderDetails.map((row) => row?.OrderId).filter(Boolean);
Boolean
);
if (orderIdsArray.length === 0) { if (orderIdsArray.length === 0) return;
console.warn('⚠️ No order IDs found for WhatsApp sharing.');
return;
}
// 2 Encrypt order IDs
const encrypted = encryptObject(orderIdsArray); const encrypted = encryptObject(orderIdsArray);
// 3 Prepare payload for backend
const postData = { const postData = {
Url: encrypted, Url: encrypted,
CreatedBy: UserId, // Make sure UserId is passed correctly to your function CreatedBy: UserId,
}; };
try { try {
// 4 Call backend to get a ReferenceNo
const res = await dispatch(PublicQrCodepost(postData)).unwrap(); const res = await dispatch(PublicQrCodepost(postData)).unwrap();
console.log('Backend response:', res);
// 5 Extract ReferenceNo (adjust based on your backend response structure)
// Example: it might be res.data.ReferenceNo or res.ReferenceNo
const urlData = res?.data?.ReferenceNo || res?.ReferenceNo; const urlData = res?.data?.ReferenceNo || res?.ReferenceNo;
if (!urlData) return;
if (!urlData) {
console.error('❌ No ReferenceNo returned from backend');
return;
}
// 6 Build the shareable URL
const documentUrl = `${MainHomeUrl}viewbill?code=${urlData}`; const documentUrl = `${MainHomeUrl}viewbill?code=${urlData}`;
// 7 Open WhatsApp Web with URL
const waUrl = const waUrl =
'https://web.whatsapp.com/send?text=' + 'https://web.whatsapp.com/send?text=' +
encodeURIComponent('Here are your receipts 👉 ' + documentUrl); encodeURIComponent('Here are your receipts 👉 ' + documentUrl);
window.open(waUrl, '_blank'); window.open(waUrl, '_blank');
} catch (err) { } catch (err) {
console.error('❌ WhatsApp sharing failed:', err); console.error('❌ WhatsApp error:', err);
} }
} }
// 🔥 EMAIL
if (PrintComing === 'email') { if (PrintComing === 'email') {
try { try {
const payload = { const payload = {
@ -247,13 +204,13 @@ export const MobileMultiPdfPrint = async ({
}; };
const res = await dispatch(Emailsend(payload))?.unwrap(); const res = await dispatch(Emailsend(payload))?.unwrap();
console.log('✅ Email sent response:', res?.data || res);
return res; return res;
} catch (err) { } catch (err) {
console.error('❌ Email error:', err); console.error('❌ Email error:', err);
} }
} }
// 🔥 PRINT VIEW
if (!['download', 'whatsapp', 'email'].includes(PrintComing)) { if (!['download', 'whatsapp', 'email'].includes(PrintComing)) {
const blobUrl = createSecureBlobUrl(combinedPdfBlob); const blobUrl = createSecureBlobUrl(combinedPdfBlob);
@ -261,17 +218,10 @@ export const MobileMultiPdfPrint = async ({
if (newWindow) { if (newWindow) {
newWindow.document.write(` newWindow.document.write(`
<html> <html>
<head> <body style="margin:0">
<title>Receipts</title> <button onclick="window.print()"
<style> style="position:fixed;top:10px;right:10px;">Print</button>
body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background: #f0f0f0; } <iframe src="${blobUrl}" style="width:100%;height:100%;border:none"></iframe>
iframe { width: 100%; height: 100%; border: none; }
.print-button { position: fixed; top: 20px; right: 20px; z-index: 1000; padding: 10px 15px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; }
</style>
</head>
<body>
<button class="print-button" onclick="window.print()">Print</button>
<iframe src="${blobUrl}" type="application/pdf"></iframe>
</body> </body>
</html> </html>
`); `);
@ -280,43 +230,299 @@ export const MobileMultiPdfPrint = async ({
window.location.href = blobUrl; window.location.href = blobUrl;
} }
// For mobile intent URL
const intentUrl = const intentUrl =
'intent://open?' + 'intent://open?' +
'filename=' + 'filename=' + encodeURIComponent('Receipts.pdf') +
encodeURIComponent('Receipts.pdf') + '&size=' + combinedPdfBlob.size +
'&size=' +
combinedPdfBlob.size +
'&type=application/pdf' + '&type=application/pdf' +
'&data=' + '&data=' + encodeURIComponent(base64Pdf) +
encodeURIComponent(base64Pdf) + '&PrintTypeS=' + encodeURIComponent(Printsize) +
'&PrintTypeS=' +
encodeURIComponent(Printsize) +
'&PrintTypeE' +
'#Intent;scheme=pozoprinter;package=com.example.pozoprinter;end'; '#Intent;scheme=pozoprinter;package=com.example.pozoprinter;end';
setTimeout(() => { setTimeout(() => {
try { try {
window.location.href = intentUrl; window.location.href = intentUrl;
} catch (e) { } catch {console.log("some errors")}
console.warn('Could not open intent URL:', e);
}
}, 500); }, 500);
} }
// Clean up // 🔥 CLEANUP
setPrintOrderDetails([]); setPrintOrderDetails([]);
if (container) {
document.body.removeChild(container);
}
return { success: true, message: 'PDF processed successfully' }; return { success: true, message: 'PDF processed successfully' };
} catch (error) { } catch (error) {
console.error('MobileMultiPdfPrint error:', error); console.error('MobileMultiPdfPrint error:', error);
return { success: false, message: error.message }; return { success: false, message: error.message };
} }
}; };
// export const MobileMultiPdfPrint = async ({
// printerTemplateStyle = 'Style 13',
// printDatas = {},
// PrintOrderDetails = [],
// setPrintOrderDetails = () => {},
// PrintComing = '',
// emailId = '',
// dispatch,
// UserId = '',
// onProgress = () => {},
// returnBlob = false,
// }) => {
// try {
// const style = await PrintStyleFunction(printerTemplateStyle, 'Sales');
// const stylesMap = {
// 'Style 1': 'PrintStyle1',
// 'Style 2': 'PrintStyle2',
// 'Style 3': 'PrintStyle3',
// 'Style 4': 'PrintStyle4',
// 'Style 5': 'PrintStyle5',
// 'Style 6': 'PrintStyle6',
// 'Style 7': 'PrintStyle7',
// 'Style 8': 'PrintStyle8',
// 'Style 9': 'PrintStyle9',
// 'Style 10': 'PrintStyle10',
// 'Style 11': 'PrintStyle11',
// 'Style 12': 'PrintStyle12',
// 'Style 13': 'RePrint',
// A4: 'PrintStyleA4',
// A5: 'PrintStyleA5',
// A4Standard: 'A4Standard',
// TaxInvoice: 'TaxInvoice',
// };
// const selectedStyle = stylesMap[printerTemplateStyle] || 'RePrint';
// const Printsize = printDatas?.PageSize || 'A4';
// // Create or clear the container
// let container = document.getElementById('print-merged');
// if (!container) {
// container = document.createElement('div');
// container.id = 'print-merged';
// container.style.display = 'none';
// document.body.appendChild(container);
// } else {
// container.innerHTML = '';
// }
// // Process in batches for large files
// const BATCH_SIZE = 5;
// const totalOrders = PrintOrderDetails.length;
// let processedOrders = 0;
// let allBlobs = [];
// for (let i = 0; i < totalOrders; i += BATCH_SIZE) {
// const batch = PrintOrderDetails.slice(i, i + BATCH_SIZE);
// // Clear container for each batch
// container.innerHTML = '';
// // Process each order in the current batch
// batch.forEach((_, index) => {
// const globalIndex = i + index;
// const element = document.getElementById(
// `${selectedStyle}-${globalIndex}`
// );
// if (element) {
// const cloned = element.cloneNode(true);
// // Add page break between orders (except the last one)
// if (globalIndex < totalOrders - 1) {
// const pageBreak = document.createElement('div');
// pageBreak.style.pageBreakAfter = 'always';
// container.appendChild(cloned);
// container.appendChild(pageBreak);
// } else {
// container.appendChild(cloned);
// }
// } else {
// console.warn(
// ' Element not found:',
// `${selectedStyle}-${globalIndex}`
// );
// }
// });
// // Generate PDF for this batch
// try {
// const pdfBlob = await pdfDiv('print-merged', style);
// allBlobs.push(pdfBlob);
// processedOrders += batch.length;
// const progress = Math.round((processedOrders / totalOrders) * 100);
// onProgress(progress);
// } catch (error) {
// console.error('Error generating PDF batch:', error);
// throw new Error(
// `Failed to generate PDF for batch starting at index ${i}`
// );
// }
// }
// // Merge all PDF blobs into one
// const combinedPdfBlob = await mergePdfBlobs(allBlobs);
// const base64Pdf = await blobToBase64(combinedPdfBlob);
// // Handle different print actions
// if (PrintComing === 'download') {
// const blobUrl = createSecureBlobUrl(combinedPdfBlob);
// const link = document.createElement('a');
// link.href = blobUrl;
// link.download = 'Receipts.pdf';
// document.body.appendChild(link);
// link.click();
// setTimeout(() => {
// document.body.removeChild(link);
// revokeSecureBlobUrl(blobUrl);
// }, 100);
// }
// // Return blob if requested (after download completes)
// if (returnBlob && PrintComing === 'download') {
// setPrintOrderDetails([]);
// if (container) {
// document.body.removeChild(container);
// }
// return { success: true, pdfBlob: combinedPdfBlob };
// }
// // if (PrintComing === "whatsapp") {
// // const orderIds = PrintOrderDetails.map((row) => row?.OrderId).filter(Boolean);
// // const documentUrl = `${MainHomeUrl}viewbill?code=${orderIds}`;
// // const waUrl =
// // "https://web.whatsapp.com/send?text=" +
// // encodeURIComponent("Here are your receipts 👉 " + documentUrl);
// // window.open(waUrl, "_blank");
// // }
// if (PrintComing === 'whatsapp') {
// const orderIdsArray = PrintOrderDetails.map((row) => row?.OrderId).filter(
// Boolean
// );
// if (orderIdsArray.length === 0) {
// console.warn(' No order IDs found for WhatsApp sharing.');
// return;
// }
// // 2 Encrypt order IDs
// const encrypted = encryptObject(orderIdsArray);
// // 3 Prepare payload for backend
// const postData = {
// Url: encrypted,
// CreatedBy: UserId, // Make sure UserId is passed correctly to your function
// };
// try {
// // 4 Call backend to get a ReferenceNo
// const res = await dispatch(PublicQrCodepost(postData)).unwrap();
// console.log('Backend response:', res);
// // 5 Extract ReferenceNo (adjust based on your backend response structure)
// // Example: it might be res.data.ReferenceNo or res.ReferenceNo
// const urlData = res?.data?.ReferenceNo || res?.ReferenceNo;
// if (!urlData) {
// console.error(' No ReferenceNo returned from backend');
// return;
// }
// // 6 Build the shareable URL
// const documentUrl = `${MainHomeUrl}viewbill?code=${urlData}`;
// // 7 Open WhatsApp Web with URL
// const waUrl =
// 'https://web.whatsapp.com/send?text=' +
// encodeURIComponent('Here are your receipts 👉 ' + documentUrl);
// window.open(waUrl, '_blank');
// } catch (err) {
// console.error(' WhatsApp sharing failed:', err);
// }
// }
// if (PrintComing === 'email') {
// try {
// const payload = {
// fileBlob: combinedPdfBlob,
// toEmail: emailId,
// type: 'Sales',
// messageTemplatesList: [],
// };
// const res = await dispatch(Emailsend(payload))?.unwrap();
// console.log(' Email sent response:', res?.data || res);
// return res;
// } catch (err) {
// console.error(' Email error:', err);
// }
// }
// if (!['download', 'whatsapp', 'email'].includes(PrintComing)) {
// const blobUrl = createSecureBlobUrl(combinedPdfBlob);
// const newWindow = window.open('', '_blank');
// if (newWindow) {
// newWindow.document.write(`
// <html>
// <head>
// <title>Receipts</title>
// <style>
// body { margin: 0; display: flex; justify-content: center; align-items: center; height: 100vh; background: #f0f0f0; }
// iframe { width: 100%; height: 100%; border: none; }
// .print-button { position: fixed; top: 20px; right: 20px; z-index: 1000; padding: 10px 15px; background: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; }
// </style>
// </head>
// <body>
// <button class="print-button" onclick="window.print()">Print</button>
// <iframe src="${blobUrl}" type="application/pdf"></iframe>
// </body>
// </html>
// `);
// newWindow.document.close();
// } else {
// window.location.href = blobUrl;
// }
// // For mobile intent URL
// const intentUrl =
// 'intent://open?' +
// 'filename=' +
// encodeURIComponent('Receipts.pdf') +
// '&size=' +
// combinedPdfBlob.size +
// '&type=application/pdf' +
// '&data=' +
// encodeURIComponent(base64Pdf) +
// '&PrintTypeS=' +
// encodeURIComponent(Printsize) +
// '&PrintTypeE' +
// '#Intent;scheme=pozoprinter;package=com.example.pozoprinter;end';
// setTimeout(() => {
// try {
// window.location.href = intentUrl;
// } catch (e) {
// console.warn('Could not open intent URL:', e);
// }
// }, 500);
// }
// // Clean up
// setPrintOrderDetails([]);
// if (container) {
// document.body.removeChild(container);
// }
// return { success: true, message: 'PDF processed successfully' };
// } catch (error) {
// console.error('MobileMultiPdfPrint error:', error);
// return { success: false, message: error.message };
// }
// };
// Progress component to show processing status // Progress component to show processing status
export const PdfProcessingProgress = ({ progress, isProcessing }) => { export const PdfProcessingProgress = ({ progress, isProcessing }) => {
if (!isProcessing) return null; if (!isProcessing) return null;

View File

@ -1,10 +1,9 @@
import React, { useEffect, useState } from 'react'; import { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { ShopOutlined, ShoppingOutlined, UpOutlined } from '@ant-design/icons'; import { UpOutlined } from '@ant-design/icons';
import { import {
ChangeDineInDefaultFn, ChangeDineInDefaultFn,
ChangeSelectedCustDisable,
GlobalBookingType, GlobalBookingType,
GlobalChairLevelServiceDefault, GlobalChairLevelServiceDefault,
GlobalDefaultBookingType, GlobalDefaultBookingType,
@ -13,24 +12,20 @@ import {
Globalunpaidflow, Globalunpaidflow,
changeBookingType, changeBookingType,
changeChairLevelServiceDefault, changeChairLevelServiceDefault,
changeCustomerID,
changeDefaultBookingType, changeDefaultBookingType,
changeEstimateBooking, changeEstimateBooking,
changeOrderCardDetails, changeOrderCardDetails,
changeOrderType, changeOrderType,
changeReorderHoldDetails, changeReorderHoldDetails,
changeReorderProductDetails, changeReorderProductDetails,
changeSelectedCustId,
changeSelectedOption,
changeSelectedTableDetails, changeSelectedTableDetails,
} from '../../../../Features/BookingScreen/BookingData/BookingData'; } from '../../../../Features/BookingScreen/BookingData/BookingData';
import PozoDineInIcon from './Pozo retail icons/PozoDineIn.jsx'; import PozoDineInIcon from './Pozo retail icons/PozoDineIn.jsx';
import TakeAway from './Pozo retail icons/PozoTakeAwayIcon'; import TakeAway from './Pozo retail icons/PozoTakeAwayIcon';
import { ChangeTotalAmount } from '../../../../Features/ExteraCharges/ExtraCharges.js';
import TooltipWrapper from '../../../../Components/Tooltip/Tooltip'; import TooltipWrapper from '../../../../Components/Tooltip/Tooltip';
import { isMobile } from 'react-device-detect'; import { isMobile } from 'react-device-detect';
import { Badge, Popover, Space, Switch, Tooltip } from 'antd'; import { Popover, Space, Switch, Tooltip } from 'antd';
import { color } from 'highcharts';
import './BSNavBarTable.scss'; import './BSNavBarTable.scss';
import { RiRefreshLine } from "react-icons/ri"; import { RiRefreshLine } from "react-icons/ri";
@ -72,7 +67,7 @@ const BSNavBarTable = () => {
await dispatch(changeReorderHoldDetails({})); await dispatch(changeReorderHoldDetails({}));
await dispatch(changeReorderProductDetails([])); await dispatch(changeReorderProductDetails([]));
await dispatch(changeOrderCardDetails([])); await dispatch(changeOrderCardDetails([]));
dispatch(changeunpaidflow(false)); // dispatch(changeunpaidflow(false));
} }
// await dispatch(changeReorderHoldDetails({})) // await dispatch(changeReorderHoldDetails({}))
// await dispatch(ChangeTotalAmount([])) // await dispatch(ChangeTotalAmount([]))

View File

@ -1,7 +1,7 @@
import React, { useRef, useEffect, useState, useContext } from 'react'; import React, { useRef, useEffect, useState, useContext } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { Table, Button, Form, Input, Tooltip, Modal } from 'antd'; import { Table, Form, Input, Tooltip, Modal } from 'antd';
import { read, utils } from 'xlsx'; // import { read, utils } from 'xlsx';
import ExcelJS from 'exceljs'; import ExcelJS from 'exceljs';
import { import {
emptyExcelData, emptyExcelData,
@ -32,6 +32,7 @@ const ConfigExcel = ({ handleSubmit, CategoryNames, SubCatData }) => {
const [worksheet1, setWorksheet1] = useState(null); const [worksheet1, setWorksheet1] = useState(null);
const [editdelete, seteditdelete] = useState(''); const [editdelete, seteditdelete] = useState('');
const [Datas, setDatas] = useState(); const [Datas, setDatas] = useState();
const [currentPage, setCurrentPage] = useState(1);
const [radioValue, setRadioValue] = useState('Category Name'); const [radioValue, setRadioValue] = useState('Category Name');
@ -146,58 +147,137 @@ const ConfigExcel = ({ handleSubmit, CategoryNames, SubCatData }) => {
// Programmatically trigger the file input click event // Programmatically trigger the file input click event
fileInputRef.current.click(); fileInputRef.current.click();
}; };
const uploadAndProcessExcel = (file) => { const uploadAndProcessExcel = (file) => {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (e) => { reader.onload = async (e) => {
const data = new Uint8Array(e.target.result); try {
const buffer = e.target.result;
const workbook = read(data, { type: 'array' }); const workbook = new ExcelJS.Workbook();
const worksheet = workbook.Sheets[workbook.SheetNames[0]]; await workbook.xlsx.load(buffer);
const jsonData = utils.sheet_to_json(worksheet, { const worksheet = workbook.worksheets[0];
header: 1,
raw: false,
dateNF: 'DD/MM/YY',
});
const nonEmptyRows = jsonData.filter((row) => const jsonData = [];
row.some((cell) => cell !== '')
);
const header = nonEmptyRows[0];
const rows = nonEmptyRows.slice(1);
const dateFields = ['Manufacture Date', 'Expire Date', 'Stock Date']; worksheet.eachRow((row) => {
const rowData = [];
function convertToDisplayFormat(dateString) { row.eachCell({ includeEmpty: true }, (cell) => {
const date = new Date(dateString); let value = cell.value;
const day = date.getDate().toString().padStart(2, '0');
const month = (date.getMonth() + 1).toString().padStart(2, '0'); // handle different ExcelJS value types
const year = date.getFullYear().toString(); if (value && typeof value === 'object') {
return `${day}-${month}-${year}`; if (value.text) value = value.text;
else if (value.result) value = value.result;
else if (value.richText) {
value = value.richText.map((t) => t.text).join('');
}
}
rowData.push(value ?? '');
});
jsonData.push(rowData);
});
// remove empty rows (same as before)
const nonEmptyRows = jsonData.filter((row) =>
row.some((cell) => cell !== '')
);
const header = nonEmptyRows[0];
const rows = nonEmptyRows.slice(1);
const dateFields = ['Manufacture Date', 'Expire Date', 'Stock Date'];
const convertToDisplayFormat = (dateString) => {
const date = new Date(dateString);
if (isNaN(date)) return '';
return `${String(date.getDate()).padStart(2, '0')}-${String(
date.getMonth() + 1
).padStart(2, '0')}-${date.getFullYear()}`;
};
const formattedData = rows.map((row) => {
return header.reduce((acc, col, index) => {
if (dateFields.includes(col)) {
acc[col] = convertToDisplayFormat(row[index]);
} else {
acc[col] = row[index];
}
return acc;
}, {});
});
// SAME AS BEFORE (no break)
dispatch(uploadExcel({ file, jsonData: nonEmptyRows }));
setDatas(formattedData);
} catch (err) {
console.error(err);
Modal.error({
title: 'Error',
content: 'Failed to process Excel file',
});
} }
const formattedData = rows.map((row) => {
let rowData = header.reduce((acc, col, columnIndex) => {
if (dateFields.includes(col)) {
acc[col] = convertToDisplayFormat(row[columnIndex]);
} else {
acc[col] = row[columnIndex];
}
return acc;
}, {});
return rowData;
});
dispatch(uploadExcel({ file, jsonData: nonEmptyRows }));
setWorksheet1(worksheet1);
setDatas(formattedData);
}; };
reader.readAsArrayBuffer(file); reader.readAsArrayBuffer(file);
}; };
// const uploadAndProcessExcel = (file) => {
// const reader = new FileReader();
// reader.onload = (e) => {
// const data = new Uint8Array(e.target.result);
// const workbook = read(data, { type: 'array' });
// const worksheet = workbook.Sheets[workbook.SheetNames[0]];
// const jsonData = utils.sheet_to_json(worksheet, {
// header: 1,
// raw: false,
// dateNF: 'DD/MM/YY',
// });
// const nonEmptyRows = jsonData.filter((row) =>
// row.some((cell) => cell !== '')
// );
// const header = nonEmptyRows[0];
// const rows = nonEmptyRows.slice(1);
// const dateFields = ['Manufacture Date', 'Expire Date', 'Stock Date'];
// function convertToDisplayFormat(dateString) {
// const date = new Date(dateString);
// const day = date.getDate().toString().padStart(2, '0');
// const month = (date.getMonth() + 1).toString().padStart(2, '0');
// const year = date.getFullYear().toString();
// return `${day}-${month}-${year}`;
// }
// const formattedData = rows.map((row) => {
// let rowData = header.reduce((acc, col, columnIndex) => {
// if (dateFields.includes(col)) {
// acc[col] = convertToDisplayFormat(row[columnIndex]);
// } else {
// acc[col] = row[columnIndex];
// }
// return acc;
// }, {});
// return rowData;
// });
// dispatch(uploadExcel({ file, jsonData: nonEmptyRows }));
// setWorksheet1(worksheet1);
// setDatas(formattedData);
// };
// reader.readAsArrayBuffer(file);
// };
const handleDownload = async () => { const handleDownload = async () => {
const workbook = new ExcelJS.Workbook(); const workbook = new ExcelJS.Workbook();

View File

@ -1,6 +1,7 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import Highcharts from 'highcharts'; import Chart from 'react-apexcharts';
import HighchartsReact from 'highcharts-react-official';
import './PieChart.scss'; import './PieChart.scss';
const DynamicPieChart = ({ chartData }) => { const DynamicPieChart = ({ chartData }) => {
@ -37,16 +38,35 @@ const DynamicPieChart = ({ chartData }) => {
); );
} }
// 🔥 Convert Highcharts format ApexCharts format
const series = chartData.map((item) => item.y);
const labels = chartData.map((item) => item.name);
const options = {
chart: {
type: 'pie',
},
labels: labels,
title: {
text: `Overall Amount : ${safeRound(overallValue)}`,
align: 'center',
},
tooltip: {
y: {
formatter: (val) => safeRound(val),
},
},
legend: {
position: 'bottom',
},
dataLabels: {
formatter: (val) => `${val.toFixed(1)}%`,
},
};
return ( return (
<div style={{ width: 'inherit' }}> <div style={{ width: '100%' }}>
<HighchartsReact <Chart options={options} series={series} type="pie" height={300} />
highcharts={Highcharts}
options={{
chart: { type: 'pie' },
title: { text: `Overall Amount : ${safeRound(overallValue)}` },
series: [{ name: 'Amount', data: chartData }],
}}
/>
</div> </div>
); );
}; };

View File

@ -5,11 +5,9 @@ import { changeKioskOrderDetails } from '../../../Features/BookingScreen/Booking
import '../../../Styles/Kiosk/OrderCart/OrderCart2.scss'; import '../../../Styles/Kiosk/OrderCart/OrderCart2.scss';
import PozoDineInIcon from '../KioskIcons/KioskDineIn'; import PozoDineInIcon from '../KioskIcons/KioskDineIn';
import TakeAway from '../KioskIcons/KioskTakeAway'; import TakeAway from '../KioskIcons/KioskTakeAway';
import { color } from 'highcharts';
import { calculateOverAllQtyLimit } from '../../../utils/calculations'; import { calculateOverAllQtyLimit } from '../../../utils/calculations';
import { PreferenceData } from '../../../Features/BookingScreen/BookingData/BookingData'; import { PreferenceData } from '../../../Features/BookingScreen/BookingData/BookingData';
import { useSelector } from 'react-redux'; import { useSelector } from 'react-redux';
import { message } from 'antd';
import { useState } from 'react'; import { useState } from 'react';
import { IoIosWarning } from 'react-icons/io'; import { IoIosWarning } from 'react-icons/io';

File diff suppressed because it is too large Load Diff

View File

@ -158,7 +158,7 @@ const ProductList = () => {
UomTypePreference?.some?.( UomTypePreference?.some?.(
(e) => (e) =>
e?.PreferredSubCatName?.toLowerCase() == e?.PreferredSubCatName?.toLowerCase() ==
item?.ConfigName?.toLowerCase() && e?.PreferredStatus == 'Y' item?.ConfigName?.toLowerCase() && e?.PreferredStatus == 'Y'
) )
); );
@ -965,19 +965,19 @@ const ProductList = () => {
}, },
...(brandColumn && subCategory ...(brandColumn && subCategory
? [ ? [
{ {
title: 'Brand', title: 'Brand',
dataIndex: 'BrandName', dataIndex: 'BrandName',
key: 'BrandName', key: 'BrandName',
columnKey: 'BrandName', columnKey: 'BrandName',
width: '100px', width: '100px',
render: (text) => <a style={{ color: 'black' }}>{text}</a>, render: (text) => <a style={{ color: 'black' }}>{text}</a>,
sorter: (a, b) => a?.BrandName?.localeCompare(b?.BrandName), sorter: (a, b) => a?.BrandName?.localeCompare(b?.BrandName),
sortOrder: sortOrder:
sortedInfo.columnKey === 'BrandName' ? sortedInfo.order : null, sortedInfo.columnKey === 'BrandName' ? sortedInfo.order : null,
ellipsis: true, ellipsis: true,
}, },
] ]
: []), : []),
{ {
@ -996,127 +996,127 @@ const ProductList = () => {
...(qrCodeColumn ...(qrCodeColumn
? [ ? [
{ {
title: 'QRCode', title: 'QRCode',
dataIndex: 'QRCode', dataIndex: 'QRCode',
key: 'QRCode', key: 'QRCode',
width: '100px', width: '100px',
align: 'center', align: 'center',
render: (text) => ( render: (text) => (
<a style={{ color: 'black' }}>{text ? text : '-'}</a> <a style={{ color: 'black' }}>{text ? text : '-'}</a>
), ),
ellipsis: true, ellipsis: true,
}, },
{ {
title: ( title: (
<Tooltip title=" printing multiple QR codes"> <Tooltip title=" printing multiple QR codes">
<Space> <Space>
{MultiCode && ( {MultiCode && (
<input
type="checkbox"
checked={
selectedRecords.length > 0 &&
productData
.filter((p) => p?.QRCode)
.every((p) =>
selectedRecords.some((r) => r.ProdId === p.ProdId)
)
}
onChange={(e) => {
const productsWithQR = productData.filter(
(p) => p?.QRCode
);
if (e.target.checked) {
const newSelections = productsWithQR.filter(
(p) =>
!selectedRecords.some(
(r) => r.ProdId === p.ProdId
)
);
setSelectedRecords((prev) => [
...prev,
...newSelections,
]);
// Create QR codes for newly selected products
CreateQrcodesBatch(newSelections, true);
} else {
// Remove QR codes for deselected products
CreateQrcodesBatch(productsWithQR, false);
setSelectedRecords((prev) =>
prev.filter(
(r) =>
!productsWithQR.some(
(p) => p.ProdId === r.ProdId
)
)
);
}
}}
/>
)}
<span>Print QRCode</span>
<CheckSquareOutlined
style={{
fontSize: '18px',
cursor: 'pointer',
color: MultiCode ? '#1890ff' : '#666',
}}
onClick={() => {
setMultiCode((prev) => {
const newValue = !prev;
if (!newValue) {
setSelectedRecords([]);
}
return newValue;
});
}}
/>
</Space>
</Tooltip>
),
key: 'Print',
dataIndex: 'Print',
width: '100px',
align: 'center',
render: (_, record) =>
productData.length >= 1 ? (
<Space size="middle">
{MultiCode ? (
record?.QRCode ? (
<input <input
type="checkbox" type="checkbox"
checked={selectedRecords.some( checked={
(r) => r.ProdId == record.ProdId selectedRecords.length > 0 &&
)} productData
.filter((p) => p?.QRCode)
.every((p) =>
selectedRecords.some((r) => r.ProdId === p.ProdId)
)
}
onChange={(e) => { onChange={(e) => {
handleCheckboxChange(record, e.target.checked); const productsWithQR = productData.filter(
CreateQrcode( (p) => p?.QRCode
e.target.checked,
record.QRCode,
record?.ProdName,
record?.StockAvailable,
record?.TotalBalanceQty
); );
if (e.target.checked) {
const newSelections = productsWithQR.filter(
(p) =>
!selectedRecords.some(
(r) => r.ProdId === p.ProdId
)
);
setSelectedRecords((prev) => [
...prev,
...newSelections,
]);
// Create QR codes for newly selected products
CreateQrcodesBatch(newSelections, true);
} else {
// Remove QR codes for deselected products
CreateQrcodesBatch(productsWithQR, false);
setSelectedRecords((prev) =>
prev.filter(
(r) =>
!productsWithQR.some(
(p) => p.ProdId === r.ProdId
)
)
);
}
}} }}
/> />
)}
<span>Print QRCode</span>
<CheckSquareOutlined
style={{
fontSize: '18px',
cursor: 'pointer',
color: MultiCode ? '#1890ff' : '#666',
}}
onClick={() => {
setMultiCode((prev) => {
const newValue = !prev;
if (!newValue) {
setSelectedRecords([]);
}
return newValue;
});
}}
/>
</Space>
</Tooltip>
),
key: 'Print',
dataIndex: 'Print',
width: '100px',
align: 'center',
render: (_, record) =>
productData.length >= 1 ? (
<Space size="middle">
{MultiCode ? (
record?.QRCode ? (
<input
type="checkbox"
checked={selectedRecords.some(
(r) => r.ProdId == record.ProdId
)}
onChange={(e) => {
handleCheckboxChange(record, e.target.checked);
CreateQrcode(
e.target.checked,
record.QRCode,
record?.ProdName,
record?.StockAvailable,
record?.TotalBalanceQty
);
}}
/>
) : (
'-'
)
) : record?.QRCode ? (
<a>
<PrinterFilled
style={{ fontSize: '20px' }}
onClick={() => Qrcode(record)}
/>
</a>
) : ( ) : (
'-' '-'
) )}
) : record?.QRCode ? ( </Space>
<a> ) : null,
<PrinterFilled },
style={{ fontSize: '20px' }} ]
onClick={() => Qrcode(record)}
/>
</a>
) : (
'-'
)}
</Space>
) : null,
},
]
: []), : []),
{ {
@ -1134,11 +1134,11 @@ const ProductList = () => {
style={{ color: '#1292EE' }} style={{ color: '#1292EE' }}
onClick={() => onClick={() =>
UserType === 'Super Admin' || UserType === 'Super Admin' ||
(UserType === 'Super Admin User' && (UserType === 'Super Admin User' &&
SAAccessCommonMaster?.UpdateAccess === 'Y') || SAAccessCommonMaster?.UpdateAccess === 'Y') ||
(UserType === 'Employee' && (UserType === 'Employee' &&
empData?.UpdateAccess === 'Y') || empData?.UpdateAccess === 'Y') ||
UserType === 'Admin' UserType === 'Admin'
? actionsFormatter(record, index) ? actionsFormatter(record, index)
: null : null
} }
@ -1155,11 +1155,11 @@ const ProductList = () => {
}} }}
onClick={() => onClick={() =>
UserType === 'Super Admin' || UserType === 'Super Admin' ||
(UserType === 'Super Admin User' && (UserType === 'Super Admin User' &&
SAAccessCommonMaster?.DeleteAccess === 'Y') || SAAccessCommonMaster?.DeleteAccess === 'Y') ||
(UserType === 'Employee' && (UserType === 'Employee' &&
empData?.DeleteAccess === 'Y') || empData?.DeleteAccess === 'Y') ||
UserType === 'Admin' UserType === 'Admin'
? statusFormatter(record) ? statusFormatter(record)
: null : null
} }
@ -1171,11 +1171,11 @@ const ProductList = () => {
}} }}
onClick={() => onClick={() =>
UserType === 'Super Admin' || UserType === 'Super Admin' ||
(UserType === 'Super Admin User' && (UserType === 'Super Admin User' &&
SAAccessCommonMaster?.DeleteAccess === 'Y') || SAAccessCommonMaster?.DeleteAccess === 'Y') ||
(UserType === 'Employee' && (UserType === 'Employee' &&
empData?.DeleteAccess === 'Y') || empData?.DeleteAccess === 'Y') ||
UserType === 'Admin' UserType === 'Admin'
? statusFormatter(record) ? statusFormatter(record)
: null : null
} }
@ -1596,7 +1596,12 @@ const ProductList = () => {
} }
}; };
const getPreference = async () => { const getPreference = async () => {
const data = { AppId: AppId, CompId: CompId, BranchId: BranchId,UserId: UserId }; const data = {
AppId: AppId,
CompId: CompId,
BranchId: BranchId,
UserId: UserId,
};
const { data: res } = await dispatch(getPreferenceData(data)).unwrap(); const { data: res } = await dispatch(getPreferenceData(data)).unwrap();
const decimalSetting = res?.data?.[0]?.SettingDtlDetails?.find( const decimalSetting = res?.data?.[0]?.SettingDtlDetails?.find(
(setting) => (setting) =>
@ -2029,9 +2034,6 @@ const ProductList = () => {
formRef?.current?.resetFields(); formRef?.current?.resetFields();
}; };
const handleResetExcelData = useCallback(() => { const handleResetExcelData = useCallback(() => {
dispatch(emptyExcelData()); dispatch(emptyExcelData());
}, []); }, []);
@ -2051,9 +2053,7 @@ const ProductList = () => {
const handleSubmit = useCallback(async () => { const handleSubmit = useCallback(async () => {
setBulkpost(true); setBulkpost(true);
const FilterData = Exceldatasubmit?.filter( const FilterData = Exceldatasubmit?.filter((a) => a.ProductName !== '');
a => a.ProductName !== ''
);
if (!FilterData || FilterData.length === 0) { if (!FilterData || FilterData.length === 0) {
setBulkpost(false); setBulkpost(false);
@ -2062,7 +2062,7 @@ const ProductList = () => {
return; return;
} }
const formattedData = FilterData.map(data => ({ const formattedData = FilterData.map((data) => ({
AppId: getSession('AppId'), AppId: getSession('AppId'),
CompId: getSession('CompId'), CompId: getSession('CompId'),
BranchId: getSession('BranchId'), BranchId: getSession('BranchId'),
@ -2139,7 +2139,7 @@ const ProductList = () => {
BranchId, BranchId,
AppId, AppId,
handleResetExcelData, handleResetExcelData,
handleCancel handleCancel,
]); ]);
const Unit = useMemo( const Unit = useMemo(
@ -2197,10 +2197,7 @@ const ProductList = () => {
[SupplierData] [SupplierData]
); );
const TaxDatas = useMemo( const TaxDatas = useMemo(() => TaxData?.map((e) => e.TaxIdName), [TaxData]);
() => TaxData?.map((e) => e.TaxIdName),
[TaxData]
);
const TaxData1 = useMemo( const TaxData1 = useMemo(
() => TaxData?.map((e) => e.TaxPercentage), () => TaxData?.map((e) => e.TaxPercentage),
@ -2209,9 +2206,7 @@ const ProductList = () => {
const combinedTaxData = useMemo( const combinedTaxData = useMemo(
() => () =>
TaxDatas?.map( TaxDatas?.map((taxIdName, index) => `${taxIdName} - ${TaxData1[index]}%`),
(taxIdName, index) => `${taxIdName} - ${TaxData1[index]}%`
),
[TaxDatas, TaxData1] [TaxDatas, TaxData1]
); );
const Deletebulk = async () => { const Deletebulk = async () => {
@ -2706,7 +2701,9 @@ const ProductList = () => {
generateQRCodeCopy={(code) => generateQRCodeCopy={(code) =>
generateQRCodeCopy(QrandbarcodeDatas, code) generateQRCodeCopy(QrandbarcodeDatas, code)
} }
generateCodeImage={(code, codeType = 'Q') => generateCodeImage(code, codeType, QrandbarcodeDatas)} generateCodeImage={(code, codeType = 'Q') =>
generateCodeImage(code, codeType, QrandbarcodeDatas)
}
imageTagBarcodeAndQR={imageTagBarcodeAndQR} imageTagBarcodeAndQR={imageTagBarcodeAndQR}
dropdownValue={dropdownValue} dropdownValue={dropdownValue}
barcodeTemplateDetails={barcodeTemplateDetails} barcodeTemplateDetails={barcodeTemplateDetails}
@ -2739,7 +2736,7 @@ const ProductList = () => {
/> />
{Bulkpost && ( {Bulkpost && (
<div className="Bulkuploadpost"> <div className="Bulkuploadpost">
<div align="center" class="fondLoader"> <div align="center" className="fondLoader">
<div className="contener_general"> <div className="contener_general">
<div className="contener_mixte"> <div className="contener_mixte">
<div className="ballcolor ball_1">&nbsp;</div> <div className="ballcolor ball_1">&nbsp;</div>
@ -2812,7 +2809,7 @@ const ProductList = () => {
}} }}
onlineImage={onlineImage} onlineImage={onlineImage}
ImageLink={rowRecord?.ProdLogo || null} ImageLink={rowRecord?.ProdLogo || null}
updateImageUrl={imageModalRowIndex ? updateImageUrl : () => { }} updateImageUrl={imageModalRowIndex ? updateImageUrl : () => {}}
handleClose={handleImageModalClose} handleClose={handleImageModalClose}
/> />
<button <button

View File

@ -1,6 +1,13 @@
import React, { useState, useRef, useEffect, useContext, useCallback, useMemo } from 'react'; import React, {
useState,
useRef,
useEffect,
useContext,
useCallback,
useMemo,
} from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { read, utils } from 'xlsx'; // import { read, utils } from 'xlsx';
import ExcelJS from 'exceljs'; import ExcelJS from 'exceljs';
import { Table, Form, Input, Tooltip } from 'antd'; import { Table, Form, Input, Tooltip } from 'antd';
import { AiFillDelete } from 'react-icons/ai'; import { AiFillDelete } from 'react-icons/ai';
@ -8,7 +15,6 @@ import { FiDelete, FiDownload } from 'react-icons/fi';
import { import {
emptyExcelData, emptyExcelData,
excelDataSelector, excelDataSelector,
excelFileSelector,
excelFileErrorSelector, excelFileErrorSelector,
uploadExcel, uploadExcel,
} from '../../Features/ExcelUploadPage/ExcelUploadPage.js'; } from '../../Features/ExcelUploadPage/ExcelUploadPage.js';
@ -38,22 +44,6 @@ const EXCEL_COLUMNS = [
{ header: 'Variant Name', key: 'VariantName', width: 20, field: '4' }, { header: 'Variant Name', key: 'VariantName', width: 20, field: '4' },
]; ];
const HEADER_STYLE = {
font: { bold: true },
border: {
top: { style: 'thin' },
left: { style: 'thin' },
bottom: { style: 'thin' },
right: { style: 'thin' },
},
alignment: { horizontal: 'center', vertical: 'middle' },
};
const HEADER_FILL = {
type: 'pattern',
pattern: 'solid',
fgColor: { argb: 'FF2929' },
};
// Utility Functions // Utility Functions
const isValidExcelFile = (file) => ALLOWED_EXCEL_TYPES.includes(file.type); const isValidExcelFile = (file) => ALLOWED_EXCEL_TYPES.includes(file.type);
@ -187,14 +177,22 @@ const PurchaseExcel = ({ handleSubmit }) => {
const type = supplierData.Type; const type = supplierData.Type;
const LocationType = const LocationType =
type === 'Supplier' ? 'S' : type === 'Branch' ? 'B' : type === 'WareHouse' ? 'W' : ''; type === 'Supplier'
? 'S'
: type === 'Branch'
? 'B'
: type === 'WareHouse'
? 'W'
: '';
const response = await dispatch( const response = await dispatch(
getSupplaierIdwithTypeBasedProducts({ getSupplaierIdwithTypeBasedProducts({
CompId: LocationType === 'S' ? CompId : supplierData?.SuppCompId, CompId: LocationType === 'S' ? CompId : supplierData?.SuppCompId,
AppId: LocationType === 'S' ? AppId : supplierData?.SuppAppId, AppId: LocationType === 'S' ? AppId : supplierData?.SuppAppId,
BranchId: LocationType === 'S' ? BranchId : supplierData?.SuppBranchId, BranchId:
SuppId: LocationType === 'S' ? supplierId : supplierData?.SuppBranchId, LocationType === 'S' ? BranchId : supplierData?.SuppBranchId,
SuppId:
LocationType === 'S' ? supplierId : supplierData?.SuppBranchId,
LocationType, LocationType,
}) })
)?.unwrap(); )?.unwrap();
@ -205,17 +203,17 @@ const PurchaseExcel = ({ handleSubmit }) => {
} else { } else {
setSupplierProducts([]); setSupplierProducts([]);
setMessageData('No products are mapped to this supplier.'); setMessageData('No products are mapped to this supplier.');
setMessageType('warning') setMessageType('warning');
} }
} else { } else {
setSupplierProducts([]); setSupplierProducts([]);
setMessageData('Failed to fetch supplier products.'); setMessageData('Failed to fetch supplier products.');
setMessageType('error') setMessageType('error');
} }
} catch (error) { } catch (error) {
console.error('Error fetching supplier products:', error); console.error('Error fetching supplier products:', error);
setMessageData('An error occurred while fetching products.'); setMessageData('An error occurred while fetching products.');
setMessageType('error') setMessageType('error');
} }
}; };
@ -227,23 +225,55 @@ const PurchaseExcel = ({ handleSubmit }) => {
}, [data, handleSubmit]); }, [data, handleSubmit]);
// File upload handler // File upload handler
// const uploadAndProcessExcel = useCallback(
// (file) => {
// const reader = new FileReader();
// reader.onload = (e) => {
// const arrayBuffer = new Uint8Array(e.target.result);
// const workbook = read(arrayBuffer, { type: 'array' });
// const worksheet = workbook.Sheets[workbook.SheetNames[0]];
// const jsonData = utils.sheet_to_json(worksheet, {
// header: 1,
// raw: false,
// dateNF: 'DD/MM/YY',
// });
// const nonEmptyRows = jsonData.filter((row) => row.some((cell) => cell !== ''));
// dispatch(uploadExcel({ file, jsonData: nonEmptyRows }));
// };
// reader.readAsArrayBuffer(file);
// },
// [dispatch]
// );
const uploadAndProcessExcel = useCallback( const uploadAndProcessExcel = useCallback(
(file) => { (file) => {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (e) => { reader.onload = async (e) => {
const arrayBuffer = new Uint8Array(e.target.result); const buffer = e.target.result;
const workbook = read(arrayBuffer, { type: 'array' });
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const jsonData = utils.sheet_to_json(worksheet, { const workbook = new ExcelJS.Workbook();
header: 1, await workbook.xlsx.load(buffer);
raw: false,
dateNF: 'DD/MM/YY', const worksheet = workbook.worksheets[0];
// 🔥 same structure as sheet_to_json({ header: 1 })
const jsonData = [];
worksheet.eachRow((row) => {
const rowValues = row.values.slice(1); // remove first empty index
jsonData.push(rowValues);
}); });
const nonEmptyRows = jsonData.filter((row) => row.some((cell) => cell !== '')); const nonEmptyRows = jsonData.filter((row) =>
row.some((cell) => cell !== '' && cell !== null && cell !== undefined)
);
// unchanged
dispatch(uploadExcel({ file, jsonData: nonEmptyRows })); dispatch(uploadExcel({ file, jsonData: nonEmptyRows }));
}; };
@ -263,7 +293,7 @@ const PurchaseExcel = ({ handleSubmit }) => {
if (!isValidExcelFile(file)) { if (!isValidExcelFile(file)) {
setMessageData('Only Excel files (.xls, .xlsx) are allowed.'); setMessageData('Only Excel files (.xls, .xlsx) are allowed.');
setMessageType('error') setMessageType('error');
handleCancelData(); handleCancelData();
return; return;
} }
@ -290,16 +320,20 @@ const PurchaseExcel = ({ handleSubmit }) => {
// Download Excel template with supplier-specific products and dynamic variants // Download Excel template with supplier-specific products and dynamic variants
const handleDownload = useCallback(async () => { const handleDownload = useCallback(async () => {
if (!supplierProducts || supplierProducts.length === 0 || !selectedSupplierData) { if (
!supplierProducts ||
supplierProducts.length === 0 ||
!selectedSupplierData
) {
setMessageData('No products available to generate Excel.'); setMessageData('No products available to generate Excel.');
setMessageType('warning') setMessageType('warning');
return; return;
} }
const workbook = new ExcelJS.Workbook(); const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet("Sheet1"); const worksheet = workbook.addWorksheet('Sheet1');
const hiddenSheet = workbook.addWorksheet("ProductVariantMap"); const hiddenSheet = workbook.addWorksheet('ProductVariantMap');
const validationSheet = workbook.addWorksheet("ValidationHelper"); const validationSheet = workbook.addWorksheet('ValidationHelper');
// __define-ocg__ - defining column headers for Excel generation // __define-ocg__ - defining column headers for Excel generation
worksheet.columns = EXCEL_COLUMNS.map(({ header, key, width }) => ({ worksheet.columns = EXCEL_COLUMNS.map(({ header, key, width }) => ({
@ -313,11 +347,11 @@ const PurchaseExcel = ({ handleSubmit }) => {
cell.fill = { cell.fill = {
type: 'pattern', type: 'pattern',
pattern: 'solid', pattern: 'solid',
fgColor: { argb: 'FF2929' } // Red background fgColor: { argb: 'FF2929' }, // Red background
}; };
cell.font = { cell.font = {
bold: true, bold: true,
color: { argb: '000000' } // White text color: { argb: '000000' }, // White text
}; };
cell.border = { cell.border = {
top: { style: 'thin' }, top: { style: 'thin' },
@ -327,14 +361,14 @@ const PurchaseExcel = ({ handleSubmit }) => {
}; };
cell.alignment = { cell.alignment = {
horizontal: 'center', horizontal: 'center',
vertical: 'middle' vertical: 'middle',
}; };
}); });
hiddenSheet.addRow(["ProductName", "VariantName"]); hiddenSheet.addRow(['ProductName', 'VariantName']);
// Add validation helper columns // Add validation helper columns
validationSheet.addRow(["IsDuplicate"]); validationSheet.addRow(['IsDuplicate']);
const supplierDisplayName = getSupplierDisplayName(selectedSupplierData); const supplierDisplayName = getSupplierDisplayName(selectedSupplierData);
const productNames = supplierProducts.map((p) => p.ProdName); const productNames = supplierProducts.map((p) => p.ProdName);
@ -347,7 +381,7 @@ const PurchaseExcel = ({ handleSubmit }) => {
const uniqueVariantNames = [ const uniqueVariantNames = [
...new Set( ...new Set(
(product.ProdVariantPriceDetails || []) (product.ProdVariantPriceDetails || [])
.filter((v) => v.ActiveStatus === "A") .filter((v) => v.ActiveStatus === 'A')
.map((v) => v.ProdVariantName) .map((v) => v.ProdVariantName)
), ),
]; ];
@ -362,10 +396,9 @@ const PurchaseExcel = ({ handleSubmit }) => {
const endRow = currentRow - 1; const endRow = currentRow - 1;
const safeName = product.ProdName const safeName = product.ProdName.trim()
.trim() .replace(/[^A-Za-z0-9_]/g, '_')
.replace(/[^A-Za-z0-9_]/g, "_") .replace(/^(\d)/, '_$1');
.replace(/^(\d)/, "_$1");
const rangeRef = `ProductVariantMap!$B$${startRow}:$B$${endRow}`; const rangeRef = `ProductVariantMap!$B$${startRow}:$B$${endRow}`;
productRanges[safeName] = rangeRef; productRanges[safeName] = rangeRef;
@ -378,21 +411,23 @@ const PurchaseExcel = ({ handleSubmit }) => {
for (let i = 0; i < 50; i++) worksheet.addRow({}); for (let i = 0; i < 50; i++) worksheet.addRow({});
// Find ProductName column index // Find ProductName column index
const productNameColIndex = EXCEL_COLUMNS.findIndex(col => col.key === "ProductName"); const productNameColIndex = EXCEL_COLUMNS.findIndex(
(col) => col.key === 'ProductName'
);
const productNameColLetter = String.fromCharCode(65 + productNameColIndex); const productNameColLetter = String.fromCharCode(65 + productNameColIndex);
// Step 3: Add a helper column for duplicate detection (will be hidden) // Step 3: Add a helper column for duplicate detection (will be hidden)
const helperColIndex = EXCEL_COLUMNS.length; const helperColIndex = EXCEL_COLUMNS.length;
const helperColLetter = String.fromCharCode(65 + helperColIndex); const helperColLetter = String.fromCharCode(65 + helperColIndex);
worksheet.getColumn(helperColLetter).header = "DuplicateCheck"; worksheet.getColumn(helperColLetter).header = 'DuplicateCheck';
worksheet.getColumn(helperColLetter).width = 5; worksheet.getColumn(helperColLetter).width = 5;
// Add formula to detect duplicates in helper column // Add formula to detect duplicates in helper column
for (let rowNumber = 2; rowNumber <= 51; rowNumber++) { for (let rowNumber = 2; rowNumber <= 51; rowNumber++) {
const helperCell = worksheet.getCell(`${helperColLetter}${rowNumber}`); const helperCell = worksheet.getCell(`${helperColLetter}${rowNumber}`);
helperCell.value = { helperCell.value = {
formula: `IF(${productNameColLetter}${rowNumber}="","",COUNTIF($${productNameColLetter}$2:$${productNameColLetter}$51,${productNameColLetter}${rowNumber})>1)` formula: `IF(${productNameColLetter}${rowNumber}="","",COUNTIF($${productNameColLetter}$2:$${productNameColLetter}$51,${productNameColLetter}${rowNumber})>1)`,
}; };
} }
@ -400,96 +435,99 @@ const PurchaseExcel = ({ handleSubmit }) => {
EXCEL_COLUMNS.forEach(({ header, key }, index) => { EXCEL_COLUMNS.forEach(({ header, key }, index) => {
const columnLetter = String.fromCharCode(65 + index); const columnLetter = String.fromCharCode(65 + index);
worksheet.getColumn(columnLetter).eachCell({ includeEmpty: true }, (cell, rowNumber) => { worksheet
if (rowNumber <= 1) return; .getColumn(columnLetter)
.eachCell({ includeEmpty: true }, (cell, rowNumber) => {
if (rowNumber <= 1) return;
if (key === "Supplier") { if (key === 'Supplier') {
if (rowNumber === 2) cell.value = supplierDisplayName; if (rowNumber === 2) cell.value = supplierDisplayName;
cell.dataValidation = { cell.dataValidation = {
type: "list", type: 'list',
allowBlank: false, allowBlank: false,
formulae: [`"${supplierDisplayName}"`], formulae: [`"${supplierDisplayName}"`],
}; };
} else if (key === "ProductName") { } else if (key === 'ProductName') {
// Keep the dropdown list // Keep the dropdown list
cell.dataValidation = { cell.dataValidation = {
type: "list", type: 'list',
allowBlank: true, allowBlank: true,
formulae: [`"${productNames.join(",")}"`], formulae: [`"${productNames.join(',')}"`],
}; };
// Add conditional formatting to highlight duplicates // Add conditional formatting to highlight duplicates
worksheet.addConditionalFormatting({ worksheet.addConditionalFormatting({
ref: `${productNameColLetter}${rowNumber}`, ref: `${productNameColLetter}${rowNumber}`,
rules: [ rules: [
{ {
type: 'expression', type: 'expression',
formulae: [`${helperColLetter}${rowNumber}=TRUE`], formulae: [`${helperColLetter}${rowNumber}=TRUE`],
style: { style: {
fill: { fill: {
type: 'pattern', type: 'pattern',
pattern: 'solid', pattern: 'solid',
bgColor: { argb: 'FFFF6B6B' } bgColor: { argb: 'FFFF6B6B' },
},
font: {
color: { argb: 'FFFFFFFF' },
bold: true,
},
}, },
font: { },
color: { argb: 'FFFFFFFF' }, ],
bold: true });
}
}
}
]
});
// Add a warning note // Add a warning note
cell.note = "⚠️ WARNING: Each product should only be selected once. Duplicates will be highlighted in red."; cell.note =
'⚠️ WARNING: Each product should only be selected once. Duplicates will be highlighted in red.';
} else if (key === 'VariantName') {
const productCell = `${productNameColLetter}${rowNumber}`;
const formula = `INDIRECT(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(${productCell}," ","_"),"-","_"),"&","_"),"(","_"),")","_"))`;
} else if (key === "VariantName") { cell.dataValidation = {
const productCell = `${productNameColLetter}${rowNumber}`; type: 'list',
const formula = `INDIRECT(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(SUBSTITUTE(${productCell}," ","_"),"-","_"),"&","_"),"(","_"),")","_"))`; allowBlank: true,
formulae: [formula],
cell.dataValidation = { };
type: "list", cell.note =
allowBlank: true, 'Select product first; variants depend on chosen product.';
formulae: [formula], } else if (key === 'Quantity') {
}; cell.dataValidation = {
cell.note = "Select product first; variants depend on chosen product."; type: 'custom',
} else if (key === "Quantity") { allowBlank: true,
cell.dataValidation = { formulae: [`ISNUMBER(${columnLetter}${rowNumber})`],
type: "custom", showErrorMessage: true,
allowBlank: true, errorTitle: 'Validation Error',
formulae: [`ISNUMBER(${columnLetter}${rowNumber})`], error: 'Enter a valid number.',
showErrorMessage: true, };
errorTitle: "Validation Error", } else if (key === 'QrCode') {
error: "Enter a valid number.", cell.dataValidation = {
}; type: 'custom',
} else if (key === "QrCode") { allowBlank: false,
cell.dataValidation = { formulae: [`ISNUMBER(${columnLetter}${rowNumber})`],
type: "custom", showErrorMessage: true,
allowBlank: false, errorTitle: 'Validation Error',
formulae: [`ISNUMBER(${columnLetter}${rowNumber})`], error: 'Enter a valid number.',
showErrorMessage: true, };
errorTitle: "Validation Error", }
error: "Enter a valid number.", });
};
}
});
}); });
// Step 5: Hide the helper column // Step 5: Hide the helper column
worksheet.getColumn(helperColLetter).hidden = true; worksheet.getColumn(helperColLetter).hidden = true;
// Step 6: Hide the mapping sheets // Step 6: Hide the mapping sheets
hiddenSheet.state = "veryHidden"; hiddenSheet.state = 'veryHidden';
validationSheet.state = "veryHidden"; validationSheet.state = 'veryHidden';
// Step 7: Export file // Step 7: Export file
const buffer = await workbook.xlsx.writeBuffer(); const buffer = await workbook.xlsx.writeBuffer();
const blob = new Blob([buffer], { const blob = new Blob([buffer], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
}); });
const filename = `PurchaseOrder_${supplierDisplayName}.xlsx`; const filename = `PurchaseOrder_${supplierDisplayName}.xlsx`;
const link = document.createElement("a"); const link = document.createElement('a');
link.href = URL.createObjectURL(blob); link.href = URL.createObjectURL(blob);
link.download = filename; link.download = filename;
link.click(); link.click();
@ -499,7 +537,6 @@ const PurchaseExcel = ({ handleSubmit }) => {
handleModalCancel(); handleModalCancel();
}, [supplierProducts, selectedSupplierData, getSupplierDisplayName]); }, [supplierProducts, selectedSupplierData, getSupplierDisplayName]);
const handleCancelData = useCallback(() => { const handleCancelData = useCallback(() => {
dispatch(emptyExcelData()); dispatch(emptyExcelData());
if (fileInputRef.current) { if (fileInputRef.current) {
@ -601,7 +638,7 @@ const PurchaseExcel = ({ handleSubmit }) => {
} catch (error) { } catch (error) {
console.error('Error fetching suppliers:', error); console.error('Error fetching suppliers:', error);
setMessageData('Failed to fetch suppliers.'); setMessageData('Failed to fetch suppliers.');
setMessageType('error') setMessageType('error');
} }
}, [CompId, AppId, BranchId, dispatch]); }, [CompId, AppId, BranchId, dispatch]);
@ -620,7 +657,7 @@ const PurchaseExcel = ({ handleSubmit }) => {
const handleSupplierBasedProducts = useCallback(async () => { const handleSupplierBasedProducts = useCallback(async () => {
if (!selectedSupplier) { if (!selectedSupplier) {
setMessageData('Please select a supplier.'); setMessageData('Please select a supplier.');
setMessageType('warning') setMessageType('warning');
return; return;
} }
@ -629,7 +666,14 @@ const PurchaseExcel = ({ handleSubmit }) => {
return ( return (
<div className="container"> <div className="container">
<Messages messageType={messageType} messageData={messageData} onComplete={() => { setMessageData(null); setMessageType(null); }} /> <Messages
messageType={messageType}
messageData={messageData}
onComplete={() => {
setMessageData(null);
setMessageType(null);
}}
/>
<div className="form"> <div className="form">
<div style={{ display: 'flex', justifyContent: 'space-between' }}> <div style={{ display: 'flex', justifyContent: 'space-between' }}>
<label> <label>
@ -698,7 +742,10 @@ const PurchaseExcel = ({ handleSubmit }) => {
)} )}
</div> </div>
<div className="viewerExcelupload" style={{ maxHeight: '500px', overflow: 'auto' }}> <div
className="viewerExcelupload"
style={{ maxHeight: '500px', overflow: 'auto' }}
>
{hasData && ( {hasData && (
<Table <Table
columns={columns} columns={columns}
@ -808,4 +855,4 @@ const PurchaseExcel = ({ handleSubmit }) => {
); );
}; };
export default PurchaseExcel; export default PurchaseExcel;

View File

@ -15,16 +15,12 @@ import {
PurchaseOrderMailPost, PurchaseOrderMailPost,
} from '../../Features/PurchaseOrder/PurchaseOrder'; } from '../../Features/PurchaseOrder/PurchaseOrder';
import { Breadcrumb } from 'antd'; import { Breadcrumb } from 'antd';
import { styled } from '@mui/system';
import { import {
breadCrumbSelector, breadCrumbSelector,
changeBreadCrumb, changeBreadCrumb,
} from '../../Features/AppPage/CenterPage'; } from '../../Features/AppPage/CenterPage';
const Input = styled('input')({
display: 'none',
});
const subDirectory = import.meta.env.BASE_URL; const subDirectory = import.meta.env.BASE_URL;
const PurchaseOrderMail = () => { const PurchaseOrderMail = () => {
@ -599,7 +595,7 @@ const PurchaseOrderMail = () => {
]} ]}
> >
<InputField <InputField
label={<label class="required">Receiver Name</label>} label={<label className="required">Receiver Name</label>}
autoComplete="off" autoComplete="off"
onChange={(e) => { onChange={(e) => {
setdeliveryName(e?.target?.value); setdeliveryName(e?.target?.value);
@ -631,7 +627,7 @@ const PurchaseOrderMail = () => {
]} ]}
> >
<InputField <InputField
label={<label class="required">Mobile Number</label>} label={<label className="required">Mobile Number</label>}
autoComplete="off" autoComplete="off"
maxLength={10} maxLength={10}
onChange={(e) => { onChange={(e) => {
@ -661,7 +657,7 @@ const PurchaseOrderMail = () => {
]} ]}
> >
<InputField <InputField
label={<label class="required">Shipping Address</label>} label={<label className="required">Shipping Address</label>}
autoComplete="off" autoComplete="off"
onChange={(e) => { onChange={(e) => {
setdeliveryAddress(e?.target?.value); setdeliveryAddress(e?.target?.value);
@ -688,7 +684,7 @@ const PurchaseOrderMail = () => {
]} ]}
> >
<InputField <InputField
label={<label class="required">City / Town</label>} label={<label className="required">City / Town</label>}
autoComplete="off" autoComplete="off"
onChange={(e) => { onChange={(e) => {
setdeliverycity(e?.target?.value); setdeliverycity(e?.target?.value);
@ -715,7 +711,7 @@ const PurchaseOrderMail = () => {
]} ]}
> >
<InputField <InputField
label={<label class="required">State </label>} label={<label className="required">State </label>}
autoComplete="off" autoComplete="off"
onChange={(e) => { onChange={(e) => {
setdeliveryState(e?.target?.value); setdeliveryState(e?.target?.value);
@ -745,7 +741,7 @@ const PurchaseOrderMail = () => {
]} ]}
> >
<InputField <InputField
label={<label class="required">PIN Code</label>} label={<label className="required">PIN Code</label>}
autoComplete="off" autoComplete="off"
onChange={(e) => { onChange={(e) => {
setdeliveryZip(e?.target?.value); setdeliveryZip(e?.target?.value);

View File

@ -6,9 +6,9 @@ import React, {
useCallback, useCallback,
} from 'react'; } from 'react';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { read, utils } from 'xlsx'; // import { read, utils } from 'xlsx';
import ExcelJS from 'exceljs'; import ExcelJS from 'exceljs';
import { Table, Form, Input, Button, Tooltip, Modal } from 'antd'; import { Table, Form, Input, Tooltip, Modal } from 'antd';
import { import {
emptyExcelData, emptyExcelData,
excelDataSelector, excelDataSelector,
@ -272,28 +272,36 @@ const StockExcel = ({
const uploadAndProcessExcel = (file) => { const uploadAndProcessExcel = (file) => {
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (e) => { reader.onload = async (e) => {
const data = new Uint8Array(e.target.result); const buffer = e.target.result;
const workbook = read(data, { type: 'array' }); const workbook = new ExcelJS.Workbook();
const worksheet = workbook.Sheets[workbook.SheetNames[0]]; await workbook.xlsx.load(buffer);
const jsonData = utils.sheet_to_json(worksheet, { const worksheet = workbook.worksheets[0];
header: 1,
raw: false, // 🔥 same structure as xlsx (array of arrays)
dateNF: 'DD/MM/YY', const jsonData = [];
worksheet.eachRow((row) => {
const rowValues = row.values.slice(1); // remove empty index
jsonData.push(rowValues);
}); });
const nonEmptyRows = jsonData.filter((row) => const nonEmptyRows = jsonData.filter((row) =>
row.some((cell) => cell !== '') row.some((cell) => cell !== '' && cell !== null && cell !== undefined)
); );
const header = nonEmptyRows[0]; const header = nonEmptyRows[0];
const rows = nonEmptyRows.slice(1); const rows = nonEmptyRows.slice(1);
const dateFields = ['Manufacture Date', 'Expire Date', 'Stock Date']; const dateFields = ['Manufacture Date', 'Expire Date', 'Stock Date'];
function convertToDisplayFormat(dateString) { function convertToDisplayFormat(dateString) {
if (!dateString) return '';
const date = new Date(dateString); const date = new Date(dateString);
if (isNaN(date)) return dateString;
const day = date.getDate().toString().padStart(2, '0'); const day = date.getDate().toString().padStart(2, '0');
const month = (date.getMonth() + 1).toString().padStart(2, '0'); const month = (date.getMonth() + 1).toString().padStart(2, '0');
const year = date.getFullYear().toString(); const year = date.getFullYear().toString();
@ -307,13 +315,12 @@ const StockExcel = ({
} else { } else {
acc[col] = row[columnIndex]; acc[col] = row[columnIndex];
} }
return acc; return acc;
}, {}); }, {});
return rowData; return rowData;
}); });
// unchanged logic
dispatch(uploadExcel({ file, jsonData: nonEmptyRows })); dispatch(uploadExcel({ file, jsonData: nonEmptyRows }));
setWorksheet1(worksheet1); setWorksheet1(worksheet1);
setDatas(formattedData); setDatas(formattedData);
@ -322,6 +329,59 @@ const StockExcel = ({
reader.readAsArrayBuffer(file); reader.readAsArrayBuffer(file);
}; };
// const uploadAndProcessExcel = (file) => {
// const reader = new FileReader();
// reader.onload = (e) => {
// const data = new Uint8Array(e.target.result);
// const workbook = read(data, { type: 'array' });
// const worksheet = workbook.Sheets[workbook.SheetNames[0]];
// const jsonData = utils.sheet_to_json(worksheet, {
// header: 1,
// raw: false,
// dateNF: 'DD/MM/YY',
// });
// const nonEmptyRows = jsonData.filter((row) =>
// row.some((cell) => cell !== '')
// );
// const header = nonEmptyRows[0];
// const rows = nonEmptyRows.slice(1);
// const dateFields = ['Manufacture Date', 'Expire Date', 'Stock Date'];
// function convertToDisplayFormat(dateString) {
// const date = new Date(dateString);
// const day = date.getDate().toString().padStart(2, '0');
// const month = (date.getMonth() + 1).toString().padStart(2, '0');
// const year = date.getFullYear().toString();
// return `${day}-${month}-${year}`;
// }
// const formattedData = rows.map((row) => {
// let rowData = header.reduce((acc, col, columnIndex) => {
// if (dateFields.includes(col)) {
// acc[col] = convertToDisplayFormat(row[columnIndex]);
// } else {
// acc[col] = row[columnIndex];
// }
// return acc;
// }, {});
// return rowData;
// });
// dispatch(uploadExcel({ file, jsonData: nonEmptyRows }));
// setWorksheet1(worksheet1);
// setDatas(formattedData);
// };
// reader.readAsArrayBuffer(file);
// };
// ...existing code... // ...existing code...
const handleDownload = async () => { const handleDownload = async () => {
try { try {
@ -441,8 +501,8 @@ const StockExcel = ({
: c.key === 'Quantity' : c.key === 'Quantity'
? 10 ? 10
: c.key === 'Supplier' || : c.key === 'Supplier' ||
c.key === 'InvoiceDeliveryChallan' || c.key === 'InvoiceDeliveryChallan' ||
c.key === 'SupplierInvoiceNumber' c.key === 'SupplierInvoiceNumber'
? 30 ? 30
: 20, : 20,
})); }));
@ -651,7 +711,7 @@ const StockExcel = ({
cell.dataValidation = { cell.dataValidation = {
type: 'list', type: 'list',
formulae: ['"Own,With Paid"'], formulae: ['"Own,With Paid"'],
allowBlank: true allowBlank: true,
}; };
if (!cell.value) { if (!cell.value) {
@ -1239,7 +1299,7 @@ const StockExcel = ({
...record, ...record,
...values, ...values,
}); });
} catch (errInfo) { } } catch (errInfo) {}
}; };
let childNode = children; let childNode = children;

View File

@ -48,9 +48,8 @@ import {
} from '../../Features/ExcelUploadPage/ExcelUploadPage.js'; } from '../../Features/ExcelUploadPage/ExcelUploadPage.js';
const subDirectory = import.meta.env.BASE_URL; const subDirectory = import.meta.env.BASE_URL;
import ExcelJS from 'exceljs'; import ExcelJS from 'exceljs';
import { read, utils } from 'xlsx'; // import { read, utils } from 'xlsx';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { InputField } from '../../Components/Forms/InputField.jsx';
import { MdOutlineAppRegistration } from 'react-icons/md'; import { MdOutlineAppRegistration } from 'react-icons/md';
import { import {
getFieldSetupData, getFieldSetupData,
@ -737,7 +736,12 @@ const StockPriceUpdate = () => {
fetchData(); fetchData();
}, []); }, []);
const getPreference = async () => { const getPreference = async () => {
const data = { AppId: AppId, CompId: CompId, BranchId: BranchId,UserId: UserId }; const data = {
AppId: AppId,
CompId: CompId,
BranchId: BranchId,
UserId: UserId,
};
const { data: res } = await dispatch(getPreferenceData(data)).unwrap(); const { data: res } = await dispatch(getPreferenceData(data)).unwrap();
const decimalSetting = res?.data?.[0]?.SettingDtlDetails?.find( const decimalSetting = res?.data?.[0]?.SettingDtlDetails?.find(
(setting) => (setting) =>
@ -1820,15 +1824,17 @@ const StockPriceUpdate = () => {
excelDataRef.current?.setFieldsValue(formValues); excelDataRef.current?.setFieldsValue(formValues);
}; };
const extractPriceChangeDataFromExcel = async (arrayBuffer) => {
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.load(arrayBuffer);
const extractPriceChangeDataFromExcel = (arrayBuffer) => { const worksheet = workbook.worksheets[0];
const workbook = read(arrayBuffer, { type: 'array' });
const worksheet = workbook.Sheets[workbook.SheetNames[0]];
const rows = utils.sheet_to_json(worksheet, { // 🔥 same structure as xlsx (array of arrays)
header: 1, const rows = [];
raw: false, worksheet.eachRow((row) => {
defval: '', const rowValues = row.values.slice(1); // remove empty index
rows.push(rowValues.map((cell) => cell ?? '')); // match defval: ''
}); });
if (!rows.length) return []; if (!rows.length) return [];
@ -1901,7 +1907,7 @@ const StockPriceUpdate = () => {
? Number(getVal(row, 'Old Wholesale Price')) ? Number(getVal(row, 'Old Wholesale Price'))
: null, : null,
// 🔹 New prices (normalized) // 🔹 New prices
NewMRP: newMRP, NewMRP: newMRP,
NewSellPrice: newSP, NewSellPrice: newSP,
NewWholeSalePrice: newWholesale, NewWholeSalePrice: newWholesale,
@ -1911,6 +1917,96 @@ const StockPriceUpdate = () => {
return result; return result;
}; };
// const extractPriceChangeDataFromExcel = (arrayBuffer) => {
// const workbook = read(arrayBuffer, { type: 'array' });
// const worksheet = workbook.Sheets[workbook.SheetNames[0]];
// const rows = utils.sheet_to_json(worksheet, {
// header: 1,
// raw: false,
// defval: '',
// });
// if (!rows.length) return [];
// const header = rows[0];
// setHeaders(header);
// const dataRows = rows.slice(1);
// // Helper to safely get column value
// const getVal = (row, colName) => {
// const idx = header.indexOf(colName);
// return idx !== -1 ? row[idx] : '';
// };
// const result = dataRows
// .map((row) => {
// const prodId = getVal(row, 'ProdId')?.toString().trim();
// if (!prodId) return null;
// const newMRP =
// getVal(row, 'New MRP') !== '' ? Number(getVal(row, 'New MRP')) : null;
// const newSP =
// getVal(row, 'New SP') !== '' ? Number(getVal(row, 'New SP')) : null;
// const newWholesale =
// getVal(row, 'New Wholesale Price') !== ''
// ? Number(getVal(row, 'New Wholesale Price'))
// : null;
// // Must have at least one change
// if (newMRP === null && newSP === null && newWholesale === null) {
// return null;
// }
// return {
// // 🔹 Product identity
// localId: uuidv4(),
// ProdId: prodId,
// InwardDtlId: getVal(row, 'InwardDtlId')
// ? getVal(row, 'InwardDtlId').toString()
// : null,
// // 🔹 Product info
// VariantName: getVal(row, 'Variant Name') || null,
// BatchRef: getVal(row, 'Batch No') || null,
// ProductName: getVal(row, 'Product Name') || null,
// Size: getVal(row, 'Size') || null,
// UOMName: getVal(row, 'UOMName') || null,
// StockMaintainence: getVal(row, 'Stock Maintainence') || null,
// // 🔹 Stock / dates
// StockDate: getVal(row, 'Stock Date') || null,
// ExpDate: getVal(row, 'Exp Date') || null,
// downloadType: getVal(row, 'downloadType') || null,
// Stock:
// getVal(row, 'Stock') !== '' ? Number(getVal(row, 'Stock')) : null,
// // 🔹 Old prices
// OldMRP:
// getVal(row, 'Old MRP') !== ''
// ? Number(getVal(row, 'Old MRP'))
// : null,
// OldSP:
// getVal(row, 'Old SP') !== '' ? Number(getVal(row, 'Old SP')) : null,
// OldWholeSalePrice:
// getVal(row, 'Old Wholesale Price') !== ''
// ? Number(getVal(row, 'Old Wholesale Price'))
// : null,
// // 🔹 New prices (normalized)
// NewMRP: newMRP,
// NewSellPrice: newSP,
// NewWholeSalePrice: newWholesale,
// };
// })
// .filter(Boolean);
// return result;
// };
const handleCancelData = () => { const handleCancelData = () => {
dispatch(emptyExcelData()); dispatch(emptyExcelData());

View File

@ -1,28 +1,28 @@
import React, { useCallback, useMemo, useRef, useState } from "react"; import { useCallback, useMemo, useRef, useState } from 'react';
import { read, utils } from "xlsx"; // import { read, utils } from "xlsx";
import ExcelJS from "exceljs"; import ExcelJS from 'exceljs';
import { Button } from "antd"; import { Button } from 'antd';
import { MdRefresh, MdInfo } from "react-icons/md"; import { MdRefresh, MdInfo } from 'react-icons/md';
import { RiFileExcel2Fill } from "react-icons/ri"; import { RiFileExcel2Fill } from 'react-icons/ri';
import { Messages } from "../../Components/Notifications/Messages.jsx"; import { Messages } from '../../Components/Notifications/Messages.jsx';
import { getSession } from "../../Services/Others.js"; import { getSession } from '../../Services/Others.js';
import "./OpeningStockExcel.scss" import './OpeningStockExcel.scss';
const TEMPLATE_HEADERS = [ const TEMPLATE_HEADERS = [
{ key: "ProdName", header: "Product Name" }, { key: 'ProdName', header: 'Product Name' },
{ key: "TotalQty", header: "Total Qty" }, { key: 'TotalQty', header: 'Total Qty' },
{ key: "TotalPiece", header: "Total Pieces" }, { key: 'TotalPiece', header: 'Total Pieces' },
]; ];
const OpeningStockExcel = ({ products = [], onApply, onCancel }) => { const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
const UserId = getSession("UserId"); const UserId = getSession('UserId');
const fileInputRef = useRef(null); const fileInputRef = useRef(null);
const [previewRows, setPreviewRows] = useState([]); const [previewRows, setPreviewRows] = useState([]);
const [messageType, setMessageType] = useState(null); const [messageType, setMessageType] = useState(null);
const [messageData, setMessageData] = useState(null); const [messageData, setMessageData] = useState(null);
const [isParsing, setIsParsing] = useState(false); const [isParsing, setIsParsing] = useState(false);
const [inst, setInst] = useState(false) const [inst, setInst] = useState(false);
console.log(previewRows, "previewRows") console.log(previewRows, 'previewRows');
const onComplete = useCallback(() => { const onComplete = useCallback(() => {
setMessageData(null); setMessageData(null);
setMessageType(null); setMessageType(null);
@ -31,9 +31,9 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
const templateColumns = useMemo( const templateColumns = useMemo(
() => [ () => [
{ {
title: "Product Name", title: 'Product Name',
dataIndex: "productDisplayName", dataIndex: 'productDisplayName',
key: "productDisplayName", key: 'productDisplayName',
width: 200, width: 200,
}, },
// { // {
@ -43,36 +43,35 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
// width: 180, // width: 180,
// }, // },
{ {
title: "Total Qty", title: 'Total Qty',
dataIndex: "TotalQty", dataIndex: 'TotalQty',
key: "TotalQty", key: 'TotalQty',
width: 50, width: 50,
}, },
{ {
title: "Total Pcs", title: 'Total Pcs',
dataIndex: "TotalPiece", dataIndex: 'TotalPiece',
key: "TotalPiece", key: 'TotalPiece',
width: 50, width: 50,
render: (value, row) => render: (value, row) => (row?.OnePcsAvailable === 'Y' ? value : 'N/A'),
row?.OnePcsAvailable === "Y" ? value : "N/A",
}, },
], ],
[] []
); );
const handleInstrction = () => { const handleInstrction = () => {
setInst(!inst) setInst(!inst);
} };
const handleDownloadTemplate = useCallback(async () => { const handleDownloadTemplate = useCallback(async () => {
if (!products?.length) { if (!products?.length) {
setMessageType("warning"); setMessageType('warning');
setMessageData("No products available to include in the template."); setMessageData('No products available to include in the template.');
return; return;
} }
try { try {
const workbook = new ExcelJS.Workbook(); const workbook = new ExcelJS.Workbook();
const worksheet = workbook.addWorksheet("Opening Stock"); const worksheet = workbook.addWorksheet('Opening Stock');
const headerRow = worksheet.addRow( const headerRow = worksheet.addRow(
TEMPLATE_HEADERS.map((item) => item.header) TEMPLATE_HEADERS.map((item) => item.header)
@ -80,33 +79,29 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
headerRow.eachCell((cell) => { headerRow.eachCell((cell) => {
cell.protection = { locked: true }; cell.protection = { locked: true };
cell.fill = { cell.fill = {
type: "pattern", type: 'pattern',
pattern: "solid", pattern: 'solid',
fgColor: { argb: "FF0F6CB6" }, fgColor: { argb: 'FF0F6CB6' },
}; };
cell.font = { cell.font = {
color: { argb: "FFFFFFFF" }, color: { argb: 'FFFFFFFF' },
bold: true, bold: true,
}; };
cell.alignment = { vertical: "middle", horizontal: "center" }; cell.alignment = { vertical: 'middle', horizontal: 'center' };
cell.border = { cell.border = {
top: { style: "thin", color: { argb: "FFFFFFFF" } }, top: { style: 'thin', color: { argb: 'FFFFFFFF' } },
left: { style: "thin", color: { argb: "FFFFFFFF" } }, left: { style: 'thin', color: { argb: 'FFFFFFFF' } },
bottom: { style: "thin", color: { argb: "FFFFFFFF" } }, bottom: { style: 'thin', color: { argb: 'FFFFFFFF' } },
right: { style: "thin", color: { argb: "FFFFFFFF" } }, right: { style: 'thin', color: { argb: 'FFFFFFFF' } },
}; };
}); });
products.forEach((product) => { products.forEach((product) => {
const displayName = product?.ProdVariantName const displayName = product?.ProdVariantName
? `${product?.ProdName ?? ""} (${product?.ProdVariantName ?? ""})` ? `${product?.ProdName ?? ''} (${product?.ProdVariantName ?? ''})`
: product?.ProdName ?? ""; : (product?.ProdName ?? '');
const newRow = worksheet.addRow([ const newRow = worksheet.addRow([displayName, '', '']);
displayName,
"",
"",
]);
const rowNumber = newRow.number; const rowNumber = newRow.number;
const productCell = worksheet.getCell(rowNumber, 1); const productCell = worksheet.getCell(rowNumber, 1);
@ -114,61 +109,61 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
const qtyCell = worksheet.getCell(rowNumber, 2); const qtyCell = worksheet.getCell(rowNumber, 2);
qtyCell.dataValidation = { qtyCell.dataValidation = {
type: "decimal", type: 'decimal',
operator: "greaterThanOrEqual", operator: 'greaterThanOrEqual',
allowBlank: true, allowBlank: true,
showErrorMessage: true, showErrorMessage: true,
showInputMessage: true, showInputMessage: true,
formulae: [0], formulae: [0],
errorTitle: "Invalid Quantity", errorTitle: 'Invalid Quantity',
error: "Please enter a numeric value greater than or equal to 0.", error: 'Please enter a numeric value greater than or equal to 0.',
inputTitle: "Total Qty", inputTitle: 'Total Qty',
inputMessage: "Enter the total quantity (numeric).", inputMessage: 'Enter the total quantity (numeric).',
}; };
qtyCell.protection = { locked: false }; qtyCell.protection = { locked: false };
const pieceCell = worksheet.getCell(rowNumber, 3); const pieceCell = worksheet.getCell(rowNumber, 3);
const allowPieces = product?.OnePcsAvailable === "Y"; const allowPieces = product?.OnePcsAvailable === 'Y';
if (allowPieces) { if (allowPieces) {
pieceCell.dataValidation = { pieceCell.dataValidation = {
type: "decimal", type: 'decimal',
operator: "greaterThanOrEqual", operator: 'greaterThanOrEqual',
allowBlank: true, allowBlank: true,
showErrorMessage: true, showErrorMessage: true,
showInputMessage: true, showInputMessage: true,
formulae: [0], formulae: [0],
errorTitle: "Invalid Total Pieces", errorTitle: 'Invalid Total Pieces',
error: "Please enter a numeric value greater than or equal to 0.", error: 'Please enter a numeric value greater than or equal to 0.',
inputTitle: "Total Pieces", inputTitle: 'Total Pieces',
inputMessage: "Enter the total pieces (numeric).", inputMessage: 'Enter the total pieces (numeric).',
}; };
pieceCell.protection = { locked: false }; pieceCell.protection = { locked: false };
} else { } else {
pieceCell.dataValidation = { pieceCell.dataValidation = {
type: "custom", type: 'custom',
allowBlank: true, allowBlank: true,
showErrorMessage: true, showErrorMessage: true,
showInputMessage: true, showInputMessage: true,
formulae: [`=LEN(TRIM(C${rowNumber}))=0`], formulae: [`=LEN(TRIM(C${rowNumber}))=0`],
errorTitle: "Not Allowed", errorTitle: 'Not Allowed',
error: error:
"Total Pieces entry is not allowed for this product. Leave this cell empty.", 'Total Pieces entry is not allowed for this product. Leave this cell empty.',
inputTitle: "Total Pieces", inputTitle: 'Total Pieces',
inputMessage: inputMessage:
"Total Pieces is not allowed for this product and must remain empty.", 'Total Pieces is not allowed for this product and must remain empty.',
}; };
pieceCell.fill = { pieceCell.fill = {
type: "pattern", type: 'pattern',
pattern: "solid", pattern: 'solid',
fgColor: { argb: "FFEFEFEF" }, fgColor: { argb: 'FFEFEFEF' },
}; };
pieceCell.font = { pieceCell.font = {
color: { argb: "FF6E6E6E" }, color: { argb: 'FF6E6E6E' },
italic: true, italic: true,
}; };
pieceCell.note = pieceCell.note =
"Total Pieces entry is disabled because single-piece handling is not available for this product."; 'Total Pieces entry is disabled because single-piece handling is not available for this product.';
pieceCell.protection = { locked: true }; pieceCell.protection = { locked: true };
} }
}); });
@ -185,7 +180,7 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
}); });
try { try {
await worksheet.protect("OpeningStockTemplate", { await worksheet.protect('OpeningStockTemplate', {
selectLockedCells: true, selectLockedCells: true,
selectUnlockedCells: true, selectUnlockedCells: true,
formatCells: false, formatCells: false,
@ -197,143 +192,306 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
deleteRows: false, deleteRows: false,
}); });
} catch (protectError) { } catch (protectError) {
console.warn("Worksheet protection not supported in this environment:", protectError); console.warn(
setMessageType("warning"); 'Worksheet protection not supported in this environment:',
protectError
);
setMessageType('warning');
setMessageData( setMessageData(
"Template protection is not supported in this browser. The sheet will still download without protection." 'Template protection is not supported in this browser. The sheet will still download without protection.'
); );
} }
const buffer = await workbook.xlsx.writeBuffer(); const buffer = await workbook.xlsx.writeBuffer();
const blob = new Blob([buffer], { const blob = new Blob([buffer], {
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
}); });
const link = document.createElement("a"); const link = document.createElement('a');
link.href = URL.createObjectURL(blob); link.href = URL.createObjectURL(blob);
link.download = "opening_stock_template.xlsx"; link.download = 'opening_stock_template.xlsx';
link.click(); link.click();
URL.revokeObjectURL(link.href); URL.revokeObjectURL(link.href);
} catch (error) { } catch (error) {
console.error("Template download failed:", error); console.error('Template download failed:', error);
setMessageType("error"); setMessageType('error');
setMessageData("Failed to generate the template. Please try again."); setMessageData('Failed to generate the template. Please try again.');
} }
}, [products]); }, [products]);
// const parseExcelFile = useCallback(
// async (file) => {
// if (!file) return;
// setIsParsing(true);
// try {
// const arrayBuffer = await file.arrayBuffer();
// const workbook = read(arrayBuffer, { type: "array" });
// const worksheet = workbook.Sheets[workbook.SheetNames[0]];
// const rows = utils.sheet_to_json(worksheet, { header: 1, defval: "" });
// if (!rows.length) {
// setMessageType("warning");
// setMessageData("Uploaded file is empty.");
// setPreviewRows([]);
// return;
// }
// const [headerRow, ...dataRows] = rows;
// const headerMap = TEMPLATE_HEADERS.reduce(
// (acc, item) => ({
// ...acc,
// [item.key]: headerRow.findIndex(
// (header) =>
// header?.toString().trim().toLowerCase() ===
// item.header.toLowerCase()
// ),
// }),
// {}
// );
// const missingHeaders = Object.entries(headerMap)
// .filter(([, index]) => index === -1)
// .map(([key]) => key);
// if (missingHeaders.length) {
// setMessageType("error");
// setMessageData(
// `Missing required column(s) in the Excel file: ${missingHeaders.join(
// ", "
// )}`
// );
// setPreviewRows([]);
// return;
// }
// const productIndex = products.reduce((acc, product) => {
// const displayName = product?.ProdVariantName
// ? `${product?.ProdName ?? ""} (${product?.ProdVariantName ?? ""})`
// : product?.ProdName ?? "";
// if (displayName) {
// acc[displayName.trim().toLowerCase()] = product;
// }
// return acc;
// }, {});
// const parsedRows = [];
// const issues = [];
// dataRows.forEach((row, rowIndex) => {
// const prodNameCell = row[headerMap.ProdName];
// const qtyCell = row[headerMap.TotalQty];
// const pieceCell = row[headerMap.TotalPiece];
// const productNameValue = prodNameCell?.toString().trim();
// if (!productNameValue) {
// if (qtyCell || pieceCell) {
// issues.push(
// `Row ${rowIndex + 2}: Product Name is empty while quantities are provided.`
// );
// }
// return;
// }
// const productMatch =
// productIndex[productNameValue.trim().toLowerCase()];
// if (!productMatch) {
// issues.push(
// `Row ${rowIndex + 2}: Product "${productNameValue}" is not found in available products.`
// );
// return;
// }
// const totalQty = qtyCell?.toString().trim();
// const totalPiece = pieceCell?.toString().trim();
// const hasValues =
// (totalQty && totalQty.length > 0) ||
// (totalPiece && totalPiece.length > 0);
// if (!hasValues) {
// return;
// }
// const isQtyValid =
// !totalQty || /^[0-9]+(\.[0-9]+)?$/.test(totalQty.trim());
// const isPieceValid =
// !totalPiece || /^[0-9]+(\.[0-9]+)?$/.test(totalPiece.trim());
// if (!isQtyValid || !isPieceValid) {
// issues.push(
// `Row ${rowIndex + 2}: Total Qty or Total Pieces is not a valid number.`
// );
// return;
// }
// const allowPieces = productMatch?.OnePcsAvailable === "Y";
// let cleanedPieces = totalPiece ?? "";
// if (!allowPieces && cleanedPieces) {
// issues.push(
// `Row ${rowIndex + 2}: Total Pieces is not allowed for "${productNameValue}" and the value has been ignored.`
// );
// cleanedPieces = "";
// }
// parsedRows.push({
// key: productMatch.ProdId,
// ProdId: productMatch.ProdId,
// ProdName: productMatch.ProdName,
// ProdVariantName: productMatch.ProdVariantName,
// productDisplayName: productNameValue,
// TotalQty: totalQty ?? "",
// TotalPiece: cleanedPieces,
// OnePcsAvailable: productMatch?.OnePcsAvailable,
// });
// });
// if (!parsedRows.length) {
// setMessageType("warning");
// setMessageData(
// issues.length
// ? issues.join(" ")
// : "No valid rows found in the uploaded file."
// );
// setPreviewRows([]);
// return;
// }
// if (issues.length) {
// setMessageType("warning");
// setMessageData(
// `Some rows were skipped:\n${issues.join("\n")}`
// );
// } else {
// setMessageType("success");
// setMessageData(
// `Successfully parsed ${parsedRows.length} row(s) from the uploaded file.`
// );
// }
// setPreviewRows(parsedRows);
// } catch (error) {
// console.error("Failed to parse Excel file:", error);
// setMessageType("error");
// setMessageData(
// "Failed to read the uploaded file. Please ensure it is a valid Excel document."
// );
// setPreviewRows([]);
// } finally {
// setIsParsing(false);
// }
// },
// [products]
// );
const parseExcelFile = useCallback( const parseExcelFile = useCallback(
async (file) => { async (file) => {
if (!file) return; if (!file) return;
setIsParsing(true); setIsParsing(true);
try { try {
const workbook = new ExcelJS.Workbook();
const arrayBuffer = await file.arrayBuffer(); const arrayBuffer = await file.arrayBuffer();
const workbook = read(arrayBuffer, { type: "array" });
const worksheet = workbook.Sheets[workbook.SheetNames[0]]; await workbook.xlsx.load(arrayBuffer);
const rows = utils.sheet_to_json(worksheet, { header: 1, defval: "" });
const worksheet = workbook.worksheets[0];
if (!worksheet) throw new Error('No sheet found');
const rows = [];
worksheet.eachRow((row, rowNumber) => {
rows.push(row.values.slice(1)); // remove first empty index
});
if (!rows.length) { if (!rows.length) {
setMessageType("warning"); setMessageType('warning');
setMessageData("Uploaded file is empty."); setMessageData('Uploaded file is empty.');
setPreviewRows([]); setPreviewRows([]);
return; return;
} }
const [headerRow, ...dataRows] = rows; const [headerRow = [], ...dataRows] = rows;
const headerMap = TEMPLATE_HEADERS.reduce(
(acc, item) => ({ const normalizedHeaders = headerRow.map((h) =>
...acc, h?.toString().trim().toLowerCase()
[item.key]: headerRow.findIndex(
(header) =>
header?.toString().trim().toLowerCase() ===
item.header.toLowerCase()
),
}),
{}
); );
const headerMap = {};
TEMPLATE_HEADERS.forEach((item) => {
headerMap[item.key] = normalizedHeaders.indexOf(
item.header.toLowerCase()
);
});
const missingHeaders = Object.entries(headerMap) const missingHeaders = Object.entries(headerMap)
.filter(([, index]) => index === -1) .filter(([, index]) => index === -1)
.map(([key]) => key); .map(([key]) => key);
if (missingHeaders.length) { if (missingHeaders.length) {
setMessageType("error"); setMessageType('error');
setMessageData( setMessageData(
`Missing required column(s) in the Excel file: ${missingHeaders.join( `Missing required column(s): ${missingHeaders.join(', ')}`
", "
)}`
); );
setPreviewRows([]); setPreviewRows([]);
return; return;
} }
const productIndex = products.reduce((acc, product) => { // 🔥 Product index
const productIndex = {};
for (const product of products) {
const displayName = product?.ProdVariantName const displayName = product?.ProdVariantName
? `${product?.ProdName ?? ""} (${product?.ProdVariantName ?? ""})` ? `${product?.ProdName ?? ''} (${product?.ProdVariantName ?? ''})`
: product?.ProdName ?? ""; : (product?.ProdName ?? '');
if (displayName) { if (displayName) {
acc[displayName.trim().toLowerCase()] = product; productIndex[displayName.trim().toLowerCase()] = product;
} }
return acc; }
}, {});
const parsedRows = []; const parsedRows = [];
const issues = []; const issues = [];
dataRows.forEach((row, rowIndex) => { dataRows.forEach((row, rowIndex) => {
const prodNameCell = row[headerMap.ProdName]; const rowNum = rowIndex + 2;
const qtyCell = row[headerMap.TotalQty];
const pieceCell = row[headerMap.TotalPiece]; const productNameValue = row[headerMap.ProdName]?.toString().trim();
const totalQty = row[headerMap.TotalQty]?.toString().trim();
const totalPiece = row[headerMap.TotalPiece]?.toString().trim();
const productNameValue = prodNameCell?.toString().trim();
if (!productNameValue) { if (!productNameValue) {
if (qtyCell || pieceCell) { if (totalQty || totalPiece) {
issues.push( issues.push(`Row ${rowNum}: Product Name is empty.`);
`Row ${rowIndex + 2}: Product Name is empty while quantities are provided.`
);
} }
return; return;
} }
const productMatch = const productMatch = productIndex[productNameValue.toLowerCase()];
productIndex[productNameValue.trim().toLowerCase()];
if (!productMatch) { if (!productMatch) {
issues.push( issues.push(
`Row ${rowIndex + 2}: Product "${productNameValue}" is not found in available products.` `Row ${rowNum}: Product "${productNameValue}" not found.`
); );
return; return;
} }
const totalQty = qtyCell?.toString().trim(); if (!totalQty && !totalPiece) return;
const totalPiece = pieceCell?.toString().trim();
const hasValues = const isValidNumber = (val) =>
(totalQty && totalQty.length > 0) || !val || /^[0-9]+(\.[0-9]+)?$/.test(val);
(totalPiece && totalPiece.length > 0);
if (!hasValues) { if (!isValidNumber(totalQty) || !isValidNumber(totalPiece)) {
issues.push(`Row ${rowNum}: Invalid number.`);
return; return;
} }
const isQtyValid = let cleanedPieces = totalPiece || '';
!totalQty || /^[0-9]+(\.[0-9]+)?$/.test(totalQty.trim()); if (productMatch?.OnePcsAvailable !== 'Y' && cleanedPieces) {
const isPieceValid =
!totalPiece || /^[0-9]+(\.[0-9]+)?$/.test(totalPiece.trim());
if (!isQtyValid || !isPieceValid) {
issues.push( issues.push(
`Row ${rowIndex + 2}: Total Qty or Total Pieces is not a valid number.` `Row ${rowNum}: Pieces not allowed for "${productNameValue}". Ignored.`
); );
return; cleanedPieces = '';
}
const allowPieces = productMatch?.OnePcsAvailable === "Y";
let cleanedPieces = totalPiece ?? "";
if (!allowPieces && cleanedPieces) {
issues.push(
`Row ${rowIndex + 2}: Total Pieces is not allowed for "${productNameValue}" and the value has been ignored.`
);
cleanedPieces = "";
} }
parsedRows.push({ parsedRows.push({
@ -342,42 +500,33 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
ProdName: productMatch.ProdName, ProdName: productMatch.ProdName,
ProdVariantName: productMatch.ProdVariantName, ProdVariantName: productMatch.ProdVariantName,
productDisplayName: productNameValue, productDisplayName: productNameValue,
TotalQty: totalQty ?? "", TotalQty: totalQty || '',
TotalPiece: cleanedPieces, TotalPiece: cleanedPieces,
OnePcsAvailable: productMatch?.OnePcsAvailable, OnePcsAvailable: productMatch?.OnePcsAvailable,
}); });
}); });
if (!parsedRows.length) { if (!parsedRows.length) {
setMessageType("warning"); setMessageType('warning');
setMessageData( setMessageData(
issues.length issues.length ? issues.join('\n') : 'No valid rows found.'
? issues.join(" ")
: "No valid rows found in the uploaded file."
); );
setPreviewRows([]); setPreviewRows([]);
return; return;
} }
if (issues.length) { setMessageType(issues.length ? 'warning' : 'success');
setMessageType("warning"); setMessageData(
setMessageData( issues.length
`Some rows were skipped:\n${issues.join("\n")}` ? `Some rows skipped:\n${issues.join('\n')}`
); : `Parsed ${parsedRows.length} row(s).`
} else { );
setMessageType("success");
setMessageData(
`Successfully parsed ${parsedRows.length} row(s) from the uploaded file.`
);
}
setPreviewRows(parsedRows); setPreviewRows(parsedRows);
} catch (error) { } catch (error) {
console.error("Failed to parse Excel file:", error); console.error('Excel parse error:', error);
setMessageType("error"); setMessageType('error');
setMessageData( setMessageData('Invalid Excel file.');
"Failed to read the uploaded file. Please ensure it is a valid Excel document."
);
setPreviewRows([]); setPreviewRows([]);
} finally { } finally {
setIsParsing(false); setIsParsing(false);
@ -396,8 +545,8 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
const handleApply = useCallback(() => { const handleApply = useCallback(() => {
if (!previewRows.length) { if (!previewRows.length) {
setMessageType("warning"); setMessageType('warning');
setMessageData("No parsed rows available to apply."); setMessageData('No parsed rows available to apply.');
return; return;
} }
@ -405,17 +554,19 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
previewRows.map((row) => ({ previewRows.map((row) => ({
ProdId: row.ProdId, ProdId: row.ProdId,
ProdVariantName: row?.ProdVariantName, ProdVariantName: row?.ProdVariantName,
OnePcsAvailable: row.OnePcsAvailable || "N", OnePcsAvailable: row.OnePcsAvailable || 'N',
"CreatedBy": UserId, CreatedBy: UserId,
TotalQty: TotalQty:
row.TotalQty !== undefined && row.TotalQty !== null row.TotalQty !== undefined && row.TotalQty !== null
? row.TotalQty.toString() ? row.TotalQty.toString()
: "", : '',
TotalPcs: TotalPcs:
row.OnePcsAvailable === "Y" && row.TotalPiece !== undefined && row.TotalPiece !== null row.OnePcsAvailable === 'Y' &&
row.TotalPiece !== undefined &&
row.TotalPiece !== null
? row.TotalPiece.toString() ? row.TotalPiece.toString()
: 0, : 0,
InwardDetails: [] InwardDetails: [],
})) }))
); );
}, [onApply, previewRows]); }, [onApply, previewRows]);
@ -425,7 +576,7 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
setMessageData(null); setMessageData(null);
setMessageType(null); setMessageType(null);
if (fileInputRef.current) { if (fileInputRef.current) {
fileInputRef.current.value = ""; fileInputRef.current.value = '';
} }
}, []); }, []);
@ -440,7 +591,7 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
<div className="openingStock-excel-actions"> <div className="openingStock-excel-actions">
<Button <Button
className="openingStock-excel-download-btn" className="openingStock-excel-download-btn"
icon={<RiFileExcel2Fill />} icon={<RiFileExcel2Fill />}
onClick={handleDownloadTemplate} onClick={handleDownloadTemplate}
disabled={!products?.length} disabled={!products?.length}
> >
@ -448,7 +599,7 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
</Button> </Button>
<label className="openingStock-excel-upload-label"> <label className="openingStock-excel-upload-label">
<RiFileExcel2Fill className="openingStock-excel-upload-icon" /> <RiFileExcel2Fill className="openingStock-excel-upload-icon" />
Upload Filled Excel Upload Filled Excel
<input <input
ref={fileInputRef} ref={fileInputRef}
@ -461,7 +612,7 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
</label> </label>
{previewRows.length > 0 && ( {previewRows.length > 0 && (
<Button <Button
className="openingStock-excel-reset-btn" className="openingStock-excel-reset-btn"
icon={<MdRefresh />} icon={<MdRefresh />}
onClick={handleReset} onClick={handleReset}
@ -471,7 +622,10 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
)} )}
</div> </div>
<div onClick={handleInstrction} className="openingStock-excel-instruction-toggle"> <div
onClick={handleInstrction}
className="openingStock-excel-instruction-toggle"
>
<MdInfo className="openingStock-excel-info-icon" /> <MdInfo className="openingStock-excel-info-icon" />
Instructions Instructions
</div> </div>
@ -479,7 +633,8 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
{inst && ( {inst && (
<div className="openingStock-excel-instructions"> <div className="openingStock-excel-instructions">
<div className="openingStock-excel-instruction-item"> <div className="openingStock-excel-instruction-item">
1. Download the template and provide the quantities for each product. 1. Download the template and provide the quantities for each
product.
</div> </div>
<div className="openingStock-excel-instruction-item"> <div className="openingStock-excel-instruction-item">
2. Only update the `Total Qty` and `Total Pieces` columns. 2. Only update the `Total Qty` and `Total Pieces` columns.
@ -505,15 +660,17 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
<tr key={row.key}> <tr key={row.key}>
<td>{row.productDisplayName}</td> <td>{row.productDisplayName}</td>
<td>{row.TotalQty}</td> <td>{row.TotalQty}</td>
<td>{row?.OnePcsAvailable === "Y" ? row.TotalPiece : "N/A"}</td> <td>
{row?.OnePcsAvailable === 'Y' ? row.TotalPiece : 'N/A'}
</td>
</tr> </tr>
)) ))
) : ( ) : (
<tr> <tr>
<td colSpan="3" className="openingStock-excel-empty-state"> <td colSpan="3" className="openingStock-excel-empty-state">
{isParsing {isParsing
? "Parsing uploaded file..." ? 'Parsing uploaded file...'
: "No data parsed yet. Upload a filled Excel file to preview changes."} : 'No data parsed yet. Upload a filled Excel file to preview changes.'}
</td> </td>
</tr> </tr>
)} )}
@ -522,11 +679,10 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
</div> </div>
<div className="openingStock-excel-footer"> <div className="openingStock-excel-footer">
<Button <Button
className="openingStock-excel-apply-btn" className="openingStock-excel-apply-btn"
type="primary" type="primary"
icon={<RiFileExcel2Fill />} icon={<RiFileExcel2Fill />}
onClick={handleApply} onClick={handleApply}
disabled={!previewRows.length} disabled={!previewRows.length}
> >
@ -538,4 +694,3 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
}; };
export default OpeningStockExcel; export default OpeningStockExcel;

View File

@ -25,7 +25,6 @@ const commonUrl = import.meta.env.ENV_COMMON_BASE_URL;
const subDirectory = import.meta.env.BASE_URL; const subDirectory = import.meta.env.BASE_URL;
const ProtectedRoutes = ({ routesConfig }) => { const ProtectedRoutes = ({ routesConfig }) => {
useTemplate( useTemplate(
getSession('CompId'), getSession('CompId'),
getSession('BranchId'), getSession('BranchId'),
@ -91,7 +90,7 @@ const ProtectedRoutes = ({ routesConfig }) => {
AppId: AppId, AppId: AppId,
CompId: CompId, CompId: CompId,
BranchId: BranchId, BranchId: BranchId,
UserId:UserId UserId: UserId,
}; };
const response = await dispatch(gettableData(DineinData)).unwrap(); const response = await dispatch(gettableData(DineinData)).unwrap();
if (response?.data?.statusCode === 1) { if (response?.data?.statusCode === 1) {

View File

@ -4,7 +4,9 @@ import moment from 'moment';
import jsPDF from 'jspdf'; import jsPDF from 'jspdf';
import { changePrintStatus } from '../Features/BookingScreen/BookingData/BookingData'; import { changePrintStatus } from '../Features/BookingScreen/BookingData/BookingData';
import store from '../app/store.js'; import store from '../app/store.js';
import html2pdf from 'html2pdf.js'; // import html2pdf from 'html2pdf.js';
import html2canvas from 'html2canvas';
const cookies = new Cookies(); const cookies = new Cookies();
const SECRET_KEY = import.meta.env.ENV_SECRET_KEY; const SECRET_KEY = import.meta.env.ENV_SECRET_KEY;
@ -82,42 +84,40 @@ export const decryptedValuesFun = (value) => {
} }
}; };
export const encryptObject=(obj)=> { export const encryptObject = (obj) => {
const jsonString = JSON.stringify(obj); const jsonString = JSON.stringify(obj);
const encrypted = CryptoJS.AES.encrypt(jsonString, SECRET_KEY).toString(); const encrypted = CryptoJS.AES.encrypt(jsonString, SECRET_KEY).toString();
// Make Base64 string URL-safe // Make Base64 string URL-safe
const urlSafe = encrypted const urlSafe = encrypted
.replace(/\+/g, "-") .replace(/\+/g, '-')
.replace(/\//g, "_") .replace(/\//g, '_')
.replace(/=+$/, ""); .replace(/=+$/, '');
return urlSafe; return urlSafe;
} };
// Decrypt URL-safe AES string to original object // Decrypt URL-safe AES string to original object
export const decryptToObject=(encryptedUrlSafeStr) =>{ export const decryptToObject = (encryptedUrlSafeStr) => {
try { try {
if (!encryptedUrlSafeStr) return null; if (!encryptedUrlSafeStr) return null;
// Restore base64 format // Restore base64 format
let base64 = encryptedUrlSafeStr let base64 = encryptedUrlSafeStr.replace(/-/g, '+').replace(/_/g, '/');
.replace(/-/g, "+")
.replace(/_/g, "/");
// Add missing padding // Add missing padding
while (base64.length % 4 !== 0) { while (base64.length % 4 !== 0) {
base64 += "="; base64 += '=';
}
const bytes = CryptoJS.AES.decrypt(base64, SECRET_KEY);
const decrypted = bytes.toString(CryptoJS.enc.Utf8);
return JSON.parse(decrypted);
} catch (err) {
console.error("Decryption failed:", err);
return null;
} }
const bytes = CryptoJS.AES.decrypt(base64, SECRET_KEY);
const decrypted = bytes.toString(CryptoJS.enc.Utf8);
return JSON.parse(decrypted);
} catch (err) {
console.error('Decryption failed:', err);
return null;
} }
};
export const TokendecryptedValuesFun = (value) => { export const TokendecryptedValuesFun = (value) => {
if (!value) { if (!value) {
return null; return null;
@ -173,7 +173,7 @@ export async function ClearCookie() {
} }
export async function printDiv(id, style) { export async function printDiv(id, style) {
console.log("printDiv",id, style) console.log('printDiv', id, style);
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const content = document.getElementById(id); const content = document.getElementById(id);
@ -251,21 +251,20 @@ export function dateFormatChange(dateString) {
export function dateFormatChange1(dateString) { export function dateFormatChange1(dateString) {
return moment(dateString, [ return moment(dateString, [
'YYYY-MM-DDTHH:mm:ss.SSS', 'YYYY-MM-DDTHH:mm:ss.SSS',
'YYYY-MM-DDTHH:mm:ss' 'YYYY-MM-DDTHH:mm:ss',
]).format('DD-MMM-YYYY hh:mm A'); ]).format('DD-MMM-YYYY hh:mm A');
} }
export function ExtractDateFormate(dateString) { export function ExtractDateFormate(dateString) {
if (!dateString) return ""; if (!dateString) return '';
const date = new Date(dateString); const date = new Date(dateString);
if (isNaN(date)) return ""; if (isNaN(date)) return '';
const day = date.getDate(); const day = date.getDate();
const month = date.toLocaleString("en-US", { month: "short" }); const month = date.toLocaleString('en-US', { month: 'short' });
const year = date.getFullYear(); const year = date.getFullYear();
return `${day}-${month}-${year}`; return `${day}-${month}-${year}`;
} }
// export function dateTimeFormatChange(dateString) { // export function dateTimeFormatChange(dateString) {
// let date = moment(dateString, ['YYYY-MM-DDTHH:mm:ss']).format('DD-MM-YYYY hh:mm:ss A'); // let date = moment(dateString, ['YYYY-MM-DDTHH:mm:ss']).format('DD-MM-YYYY hh:mm:ss A');
// let finaldate = date.replace('T', ' '); // let finaldate = date.replace('T', ' ');
@ -273,7 +272,9 @@ export function ExtractDateFormate(dateString) {
// } // }
export function dateTimeFormatChange(dateString) { export function dateTimeFormatChange(dateString) {
return moment(dateString, ['YYYY-MM-DDTHH:mm:ss']).format('DD-MMM-YYYY hh:mm A'); return moment(dateString, ['YYYY-MM-DDTHH:mm:ss']).format(
'DD-MMM-YYYY hh:mm A'
);
} }
export function convertTo12HourFormat(inputDate) { export function convertTo12HourFormat(inputDate) {
@ -301,8 +302,69 @@ export function handleGeneratePDF(id) {
}); });
} }
} }
// export async function pdfDiv(id, style) {
// return new Promise(async (resolve, reject) => { export const pdfDiv = async (id, style = '') => {
const content = document.getElementById(id);
if (!content) {
throw new Error(`Element with ID "${id}" not found`);
}
// 🔥 wrapper
const wrapper = document.createElement('div');
wrapper.style.width = '794px';
wrapper.style.background = '#fff';
wrapper.innerHTML = `
${style}
${content.innerHTML}
`;
document.body.appendChild(wrapper);
try {
const canvas = await html2canvas(wrapper, {
scale: 2,
useCORS: true,
});
const pdf = new jsPDF('p', 'mm', 'a4');
const imgWidth = 210;
const pageHeight = 297;
const imgHeight = (canvas.height * imgWidth) / canvas.width;
const imgData = canvas.toDataURL('image/jpeg', 1.0);
let heightLeft = imgHeight;
let position = 0;
// ✅ First page
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
// ✅ Correct page splitting
while (heightLeft > 0) {
position = -(imgHeight - heightLeft); // 🔥 FIX HERE
pdf.addPage();
pdf.addImage(imgData, 'JPEG', 0, position, imgWidth, imgHeight);
heightLeft -= pageHeight;
}
document.body.removeChild(wrapper);
return pdf.output('blob');
} catch (err) {
document.body.removeChild(wrapper);
throw err;
}
};
// export const pdfDiv = async (id, style = '') => {
// return new Promise(async(resolve, reject) => {
// const content = document.getElementById(id); // const content = document.getElementById(id);
// if (!content) { // if (!content) {
@ -311,87 +373,39 @@ export function handleGeneratePDF(id) {
// } // }
// try { // try {
// // Create a wrapper to apply styles // // Create wrapper div
// const wrapper = document.createElement('div'); // const wrapper = document.createElement('div');
// wrapper.innerHTML = ` // wrapper.innerHTML = `
// ${style} // ${style}
// ${content.innerHTML} // ${content.innerHTML}
// `; // `;
// // Generate PDF Blob // // PDF options
// const opt = { // const opt = {
// margin: 0.5, // margin: 0,
// image: { type: 'jpeg', quality: 0.98 }, // image: { type: 'jpeg', quality: 0.9 },
// html2canvas: { scale: 2, useCORS: true }, // html2canvas: {
// jsPDF: { unit: 'mm', format: 'a4', orientation: 'portrait' }, // scale: 2,
// pagebreak: { // useCORS: true,
// mode: ['avoid-all', 'css', 'legacy'], // letterRendering: true,
// before: '.page-break-before', // },
// after: '.page-break-after', // jsPDF: {
// avoid: '.page-break-avoid' // unit: 'mm',
// } // format: 'a4',
// orientation: 'portrait',
// },
// pagebreak: {
// mode: ['avoid-all', 'css', 'legacy'],
// },
// }; // };
// const pdfBlob = await html2pdf().set(opt).from(wrapper).outputPdf('blob'); // const blob = await html2pdf().set(opt).from(wrapper).outputPdf('blob');
// resolve(pdfBlob); // resolve(blob);
// } catch (error) { // } catch (err) {
// reject(error); // reject(err);
// } // }
// }); // });
// } // };
// export async function blobToBase64(blob) {
// return new Promise((resolve, reject) => {
// const reader = new FileReader();
// reader.onloadend = () => resolve(reader.result.split(',')[1]);
// reader.onerror = reject;
// reader.readAsDataURL(blob);
// });
// }
// New pdfDiv and blobToBase64 functions
export const pdfDiv = async (id, style = "") => {
return new Promise(async (resolve, reject) => {
const content = document.getElementById(id);
if (!content) {
reject(new Error(`Element with ID "${id}" not found`));
return;
}
try {
// Create wrapper div
const wrapper = document.createElement("div");
wrapper.innerHTML = `
${style}
${content.innerHTML}
`;
// PDF options
const opt = {
margin: 0,
image: { type: "jpeg", quality: 0.9 },
html2canvas: {
scale: 2,
useCORS: true,
letterRendering: true,
},
jsPDF: {
unit: "mm",
format: "a4",
orientation: "portrait",
},
pagebreak: {
mode: ["avoid-all", "css", "legacy"],
},
};
const blob = await html2pdf().set(opt).from(wrapper).outputPdf("blob");
resolve(blob);
} catch (err) {
reject(err);
}
});
};
export const blobToBase64 = (blob) => { export const blobToBase64 = (blob) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -399,7 +413,7 @@ export const blobToBase64 = (blob) => {
reader.onloadend = () => { reader.onloadend = () => {
const bytes = new Uint8Array(reader.result); const bytes = new Uint8Array(reader.result);
let binary = ''; let binary = '';
bytes.forEach((b) => binary += String.fromCharCode(b)); bytes.forEach((b) => (binary += String.fromCharCode(b)));
resolve(btoa(binary)); resolve(btoa(binary));
}; };
reader.onerror = reject; reader.onerror = reject;
@ -424,5 +438,3 @@ export const validateSafeInput = (value) => {
return Promise.resolve(); return Promise.resolve();
}; };

File diff suppressed because it is too large Load Diff

View File

@ -1,34 +1,27 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import react from "@vitejs/plugin-react"; import react from "@vitejs/plugin-react";
import { terser } from "rollup-plugin-terser";
export default defineConfig(({ command, mode }) => { export default defineConfig(({ mode }) => {
if (mode === "production") { const isProd = mode === "production";
return {
plugins: [ return {
react(), plugins: [react()],
// terser({ // Add terser plugin for production
// compress: { base: isProd ? "/apps/retail/" : "/",
// drop_console: true, // This removes all console.* statements
// }, envDir: "src",
// }), envPrefix: "ENV_",
],
base: "/apps/retail/", server: {
envDir: "src", host: true, // 🔥 auto detect your IP
envPrefix: "ENV_", port: process.env.PORT || 3015,
}; },
} else {
return { build: {
plugins: [react()], minify: "esbuild",
envDir: "src", esbuild: {
envPrefix: "ENV_", drop: isProd ? ["console", "debugger"] : [],
server: {
host: "192.168.1.34",
port: process.env.PORT || 3015,
}, },
optimizeDeps: { },
exclude: ["console.log"], };
},
};
}
}); });