Android_Retail/src/Pages/Reports/MenuQRCode/MenuQRCode.jsx

540 lines
16 KiB
React
Raw Normal View History

2026-01-27 18:27:29 +05:30
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useDispatch } from 'react-redux';
import { Messages } from '../../../Components/Notifications/Messages';
import FormHeader from '../../PageComponents/FormHeader';
import QRCode from 'react-qr-code';
import PublicQrCodeShare from '../PublicQrCodeShare/PublicQrCodeShare';
import { encryptObject, getSession } from '../../../Services/Others';
import { PublicQrCodepost } from '../../../Features/ConfigMasterPage/ConfigMasterPage';
import Buttons from '../../../Components/Forms/Buttons';
import { changeBreadCrumb } from '../../../Features/AppPage/CenterPage';
import { Modal } from 'antd';
import { jsPDF } from 'jspdf';
import {
FaFacebookF,
FaWhatsapp,
FaTwitter,
FaLinkedinIn,
FaEnvelope,
} from 'react-icons/fa';
import QRCodeLib from 'qrcode';
import { LuDownload } from 'react-icons/lu';
import { BsFileEarmarkPdf } from 'react-icons/bs';
const subDirectory = import.meta.env.ENV_MAIN_BASE_URL || '/';
const MenuQRCode = () => {
const dispatch = useDispatch();
// Session
const CompId = getSession('CompId');
const BranchId = getSession('BranchId');
const AppId = getSession('AppId');
const UserId = getSession('UserId');
// States
const [qrCodeUrl, setQrCodeUrl] = useState('');
const [qrCodeDataUrl, setQrCodeDataUrl] = useState('');
const [messageType, setMessageType] = useState(null);
const [messageData, setMessageData] = useState(null);
const [isPreviewModalOpen, setIsPreviewModalOpen] = useState(false);
const [pdfPreviewUrl, setPdfPreviewUrl] = useState('');
const [socialIconsDataUrls, setSocialIconsDataUrls] = useState([]);
const cardRef = useRef(null);
const socialIconsRef = useRef(null);
// breadcrumb
useEffect(() => {
const items = [
{
name: 'Home',
link: `${subDirectory}app-page/home`,
},
];
dispatch(changeBreadCrumb({ items }));
HandleSubmit();
}, [dispatch]);
// Generate QR Code as Data URL using qrcode library
useEffect(() => {
if (qrCodeUrl) {
QRCodeLib.toDataURL(qrCodeUrl, {
width: 200,
margin: 1,
color: {
dark: '#000000',
light: '#FFFFFF',
},
})
.then((url) => {
setQrCodeDataUrl(url);
})
.catch((err) => {
console.error('QR generation error:', err);
});
}
}, [qrCodeUrl]);
// Convert social icons to images
useEffect(() => {
if (qrCodeUrl && socialIconsRef.current) {
const convertIconsToImages = async () => {
try {
const icons = socialIconsRef.current.querySelectorAll(
'.social-icon-container'
);
const dataUrls = [];
for (let icon of icons) {
const svg = icon.querySelector('svg');
if (svg) {
const svgData = new XMLSerializer().serializeToString(svg);
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
canvas.width = 16;
canvas.height = 16;
await new Promise((resolve) => {
img.onload = () => {
ctx.fillStyle = '#0f1724';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'destination-in';
ctx.drawImage(img, 0, 0, 16, 16);
const dataUrl = canvas.toDataURL('image/png');
dataUrls.push(dataUrl);
resolve();
};
img.src =
'data:image/svg+xml;base64,' +
btoa(unescape(encodeURIComponent(svgData)));
});
}
}
setSocialIconsDataUrls(dataUrls);
} catch (err) {
console.error('Icon conversion error:', err);
}
};
setTimeout(convertIconsToImages, 100);
}
}, [qrCodeUrl]);
const HandleSubmit = async () => {
try {
let data = { BranchId, CompId, AppId, UserId };
const encrypted = encryptObject(data);
const postData = { Url: encrypted, CreatedBy: UserId };
const res = await dispatch(PublicQrCodepost(postData)).unwrap();
if (res?.data?.statusCode === 1) {
const QrUrl = `${subDirectory}MenuQRCode?code=${res?.data?.ReferenceNo}`;
setQrCodeUrl(QrUrl);
setMessageType('success');
setMessageData('QR Code generated.');
} else {
setMessageType('error');
setMessageData(res?.data?.message || 'Failed to generate QR.');
}
} catch (error) {
console.error('HandleSubmit Error:', error);
setMessageType('error');
setMessageData('An unexpected error occurred.');
}
};
const onComplete = useCallback(() => {
setMessageData(null);
setMessageType(null);
}, []);
// const clearData = () => {
// setQrCodeUrl("");
// setQrCodeDataUrl("");
// setTableId(null);
// if (pdfPreviewUrl) {
// URL.revokeObjectURL(pdfPreviewUrl);
// setPdfPreviewUrl("");
// }
// };
const generatePdfFromCard = async (openPreview = true) => {
try {
if (!qrCodeDataUrl || socialIconsDataUrls.length < 4) {
setMessageType('error');
setMessageData('QR Code and icons not ready. Please wait a moment.');
return;
}
const pdf = new jsPDF({
orientation: 'portrait',
unit: 'pt',
format: 'a4',
});
const pageWidth = pdf.internal.pageSize.getWidth();
const pageHeight = pdf.internal.pageSize.getHeight();
// Card dimensions
const cardWidth = 350;
const cardX = (pageWidth - cardWidth) / 2;
let yPos = 80;
// Background card
pdf.setFillColor(255, 255, 255);
pdf.roundedRect(cardX, yPos, cardWidth, 500, 8, 8, 'F');
// Shadow effect
pdf.setDrawColor(0, 0, 0);
pdf.setLineWidth(0.5);
pdf.setGState(pdf.GState({ opacity: 0.1 }));
pdf.roundedRect(cardX, yPos, cardWidth, 500, 8, 8, 'S');
pdf.setGState(pdf.GState({ opacity: 1 }));
yPos += 40;
// Title
pdf.setFontSize(16);
pdf.setFont('helvetica', 'bold');
pdf.setTextColor(15, 23, 36);
pdf.text('Scan the QR Code', pageWidth / 2, yPos, { align: 'center' });
yPos += 40;
// QR Code background frame
const qrSize = 200;
const qrX = (pageWidth - qrSize) / 2;
// Outer decorative frame
pdf.setFillColor(255, 255, 255);
pdf.roundedRect(
qrX - 20,
yPos - 20,
qrSize + 40,
qrSize + 40,
12,
12,
'F'
);
// Shadow for QR frame
pdf.setDrawColor(0, 0, 0);
pdf.setGState(pdf.GState({ opacity: 0.06 }));
pdf.roundedRect(
qrX - 20,
yPos - 20,
qrSize + 40,
qrSize + 40,
12,
12,
'S'
);
pdf.setGState(pdf.GState({ opacity: 1 }));
// Inner white background for QR
pdf.setFillColor(255, 255, 255);
pdf.roundedRect(qrX - 8, yPos - 8, qrSize + 16, qrSize + 16, 8, 8, 'F');
// Add QR Code image
pdf.addImage(qrCodeDataUrl, 'PNG', qrX, yPos, qrSize, qrSize);
yPos += qrSize + 40;
// MENU button
const buttonWidth = 120;
const buttonHeight = 45;
const buttonX = (pageWidth - buttonWidth) / 2;
pdf.setFillColor(15, 107, 117);
pdf.roundedRect(buttonX, yPos, buttonWidth, buttonHeight, 8, 8, 'F');
pdf.setFontSize(22);
pdf.setFont('helvetica', 'bold');
pdf.setTextColor(255, 255, 255);
pdf.text('MENU', pageWidth / 2, yPos + 30, { align: 'center' });
yPos += buttonHeight + 30;
// Social icons with actual images
const iconSize = 36;
const iconSpacing = 18;
const totalIconsWidth = iconSize * 4 + iconSpacing * 3;
let iconX = (pageWidth - totalIconsWidth) / 2;
pdf.setFillColor(255, 255, 255);
pdf.setDrawColor(230, 238, 240);
pdf.setLineWidth(1);
socialIconsDataUrls.forEach((iconDataUrl) => {
// Draw circle background
pdf.circle(iconX + iconSize / 2, yPos, iconSize / 2, 'FD');
// Draw icon image centered in circle
const iconImageSize = 16;
const iconImageX = iconX + (iconSize - iconImageSize) / 2;
const iconImageY = yPos - iconImageSize / 2;
pdf.addImage(
iconDataUrl,
'PNG',
iconImageX,
iconImageY,
iconImageSize,
iconImageSize
);
iconX += iconSize + iconSpacing;
});
yPos += 50;
// URL text at bottom
pdf.setFontSize(10);
pdf.setTextColor(156, 163, 175);
const urlText =
qrCodeUrl.length > 50 ? qrCodeUrl.substring(0, 47) + '...' : qrCodeUrl;
pdf.text(urlText, pageWidth / 2, yPos, { align: 'center' });
// Create blob for preview
const pdfBlob = pdf.output('blob');
const blobUrl = URL.createObjectURL(pdfBlob);
if (pdfPreviewUrl) {
URL.revokeObjectURL(pdfPreviewUrl);
}
setPdfPreviewUrl(blobUrl);
if (openPreview) {
setIsPreviewModalOpen(true);
} else {
pdf.save('menu-qr.pdf');
}
} catch (err) {
console.error('PDF generation failed', err);
setMessageType('error');
setMessageData('Failed to generate PDF.');
}
};
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);
});
};
return (
<div className="userPageTable">
<div className="userPageContent">
<Messages
messageType={messageType}
messageData={messageData}
onComplete={onComplete}
/>
<div className="formAddNew">
<FormHeader title={'Menu QR Code Generator'} />
</div>
<div className="reportTables">
{qrCodeUrl && (
<div
ref={socialIconsRef}
style={{ position: 'absolute', left: '-9999px' }}
>
<div className="social-icon-container">
<FaFacebookF size={16} color="#0f1724" />
</div>
<div className="social-icon-container">
<FaWhatsapp size={16} color="#0f1724" />
</div>
<div className="social-icon-container">
<FaEnvelope size={16} color="#0f1724" />
</div>
<div className="social-icon-container">
<FaLinkedinIn size={16} color="#0f1724" />
</div>
</div>
)}
{/* Card Display */}
{qrCodeUrl && qrCodeDataUrl && (
<div
ref={cardRef}
id="qr-export-card"
style={{
width: 420,
margin: '0 auto',
padding: 24,
2026-01-27 18:27:29 +05:30
background: '#fff',
borderRadius: 12,
boxShadow: '0 6px 18px rgba(0,0,0,0.08)',
textAlign: 'center',
color: '#0f1724',
border: '1px solid grey',
}}
>
<h3
style={{
fontSize: 16,
marginBottom: 12,
2026-01-27 18:27:29 +05:30
fontWeight: 500,
fontFamily: 'Poppins',
}}
>
Scan the QR Code
</h3>
{/* QR with decorative rounded frame */}
<div
style={{
display: 'inline-block',
padding: 18,
background: '#fff',
borderRadius: 18,
boxShadow: '0 2px 10px rgba(0,0,0,0.06)',
position: 'relative',
}}
>
<div
style={{
position: 'absolute',
inset: 6,
borderRadius: 18,
border: '6px solid transparent',
boxShadow: '0 0 0 6px #ffffff inset',
pointerEvents: 'none',
}}
/>
<div
style={{ background: '#fff', padding: 6, borderRadius: 12 }}
>
<img
src={qrCodeDataUrl}
alt="QR Code"
width={200}
height={200}
style={{ display: 'block', imageRendering: 'crisp-edges' }}
/>
</div>
</div>
<p
style={{
marginTop: 18,
color: '#67696d',
fontSize: 12,
wordBreak: 'break-all',
}}
>
{qrCodeUrl}
</p>
</div>
)}
{qrCodeUrl && <PublicQrCodeShare qrCodeUrl={qrCodeUrl} />}
<div
style={{
display: 'flex',
gap: 12,
justifyContent: 'center',
marginTop: 8,
}}
>
{/* {qrCodeUrl && (
<Buttons buttonText="Generate QR" className="primary_Button" handleSubmit={HandleSubmit}>
Generate
</Buttons>
)} */}
{qrCodeUrl && (
<>
<Buttons
buttonText="Preview PDF"
className="primary_Button"
handleSubmit={() => generatePdfFromCard(true)}
disabled={!qrCodeDataUrl}
icon={<BsFileEarmarkPdf size={20} />}
>
Preview PDF
</Buttons>
<Buttons
buttonText="Download PDF"
className="primary_Button"
handleSubmit={() => generatePdfFromCard(false)}
disabled={!qrCodeDataUrl}
icon={<LuDownload size={20} />}
>
Download PDF
</Buttons>
</>
)}
</div>
</div>
{/* Modal PDF Preview */}
<Modal
title="PDF Preview"
open={isPreviewModalOpen}
onCancel={() => {
setIsPreviewModalOpen(false);
}}
footer={[
<Buttons
key="download"
buttonText="Download"
className="primary_Button"
handleSubmit={downloadPreviewPdf}
>
Download
</Buttons>,
<Buttons
key="close"
buttonText="Close"
className="secondary_Button"
handleSubmit={() => setIsPreviewModalOpen(false)}
>
Close
</Buttons>,
]}
width={900}
style={{ top: 20 }}
>
{pdfPreviewUrl ? (
<div style={{ textAlign: 'center' }}>
<iframe
src={pdfPreviewUrl}
width="100%"
height="600px"
style={{ border: 'none', borderRadius: 4 }}
title="PDF Preview"
/>
</div>
) : (
<div style={{ textAlign: 'center', padding: 40 }}>
Preparing preview...
</div>
)}
</Modal>
</div>
</div>
);
};
export default MenuQRCode;