No providers configured. Please add one in the Provider Settings tab.
`;
document.getElementById('chart-container').style.display = 'none';
return;
}
const sortedResults = results.sort((a, b) => b.recipientGets - a.recipientGets);
const bestResult = sortedResults[0];
sortedResults.forEach(res => {
const isBest = res.id === bestResult.id;
const card = `
${res.name}
${isBest ? 'Best Option' : ''}
${formatCurrency(res.recipientGets, res.currency)}
Total Cost${formatUSD(res.totalCost)}
Exchange Rate1 USD = ${res.effectiveRate.toFixed(4)} ${res.currency}
Transfer Fees${formatUSD(res.totalFees)}
"Hidden" Fee${formatUSD(res.markupCost)}
`;
container.insertAdjacentHTML('beforeend', card);
});
renderChart(sortedResults);
}
function renderChart(results) {
const chartContainer = document.getElementById('chart-container');
chartContainer.style.display = 'block';
const ctx = document.getElementById('cost-comparison-chart').getContext('2d');
const labels = results.map(r => r.name);
const data = results.map(r => r.totalCost);
if(comparisonChart) comparisonChart.destroy();
comparisonChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Total Cost ($)',
data: data,
backgroundColor: '#0047bb',
borderColor: '#003b9c',
borderWidth: 1
}]
},
options: {
responsive: true,
plugins: {
legend: { display: false },
title: { display: true, text: 'Total Cost of Transfer Comparison' }
},
scales: {
y: { beginAtZero: true, title: { display: true, text: 'Cost in USD ($)' } }
}
}
});
}
// --- CORE LOGIC ---
function runCalculation() {
const sendAmount = parseFloat(document.getElementById('send-amount').value) || 0;
const currency = currencySelect.value;
const baseRate = midMarketRates[currency].rate;
const results = providers.map(p => {
const percentageFeeAmount = sendAmount * (p.percentageFee / 100);
const totalFees = p.fixedFee + percentageFeeAmount;
const amountToConvert = sendAmount - totalFees;
const effectiveRate = baseRate * (1 - (p.rateMarkup / 100));
const recipientGets = amountToConvert * effectiveRate;
const midMarketRecipientGets = sendAmount * baseRate;
const providerRecipientGetsAfterFees = (sendAmount - totalFees) * baseRate;
const markupCost = providerRecipientGetsAfterFees - recipientGets;
const totalCost = totalFees + (markupCost / baseRate); // Convert markup cost back to USD
return {
id: p.id,
name: p.name,
sendAmount,
currency,
totalFees,
effectiveRate,
recipientGets,
markupCost: markupCost / baseRate,
totalCost
};
});
renderResults(results);
}
function resetForm() {
providerForm.reset();
editingProviderId = null;
formTitle.textContent = 'Add New Provider';
saveBtn.textContent = 'Save Provider';
cancelEditBtn.style.display = 'none';
}
function setupEditForm(id) {
const provider = providers.find(p => p.id === id);
if (!provider) return;
document.getElementById('provider-name').value = provider.name;
document.getElementById('fixed-fee').value = provider.fixedFee;
document.getElementById('percentage-fee').value = provider.percentageFee;
document.getElementById('rate-markup').value = provider.rateMarkup;
editingProviderId = id;
formTitle.textContent = 'Edit Provider';
saveBtn.textContent = 'Update Provider';
cancelEditBtn.style.display = 'inline-block';
}
function deleteProvider(id) {
if (confirm('Are you sure you want to delete this provider?')) {
providers = providers.filter(p => p.id !== id);
renderProviders();
}
}
// --- EVENT HANDLERS ---
calculateBtn.addEventListener('click', runCalculation);
providerForm.addEventListener('submit', (e) => {
e.preventDefault();
const providerData = {
name: document.getElementById('provider-name').value,
fixedFee: parseFloat(document.getElementById('fixed-fee').value) || 0,
percentageFee: parseFloat(document.getElementById('percentage-fee').value) || 0,
rateMarkup: parseFloat(document.getElementById('rate-markup').value) || 0,
};
if (editingProviderId) {
const provider = providers.find(p => p.id === editingProviderId);
Object.assign(provider, providerData);
} else {
providerData.id = Date.now();
providers.push(providerData);
}
resetForm();
renderProviders();
});
cancelEditBtn.addEventListener('click', resetForm);
document.getElementById('provider-list').addEventListener('click', (e) => {
const button = e.target.closest('.action-btn');
if (!button) return;
const action = button.dataset.action;
const id = parseInt(button.dataset.id);
if (action === 'edit') setupEditForm(id);
if (action === 'delete') deleteProvider(id);
});
downloadPdfBtn.addEventListener('click', generatePdf);
// --- TABS & NAVIGATION ---
window.switchTab = (tabName) => {
currentTab = tabName;
Object.values(tabBtns).forEach(btn => btn.classList.replace('tab-active', 'tab-inactive'));
Object.values(tabContents).forEach(content => content.style.display = 'none');
tabBtns[tabName].classList.replace('tab-inactive', 'tab-active');
tabContents[tabName].style.display = 'block';
};
window.navigateTabs = (direction) => {
if (direction === 'next' && currentTab === 'calculator') switchTab('settings');
else if (direction === 'prev' && currentTab === 'settings') switchTab('calculator');
};
// --- PDF GENERATION ---
async function generatePdf() {
const { jsPDF } = window.jspdf;
const pdfReportElement = document.getElementById('pdf-report');
const results = Array.from(document.getElementById('results-container').children).map(card => {
// This is a simplified data extraction. For a real app, you'd re-run calculations or store results.
const name = card.querySelector('h3').textContent;
const totalCostText = card.querySelector('div:nth-child(2) > div:nth-child(1) strong').textContent;
const rateText = card.querySelector('div:nth-child(2) > div:nth-child(2) strong').textContent;
const recipientGetsText = card.querySelector('.text-2xl').textContent;
return { name, totalCostText, rateText, recipientGetsText, totalCost: parseFloat(totalCostText.replace(/[^0-9.-]+/g,"")) };
}).sort((a,b) => b.totalCost - a.totalCost);
const bestProvider = results.reduce((prev, current) => (prev.totalCost < current.totalCost) ? prev : current);
const worstProvider = results[0];
// Populate PDF data
document.getElementById('pdf-date').textContent = new Date().toLocaleDateString('en-US');
document.getElementById('pdf-send-amount').textContent = formatUSD(parseFloat(document.getElementById('send-amount').value));
document.getElementById('pdf-recipient-currency').textContent = currencySelect.value;
document.getElementById('pdf-best-provider').textContent = bestProvider.name;
document.getElementById('pdf-highest-amount').textContent = bestProvider.recipientGetsText;
document.getElementById('pdf-savings').textContent = formatUSD(worstProvider.totalCost - bestProvider.totalCost);
const tableBody = document.getElementById('pdf-table-body');
tableBody.innerHTML = '';
results.reverse().forEach(res => {
const row = `
| ${res.name} |
${res.totalCostText} |
${res.rateText} |
${res.recipientGetsText} |
`;
tableBody.insertAdjacentHTML('beforeend', row);
});
const chartImg = comparisonChart.toBase64Image();
document.getElementById('pdf-chart-image').src = chartImg;
const canvas = await html2canvas(pdfReportElement, { scale: 2, useCORS: true });
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF('p', 'mm', 'a4');
const pdfWidth = pdf.internal.pageSize.getWidth();
const pdfHeight = (canvas.height * pdfWidth) / canvas.width;
pdf.addImage(imgData, 'PNG', 0, 0, pdfWidth, pdfHeight);
pdf.save('Cross-Border-Payment-Analysis.pdf');
}
// --- INITIALIZATION ---
function init() {
// Populate currency dropdown
Object.keys(midMarketRates).forEach(code => {
const option = document.createElement('option');
option.value = code;
option.textContent = code;
currencySelect.appendChild(option);
});
// Load sample providers
providers = [
{ id: 1, name: 'Bank Transfer (USA)', fixedFee: 25, percentageFee: 1, rateMarkup: 2.5 },
{ id: 2, name: 'Wise (formerly TransferWise)', fixedFee: 3.5, percentageFee: 0.4, rateMarkup: 0.1 },
{ id: 3, name: 'PayPal International', fixedFee: 4.99, percentageFee: 2.9, rateMarkup: 3.0 }
];
renderProviders();
runCalculation(); // Initial calculation on load
}
init();
});