JSFiddle - React, Tailwind, and code Playground

HTML

<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>
<h1>Inputs</h1>
<form class="input-form">
  <label>
    Initial investment amount:
    <input name="initialInvestmentAmount" type="number" value="1000" step="0.01">
  </label>
  <label>
    Additional yearly contribution:
    <input name="additionalYearlyContribution" type="number" value="100" step="0.01">
  </label>
  <label>
    Rate of return (%):
    <input name="rateOfReturn" type="number" value="5" step="0.01">
  </label>
</form>

<div class="output">
  <h1>Results</h1>
  Capital at the end of term: <span class="capitalAtTheEnd">-</span>
  <div class="details">
  </div>
</div>

CSS

@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap');

body {
  font-family: Inter;
}

h1 {
  font-weight: 600;
}

.input-form label {
  display: block;
}

.input-form label+label {
  margin-top: 10px;
}

.input-form label input {
  padding: 5px 10px;
  background: #FFFFFF;
  border: 1px solid #B4B7D1;
  box-shadow: 0px 1px 2px rgba(0, 0, 0, 0.1);
  border-radius: 5px;
}

.output {
  transition: opacity 0.2s ease-in-out;
}

.output.loading {
  opacity: 0.2;
}

.output table {
  margin-top: 20px;
  border-collapse: collapse;
}

.output table thead {
  background: #F1F2F8;
  font-weight: 600;
  padding: 15px 10px;
}

.output table thead tr th {
  border: 1px solid #DEE0EF;
}

.output table tbody th,
.output table tbody td {
  padding: 10px;
  font-weight: 400;
  background: #FFFFFF;
  border: 1px solid #DEE0EF;
}

JavaScript

/**
 * Summary
 * =======
 * When an input control in the DOM changes, the /simulate API is executed to calculate
 * the the results of the Investment Growth Calculator.
 *
 * Details
 * =======
 * - onFormUpdate(): when the inputs in the DOM change, this event handler is called. It
 *                   executes the /simulate API via a call to simulate()
 * - updateDOM():    writes the result of the /simulate API to the DOM
 */

let lastRequestId = 0;

function onFormUpdate() {
  const requestId = lastRequestId + 1;
  lastRequestId = requestId;

  const inputs = readInputs();

  startLoading();

  simulate(
      inputs.initialInvestmentAmount,
      inputs.additionalYearlyContribution,
      inputs.rateOfReturn,
    )
    .then(({
      capitalAtTheEnd,
      details
    }) => {
      if (lastRequestId === requestId) {
        updateDOM(capitalAtTheEnd, details);
      }
    })
    .catch((error) => {
      console.error('Could not get simulation results', error);
    })
    .finally(() => {
      if (lastRequestId === requestId) {
        stopLoading();
      }
    });
}

onFormUpdate();
document.querySelector('.input-form').addEventListener(
  'input',
  _.debounce((event) => {
    onFormUpdate();
  }, 600),
);

async function simulate(initialInvestmentAmount, additionalYearlyContribution, rateOfReturn) {
  // Suresheet: https://www.equalto.com/suresheet/view/abc7cbef-2491-4787-94f8-6542fab12a4e
  const workbookId = 'abc7cbef-2491-4787-94f8-6542fab12a4e';
  const simulateUrl = `https://www.equalto.com/suresheet/api/v1/simulate/${workbookId}`;

  const response = await fetch(simulateUrl, {
    method: 'POST',
    body: JSON.stringify({
      inputs: {
        "Sheet1": {
          C6: initialInvestmentAmount,
          C7: additionalYearlyContribution,
          C8: rateOfReturn,
        },
      },
      outputs: {
        "Sheet1": ['C13', "B20:F41"],
      },
    }),
    headers: {
      'Content-Type': 'application/json',
    },
  });
  const responseJson =...