summaryrefslogtreecommitdiff
path: root/assets/js/app.js
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-02-21 23:46:01 -0600
committerChristian Cleberg <[email protected]>2026-02-21 23:46:01 -0600
commit59ab9790e2f03046ca7fef31a6db883e1ec31075 (patch)
treefb6962a5410ef9a614accd49fb769fc434fc42f3 /assets/js/app.js
parent4ba605214e407d49f5ebd80b42767d92c60a7bc9 (diff)
download401k-59ab9790e2f03046ca7fef31a6db883e1ec31075.tar.gz
401k-59ab9790e2f03046ca7fef31a6db883e1ec31075.tar.bz2
401k-59ab9790e2f03046ca7fef31a6db883e1ec31075.zip
modernize site and migrate to TypeScript
Diffstat (limited to 'assets/js/app.js')
-rw-r--r--assets/js/app.js254
1 files changed, 92 insertions, 162 deletions
diff --git a/assets/js/app.js b/assets/js/app.js
index ec873d4..de6d886 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -1,171 +1,101 @@
+"use strict";
function formatMoney(num) {
- return parseFloat(num).toFixed(2).replace(/\d(?=(\d{3})+\.)/g, "$&,");
+ return num.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, "$&,");
+}
+function getInputValue(id) {
+ const el = document.getElementById(id);
+ return el ? parseFloat(el.value) : NaN;
+}
+function getThemeColors() {
+ const dark = window.matchMedia("(prefers-color-scheme: dark)").matches;
+ return dark
+ ? { bgColor: "#1a1d27", fgColor: "#e8eaf0", gridColor: "#2d3141", lineColor: "#3d4258" }
+ : { bgColor: "#ffffff", fgColor: "#1a1d23", gridColor: "#e2e6ea", lineColor: "#c8cdd5" };
+}
+function buildAxisLayout(title, colors) {
+ const { bgColor, fgColor, gridColor, lineColor } = colors;
+ return {
+ title: { text: title, font: { color: fgColor, size: 13 } },
+ paper_bgcolor: bgColor,
+ plot_bgcolor: bgColor,
+ font: { color: fgColor, family: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" },
+ margin: { t: 40, r: 16, b: 48, l: 60 },
+ yaxis: { gridcolor: gridColor, zerolinecolor: gridColor, linecolor: lineColor },
+ xaxis: { title: { text: "Months", font: { size: 11 } }, gridcolor: gridColor, zerolinecolor: gridColor, linecolor: lineColor },
+ };
}
-
class Data {
- constructor(balance, contribution, returnRate, inflationRate, years, money) {
- this.balance = balance;
- this.contribution = contribution;
- this.returnRate = returnRate;
- this.inflationRate = inflationRate;
- this.years = years;
- this.money = money;
- }
- summary() {
- const summaryRow = "<tr> <td>$ " + formatMoney(this.balance) + "</td> <td>$ " + formatMoney(this.contribution) + "</td> <td>" + (this.returnRate).toFixed(2) + " %</td> <td>" + (this.inflationRate).toFixed(2) + " %</td> </tr>";
- return summaryRow;
- }
- adjustedRate() {
- const adjustedRate = ((1 + (this.returnRate / 100)) / (1 + (this.inflationRate / 100)) - 1);
- return adjustedRate;
- }
- graphData(monthsArr, balanceArr, interestArr) {
- let matched = window.matchMedia('(prefers-color-scheme: dark)').matches;
- if(matched) {
- var bgColor = "#202020";
- var fgColor = "#ffffff";
- var gridColor = "#3b3b3b";
- var lineColor = "#5b5b5b";
- } else {
- var bgColor = "#ffffff";
- var fgColor = "#222222";
- var gridColor = "#c8c8c8";
- var lineColor = "#555555";
+ constructor(balance, contribution, returnRate, inflationRate) {
+ this.balance = balance;
+ this.contribution = contribution;
+ this.returnRate = returnRate;
+ this.inflationRate = inflationRate;
+ }
+ summaryRow() {
+ return `<tr>
+ <td>$${formatMoney(this.balance)}</td>
+ <td>$${formatMoney(this.contribution)}</td>
+ <td>${this.returnRate.toFixed(2)}%</td>
+ <td>${this.inflationRate.toFixed(2)}%</td>
+ </tr>`;
+ }
+ adjustedRate() {
+ return (1 + this.returnRate / 100) / (1 + this.inflationRate / 100) - 1;
}
-
- const balanceTrace = {
- x: monthsArr,
- y: balanceArr,
- type: "scatter",
- line: {
- color: "#24ab48",
- width: 2
- }
- };
- const interestTrace = {
- x: monthsArr,
- y: interestArr,
- type: "scatter",
- line: {
- color: "#0062ff",
- width: 2
- }
- };
- const balanceTitle = {
- title: {
- text: "Total Balance",
- color: fgColor
- },
- paper_bgcolor: bgColor,
- plot_bgcolor: bgColor,
- font: {
- color: fgColor
- },
- yaxis: {
- title: "Total Balance",
- gridcolor: gridColor,
- zerolinecolor: gridColor,
- linecolor: lineColor
- },
- xaxis: {
- title: "Months",
- gridcolor: gridColor,
- zerolinecolor: gridColor,
- linecolor: lineColor
- }
- };
- const interestTitle = {
- title: {
- text: "Accrued Interest",
- color: fgColor
- },
- paper_bgcolor: bgColor,
- plot_bgcolor: bgColor,
- font: {
- color: fgColor
- },
- yaxis: {
- title: "Accrued Interest",
- gridcolor: gridColor,
- zerolinecolor: gridColor,
- linecolor: lineColor
- },
- xaxis: {
- title: "Months",
- gridcolor: gridColor,
- zerolinecolor: gridColor,
- linecolor: lineColor
- }
- };
- return [balanceTrace, interestTrace, balanceTitle, interestTitle];
- }
}
-
-function retirementYears() {
- $("tbody").empty();
- const myData = new Data(parseFloat($("#begBalance").val()), parseFloat($("#monthlyContr").val()), parseFloat($("#returnRate").val()), parseFloat($("#inflationRate").val()), parseFloat($("#years").val()), null);
- const summary = myData.summary();
- $(".infoTable tbody").append(summary);
- $(".infoTable, .resultsTable, #infoTableHeading, #resultsTableHeading").show();
- var interestArr = [],
- balanceArr = [],
- monthsArr = [];
- const adjustedRate = myData.adjustedRate();
- const monthlyContr = myData.contribution;
- let pVal = myData.balance;
- for (var i = 0; i < (myData.years * 12); i++) {
- var interest = (pVal * (adjustedRate / 12)).toFixed(2);
- var newBalance = (parseFloat(pVal) + parseFloat(interest) + monthlyContr).toFixed(2);
- $(".resultsTable tbody").append("<tr> <td>" + (i + 1) + "</td> <td>$ " + formatMoney(interest) + "</td> <td>$ " + formatMoney(monthlyContr) + "</td> <td>$ " + formatMoney(newBalance) + "</td> </tr>");
- if ((i + 1) === 1 || (i + 1) % 12 === 0 || i === (myData.years * 12)) {
- interestArr.push(parseInt(interest * 100) / 100);
- balanceArr.push(parseInt(newBalance * 100) / 100);
- monthsArr.push(i + 1);
+function showResults(data, monthsArr, balanceArr, interestArr) {
+ const infoTbody = document.querySelector("#infoTable tbody");
+ infoTbody.innerHTML = data.summaryRow();
+ document.querySelectorAll(".table-section").forEach((el) => (el.style.display = "block"));
+ const graphCard = document.getElementById("graphCard");
+ graphCard.style.display = "block";
+ const colors = getThemeColors();
+ const balanceTrace = { x: monthsArr, y: balanceArr, type: "scatter", line: { color: "#4f6ef7", width: 2 } };
+ const interestTrace = { x: monthsArr, y: interestArr, type: "scatter", line: { color: "#22c55e", width: 2 } };
+ Plotly.newPlot("balChartContainer", [balanceTrace], buildAxisLayout("Total Balance", colors), { responsive: true });
+ Plotly.newPlot("intChartContainer", [interestTrace], buildAxisLayout("Accrued Interest", colors), { responsive: true });
+}
+function runCalculation(data, stopCondition) {
+ document.querySelectorAll("tbody").forEach((el) => (el.innerHTML = ""));
+ const resultsTbody = document.querySelector(".resultsTable tbody");
+ const adjustedRate = data.adjustedRate();
+ const monthlyContr = data.contribution;
+ const monthsArr = [];
+ const balanceArr = [];
+ const interestArr = [];
+ let pVal = data.balance;
+ let i = 0;
+ while (!stopCondition(pVal, i)) {
+ const month = i + 1;
+ const interest = pVal * (adjustedRate / 12);
+ const newBalance = pVal + interest + monthlyContr;
+ const row = document.createElement("tr");
+ row.innerHTML = `<td>${month}</td><td>$${formatMoney(interest)}</td><td>$${formatMoney(monthlyContr)}</td><td>$${formatMoney(newBalance)}</td>`;
+ resultsTbody.appendChild(row);
+ if (month === 1 || month % 12 === 0) {
+ interestArr.push(Math.round(interest * 100) / 100);
+ balanceArr.push(Math.round(newBalance * 100) / 100);
+ monthsArr.push(month);
+ }
+ pVal = newBalance;
+ i++;
}
- pVal = newBalance;
- }
- $(".graphCard").css("display", "block");
- const graphData = myData.graphData(monthsArr, balanceArr, interestArr);
- Plotly.newPlot("balChartContainer", [graphData[0]], graphData[2], {
- responsive: true
- });
- Plotly.newPlot("intChartContainer", [graphData[1]], graphData[3], {
- responsive: true
- });
+ showResults(data, monthsArr, balanceArr, interestArr);
+}
+function retirementYears() {
+ var _a;
+ const data = new Data(getInputValue("begBalance"), getInputValue("monthlyContr"), getInputValue("returnRate"), getInputValue("inflationRate"));
+ const years = getInputValue("years");
+ const totalMonths = years * 12;
+ runCalculation(data, (_bal, i) => i >= totalMonths);
+ (_a = document.getElementById("graphCard")) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
}
-
function retirementMoney() {
- $("tbody").empty();
- const myData = new Data(parseFloat($("#begBalance").val()), parseFloat($("#monthlyContr").val()), parseFloat($("#returnRate").val()), parseFloat($("#inflationRate").val()), null, parseFloat($("#money").val()));
- const summary = myData.summary();
- $(".infoTable tbody").append(summary);
- $(".infoTable, .resultsTable, #infoTableHeading, #resultsTableHeading").show();
- var interestArr = [],
- balanceArr = [],
- monthsArr = [];
- const adjustedRate = myData.adjustedRate();
- const monthlyContr = myData.contribution;
- let pVal = myData.balance;
- let money = myData.money;
- for (var i = 0; pVal < money; i++) {
- var months = i + 1;
- var newBalance = (pVal + (pVal * (adjustedRate / 12)) + monthlyContr);
- $(".resultsTable tbody").append("<tr> <td>" + months + "</td> <td>$ " + formatMoney((pVal * (adjustedRate / 12))) + "</td> <td>$ " + formatMoney(monthlyContr) + "</td> <td>$ " + formatMoney(newBalance) + "</td> </tr>");
- var roundedInterest = parseInt((pVal * (adjustedRate / 12)) * 100) / 100;
- var roundedBalance = parseInt(newBalance * 100) / 100;
- if (months === 1 || months % 12 === 0 || pVal > money) {
- interestArr.push(roundedInterest);
- balanceArr.push(roundedBalance);
- monthsArr.push(months);
- }
- pVal = newBalance;
- }
- $(".graphCard").css("display", "block");
- const graphData = myData.graphData(monthsArr, balanceArr, interestArr);
- Plotly.newPlot("balChartContainer", [graphData[0]], graphData[2], {
- responsive: true
- });
- Plotly.newPlot("intChartContainer", [graphData[1]], graphData[3], {
- responsive: true
- });
+ var _a;
+ const data = new Data(getInputValue("begBalance"), getInputValue("monthlyContr"), getInputValue("returnRate"), getInputValue("inflationRate"));
+ const target = getInputValue("money");
+ runCalculation(data, (bal) => bal >= target);
+ (_a = document.getElementById("graphCard")) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
}
+window.retirementYears = retirementYears;
+window.retirementMoney = retirementMoney;