Credit Card Interest Cost Tracker

Global Settings

Add Credit Card

Card Name Current Balance APR (%) Planned Payment Action

Enter your card details and planned payments on the previous tab, then click "Track Interest Costs" to see your summary here.

Planned Monthly Payment: ${formatCurrency(card.payment)}

Est. Interest Accrued (${period} mo): ${formatCurrency(card.accruedInterest)}

Total Payments Made (${period} mo): ${formatCurrency(card.paymentsMadeThisCard)}

Est. Ending Balance: ${formatCurrency(card.endingBalance)}

`; breakdownDiv.appendChild(cardDiv); }); renderInterestDistributionChart(cardResults); } function renderInterestDistributionChart(cardResults) { const canvas = document.getElementById('ccit-interest-distribution-chart'); if (!canvas) { console.error("Interest distribution chart canvas not found."); return; } const ctx = canvas.getContext('2d'); if (interestDistributionChart) { interestDistributionChart.destroy(); } const labels = cardResults.map(card => card.name); const data = cardResults.map(card => card.accruedInterest); const backgroundColors = labels.map((_, index) => { const colorPalette = ['#E74C3C', '#3498DB', '#F1C40F', '#2ECC71', '#9B59B6', '#E67E22', '#1ABC9C', '#34495E']; return colorPalette[index % colorPalette.length]; }); if (data.every(val => val === 0) && labels.length > 0) { ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); ctx.font = "16px Arial"; ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue('--text-color').trim(); ctx.textAlign = "center"; ctx.fillText("No significant interest accrued.", ctx.canvas.width / 2, ctx.canvas.height / 2); return; } if (labels.length === 0){ ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); return; } interestDistributionChart = new Chart(ctx, { type: 'pie', data: { labels: labels, datasets: [{ label: 'Interest Accrued', data: data, backgroundColor: backgroundColors, borderColor: getComputedStyle(document.documentElement).getPropertyValue('--content-background').trim(), borderWidth: 2 }] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom', labels: { color: getComputedStyle(document.documentElement).getPropertyValue('--text-color').trim()}}, tooltip: { callbacks: { label: tooltipCtx => `${tooltipCtx.label}: ${formatCurrency(tooltipCtx.raw)}` } }, title: { display: true, text: `Interest Distribution Over ${document.getElementById('ccit-tracking-period').value} Months`, font:{size:14}, color: toolPrimaryColor } } } }); } const downloadPdfBtn = document.getElementById('ccit-download-pdf-btn'); if (downloadPdfBtn) { downloadPdfBtn.addEventListener('click', async () => { const { jsPDF } = window.jspdf; const doc = new jsPDF(); let currentY = 20; const pageMargin = 14; const pageWidth = doc.internal.pageSize.getWidth(); doc.setFontSize(18); doc.setTextColor(toolPrimaryColor); doc.text("Credit Card Interest Cost Summary", pageWidth / 2, currentY, { align: 'center' }); currentY += 8; doc.setFontSize(10); doc.setTextColor(getComputedStyle(document.documentElement).getPropertyValue('--text-color').trim()); const trackingPeriodInput = document.getElementById('ccit-tracking-period'); const trackingPeriod = trackingPeriodInput ? trackingPeriodInput.value : 'N/A'; doc.text(`Date: ${new Date().toLocaleDateString()} | Currency: ${currencySymbol} | Tracking Period: ${trackingPeriod} Months`, pageWidth / 2, currentY, { align: 'center' }); currentY += 12; function addSectionTitleToPdf(title, yPos) { // Renamed to avoid conflict if (yPos > 260) { doc.addPage(); return 20; } doc.setFontSize(14); doc.setTextColor(toolPrimaryColor); doc.text(title, pageMargin, yPos); return yPos + 8; } doc.setFontSize(10); currentY = addSectionTitleToPdf("Overall Totals", currentY); const totalInterestEl = document.getElementById('ccit-total-interest-accrued'); const totalPaymentsEl = document.getElementById('ccit-total-payments-made'); const totalInterestText = totalInterestEl ? totalInterestEl.textContent : 'N/A'; const totalPaymentsText = totalPaymentsEl ? totalPaymentsEl.textContent : 'N/A'; doc.text(`Total Estimated Interest Accrued: ${totalInterestText}`, pageMargin, currentY); currentY += 6; doc.text(`Total Payments Made: ${totalPaymentsText}`, pageMargin, currentY); currentY += 10; currentY = addSectionTitleToPdf("Initial Card Setup", currentY); // Use the creditCards array directly as it holds the initial state const initialCardsData = creditCards.map(c => [c.name, formatCurrency(c.originalBalance), `${c.apr.toFixed(2)}%`, formatCurrency(c.payment)]); doc.autoTable({ startY: currentY, head: [['Card Name', 'Initial Balance', 'APR', 'Planned Monthly Payment']], body: initialCardsData, theme: 'grid', headStyles: { fillColor: toolPrimaryColor, textColor: '#FFFFFF' }, styles: { fontSize: 9, cellPadding: 2 }, margin: { left: pageMargin, right: pageMargin }, didDrawPage: (data) => { currentY = data.cursor.y; } }); currentY = doc.lastAutoTable.finalY + 10; currentY = addSectionTitleToPdf("Individual Card Summaries (After Tracking Period)", currentY); const cardResultsForPdf = []; // Re-calculate or fetch from stored results if possible // For simplicity, re-running the core logic to populate cardResultsForPdf for the PDF // This assumes calculateAndDisplayInterestCosts() was the last successful calculation // A better way would be to store results from calculateAndDisplayInterestCosts() and use them here. // For this iteration, I will reconstruct from displayed results if possible or re-run: const resultsContent = document.getElementById('ccit-results-content'); if(resultsContent && resultsContent.style.display !== 'none') { document.querySelectorAll('#ccit-individual-card-summary .ccit-card-summary').forEach(div => { const name = div.querySelector('h5').textContent.split(' (APR:')[0]; const aprText = div.querySelector('h5').textContent.split('APR: ')[1].replace('%)',''); const initialBalText = div.querySelector('p:nth-child(2) .value').textContent; const plannedPayText = div.querySelector('p:nth-child(3) .value').textContent; const interestAccText = div.querySelector('p:nth-child(4) .value').textContent; const endingBalText = div.querySelector('p:nth-child(6) .value').textContent; cardResultsForPdf.push([name, aprText + '%', initialBalText, plannedPayText, interestAccText, endingBalText]); }); } doc.autoTable({ startY: currentY, head: [['Card Name', 'APR', 'Initial Bal.', 'Planned Mth. Pay.', 'Est. Interest', 'Est. Ending Bal.']], body: cardResultsForPdf, theme: 'grid', headStyles: { fillColor: toolPrimaryColor, textColor: '#FFFFFF' }, styles: { fontSize: 8, cellPadding: 1.5 }, margin: { left: pageMargin, right: pageMargin }, didDrawPage: (data) => { currentY = data.cursor.y; } }); currentY = doc.lastAutoTable.finalY + 10; const chartCanvas = document.getElementById('ccit-interest-distribution-chart'); // Check if chart has meaningful data (not just empty or "no interest accrued" message) let chartHasData = false; if (interestDistributionChart && interestDistributionChart.data.datasets.length > 0 && interestDistributionChart.data.datasets[0].data.some(d => d > 0)) { chartHasData = true; } if (chartHasData && chartCanvas && chartCanvas.offsetParent !== null) { if (currentY > 190) { doc.addPage(); currentY = 20; } currentY = addSectionTitleToPdf("Interest Distribution Chart", currentY); try { const chartImage = await html2canvas(chartCanvas, { backgroundColor: '#FFFFFF', scale: 2 }); doc.addImage(chartImage.toDataURL('image/png'), 'PNG', pageMargin, currentY, pageWidth - (2*pageMargin), 80); } catch (e) { console.error("Error adding chart to PDF:", e); } } doc.save('Credit_Card_Interest_Tracker.pdf'); }); } else { console.error("Download PDF button not found."); } updateTabDisplay(); // Initial call } catch (e) { console.error("An error occurred while initializing the Credit Card Interest Tracker:", e); const errorDisplay = document.getElementById('creditCardInterestTracker'); if(errorDisplay) { errorDisplay.innerHTML = "

A critical error occurred while loading the tool. Please ensure the code is correctly placed in the HTML block and check the browser console for more details.

"; } } });
Scroll to Top