Merge pull request 'feat: Add XLSX/PDF Download & Bulk Upload for Product, Stock, Purchase Order & Common Master' (#320) from beforeaddandroidfileforcapcitor into main

Reviewed-on: Pozomind/pozo-retail-app#320
This commit is contained in:
karthikalakshmi 2026-03-27 15:32:29 +05:30
commit e20494d2fb
12 changed files with 3027 additions and 327 deletions

2556
README.md Normal file

File diff suppressed because it is too large Load Diff

10
capacitor.config.json Normal file
View File

@ -0,0 +1,10 @@
{
"appId": "com.example.app",
"appName": "PozoApp",
"webDir": "dist",
"plugins": {
"StatusBar": {
"overlay": false
}
}
}

View File

@ -12,10 +12,14 @@
}, },
"dependencies": { "dependencies": {
"@ant-design/plots": "^2.6.8", "@ant-design/plots": "^2.6.8",
"@capacitor-community/file-opener": "^8.0.0",
"@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/android": "^8.3.0",
"@capacitor/app": "^8.0.1", "@capacitor/app": "^8.0.1",
"@capacitor/core": "^8.1.0", "@capacitor/cli": "^8.3.0",
"@capacitor/core": "^8.3.0",
"@capacitor/filesystem": "^8.1.2",
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
"@emotion/react": "^11.11.4", "@emotion/react": "^11.11.4",
@ -27,6 +31,7 @@
"@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", "@types/node": "^25.5.0",
"@vitejs/plugin-legacy": "^8.0.1",
"@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",
@ -92,7 +97,7 @@
"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",
"sass": "^1.77.2", "sass": "^1.98.0",
"terser": "^5.31.3", "terser": "^5.31.3",
"vite": "^8.0.0", "vite": "^8.0.0",
"vite-plugin-obfuscator": "^1.0.5", "vite-plugin-obfuscator": "^1.0.5",

View File

@ -16,6 +16,7 @@ import { RadioGrpButton } from '../../Components/Forms/RadioGroup.jsx';
import { AiFillDelete } from 'react-icons/ai'; import { AiFillDelete } from 'react-icons/ai';
import upldimg from '../../Images/upldimg.png'; import upldimg from '../../Images/upldimg.png';
import { FiDelete, FiDownload } from 'react-icons/fi'; import { FiDelete, FiDownload } from 'react-icons/fi';
import { downloadFile } from '../../utils/downloadFile.js';
const allowedExcelTypes = [ const allowedExcelTypes = [
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
'application/vnd.ms-excel', // .xls 'application/vnd.ms-excel', // .xls
@ -328,10 +329,17 @@ const ConfigExcel = ({ handleSubmit, CategoryNames, SubCatData }) => {
window.navigator.msSaveOrOpenBlob(blob, filename); window.navigator.msSaveOrOpenBlob(blob, filename);
} else { } else {
// For other browsers // For other browsers
const downloadLink = document.createElement('a'); downloadFile(
downloadLink.href = window.URL.createObjectURL(blob); buffer,
downloadLink.download = filename; "CategoryDatas.xlsx",
downloadLink.click(); "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
(success) => {
if (!success) {
console.error("error occured while downloading")
}
}
);
} }
} }
}; };
@ -415,10 +423,17 @@ const ConfigExcel = ({ handleSubmit, CategoryNames, SubCatData }) => {
window.navigator.msSaveOrOpenBlob(blob, filename); window.navigator.msSaveOrOpenBlob(blob, filename);
} else { } else {
// For other browsers // For other browsers
const downloadLink = document.createElement('a');
downloadLink.href = window.URL.createObjectURL(blob); downloadFile(
downloadLink.download = filename; buffer,
downloadLink.click(); "SubcatagoryDatas.xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
(success) => {
if (!success) {
console.error("Failed to download the file. Please try again.")
}
}
);
} }
} }
}; };
@ -529,10 +544,17 @@ const ConfigExcel = ({ handleSubmit, CategoryNames, SubCatData }) => {
window.navigator.msSaveOrOpenBlob(blob, filename); window.navigator.msSaveOrOpenBlob(blob, filename);
} else { } else {
// For other browsers // For other browsers
const downloadLink = document.createElement('a'); downloadFile(
downloadLink.href = window.URL.createObjectURL(blob); buffer,
downloadLink.download = filename; "BrandDatas.xlsx",
downloadLink.click(); "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
(success) => {
if (!success) {
console.error("Failed to download the file. Please try again.")
}
}
);
} }
} }
}; };
@ -673,7 +695,7 @@ const ConfigExcel = ({ handleSubmit, CategoryNames, SubCatData }) => {
...record, ...record,
...values, ...values,
}); });
} catch (errInfo) {} } catch (errInfo) { }
}; };
let childNode = children; let childNode = children;

