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:
commit
e20494d2fb
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"appId": "com.example.app",
|
||||
"appName": "PozoApp",
|
||||
"webDir": "dist",
|
||||
"plugins": {
|
||||
"StatusBar": {
|
||||
"overlay": false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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.")
|
||||
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.');
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
|
@ -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_",
|
||||
|
|
|
|||
Loading…
Reference in New Issue