1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* This credit card payoff calculator app allows users to:
* 1. Add and remove multiple credit cards with their balances and interest rates
* 2. Calculate monthly payments based on desired payoff time
* 3. View individual card payoff information
* 4. See a combined view of all cards
* 5. Visualize payoff data with different types of charts
*
* It uses React for the UI, SQLite for data persistence, and Chart.js for visualization.
* The app is now optimized for mobile devices with responsive design.
*/
/** @jsxImportSource https://esm.sh/react */
import React, { useState, useEffect, useRef } from "https://esm.sh/react";
import { createRoot } from "https://esm.sh/react-dom/client";
import Chart from "https://esm.sh/chart.js/auto";
interface CreditCard {
id: number;
name: string;
balance: number;
interestRate: number;
}
type ChartType = 'bar' | 'pie' | 'line';
function App() {
const [cards, setCards] = useState<CreditCard[]>([]);
const [newCard, setNewCard] = useState({ name: '', balance: 0, interestRate: 0 });
const [payoffMonths, setPayoffMonths] = useState(12);
const [selectedCardId, setSelectedCardId] = useState<number | null>(null);
const [chartType, setChartType] = useState<ChartType>('bar');
const chartRef = useRef<Chart | null>(null);
const chartCanvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
fetchCards();
}, []);
useEffect(() => {
if (cards.length > 0) {
updateChart();
}
}, [cards, payoffMonths, selectedCardId, chartType]);
const fetchCards = async () => {
const response = await fetch('/cards');
const data = await response.json();
setCards(data);
};
const addCard = async (e: React.FormEvent) => {
e.preventDefault();
await fetch('/cards', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(newCard),
});
setNewCard({ name: '', balance: 0, interestRate: 0 });
fetchCards();
};
const removeCard = async (id: number) => {
await fetch(`/cards/${id}`, { method: 'DELETE' });
fetchCards();
};
const calculatePayoffDetails = (balance: number, interestRate: number, months: number) => {
const monthlyRate = interestRate / 100 / 12;
const monthlyPayment = (balance * monthlyRate * Math.pow(1 + monthlyRate, months)) / (Math.pow(1 + monthlyRate, months) - 1);
const totalPaid = monthlyPayment * months;
const interestPaid = totalPaid - balance;
return { monthlyPayment, totalPaid, interestPaid };
};
const updateChart = () => {
if (chartRef.current) {
chartRef.current.destroy();
}
const ctx = chartCanvasRef.current?.getContext('2d');
if (!ctx) return;
const selectedCards = selectedCardId ? cards.filter(card => card.id === selectedCardId) : cards;
const labels = selectedCards.map(card => card.name);
const balances = selectedCards.map(card => card.balance);
const interestPaid = selectedCards.map(card =>
calculatePayoffDetails(card.balance, card.interestRate, payoffMonths).interestPaid
);
let datasets;
if (chartType === 'line') {
datasets = [{
label: 'Balance',
data: balances,
borderColor: 'rgba(54, 162, 235, 1)',
fill: false
}, {
label: 'Interest to be Paid',
data: interestPaid,
borderColor: 'rgba(255, 99, 132, 1)',