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": {
"@ant-design/plots": "^2.6.8",
"@capacitor-community/file-opener": "^8.0.0",
"@capacitor-community/speech-recognition": "^7.0.1",
"@capacitor-mlkit/barcode-scanning": "^8.0.1",
"@capacitor/android": "^8.3.0",
"@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/sortable": "^10.0.0",
"@emotion/react": "^11.11.4",
@ -27,6 +31,7 @@
"@tanstack/react-query-devtools": "^5.91.1",
"@tanstack/react-virtual": "^3.13.18",
"@types/node": "^25.5.0",
"@vitejs/plugin-legacy": "^8.0.1",
"@zxing/library": "^0.21.3",
"antd": "^5.17.4",
"antd-img-crop": "^4.22.0",
@ -92,7 +97,7 @@
"eslint-plugin-react-refresh": "^0.4.7",
"prettier": "^3.5.3",
"rollup-plugin-obfuscator": "^1.1.0",
"sass": "^1.77.2",
"sass": "^1.98.0",
"terser": "^5.31.3",
"vite": "^8.0.0",
"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 upldimg from '../../Images/upldimg.png';
import { FiDelete, FiDownload } from 'react-icons/fi';
import { downloadFile } from '../../utils/downloadFile.js';
const allowedExcelTypes = [
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', // .xlsx
'application/vnd.ms-excel', // .xls
@ -328,10 +329,17 @@ const ConfigExcel = ({ handleSubmit, CategoryNames, SubCatData }) => {
window.navigator.msSaveOrOpenBlob(blob, filename);
} else {
// For other browsers
const downloadLink = document.createElement('a');
downloadLink.href = window.URL.createObjectURL(blob);
downloadLink.download = filename;
downloadLink.click();
downloadFile(
buffer,
"CategoryDatas.xlsx",
"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);
} else {
// For other browsers
const downloadLink = document.createElement('a');
downloadLink.href = window.URL.createObjectURL(blob);
downloadLink.download = filename;
downloadLink.click();
downloadFile(
buffer,
"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);
} else {
// For other browsers
const downloadLink = document.createElement('a');
downloadLink.href = window.URL.createObjectURL(blob);
downloadLink.download = filename;
downloadLink.click();
downloadFile(
buffer,
"BrandDatas.xlsx",
"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,
...values,
});
} catch (errInfo) {}
} catch (errInfo) { }
};
let childNode = children;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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