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.")
}
}
);
}
}
};

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 = {
@ -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);
downloadFile(
buffer,
"Product Data.xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
(success) => {
if (success) {
console.log("File downloaded and opened successfully!");
} else {
const downloadLink = document.createElement('a');
downloadLink.href = window.URL.createObjectURL(blob);
downloadLink.download = filename;
downloadLink.click();
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);

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
@ -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);

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();
@ -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);
};

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');

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_",