View File

@ -41,6 +41,7 @@ import Buttons from '../../Components/Forms/Buttons.jsx';
import { IoClose } from 'react-icons/io5'; import { IoClose } from 'react-icons/io5';
import { Messages } from '../../Components/Notifications/Messages.jsx'; import { Messages } from '../../Components/Notifications/Messages.jsx';
import useWhyDidYouUpdate from '../../Services/findrerender.js'; import useWhyDidYouUpdate from '../../Services/findrerender.js';
import { downloadFile } from '../../utils/downloadFile.js';
const getApplicationSampleData = (appName) => { const getApplicationSampleData = (appName) => {
const sampleDataMap = { const sampleDataMap = {
@ -1227,8 +1228,8 @@ const ProductExcel = ({
allowBlank: true, allowBlank: true,
formulae: [ formulae: [
'"' + '"' +
(typeof Unit !== 'undefined' ? Unit.join(',') : 'KGS,PCS,LITER') + (typeof Unit !== 'undefined' ? Unit.join(',') : 'KGS,PCS,LITER') +
'"', '"',
], ],
}, },
sampleValue: sampleData?.uom || 'KGS', sampleValue: sampleData?.uom || 'KGS',
@ -2357,23 +2358,21 @@ const ProductExcel = ({
} }
}); });
// Generate and download the file
const buffer = await workbook.xlsx.writeBuffer(); const buffer = await workbook.xlsx.writeBuffer();
const blob = new Blob([buffer], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
});
const filename = 'Product Data.xlsx';
if (typeof window !== 'undefined') { downloadFile(
if (window.navigator && window.navigator.msSaveOrOpenBlob) { buffer,
window.navigator.msSaveOrOpenBlob(blob, filename); "Product Data.xlsx",
} else { "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
const downloadLink = document.createElement('a'); (success) => {
downloadLink.href = window.URL.createObjectURL(blob); if (success) {
downloadLink.download = filename; console.log("File downloaded and opened successfully!");
downloadLink.click(); } else {
console.log("Failed to download/open file.");
}
} }
} );
}; };
const handleFileSubmit = (e) => { const handleFileSubmit = (e) => {
@ -2908,15 +2907,21 @@ const ProductExcel = ({
selectUnlockedCells: true, selectUnlockedCells: true,
}); });
// const buffer = await workbook.xlsx.writeBuffer();
// const blob = new Blob([buffer], {
// type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
// });
// const downloadLink = document.createElement('a');
// downloadLink.href = window.URL.createObjectURL(blob);
// downloadLink.download = 'Product_Data.xlsx';
// downloadLink.click();
const buffer = await workbook.xlsx.writeBuffer(); const buffer = await workbook.xlsx.writeBuffer();
const blob = new Blob([buffer], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
});
const downloadLink = document.createElement('a');
downloadLink.href = window.URL.createObjectURL(blob);
downloadLink.download = 'Product_Data.xlsx';
downloadLink.click();
downloadFile(
buffer,
"Product_Data.xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
);
setLoading(false); setLoading(false);
} else { } else {
setLoading(false); setLoading(false);
@ -3228,98 +3233,98 @@ const ProductExcel = ({
<div className="download-linkMain"> <div className="download-linkMain">
<div className="HeadingUploadExl"> <div className="HeadingUploadExl">
<p <p
style={{ style={{
fontSize: '16px', fontSize: '16px',
fontWeight: '500', fontWeight: '500',
whiteSpace: 'nowrap', whiteSpace: 'nowrap',
}} }}
> >
Upload Your Excel Upload Your Excel
</p> </p>
<div <div
style={{ style={{
display: 'flex', display: 'flex',
flexDirection: 'row', flexDirection: 'row',
gap: '0.5rem', gap: '0.5rem',
}} }}
> >
<Tooltip title="Field Setup"> <Tooltip title="Field Setup">
<div
className="btn btn--info"
style={{
background: '#00694aff',
color: '#fff',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '5px 12px',
borderRadius: '4px',
}}
onClick={handleFieldSetup}
>
<MdOutlineAppRegistration size={19} />
</div>
</Tooltip>
<div <div
className="export-excel-div" className="btn btn--info"
onClick={() => handleExportData()} style={{
background: '#00694aff',
color: '#fff',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '5px 12px',
borderRadius: '4px',
}}
onClick={handleFieldSetup}
> >
<div> <MdOutlineAppRegistration size={19} />
<CiExport size={20} strokeWidth={1} />
</div>
<div style={{ fontSize: '14px', letterSpacing: '0.2px' }}>
Export Excel
</div>
</div> </div>
</Tooltip>
<div
className="export-excel-div"
onClick={() => handleExportData()}
>
<div>
<CiExport size={20} strokeWidth={1} />
</div>
<div style={{ fontSize: '14px', letterSpacing: '0.2px' }}>
Export Excel
</div>
</div>
{message && ( {message && (
<div style={{ color: '#ff4d4f' }}>No Data Found</div> <div style={{ color: '#ff4d4f' }}>No Data Found</div>
)} )}
</div>
{excelData?.length > 0 && excelData && (
<div>
<Tooltip title="Delete">
<AiFillDelete
onClick={handlechange22}
style={{
fontSize: '23px',
color: editdelete ? '#52c41a' : '#1292ee',
}}
/>
</Tooltip>
</div>
)}
</div> </div>
<div
className="download-link"
style={{
display: 'flex',
justifyContent:
excelData?.length > 0 ? 'space-between' : 'flex-end',
gap: '1rem',
}}
>
{excelData?.length > 0 && (
<div className="SheetIndiacte">
<div>
<p className="mdsheet"></p>
<span>Modified</span>
</div>
<div>
<p className="mdsheet2"></p>
<span>Not Modified</span>
</div>
</div>
)}
<Tooltip title={'Format Sheet'} placement="top"> {excelData?.length > 0 && excelData && (
<button onClick={handleDownload}> <div>
<FiDownload style={{ fontSize: '20px' }} /> <Tooltip title="Delete">
<span>Download Sample Sheet</span> <AiFillDelete
</button> onClick={handlechange22}
</Tooltip> style={{
fontSize: '23px',
color: editdelete ? '#52c41a' : '#1292ee',
}}
/>
</Tooltip>
</div>
)}
</div>
<div
className="download-link"
style={{
display: 'flex',
justifyContent:
excelData?.length > 0 ? 'space-between' : 'flex-end',
gap: '1rem',
}}
>
{excelData?.length > 0 && (
<div className="SheetIndiacte">
<div>
<p className="mdsheet"></p>
<span>Modified</span>
</div>
<div>
<p className="mdsheet2"></p>
<span>Not Modified</span>
</div>
</div>
)}
<Tooltip title={'Format Sheet'} placement="top">
<button onClick={handleDownload}>
<FiDownload style={{ fontSize: '20px' }} />
<span>Download Sample Sheet</span>
</button>
</Tooltip>
</div> </div>
</div> </div>
@ -3502,7 +3507,7 @@ const ProductExcel = ({
<div <div
className={`singleimg ${ className={`singleimg ${
selectedImage === item ? 'selected' : '' selectedImage === item ? 'selected' : ''
}`} }`}
key={index} key={index}
onClick={() => setSelectedImage(item)} onClick={() => setSelectedImage(item)}
> >

View File

@ -29,6 +29,7 @@ import { DefaultModal } from '../../Components/Modal/DefaultModal.jsx';
import Buttons from '../../Components/Forms/Buttons.jsx'; import Buttons from '../../Components/Forms/Buttons.jsx';
import { getPurchaseSupplierAndWareHouseData } from '../../Features/PurchaseOrder/PurchaseOrder.js'; import { getPurchaseSupplierAndWareHouseData } from '../../Features/PurchaseOrder/PurchaseOrder.js';
import { Messages } from '../../Components/Notifications/Messages.jsx'; import { Messages } from '../../Components/Notifications/Messages.jsx';
import { downloadFile } from '../../utils/downloadFile.js';
// Constants // Constants
const ALLOWED_EXCEL_TYPES = [ const ALLOWED_EXCEL_TYPES = [
@ -522,15 +523,19 @@ const PurchaseExcel = ({ handleSubmit }) => {
// 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], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
});
const filename = `PurchaseOrder_${supplierDisplayName}.xlsx`; const filename = `PurchaseOrder_${supplierDisplayName}.xlsx`;
const link = document.createElement('a');
link.href = URL.createObjectURL(blob); downloadFile(
link.download = filename; buffer,
link.click(); filename,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
(success) => {
if (!success) {
setMessageType('error');
setMessageData('Failed to download the file. Please try again.');
}
}
);
setSupplierProducts([]); setSupplierProducts([]);
setSelectedSupplierData(null); setSelectedSupplierData(null);

View File

@ -19,6 +19,7 @@ import {
import QRCodeLib from 'qrcode'; import QRCodeLib from 'qrcode';
import { LuDownload } from 'react-icons/lu'; import { LuDownload } from 'react-icons/lu';
import { BsFileEarmarkPdf } from 'react-icons/bs'; import { BsFileEarmarkPdf } from 'react-icons/bs';
import { downloadFile } from '../../../utils/downloadFile';
const subDirectory = import.meta.env.ENV_MAIN_BASE_URL || '/'; const subDirectory = import.meta.env.ENV_MAIN_BASE_URL || '/';
@ -312,7 +313,19 @@ const MenuQRCode = () => {
if (openPreview) { if (openPreview) {
setIsPreviewModalOpen(true); setIsPreviewModalOpen(true);
} else { } else {
pdf.save('menu-qr.pdf'); // pdf.save('menu-qr.pdf');
const pdfBuffer = pdf.output('arraybuffer'); // arraybuffer for downloadFile
downloadFile(
pdfBuffer,
"menu-qr.pdf",
"application/pdf",
(success) => {
if (!success) {
setMessageType('error');
setMessageData('Failed to download the PDF. Please try again.');
}
}
);
} }
} catch (err) { } catch (err) {
console.error('PDF generation failed', err); console.error('PDF generation failed', err);
@ -323,17 +336,21 @@ const MenuQRCode = () => {
const downloadPreviewPdf = () => { const downloadPreviewPdf = () => {
if (!pdfPreviewUrl) return; if (!pdfPreviewUrl) return;
fetch(pdfPreviewUrl) fetch(pdfPreviewUrl)
.then((r) => r.blob()) .then((r) => r.arrayBuffer()) // arrayBuffer instead of blob
.then((blob) => { .then((buffer) => {
const url = URL.createObjectURL(blob); downloadFile(
const a = document.createElement('a'); buffer,
a.href = url; "menu-qr.pdf",
a.download = 'menu-qr.pdf'; "application/pdf",
document.body.appendChild(a); (success) => {
a.click(); if (!success) {
a.remove(); setMessageType('error');
URL.revokeObjectURL(url); setMessageData('Failed to download the file. Please try again.');
}
}
);
}); });
}; };

View File

@ -37,6 +37,7 @@ import {
import { Messages } from '../../Components/Notifications/Messages.jsx'; import { Messages } from '../../Components/Notifications/Messages.jsx';
import { getPurchaseSupplierAndWareHouseData } from '../../Features/PurchaseOrder/PurchaseOrder.js'; import { getPurchaseSupplierAndWareHouseData } from '../../Features/PurchaseOrder/PurchaseOrder.js';
import { getSupplaierIdwithTypeBasedProducts } from '../../Features/SupplierProductMapping/SupplierProductMapping.js'; import { getSupplaierIdwithTypeBasedProducts } from '../../Features/SupplierProductMapping/SupplierProductMapping.js';
import { downloadFile } from '../../utils/downloadFile.js';
const allowedExcelTypes = [ const allowedExcelTypes = [
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
@ -478,8 +479,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,
})); }));
@ -1007,14 +1008,19 @@ const StockExcel = ({
}); });
const buffer = await workbook.xlsx.writeBuffer(); const buffer = await workbook.xlsx.writeBuffer();
const blob = new Blob([buffer], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
});
const filename = `PurchaseEntry_${supplierDisplayName}_${Date.now()}.xlsx`; const filename = `PurchaseEntry_${supplierDisplayName}_${Date.now()}.xlsx`;
const link = document.createElement('a');
link.href = URL.createObjectURL(blob); downloadFile(
link.download = filename; buffer,
link.click(); filename,
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
(success) => {
if (!success) {
setMessageType('error');
setMessageData('Failed to download the file. Please try again.');
}
}
);
setSupplierProducts([]); setSupplierProducts([]);
setSelectedSupplierData(null); setSelectedSupplierData(null);
@ -1332,7 +1338,7 @@ const StockExcel = ({
...record, ...record,
...values, ...values,
}); });
} catch (errInfo) {} } catch (errInfo) { }
}; };
let childNode = children; let childNode = children;

