diff --git a/web/src/expense.ts b/web/src/expense.ts index 8d6722b..1bbd067 100644 --- a/web/src/expense.ts +++ b/web/src/expense.ts @@ -9,6 +9,7 @@ import { forEachMonth, formatFixedWidthFloat, formatCurrency, + formatPercentage, formatCurrencyCrude, Posting, restName, @@ -189,6 +190,14 @@ function renderSelectedMonth( setHtml("current-month-tax", sum(taxes), COLORS.lossText); setHtml("current-month-expenses", sum(expenses), COLORS.lossText); setHtml("current-month-investment", sum(investments), COLORS.secondary); + setHtml( + "current-month-savings-rate", + formatPercentage( + _.sumBy(investments, "amount") / + (-1 * _.sumBy(incomes, "amount") - _.sumBy(taxes, "amount")) + ), + COLORS.primary + ); } function sum(postings: Posting[], sign = 1) { diff --git a/web/src/utils.ts b/web/src/utils.ts index 66c9563..c7045a5 100644 --- a/web/src/utils.ts +++ b/web/src/utils.ts @@ -236,6 +236,21 @@ export function formatFloat(value, precision = 2) { return sprintf(`%.${precision}f`, value); } +export function formatPercentage(value, precision = 0) { + if (obscure) { + return "00"; + } + + if (!Number.isFinite(value)) { + value = 0; + } + + return Number(value).toLocaleString(undefined, { + style: "percent", + minimumFractionDigits: precision + }); +} + export function formatFixedWidthFloat(value, width, precision = 2) { if (obscure) { value = 0; diff --git a/web/static/index.html b/web/static/index.html index 5b05f3a..348d4ba 100644 --- a/web/static/index.html +++ b/web/static/index.html @@ -411,6 +411,20 @@ +
+
+
+ +
+
+