${this.formatCurrency(totals.valueRecovered)}
Total Recovered Revenue
${abandonmentRate.toFixed(1)}%
Cart Abandonment Rate
${recoveryRate.toFixed(1)}%
Cart Recovery Rate
`;
}
renderFunnel(totals) {
this.dom.funnelContainer.innerHTML = `
${totals.created.toLocaleString()}
Carts Created
${totals.abandoned.toLocaleString()}
Carts Abandoned
${totals.recovered.toLocaleString()}
Carts Recovered
`;
}
renderBarChart() {
const maxValue = Math.max(...this.state.records.map(r => r.valueAbandoned));
if (maxValue === 0) {
this.dom.barChartContainer.innerHTML = '
No value data to display.
';
return;
}
const chartHtml = this.state.records.map(r => `
${this.formatCurrency(r.valueAbandoned, true)}
${this.formatCurrency(r.valueRecovered, true)}
${this.escapeHTML(r.name)}
`).join('');
this.dom.barChartContainer.innerHTML = `
${chartHtml}
`;
}
renderPieChart(totals) {
if (totals.valueRecovered === 0) {
this.dom.pieChartContainer.innerHTML = '
No recovered value to display.
';
return;
}
const pieColors = ['#4A90E2', '#50E3C2', '#B8E986', '#F8E71C', '#F5A623', '#BD10E0', '#9013FE', '#4A4A4A'];
let gradientString = 'conic-gradient(';
let currentPercentage = 0;
const legendItems = this.state.records.map((r, index) => {
const percentage = (r.valueRecovered / totals.valueRecovered) * 100;
const color = pieColors[index % pieColors.length];
if (percentage > 0) {
gradientString += `${color} ${currentPercentage}% ${currentPercentage + percentage}%, `;
currentPercentage += percentage;
}
return `
${this.escapeHTML(r.name)} (${percentage.toFixed(1)}%)`;
}).join('');
gradientString = gradientString.slice(0, -2) + ')'; // Remove last comma and space
this.dom.pieChartContainer.innerHTML = `
`;
}
// --- Utilities ---
formatCurrency(value, compact = false) {
const options = { style: 'currency', currency: 'USD', currencyDisplay: 'narrowSymbol' };
if (compact) {
options.notation = 'compact';
options.maximumFractionDigits = 1;
} else {
options.minimumFractionDigits = 2;
options.maximumFractionDigits = 2;
}
// Override currency symbol with user input
return new Intl.NumberFormat('en-US', options).format(value).replace('$', this.state.currency);
}
escapeHTML(str) {
const p = document.createElement('p');
p.appendChild(document.createTextNode(str));
return p.innerHTML;
}
openTab(event, tabName) {
this.state.activeTab = tabName;
this.updateUI();
}
navigateTabs(direction) {
const currentIndex = this.TABS_ORDER.indexOf(this.state.activeTab);
let newIndex = currentIndex;
if (direction === 'next' && currentIndex < this.TABS_ORDER.length - 1) newIndex++;
if (direction === 'prev' && currentIndex > 0) newIndex--;
this.state.activeTab = this.TABS_ORDER[newIndex];
this.updateUI();
}
// --- PDF Generation ---
async generatePdf() {
// Ensure dashboard is rendered before capture
if(this.state.activeTab !== 'dashboard') {
this.state.activeTab = 'dashboard';
this.updateUI();
// Wait for render
await new Promise(resolve => setTimeout(resolve, 50));
}
const { jsPDF } = window.jspdf;
const pdfOutputElement = this.dom.pdfOutput;
this.dom.downloadPdfBtn.disabled = true;
this.dom.downloadPdfBtn.textContent = 'Generating PDF...';
try {
const canvas = await html2canvas(pdfOutputElement, { scale: 2, useCORS: true, logging: false, backgroundColor: '#ffffff' });
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({ orientation: 'portrait', unit: 'pt', format: 'a4' });
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = pdf.internal.pageSize.getHeight();
const canvasAspectRatio = canvas.width / canvas.height;
const margin = 40;
const imgWidth = pdfWidth - (margin * 2);
const imgHeight = imgWidth / canvasAspectRatio;
let heightLeft = imgHeight;
let position = margin;
pdf.addImage(imgData, 'PNG', margin, position, imgWidth, imgHeight);
heightLeft -= (pdfHeight - margin * 2);
while (heightLeft > 0) {
position = -heightLeft - margin;
pdf.addPage();
pdf.addImage(imgData, 'PNG', margin, position, imgWidth, imgHeight);
heightLeft -= pdfHeight;
}
const fileName = `${this.state.dashboardTitle.replace(/\s+/g, '_')}.pdf`;
pdf.save(fileName);
} catch (error) {
console.error("PDF Generation Error:", error);
alert("An error occurred while creating the PDF.");
} finally {
this.dom.downloadPdfBtn.disabled = false;
this.dom.downloadPdfBtn.textContent = 'Download Dashboard as PDF';
}
}
}
const app = new CartRecoveryDashboard();