View File

@ -60,6 +60,7 @@ import {
getCommonAppPreference, getCommonAppPreference,
} from '../../Features/BrachLogin/BranchLogin.js'; } from '../../Features/BrachLogin/BranchLogin.js';
import { IoClose } from 'react-icons/io5'; import { IoClose } from 'react-icons/io5';
import { downloadFile } from '../../utils/downloadFile.js';
const StockPriceUpdate = () => { const StockPriceUpdate = () => {
const { SadminuserAccess } = useAuth(); const { SadminuserAccess } = useAuth();
@ -179,53 +180,53 @@ const StockPriceUpdate = () => {
}, },
...(hasFieldExists('Stock') ...(hasFieldExists('Stock')
? [ ? [
{ {
title: 'Stock', title: 'Stock',
dataIndex: 'Stock', dataIndex: 'Stock',
key: 'Stock', key: 'Stock',
width: 80, width: 80,
}, },
] ]
: []), : []),
...(hasFieldExists('Stock Date') ...(hasFieldExists('Stock Date')
? [ ? [
{ {
title: 'Stock Date', title: 'Stock Date',
dataIndex: 'StockDate', dataIndex: 'StockDate',
key: 'StockDate', key: 'StockDate',
width: 80, width: 80,
}, },
] ]
: []), : []),
...(hasFieldExists('Old MRP') ...(hasFieldExists('Old MRP')
? [ ? [
{ {
title: 'Old MRP', title: 'Old MRP',
dataIndex: 'OldMRP', dataIndex: 'OldMRP',
key: 'OldMRP', key: 'OldMRP',
width: 100, width: 100,
}, },
] ]
: []), : []),
...(hasFieldExists('Old SP') ...(hasFieldExists('Old SP')
? [ ? [
{ {
title: 'Old SP', title: 'Old SP',
dataIndex: 'OldSP', dataIndex: 'OldSP',
key: 'OldSP', key: 'OldSP',
width: 100, width: 100,
}, },
] ]
: []), : []),
...(hasFieldExists('Old Wholesale Price') ...(hasFieldExists('Old Wholesale Price')
? [ ? [
{ {
title: 'Old Wholesale Price', title: 'Old Wholesale Price',
dataIndex: 'OldWholeSalePrice', dataIndex: 'OldWholeSalePrice',
key: 'OldWholeSalePrice', key: 'OldWholeSalePrice',
width: 140, width: 140,
}, },
] ]
: []), : []),
// EDITABLE // EDITABLE
@ -322,7 +323,7 @@ const StockPriceUpdate = () => {
); );
excelDataRef.current excelDataRef.current
?.validateFields([`NewSellPrice${record.localId}`]) ?.validateFields([`NewSellPrice${record.localId}`])
.catch(() => {}); .catch(() => { });
} }
}} }}
/> />
@ -331,40 +332,40 @@ const StockPriceUpdate = () => {
}, },
...(hasFieldExists('New Wholesale Price') ...(hasFieldExists('New Wholesale Price')
? [ ? [
{ {
title: 'New Wholesale Price', title: 'New Wholesale Price',
dataIndex: 'NewWholeSalePrice', dataIndex: 'NewWholeSalePrice',
key: 'NewWholeSalePrice', key: 'NewWholeSalePrice',
width: 160, width: 160,
render: (value, record, index) => ( render: (value, record, index) => (
<Form.Item name={`NewWholeSalePrice${record?.localId}`}> <Form.Item name={`NewWholeSalePrice${record?.localId}`}>
<Input <Input
onChange={(e) => onChange={(e) =>
handlePriceChange( handlePriceChange(
index, index,
'NewWholeSalePrice', 'NewWholeSalePrice',
e.target.value, e.target.value,
record.localId record.localId
) )
}
placeholder="New Wholesale"
inputMode="decimal"
onInput={(e) => {
let cleanedValue = e.target.value.replace(/[^0-9.]/g, '');
const parts = cleanedValue.split('.');
if (cleanedValue.startsWith('.')) {
cleanedValue = '0' + cleanedValue;
} }
placeholder="New Wholesale" e.target.value =
inputMode="decimal" parts.length > 2
onInput={(e) => { ? `${parts[0]}.${parts.slice(1).join('')}`
let cleanedValue = e.target.value.replace(/[^0-9.]/g, ''); : cleanedValue;
const parts = cleanedValue.split('.'); }}
if (cleanedValue.startsWith('.')) { />
cleanedValue = '0' + cleanedValue; </Form.Item>
} ),
e.target.value = },
parts.length > 2 ]
? `${parts[0]}.${parts.slice(1).join('')}`
: cleanedValue;
}}
/>
</Form.Item>
),
},
]
: []), : []),
{ {
title: 'Action', title: 'Action',
@ -1092,79 +1093,79 @@ const StockPriceUpdate = () => {
}, },
...(hasField('Stock Date') ...(hasField('Stock Date')
? [ ? [
{ {
title: 'stock Date', title: 'stock Date',
dataIndex: 'InwardDate', dataIndex: 'InwardDate',
key: 'InwardDate', key: 'InwardDate',
align: 'center', align: 'center',
width: 120, width: 120,
render: (InwardDate) => render: (InwardDate) =>
InwardDate !== null ? ExtractDateFormate(InwardDate) : '-', InwardDate !== null ? ExtractDateFormate(InwardDate) : '-',
}, },
] ]
: []), : []),
...(hasField('Expiry Date') ...(hasField('Expiry Date')
? [ ? [
{ {
title: 'Exp date', title: 'Exp date',
dataIndex: 'ExpDate', dataIndex: 'ExpDate',
key: 'ExpDate', key: 'ExpDate',
align: 'center', align: 'center',
width: 100, width: 100,
render: (ExpDate) => render: (ExpDate) =>
ExpDate !== null ? ExtractDateFormate(ExpDate) : '-', ExpDate !== null ? ExtractDateFormate(ExpDate) : '-',
}, },
] ]
: []), : []),
...(hasField('Stock') ...(hasField('Stock')
? [ ? [
{ {
title: 'stock', title: 'stock',
dataIndex: 'BalanceQty', dataIndex: 'BalanceQty',
key: 'BalanceQty', key: 'BalanceQty',
align: 'right', align: 'right',
width: '60px', width: '60px',
render: (BalanceQty) => (BalanceQty !== null ? BalanceQty : '-'), render: (BalanceQty) => (BalanceQty !== null ? BalanceQty : '-'),
}, },
] ]
: []), : []),
...(hasField('Old MRP') ...(hasField('Old MRP')
? [ ? [
{ {
title: 'Old MRP', title: 'Old MRP',
dataIndex: 'MRP', dataIndex: 'MRP',
key: 'MRP', key: 'MRP',
align: 'right', align: 'right',
width: '70px', width: '70px',
render: (MRP) => (safeRound(MRP) !== null ? safeRound(MRP) : '-'), render: (MRP) => (safeRound(MRP) !== null ? safeRound(MRP) : '-'),
}, },
] ]
: []), : []),
...(hasField('Old SP') ...(hasField('Old SP')
? [ ? [
{ {
title: 'Old SP', title: 'Old SP',
dataIndex: 'SellPrice', dataIndex: 'SellPrice',
key: 'SellPrice', key: 'SellPrice',
align: 'right', align: 'right',
width: '70px', width: '70px',
render: (SellPrice) => render: (SellPrice) =>
safeRound(SellPrice) !== null ? safeRound(SellPrice) : '-', safeRound(SellPrice) !== null ? safeRound(SellPrice) : '-',
}, },
] ]
: []), : []),
...(hasField('Old Wholesale Price') ...(hasField('Old Wholesale Price')
? [ ? [
{ {
title: 'Old Wholesale Price', title: 'Old Wholesale Price',
dataIndex: 'WhSalePrice', dataIndex: 'WhSalePrice',
key: 'WhSalePrice', key: 'WhSalePrice',
align: 'right', align: 'right',
width: '120px', width: '120px',
render: (WhSalePrice) => render: (WhSalePrice) =>
safeRound(WhSalePrice) !== null ? safeRound(WhSalePrice) : '-', safeRound(WhSalePrice) !== null ? safeRound(WhSalePrice) : '-',
}, },
] ]
: []), : []),
{ {
title: 'New MRP', title: 'New MRP',
@ -1188,19 +1189,19 @@ const StockPriceUpdate = () => {
}, },
...(hasField('New Wholesale Price') ...(hasField('New Wholesale Price')
? [ ? [
{ {
title: 'New Wholesale Price', title: 'New Wholesale Price',
dataIndex: 'CurrentWhAmt', dataIndex: 'CurrentWhAmt',
key: 'CurrentWhAmt', key: 'CurrentWhAmt',
align: 'center', align: 'center',
with: '4rem', with: '4rem',
editable: true, editable: true,
render: (text, record) => ( render: (text, record) => (
<p>{text ? text : 'Enter Wholesale Price'}</p> <p>{text ? text : 'Enter Wholesale Price'}</p>
), ),
width: 120, width: 120,
}, },
] ]
: []), : []),
]; ];
@ -1481,9 +1482,9 @@ const StockPriceUpdate = () => {
isStockVariantBased === 'Yes' isStockVariantBased === 'Yes'
? BulkProductData || [] ? BulkProductData || []
: (BulkProductData || [])?.filter( : (BulkProductData || [])?.filter(
(item, index, self) => (item, index, self) =>
index === self.findIndex((p) => p.ProdId === item.ProdId) index === self.findIndex((p) => p.ProdId === item.ProdId)
); );
if (!uniqueBulkProductData?.length) { if (!uniqueBulkProductData?.length) {
setMessageType('error'); setMessageType('error');
@ -1596,9 +1597,9 @@ const StockPriceUpdate = () => {
cell.border = isHiddenCol cell.border = isHiddenCol
? undefined ? undefined
: { : {
right: { style: 'thin', color: { argb: 'FF000000' } }, right: { style: 'thin', color: { argb: 'FF000000' } },
bottom: { style: 'thin', color: { argb: 'FF000000' } }, bottom: { style: 'thin', color: { argb: 'FF000000' } },
}; };
}); });
/* ---------------- ADD DATA ---------------- */ /* ---------------- ADD DATA ---------------- */
@ -1713,14 +1714,19 @@ const StockPriceUpdate = () => {
/* ---------------- DOWNLOAD ---------------- */ /* ---------------- DOWNLOAD ---------------- */
const buffer = await workbook.xlsx.writeBuffer(); const buffer = await workbook.xlsx.writeBuffer();
const blob = new Blob([buffer], { downloadFile(
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', buffer,
}); "PriceChange_Data.xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
const link = document.createElement('a'); (success) => {
link.href = URL.createObjectURL(blob); if (success) {
link.download = 'PriceChange_Data.xlsx'; console.log("Template downloaded successfully!");
link.click(); } else {
setMessageType('error');
setMessageData('Failed to download the template. Please try again.');
}
}
)
setExportLoading(false); setExportLoading(false);
}; };
@ -1780,7 +1786,7 @@ const StockPriceUpdate = () => {
Number(current.MRP) !== Number(uploaded.NewMRP) || Number(current.MRP) !== Number(uploaded.NewMRP) ||
Number(current.SellPrice) !== Number(uploaded.NewSellPrice) || Number(current.SellPrice) !== Number(uploaded.NewSellPrice) ||
Number(current.WhSalePrice || 0) !== Number(current.WhSalePrice || 0) !==
Number(uploaded.NewWholeSalePrice || 0); Number(uploaded.NewWholeSalePrice || 0);
return { return {
...uploaded, ...uploaded,
@ -2297,17 +2303,17 @@ const StockPriceUpdate = () => {
<div> <div>
{(RadioSelection == 'Bulk' || {(RadioSelection == 'Bulk' ||
(RadioSelection == 'Single' && StockDetails.length > 0)) && ( (RadioSelection == 'Single' && StockDetails.length > 0)) && (
<div className="samplereportTableButton"> <div className="samplereportTableButton">
<Buttons <Buttons
buttonText="Submit" buttonText="Submit"
color="901D77" color="901D77"
palcement={'right'} palcement={'right'}
handleSubmit={HandlePutData} handleSubmit={HandlePutData}
icon={<ArrowRightOutlined />} icon={<ArrowRightOutlined />}
disabled={addnewAccess} disabled={addnewAccess}
></Buttons> ></Buttons>
</div> </div>
)} )}
</div> </div>
</div> </div>
{RadioSelection == 'Single' && ( {RadioSelection == 'Single' && (
@ -2325,10 +2331,10 @@ const StockPriceUpdate = () => {
StockDetails?.length < 11 StockDetails?.length < 11
? false ? false
: { : {
current: page, current: page,
pageSize: pageSize, pageSize: pageSize,
onChange: handlePageChange, onChange: handlePageChange,
} }
} }
onChange={handleChange} onChange={handleChange}
></Table> ></Table>
@ -2395,7 +2401,7 @@ const StockPriceUpdate = () => {
<div <div
className={`export-excel-div ${exportLoading ? 'is-loading' : ''}`} className={`export-excel-div ${exportLoading ? 'is-loading' : ''}`}
onClick={ onClick={
exportLoading ? () => {} : () => showPriceChangeModal() exportLoading ? () => { } : () => showPriceChangeModal()
} }
> >
<div> <div>

View File

@ -7,6 +7,7 @@ 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';
import { downloadFile } from '../../utils/downloadFile.js';
const TEMPLATE_HEADERS = [ const TEMPLATE_HEADERS = [
{ key: 'ProdName', header: 'Product Name' }, { key: 'ProdName', header: 'Product Name' },
@ -203,15 +204,20 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
} }
const buffer = await workbook.xlsx.writeBuffer(); const buffer = await workbook.xlsx.writeBuffer();
const blob = new Blob([buffer], {
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
});
const link = document.createElement('a'); downloadFile(
link.href = URL.createObjectURL(blob); buffer,
link.download = 'opening_stock_template.xlsx'; "opening_stock_template.xlsx",
link.click(); "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
URL.revokeObjectURL(link.href); (success) => {
if (success) {
console.log("Template downloaded successfully!");
} else {
setMessageType('error');
setMessageData('Failed to download the template. Please try again.');
}
}
)
} catch (error) { } catch (error) {
console.error('Template download failed:', error); console.error('Template download failed:', error);
setMessageType('error'); setMessageType('error');
@ -562,8 +568,8 @@ const OpeningStockExcel = ({ products = [], onApply, onCancel }) => {
: '', : '',
TotalPcs: TotalPcs:
row.OnePcsAvailable === 'Y' && row.OnePcsAvailable === 'Y' &&
row.TotalPiece !== undefined && row.TotalPiece !== undefined &&
row.TotalPiece !== null row.TotalPiece !== null
? row.TotalPiece.toString() ? row.TotalPiece.toString()
: 0, : 0,
InwardDetails: [], InwardDetails: [],

62
src/utils/downloadFile.js Normal file
View File

@ -0,0 +1,62 @@
import { Capacitor } from "@capacitor/core";
import { Filesystem, Directory } from "@capacitor/filesystem";
import { FileOpener } from "@capacitor-community/file-opener";
/**
* Download and open a file (Excel, PDF, etc.)
* @param {ArrayBuffer | Blob} buffer - File data
* @param {string} filename - File name with extension
* @param {string} mimeType - File MIME type (example: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet)
* @param {Function} onSuccess - Optional callback, called with true/false
*/
export const downloadFile = async (buffer, filename, mimeType, onSuccess) => {
try {
const isWeb = Capacitor.getPlatform() === "web";
// 🌐 Web download
if (isWeb) {
const blob = buffer instanceof Blob ? buffer : new Blob([buffer], { type: mimeType });
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
URL.revokeObjectURL(link.href);
onSuccess?.(true);
return;
}
// 📱 Mobile (Android / iOS)
// Convert ArrayBuffer → Base64
let binary = "";
const bytes = new Uint8Array(buffer);
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
const base64Data = btoa(binary);
// Save file inside app's Documents folder
const savedFile = await Filesystem.writeFile({
path: filename,
data: base64Data,
directory: Directory.Data, // ← was Directory.Documents
recursive: true,
});
// FileOpener requires removing "file://"
await FileOpener.open({
filePath: savedFile.uri, // ← was savedFile.uri.replace("file://", "")
contentType: mimeType,
});
onSuccess?.(true);
} catch (err) {
console.error("File download/open error:", err);
onSuccess?.(false);
}
};

View File

@ -7,7 +7,7 @@ export default defineConfig(({ mode }) => {
return { return {
plugins: [react()], plugins: [react()],
base: isProd ? "/apps/retail/" : "/", base: isProd ? "/apps/retail/" : "/", //for capcitor base: isProd ? "/" : "/",
envDir: "src", envDir: "src",
envPrefix: "ENV_", envPrefix: "ENV_",