My NFT Portfolio
| Name/ID |
Collection |
Purchase Price |
Purchase Date |
Current Value |
P/L ($) |
Return (%) |
Marketplace |
Actions |
${tableRowsHtml}
Portfolio Summary
Total Investment:
${formatCurrency(totalInvestment)}
Total Current Portfolio Value:
${formatCurrency(totalCurrentValue)}
Total Profit/Loss:
${formatCurrency(totalProfitLoss)}
Overall Portfolio Return:
${overallReturnPercentage.toFixed(2)}%
Note: All values are based on user input. "Current Est. Value" is subjective and should be updated regularly for accuracy.
`;
}
async function generatePerformancePdf() {
const pdfContent = document.getElementById('pdf-content');
const downloadBtn = event.target;
if (!pdfContent) {
console.error("PDF content area not found.");
if(typeof alert === 'function') alert("Error: Could not find content to generate PDF.");
return;
}
if (!html2canvas || !jsPDF) {
console.error("html2canvas or jsPDF library not found.");
if(typeof alert === 'function') alert("Error: PDF generation library not loaded.");
return;
}
const originalButtonText = downloadBtn.textContent;
downloadBtn.textContent = "Generating...";
downloadBtn.disabled = true;
const pdfHeaderElement = pdfContent.querySelector('.pdf-header');
const screenHeaderElements = pdfContent.querySelectorAll('.screen-only');
if (pdfHeaderElement) pdfHeaderElement.style.display = 'block'; // Show for PDF capture
screenHeaderElements.forEach(el => el.style.display = 'none');
try {
const canvas = await html2canvas(pdfContent, {
scale: 1.5,
useCORS: true,
logging: false,
onclone: (doc) => {
// Ensure elements are styled correctly in the cloned document for PDF
const clonedHeader = doc.querySelector('.pdf-header');
if (clonedHeader) clonedHeader.style.display = 'block';
doc.querySelectorAll('.screen-only').forEach(el => el.style.display = 'none');
// Remove action buttons column from table in clone
const actionsHeader = doc.querySelector('.portfolio-table th:last-child.no-print');
const actionsCells = doc.querySelectorAll('.portfolio-table td:last-child .btn-danger');
if(actionsHeader) actionsHeader.style.display = 'none';
actionsCells.forEach(cell => {
if(cell.parentElement) cell.parentElement.style.display = 'none';
});
}
});
// Restore display for on-screen view
if (pdfHeaderElement) pdfHeaderElement.style.display = 'none';
screenHeaderElements.forEach(el => el.style.display = 'block');
const imgData = canvas.toDataURL('image/jpeg', 0.8);
const pdf = new jsPDF({
orientation: 'l', // Landscape for wider table
unit: 'mm',
format: 'a4'
});
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
const imgProps = pdf.getImageProperties(imgData);
const margin = 10;
const contentWidth = pdfWidth - 2 * margin;
let contentHeight = (imgProps.height * contentWidth) / imgProps.width;
let currentPosition = margin;
if (contentHeight <= pdfHeight - 2 * margin) {
pdf.addImage(imgData, 'JPEG', margin, currentPosition, contentWidth, contentHeight);
} else {
let remainingImgHeight = imgProps.height;
let sourceY = 0;
const sourceWidth = imgProps.width;
const pageCanvasHeight = ((pdfHeight - 2 * margin) / contentWidth) * sourceWidth;
while (remainingImgHeight > 0) {
const currentChunkHeight = Math.min(remainingImgHeight, pageCanvasHeight);
const tempCanvas = document.createElement('canvas');
tempCanvas.width = sourceWidth;
tempCanvas.height = currentChunkHeight;
const tempCtx = tempCanvas.getContext('2d');
tempCtx.drawImage(canvas, 0, sourceY, sourceWidth, currentChunkHeight, 0, 0, sourceWidth, currentChunkHeight);
const chunkImgData = tempCanvas.toDataURL('image/jpeg', 0.8);
const chunkDisplayHeight = (currentChunkHeight * contentWidth) / sourceWidth;
pdf.addImage(chunkImgData, 'JPEG', margin, currentPosition, contentWidth, chunkDisplayHeight);
remainingImgHeight -= currentChunkHeight;
sourceY += currentChunkHeight;
if (remainingImgHeight > 0) {
pdf.addPage();
currentPosition = margin;
}
}
}
pdf.save(`NFT_Portfolio_Performance.pdf`);
} catch (error) {
console.error("Error generating PDF:", error);
if(typeof alert === 'function') alert("An error occurred while generating the PDF: " + error.message);
} finally {
downloadBtn.textContent = originalButtonText;
downloadBtn.disabled = false;
if (pdfHeaderElement) pdfHeaderElement.style.display = 'none';
screenHeaderElements.forEach(el => el.style.display = 'block');
}
}
// Initial render in case there's saved data later or for empty state
document.addEventListener('DOMContentLoaded', renderPortfolio);