`;
summaryHtml += `
Factor Allocations:
`;
summaryHtml += '
| Smart Beta Factor | Allocation (%) | Allocated Value ($) |
';
selectedFactorsWithAllocation.forEach(factor => {
summaryHtml += `
| ${factor.name} |
${factor.allocation.toFixed(2)}% |
${this.formatCurrency(this.investmentAmount * (factor.allocation / 100))} |
`;
});
summaryHtml += '
';
summaryHtml += `
Factor Descriptions:
`;
selectedFactorsWithAllocation.forEach(factor => {
summaryHtml += `- ${factor.name}: ${factor.description}
`;
});
summaryHtml += '
';
this.portfolioSummaryContainerEl.innerHTML = summaryHtml;
},
showTab: function(tabIndex) {
if (!this.tabs || this.tabs.length === 0 || !this.tabButtons || this.tabButtons.length === 0) return;
if (tabIndex < 0 || tabIndex >= this.tabs.length) return;
// Validation before leaving Tab 2 (Factor Selection & Allocation)
if (this.currentTab === 1 && tabIndex > 1) { // Moving from Tab 2 to Tab 3
const selectedFactors = this.smartBetaFactors.filter(f => f.selected);
if (selectedFactors.length > 0 && !this.validateAndDisplayTotalAllocation()) {
alert("Total factor allocation must sum to 100%. Please adjust allocations.");
return; // Stay on Tab 2
}
if (selectedFactors.length === 0 && this.investmentAmount > 0) {
if (!confirm("You haven't selected or allocated to any smart beta factors. Do you want to proceed to the summary?")) {
return;
}
}
}
this.tabs.forEach(tab => tab.classList.remove('active'));
this.tabButtons.forEach(button => button.classList.remove('active'));
if(this.tabs[tabIndex]) this.tabs[tabIndex].classList.add('active');
if(this.tabButtons[tabIndex]) this.tabButtons[tabIndex].classList.add('active');
this.currentTab = tabIndex;
this.updateNavButtons();
if (tabIndex === 1) { // Entering Factor Selection / Allocation Tab
this.toggleAllocationSectionVisibility(); // Ensure visibility based on current selections
this.renderAllocationTable(); // Render table if factors already selected
} else if (tabIndex === 2) { // Entering Summary Tab
this.renderPortfolioSummary();
if(this.pdfButtonContainerEl) this.pdfButtonContainerEl.style.display = 'block';
} else {
if(this.pdfButtonContainerEl) this.pdfButtonContainerEl.style.display = 'none';
}
},
navigateTabs: function(direction) {
const newTabIndex = this.currentTab + direction;
this.showTab(newTabIndex);
},
updateNavButtons: function() {
if(this.prevButtonEl) this.prevButtonEl.style.display = (this.currentTab === 0) ? 'none' : 'inline-block';
if(this.nextButtonEl) this.nextButtonEl.style.display = (this.currentTab === (this.tabs.length - 1) || this.tabs.length === 0) ? 'none' : 'inline-block';
},
formatCurrency: function(value) {
if (isNaN(value) || value === null) value = 0;
return value.toLocaleString('en-US', { style: 'currency', currency: 'USD' });
},
getCssVariableValue(variableName) {
try {
const colorStr = getComputedStyle(document.documentElement).getPropertyValue(variableName).trim();
if (colorStr.startsWith('#')) {
let hex = colorStr.substring(1);
if (hex.length === 3) hex = hex.split('').map(char => char + char).join('');
const bigint = parseInt(hex, 16);
return [(bigint >> 16) & 255, (bigint >> 8) & 255, bigint & 255];
} else if (colorStr.startsWith('rgb')) {
return colorStr.match(/\d+/g).map(Number);
}
} catch (e) { console.warn("Could not parse CSS var for PDF:", variableName, e); }
return [44, 62, 80]; // Fallback to --primary-color-sbpb if parsing fails
},
generatePdf: function() {
if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') {
alert('jsPDF library not loaded.'); console.error('window.jspdf or window.jspdf.jsPDF not defined.'); return;
}
const JsPDFConstructor = window.jspdf.jsPDF;
let doc;
try { doc = new JsPDFConstructor(); } catch (e) {
alert('Failed to create PDF instance.'); console.error('Error instantiating jsPDF:', e); return;
}
if (typeof doc.autoTable !== 'function') {
alert('jsPDF-AutoTable plugin not loaded.'); console.error('doc.autoTable not a function.'); return;
}
const riskEl = this.riskToleranceEl;
const horizonEl = this.investmentHorizonEl;
if (!riskEl || !horizonEl) { alert("Profile elements missing."); return; }
doc.setFontSize(18); doc.text("Smart Beta Portfolio Report", 14, 22);
doc.setFontSize(11); doc.setTextColor(100);
doc.text(`Report Date: ${new Date().toLocaleDateString()}`, 14, 30);
let yPos = 40;
doc.setFontSize(14); doc.text("Investment Profile", 14, yPos); yPos += 7;
doc.setFontSize(10);
doc.text(`Risk Tolerance: ${riskEl.options[riskEl.selectedIndex].text}`, 14, yPos); yPos += 6;
doc.text(`Investment Horizon: ${horizonEl.options[horizonEl.selectedIndex].text}`, 14, yPos); yPos += 6;
doc.text(`Total Investment Amount: ${this.formatCurrency(this.investmentAmount)}`, 14, yPos); yPos += 10;
const allocatedFactors = this.smartBetaFactors.filter(f => f.selected && f.allocation > 0);
if (allocatedFactors.length > 0) {
doc.setFontSize(14); doc.text("Factor Allocations", 14, yPos); yPos += 2;
const portfolioData = allocatedFactors.map(f => [
f.name, `${f.allocation.toFixed(2)}%`, this.formatCurrency(this.investmentAmount * (f.allocation / 100))
]);
doc.autoTable({
startY: yPos,
head: [['Smart Beta Factor', 'Allocation (%)', 'Allocated Value ($)']],
body: portfolioData,
theme: 'striped',
headStyles: { fillColor: this.getCssVariableValue('--primary-color-sbpb') }
});
yPos = doc.lastAutoTable.finalY + 10;
doc.setFontSize(14); doc.text("Factor Descriptions", 14, yPos); yPos += 7;
doc.setFontSize(9);
allocatedFactors.forEach(factor => {
if (yPos > 260) { doc.addPage(); yPos = 20; } // Add new page if content overflows
doc.setFont(undefined, 'bold');
doc.text(factor.name + ":", 14, yPos);
doc.setFont(undefined, 'normal');
const descLines = doc.splitTextToSize(factor.description, 180);
doc.text(descLines, 14 + (doc.getStringUnitWidth(factor.name + ": ") * doc.getFontSize() / doc.internal.scaleFactor) + 2 , yPos); // Attempt to align after bold title
yPos += (descLines.length * 4) + 3; // Adjust spacing
});
} else {
doc.setFontSize(10); doc.text("No smart beta factors were allocated in this portfolio.", 14, yPos); yPos += 10;
}
doc.setFontSize(9); doc.setTextColor(150);
const disclaimer = "This report is based on user-provided data and illustrative models. It is not financial advice. Consult a qualified financial advisor for investment decisions.";
if (yPos > 265) { doc.addPage(); yPos = 20; }
const disclaimerLines = doc.splitTextToSize(disclaimer, 180);
doc.text(disclaimerLines, 14, yPos);
doc.save(`Smart_Beta_Portfolio_${new Date().toISOString().slice(0,10)}.pdf`);
}
};
sbpbApp.init();
window.sbpbApp = sbpbApp; // For debugging
});