krz/401k

A 401k projection web app.

clone: git clone https://gitbay.org/krz/401k.git

main: assets/js/app.js · raw

  1"use strict";
  2function formatMoney(num) {
  3    return num.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, "$&,");
  4}
  5function getInputValue(id) {
  6    const el = document.getElementById(id);
  7    return el ? parseFloat(el.value) : NaN;
  8}
  9function getThemeColors() {
 10    const dark = window.matchMedia("(prefers-color-scheme: dark)").matches;
 11    return dark
 12        ? { bgColor: "#1a1d27", fgColor: "#e8eaf0", gridColor: "#2d3141", lineColor: "#3d4258" }
 13        : { bgColor: "#ffffff", fgColor: "#1a1d23", gridColor: "#e2e6ea", lineColor: "#c8cdd5" };
 14}
 15function buildAxisLayout(title, colors) {
 16    const { bgColor, fgColor, gridColor, lineColor } = colors;
 17    return {
 18        title: { text: title, font: { color: fgColor, size: 13 } },
 19        paper_bgcolor: bgColor,
 20        plot_bgcolor: bgColor,
 21        font: { color: fgColor, family: "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif" },
 22        margin: { t: 40, r: 16, b: 48, l: 60 },
 23        yaxis: { gridcolor: gridColor, zerolinecolor: gridColor, linecolor: lineColor },
 24        xaxis: { title: { text: "Months", font: { size: 11 } }, gridcolor: gridColor, zerolinecolor: gridColor, linecolor: lineColor },
 25    };
 26}
 27class Data {
 28    constructor(balance, contribution, returnRate, inflationRate) {
 29        this.balance = balance;
 30        this.contribution = contribution;
 31        this.returnRate = returnRate;
 32        this.inflationRate = inflationRate;
 33    }
 34    summaryRow() {
 35        return `<tr>
 36      <td>$${formatMoney(this.balance)}</td>
 37      <td>$${formatMoney(this.contribution)}</td>
 38      <td>${this.returnRate.toFixed(2)}%</td>
 39      <td>${this.inflationRate.toFixed(2)}%</td>
 40    </tr>`;
 41    }
 42    adjustedRate() {
 43        return (1 + this.returnRate / 100) / (1 + this.inflationRate / 100) - 1;
 44    }
 45}
 46function showResults(data, monthsArr, balanceArr, interestArr) {
 47    const infoTbody = document.querySelector("#infoTable tbody");
 48    infoTbody.innerHTML = data.summaryRow();
 49    document.querySelectorAll(".table-section").forEach((el) => (el.style.display = "block"));
 50    const graphCard = document.getElementById("graphCard");
 51    graphCard.style.display = "block";
 52    const colors = getThemeColors();
 53    const balanceTrace = { x: monthsArr, y: balanceArr, type: "scatter", line: { color: "#4f6ef7", width: 2 } };
 54    const interestTrace = { x: monthsArr, y: interestArr, type: "scatter", line: { color: "#22c55e", width: 2 } };
 55    Plotly.newPlot("balChartContainer", [balanceTrace], buildAxisLayout("Total Balance", colors), { responsive: true });
 56    Plotly.newPlot("intChartContainer", [interestTrace], buildAxisLayout("Accrued Interest", colors), { responsive: true });
 57}
 58function runCalculation(data, stopCondition) {
 59    document.querySelectorAll("tbody").forEach((el) => (el.innerHTML = ""));
 60    const resultsTbody = document.querySelector(".resultsTable tbody");
 61    const adjustedRate = data.adjustedRate();
 62    const monthlyContr = data.contribution;
 63    const monthsArr = [];
 64    const balanceArr = [];
 65    const interestArr = [];
 66    let pVal = data.balance;
 67    let i = 0;
 68    while (!stopCondition(pVal, i)) {
 69        const month = i + 1;
 70        const interest = pVal * (adjustedRate / 12);
 71        const newBalance = pVal + interest + monthlyContr;
 72        const row = document.createElement("tr");
 73        row.innerHTML = `<td>${month}</td><td>$${formatMoney(interest)}</td><td>$${formatMoney(monthlyContr)}</td><td>$${formatMoney(newBalance)}</td>`;
 74        resultsTbody.appendChild(row);
 75        if (month === 1 || month % 12 === 0) {
 76            interestArr.push(Math.round(interest * 100) / 100);
 77            balanceArr.push(Math.round(newBalance * 100) / 100);
 78            monthsArr.push(month);
 79        }
 80        pVal = newBalance;
 81        i++;
 82    }
 83    showResults(data, monthsArr, balanceArr, interestArr);
 84}
 85function retirementYears() {
 86    var _a;
 87    const data = new Data(getInputValue("begBalance"), getInputValue("monthlyContr"), getInputValue("returnRate"), getInputValue("inflationRate"));
 88    const years = getInputValue("years");
 89    const totalMonths = years * 12;
 90    runCalculation(data, (_bal, i) => i >= totalMonths);
 91    (_a = document.getElementById("graphCard")) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
 92}
 93function retirementMoney() {
 94    var _a;
 95    const data = new Data(getInputValue("begBalance"), getInputValue("monthlyContr"), getInputValue("returnRate"), getInputValue("inflationRate"));
 96    const target = getInputValue("money");
 97    runCalculation(data, (bal) => bal >= target);
 98    (_a = document.getElementById("graphCard")) === null || _a === void 0 ? void 0 : _a.scrollIntoView({ behavior: "smooth" });
 99}
100window.retirementYears = retirementYears;
101window.retirementMoney = retirementMoney;