From bdf0cf8d4ce7be8302a5924f22d2c10bd84e912f Mon Sep 17 00:00:00 2001 From: Herculino Trotta Date: Sat, 10 Oct 2026 15:26:59 -0300 Subject: [PATCH] feat(simulator): add simulator tool page Hyperscript-driven page to simulate one-time, recurring and installment expenses or incomes over the projected balance. --- app/apps/mini_tools/urls.py | 5 + app/apps/mini_tools/views.py | 19 + app/templates/mini_tools/simulator.html | 479 ++++++++++++++++++++++++ 3 files changed, 503 insertions(+) create mode 100644 app/templates/mini_tools/simulator.html diff --git a/app/apps/mini_tools/urls.py b/app/apps/mini_tools/urls.py index e4740424..0166de22 100644 --- a/app/apps/mini_tools/urls.py +++ b/app/apps/mini_tools/urls.py @@ -8,6 +8,11 @@ urlpatterns = [ views.unit_price_calculator, name="unit_price_calculator", ), + path( + "tools/simulator/", + views.simulator, + name="simulator", + ), path( "tools/currency-converter/", views.currency_converter, diff --git a/app/apps/mini_tools/views.py b/app/apps/mini_tools/views.py index 2acd039d..359b1470 100644 --- a/app/apps/mini_tools/views.py +++ b/app/apps/mini_tools/views.py @@ -1,11 +1,14 @@ from django.contrib.auth.decorators import login_required from django.shortcuts import render +from django.utils import timezone +from apps.common.widgets.datepicker import AirMonthYearPickerInput from apps.common.widgets.decimal import convert_to_decimal from apps.currencies.models import Currency from apps.currencies.utils.convert import convert from apps.mini_tools.forms import CurrencyConverterForm from apps.mini_tools.utils.exchange_rate_map import get_currency_exchange_map +from apps.mini_tools.utils.simulator import get_simulator_baseline @login_required @@ -13,6 +16,22 @@ def unit_price_calculator(request): return render(request, "mini_tools/unit_price_calculator.html") +@login_required +def simulator(request): + start_month_input = AirMonthYearPickerInput( + clear_button=False, attrs={"class": "w-full sim-start"} + ).render("start_month", timezone.localdate(timezone.now()).replace(day=1)) + + return render( + request, + "mini_tools/simulator.html", + context={ + "baseline": get_simulator_baseline(request.user), + "start_month_input": start_month_input, + }, + ) + + @login_required def currency_converter(request): rate_map = get_currency_exchange_map() diff --git a/app/templates/mini_tools/simulator.html b/app/templates/mini_tools/simulator.html new file mode 100644 index 00000000..4912bb32 --- /dev/null +++ b/app/templates/mini_tools/simulator.html @@ -0,0 +1,479 @@ +{% extends "layouts/base.html" %} +{% load formats %} +{% load i18n %} + +{% block title %}{% translate 'Simulator' %}{% endblock %} + +{% block content %} + {% get_current_language as LANGUAGE_CODE %} + {{ baseline|json_script:"simulator-baseline" }} +
+
+
{% translate 'Simulator' %}
+
+

+ {% translate 'See how a one-time, recurring or installment expense or income would affect your projected balance in the coming months.' %} +

+ + {% if not baseline.currencies %} + + {% else %} +
+
+
+
+ {% translate 'Currency' %} + +
+
+ {% translate 'Months to simulate' %} + +
+
+
+
+ + + +
+ +
+ + +
+ +
+
+
+
{% translate 'Final balance without simulation' %}
+
+
+
+
+
+
{% translate 'Final balance with simulation' %}
+
+
+
+
+
+
{% translate 'Total impact' %}
+
+
+
+
+
+
{% translate 'Lowest balance with simulation' %}
+
+
+
+
+
+ +
+
+
+ +
+
+
+ +
+
+ + + + + + + + + + + +
{% translate 'Month' %}{% translate 'Projected change' %}{% translate 'Simulated change' %}{% translate 'Balance without simulation' %}{% translate 'Balance with simulation' %}
+
+
+

+ {% translate 'The simulation starts at the earliest starting month among your entries, or the current month if there are none. Balances include paid and unpaid transactions, and archived and untracked accounts are ignored. Converted amounts use the latest exchange rate.' %} +

+ {% endif %} + + +
+{% endblock %}