Compare commits

...

50 Commits

Author SHA1 Message Date
Herculino Trotta 7645153f77 feat(dca): remove ticks from price chart 2025-09-20 11:05:56 -03:00
Herculino Trotta 9dce5e9efe feat(networth): add a chart with the currency difference between each month 2025-09-20 11:02:23 -03:00
Herculino Trotta bb3cc5da6c fix(login): use full dynamic height 2025-09-20 01:40:22 -03:00
eitchtee f2abeff31a chore(locale): update translation files
[skip ci] Automatically generated by Django makemessages workflow
2025-09-14 04:57:39 +00:00
Herculino Trotta 666eaff167 Merge pull request #377
fix(rules:dry-run): rename offcanvas
2025-09-14 01:56:48 -03:00
Herculino Trotta d72454f854 fix(rules:dry-run): rename offcanvas 2025-09-14 01:56:31 -03:00
Herculino Trotta 333aa81923 Merge pull request #376
fix(rules:dry-run): current_user getting overwritten and delete on synchronous call
2025-09-14 01:37:23 -03:00
eitchtee 41b8cfd1e7 chore(locale): update translation files
[skip ci] Automatically generated by Django makemessages workflow
2025-09-14 04:37:05 +00:00
Herculino Trotta 1fa7985b01 fix(rules:dry-run): current_user getting overwritten and delete on synchronous call 2025-09-14 01:37:03 -03:00
Herculino Trotta 38392a6322 Merge pull request #375
feat(transactions): Try to convert amount to the expected Decimal if it is a str, int or float
2025-09-14 01:36:19 -03:00
Herculino Trotta 637c62319b feat(transactions): Try to convert amount to the expected Decimal if it is a str, int or float 2025-09-14 01:23:49 -03:00
Herculino Trotta f91fe67629 Merge pull request #374
feat(rules): expose if the transaction is recurring/installment
2025-09-14 01:18:28 -03:00
Herculino Trotta 9eb1818a20 feat(rules): expose if the transaction is recurring/installment 2025-09-14 01:18:08 -03:00
Herculino Trotta 50ac679e33 Merge pull request #373
fix(rules:dry-run): Edit/Update transaction not showing message when transaction can't be found
2025-09-14 00:41:28 -03:00
Herculino Trotta 2a463c63b8 fix(rules:dry-run): Edit/Update transaction not showing message when transaction can't be found 2025-09-14 00:41:04 -03:00
eitchtee dce65f2faf chore(locale): update translation files
[skip ci] Automatically generated by Django makemessages workflow
2025-09-13 06:24:57 +00:00
Herculino Trotta a053cb3947 Merge pull request #372
feat(ui:sidebar): smoother transition when clicking on menu items
2025-09-13 03:21:13 -03:00
Herculino Trotta 2d43072120 feat(ui:sidebar): smoother transition when clicking on menu items 2025-09-13 03:20:55 -03:00
Herculino Trotta 70bdee065e Merge pull request #371
feat(ui:sidebar): add a chevron to the management menu to indicate it opens another "page"
2025-09-13 03:20:01 -03:00
Herculino Trotta 95db27a32f feat(ui:sidebar): add a chevron to the management menu to indicate it opens another "page" 2025-09-13 03:19:36 -03:00
Herculino Trotta d6d4e6a102 Merge pull request #370
feat(ui:sidebar): keep management menu open if the user is on a management page
2025-09-13 03:19:06 -03:00
Herculino Trotta bc0f30fead feat(ui:sidebar): keep management menu open if the user is on a management page 2025-09-13 03:18:45 -03:00
Herculino Trotta a9a86fc491 Merge pull request #368 from eitchtee/weblate
Translations update from Weblate
2025-09-12 09:15:44 -03:00
Phillip Maizza c3b5f2bf39 locale(Italian): update translation
Currently translated at 100.0% (694 of 694 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/it/
2025-09-11 22:17:42 +00:00
Herculino Trotta 19128e5aed Merge pull request #367 from eitchtee/weblate
Translations update from Weblate
2025-09-11 18:49:31 -03:00
Phillip Maizza 9b5c6d3413 locale(Italian): update translation
Currently translated at 100.0% (694 of 694 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/it/
2025-09-11 21:17:42 +00:00
Phillip Maizza 73c873a2ad locale(Italian): update translation
Currently translated at 79.8% (554 of 694 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/it/
2025-09-11 19:17:42 +00:00
Phillip Maizza 9d2be22a77 locale(Italian): update translation
Currently translated at 28.8% (200 of 694 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/it/
2025-09-11 18:17:42 +00:00
Phillip Maizza 6a3d31f37d locale((Italian)): added translation using Weblate 2025-09-11 17:46:47 +00:00
Herculino Trotta 3be3a3c14b Merge pull request #366 from eitchtee/weblate
Translations update from Weblate
2025-09-09 23:02:50 -03:00
Dimitri Decrock a5b0f4efb7 locale(Dutch): update translation
Currently translated at 100.0% (694 of 694 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/nl/
2025-09-09 18:17:42 +00:00
Herculino Trotta 6da50db417 Merge pull request #365 from samuelthng/patch-1
fix(app): pwa title colour
2025-09-09 00:10:15 -03:00
Samuel a6c1daf902 fix(app): PWA Title Colour 2025-09-09 08:12:16 +08:00
eitchtee 6a271fb3d7 chore(locale): update translation files
[skip ci] Automatically generated by Django makemessages workflow
2025-09-08 12:20:45 +00:00
Herculino Trotta 2cf9a9dd0f Merge pull request #364
fix(accounts): unable to update accounts
2025-09-08 09:19:49 -03:00
sorcierwax 0deaabe719 locale(French): update translation
Currently translated at 100.0% (694 of 694 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/fr/
2025-09-08 06:17:42 +00:00
Herculino Trotta b14342af2e Merge pull request #362
fix(rules): duplicating transactions when ran outside of test mode
2025-09-07 22:15:11 -03:00
Herculino Trotta 2c14ce6366 Merge pull request #361
fix(rules): add .exclude() to transactions() function
2025-09-07 21:30:32 -03:00
Herculino Trotta 2dd887b0d9 Merge pull request #360
feat(rules): add .exclude() to transactions() function
2025-09-07 21:25:18 -03:00
Herculino Trotta 8be7758dc0 Merge pull request #359
feat(rules): add .exclude() to transactions() function
2025-09-07 20:41:36 -03:00
Herculino Trotta 05dd782df5 locale(Portuguese (Brazil)): update translation
Currently translated at 100.0% (694 of 694 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/pt_BR/
2025-09-07 14:17:42 +00:00
eitchtee 187fe43283 chore(locale): update translation files
[skip ci] Automatically generated by Django makemessages workflow
2025-09-07 13:08:56 +00:00
Herculino Trotta cae73376db Merge pull request #358
feat(rules): many improvements
2025-09-07 10:07:19 -03:00
Herculino Trotta 7225454a6e Merge pull request #357
fix(ui): unable to CTRL + A amount fields
2025-09-05 23:05:49 -03:00
Herculino Trotta 70c8c1e07c fix(ui): unable to CTRL + A amount fields 2025-09-05 23:04:12 -03:00
sorcierwax c392a2c988 locale(French): update translation
Currently translated at 100.0% (686 of 686 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/fr/
2025-09-01 06:17:42 +00:00
Dimitri Decrock 17ea859fd2 locale(Dutch): update translation
Currently translated at 100.0% (686 of 686 strings)

Translation: WYGIWYH/App
Translate-URL: https://translations.herculino.com/projects/wygiwyh/app/nl/
2025-09-01 06:17:42 +00:00
eitchtee 8aae6f928f chore(locale): update translation files
[skip ci] Automatically generated by Django makemessages workflow
2025-08-31 12:07:49 +00:00
Herculino Trotta 7c43b06b9f Merge pull request #356
feat(rules): add optional rules ordering
2025-08-31 09:07:07 -03:00
Herculino Trotta e16e279911 Merge pull request #355
feat(rules): add rule function to fetch transactions totals and balance
2025-08-30 15:45:45 -03:00
26 changed files with 7821 additions and 3487 deletions
+1 -1
View File
@@ -36,7 +36,7 @@ class ArbitraryDecimalDisplayNumberInput(forms.TextInput):
{ {
"x-data": "", "x-data": "",
"x-mask:dynamic": f"$money($input, '{get_format('DECIMAL_SEPARATOR')}', '{get_format('THOUSAND_SEPARATOR')}', '30')", "x-mask:dynamic": f"$money($input, '{get_format('DECIMAL_SEPARATOR')}', '{get_format('THOUSAND_SEPARATOR')}', '30')",
"x-on:keyup": "$el.dispatchEvent(new Event('input'))", "x-on:keyup": "if (!['Control', 'Shift', 'Alt', 'Meta'].includes($event.key) && !(($event.ctrlKey || $event.metaKey) && $event.key.toLowerCase() === 'a')) $el.dispatchEvent(new Event('input'))",
} }
) )
@@ -182,3 +182,29 @@ def calculate_historical_account_balance(queryset):
historical_account_balance[date_filter(end_date, "b Y")] = month_data historical_account_balance[date_filter(end_date, "b Y")] = month_data
return historical_account_balance return historical_account_balance
def calculate_monthly_net_worth_difference(historical_net_worth):
diff_dict = OrderedDict()
if not historical_net_worth:
return diff_dict
# Get all currencies
currencies = set()
for data in historical_net_worth.values():
currencies.update(data.keys())
# Initialize prev_values for all currencies
prev_values = {currency: Decimal("0.00") for currency in currencies}
for month, values in historical_net_worth.items():
diff_values = {}
for currency in sorted(list(currencies)):
current_val = values.get(currency, Decimal("0.00"))
prev_val = prev_values.get(currency, Decimal("0.00"))
diff_values[currency] = current_val - prev_val
diff_dict[month] = diff_values
prev_values = values.copy()
return diff_dict
+34
View File
@@ -8,6 +8,7 @@ from django.views.decorators.http import require_http_methods
from apps.net_worth.utils.calculate_net_worth import ( from apps.net_worth.utils.calculate_net_worth import (
calculate_historical_currency_net_worth, calculate_historical_currency_net_worth,
calculate_historical_account_balance, calculate_historical_account_balance,
calculate_monthly_net_worth_difference,
) )
from apps.transactions.models import Transaction from apps.transactions.models import Transaction
from apps.transactions.utils.calculations import ( from apps.transactions.utils.calculations import (
@@ -96,6 +97,38 @@ def net_worth(request):
chart_data_currency_json = json.dumps(chart_data_currency, cls=DjangoJSONEncoder) chart_data_currency_json = json.dumps(chart_data_currency, cls=DjangoJSONEncoder)
monthly_difference_data = calculate_monthly_net_worth_difference(
historical_net_worth=historical_currency_net_worth
)
diff_labels = (
list(monthly_difference_data.keys()) if monthly_difference_data else []
)
diff_currencies = (
list(monthly_difference_data[diff_labels[0]].keys())
if monthly_difference_data and diff_labels
else []
)
diff_datasets = []
for i, currency in enumerate(diff_currencies):
data = [
float(month_data.get(currency, 0))
for month_data in monthly_difference_data.values()
]
diff_datasets.append(
{
"label": currency,
"data": data,
"borderWidth": 3,
}
)
chart_data_monthly_difference = {"labels": diff_labels, "datasets": diff_datasets}
chart_data_monthly_difference_json = json.dumps(
chart_data_monthly_difference, cls=DjangoJSONEncoder
)
historical_account_balance = calculate_historical_account_balance( historical_account_balance = calculate_historical_account_balance(
queryset=transactions_account_queryset queryset=transactions_account_queryset
) )
@@ -140,6 +173,7 @@ def net_worth(request):
"chart_data_accounts_json": chart_data_accounts_json, "chart_data_accounts_json": chart_data_accounts_json,
"accounts": accounts, "accounts": accounts,
"type": view_type, "type": view_type,
"chart_data_monthly_difference_json": chart_data_monthly_difference_json,
}, },
) )
+21 -3
View File
@@ -95,9 +95,9 @@ class DryRunResults:
return return
if isinstance(end_instance, Transaction): if isinstance(end_instance, Transaction):
start_instance = end_instance.deepcopy() end_instance = end_instance.deepcopy()
elif isinstance(end_instance, dict): elif isinstance(end_instance, dict):
start_instance = deepcopy(end_instance) end_instance = deepcopy(end_instance)
result = { result = {
"type": "update_or_create_transaction", "type": "update_or_create_transaction",
@@ -213,6 +213,16 @@ def check_for_transaction_rules(
f"{prefix}internal_id": transaction.internal_id, f"{prefix}internal_id": transaction.internal_id,
f"{prefix}is_deleted": transaction.deleted, f"{prefix}is_deleted": transaction.deleted,
f"{prefix}is_muted": transaction.mute, f"{prefix}is_muted": transaction.mute,
f"{prefix}is_recurring": transaction.recurring_transaction is not None,
f"{prefix}is_installment": transaction.installment_plan is not None,
f"{prefix}installment_number": (
transaction.installment_id if transaction.installment_plan else None
),
f"{prefix}installment_total": (
transaction.installment_plan.number_of_installments
if transaction.installment_plan
else None
),
} }
else: else:
return { return {
@@ -256,6 +266,12 @@ def check_for_transaction_rules(
f"{prefix}internal_id": transaction.get("internal_id", ""), f"{prefix}internal_id": transaction.get("internal_id", ""),
f"{prefix}is_deleted": transaction.get("deleted", True), f"{prefix}is_deleted": transaction.get("deleted", True),
f"{prefix}is_muted": transaction.get("mute", False), f"{prefix}is_muted": transaction.get("mute", False),
f"{prefix}is_recurring": transaction.get(
"recurring_transaction", False
),
f"{prefix}is_installment": transaction.get("installment", False),
f"{prefix}installment_number": transaction.get("installment_id"),
f"{prefix}installment_total": transaction.get("installment_total"),
} }
def _process_update_or_create_transaction_action(processed_action): def _process_update_or_create_transaction_action(processed_action):
@@ -520,6 +536,7 @@ def check_for_transaction_rules(
return transaction return transaction
user = get_user_model().objects.get(id=user_id) user = get_user_model().objects.get(id=user_id)
if not dry_run:
write_current_user(user) write_current_user(user)
logs = [] if dry_run else None logs = [] if dry_run else None
dry_run_results = DryRunResults(dry_run=dry_run) dry_run_results = DryRunResults(dry_run=dry_run)
@@ -745,10 +762,11 @@ def check_for_transaction_rules(
"** Error while executing 'check_for_transaction_rules' task", "** Error while executing 'check_for_transaction_rules' task",
level="error", level="error",
) )
delete_current_user()
if not dry_run: if not dry_run:
delete_current_user()
raise e raise e
if not dry_run:
delete_current_user() delete_current_user()
return logs, dry_run_results.results return logs, dry_run_results.results
+8
View File
@@ -90,4 +90,12 @@ def serialize_transaction(sender: Transaction, deleted: bool):
"internal_note": sender.internal_note, "internal_note": sender.internal_note,
"internal_id": sender.internal_id, "internal_id": sender.internal_id,
"mute": sender.mute, "mute": sender.mute,
"installment_id": sender.installment_id if sender.installment_plan else None,
"installment_total": (
sender.installment_plan.number_of_installments
if sender.installment_plan is not None
else None
),
"installment": sender.installment_plan is not None,
"recurring_transaction": sender.recurring_transaction is not None,
} }
+4
View File
@@ -1,3 +1,4 @@
import decimal
import logging import logging
from copy import deepcopy from copy import deepcopy
@@ -381,6 +382,9 @@ class Transaction(OwnedObject):
default_manager_name = "objects" default_manager_name = "objects"
def clean_fields(self, *args, **kwargs): def clean_fields(self, *args, **kwargs):
if isinstance(self.amount, (str, int, float)):
self.amount = decimal.Decimal(str(self.amount))
self.amount = truncate_decimal( self.amount = truncate_decimal(
value=self.amount, decimal_places=self.account.currency.decimal_places value=self.amount, decimal_places=self.account.currency.decimal_places
) )
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -3,7 +3,7 @@
{% if icon %}<i class="{{ icon }}"></i>{% else %}<span class="fw-bold">{{ title.0 }}</span>{% endif %} {% if icon %}<i class="{{ icon }}"></i>{% else %}<span class="fw-bold">{{ title.0 }}</span>{% endif %}
</div> </div>
<div class="card-body"> <div class="card-body">
<h5 class="tw:text-{{ color }}-400 fw-bold tw:mr-[50px]" {{ attrs }}>{{ title }}{% if help_text %}<c-ui.help-icon :content="help_text" icon=""></c-ui.help-icon>{% endif %}</h5> <h5 class="tw:text-{{ color }}-400 fw-bold tw:mr-[50px] {{ title_css_classes }}" {{ attrs }}>{{ title }}{% if help_text %}<c-ui.help-icon :content="help_text" icon=""></c-ui.help-icon>{% endif %}</h5>
{{ slot }} {{ slot }}
</div> </div>
</div> </div>
@@ -354,6 +354,7 @@
display: false, display: false,
}, },
ticks: { ticks: {
display: false,
format: { maximumFractionDigits: 40, minimumFractionDigits: 1 } format: { maximumFractionDigits: 40, minimumFractionDigits: 1 }
} }
}, },
@@ -369,6 +370,7 @@
display: false, display: false,
}, },
ticks: { ticks: {
display: false,
format: { maximumFractionDigits: 40, minimumFractionDigits: 1 } format: { maximumFractionDigits: 40, minimumFractionDigits: 1 }
} }
} }
@@ -12,6 +12,4 @@
<link rel="icon" type="image/png" sizes="32x32" href="{% static 'img/favicon/favicon-32x32.png' %}"> <link rel="icon" type="image/png" sizes="32x32" href="{% static 'img/favicon/favicon-32x32.png' %}">
<link rel="icon" type="image/png" sizes="96x96" href="{% static 'img/favicon/favicon-96x96.png' %}"> <link rel="icon" type="image/png" sizes="96x96" href="{% static 'img/favicon/favicon-96x96.png' %}">
<link rel="icon" type="image/png" sizes="16x16" href="{% static 'img/favicon/favicon-16x16.png' %}"> <link rel="icon" type="image/png" sizes="16x16" href="{% static 'img/favicon/favicon-16x16.png' %}">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-TileImage" content="{% static 'img/favicon/ms-icon-144x144.png' %}"> <meta name="msapplication-TileImage" content="{% static 'img/favicon/ms-icon-144x144.png' %}">
<meta name="theme-color" content="#ffffff">
+4 -12
View File
@@ -10,18 +10,10 @@
<nav <nav
id="sidebar" id="sidebar"
hx-boost="true" hx-boost="true"
hx-swap="transition:true"
data-bs-scroll="true" data-bs-scroll="true"
class="offcanvas-lg offcanvas-start d-lg-flex flex-column position-fixed top-0 start-0 h-100 bg-body-tertiary shadow-sm tw:z-1020"> class="offcanvas-lg offcanvas-start d-lg-flex flex-column position-fixed top-0 start-0 h-100 bg-body-tertiary shadow-sm tw:z-1020">
{# <div>#}
{# <a href="{% url 'index' %}" class="d-none d-lg-flex tw:justify-start p-3 text-decoration-none sidebar-title">#}
{# <img src="{% static 'img/logo-icon.svg' %}" alt="WYGIWYH Logo" height="30" width="30" title="WYGIWYH"/>#}
{# <span class="fs-4 fw-bold ms-3">WYGIWYH</span>#}
{# </a>#}
{##}
{##}
{# </div>#}
<div class="d-none d-lg-flex tw:justify-between tw:items-center tw:border-b tw:border-gray-600 tw:lg:flex"> <div class="d-none d-lg-flex tw:justify-between tw:items-center tw:border-b tw:border-gray-600 tw:lg:flex">
<a href="{% url 'index' %}" class="m-0 d-none d-lg-flex tw:justify-start p-3 text-decoration-none sidebar-title"> <a href="{% url 'index' %}" class="m-0 d-none d-lg-flex tw:justify-start p-3 text-decoration-none sidebar-title">
<img src="{% static 'img/logo-icon.svg' %}" alt="WYGIWYH Logo" height="30" width="30" title="WYGIWYH"/> <img src="{% static 'img/logo-icon.svg' %}" alt="WYGIWYH Logo" height="30" width="30" title="WYGIWYH"/>
@@ -154,16 +146,16 @@
aria-controls="collapsible-panel" aria-controls="collapsible-panel"
class="sidebar-menu-item tw:text-wrap tw:lg:text-nowrap tw:lg:text-sm d-flex align-items-center text-decoration-none p-2 rounded-3 sidebar-item {% active_link views='tags_index||entities_index||categories_index||accounts_index||account_groups_index||currencies_index||exchange_rates_index||rules_index||import_profiles_index||automatic_exchange_rates_index||export_index||users_index' css_class="sidebar-active" %}"> class="sidebar-menu-item tw:text-wrap tw:lg:text-nowrap tw:lg:text-sm d-flex align-items-center text-decoration-none p-2 rounded-3 sidebar-item {% active_link views='tags_index||entities_index||categories_index||accounts_index||account_groups_index||currencies_index||exchange_rates_index||rules_index||import_profiles_index||automatic_exchange_rates_index||export_index||users_index' css_class="sidebar-active" %}">
<i class="fa-solid fa-toolbox fa-fw"></i> <i class="fa-solid fa-toolbox fa-fw"></i>
<span <span class="ms-3 fw-medium tw:lg:group-hover:truncate tw:lg:group-focus:truncate tw:lg:group-hover:text-ellipsis tw:lg:group-focus:text-ellipsis">
class="ms-3 fw-medium tw:lg:group-hover:truncate tw:lg:group-focus:truncate tw:lg:group-hover:text-ellipsis tw:lg:group-focus:text-ellipsis">
{% translate 'Management' %} {% translate 'Management' %}
</span> </span>
<i class="fa-solid fa-chevron-right fa-fw ms-auto pe-2"></i>
</div> </div>
</ul> </ul>
<div class="mt-auto p-2 w-100"> <div class="mt-auto p-2 w-100">
<div id="collapsible-panel" <div id="collapsible-panel"
class="p-0 collapse tw:absolute tw:bottom-0 tw:left-0 tw:w-full tw:z-30 tw:max-h-dvh"> class="p-0 collapse tw:absolute tw:bottom-0 tw:left-0 tw:w-full tw:z-30 tw:max-h-dvh {% active_link views='tags_index||entities_index||categories_index||accounts_index||account_groups_index||currencies_index||exchange_rates_index||rules_index||import_profiles_index||automatic_exchange_rates_index||export_index||users_index' css_class="show" %}">
<div class="tw:h-dvh tw:backdrop-blur-3xl tw:flex tw:flex-col"> <div class="tw:h-dvh tw:backdrop-blur-3xl tw:flex tw:flex-col">
<div <div
class="tw:justify-between tw:items-center tw:p-4 tw:border-b tw:border-gray-600 sidebar-submenu-header"> class="tw:justify-between tw:items-center tw:p-4 tw:border-b tw:border-gray-600 sidebar-submenu-header">
+92 -8
View File
@@ -9,7 +9,8 @@
{% block title %}{% if type == "current" %}{% translate 'Current Net Worth' %}{% else %}{% translate 'Projected Net Worth' %}{% endif %}{% endblock %} {% block title %}{% if type == "current" %}{% translate 'Current Net Worth' %}{% else %}{% translate 'Projected Net Worth' %}{% endif %}{% endblock %}
{% block content %} {% block content %}
<div hx-trigger="every 60m, updated from:window" hx-include="#view-type" class="show-loading" hx-get="" hx-target="body"> <div hx-trigger="every 60m, updated from:window" hx-include="#view-type" class="show-loading" hx-get=""
hx-target="body">
<div class="h-100 text-center mb-4 pt-2"> <div class="h-100 text-center mb-4 pt-2">
<div class="btn-group gap-3" role="group" id="view-type" _="on change trigger updated"> <div class="btn-group gap-3" role="group" id="view-type" _="on change trigger updated">
<input type="radio" class="btn-check" <input type="radio" class="btn-check"
@@ -32,17 +33,19 @@
class="fa-solid fa-rocket fa-fw me-2"></i>{% trans 'Projected' %}</label> class="fa-solid fa-rocket fa-fw me-2"></i>{% trans 'Projected' %}</label>
</div> </div>
</div> </div>
<div class="container px-md-3 py-3" _="init call initializeAccountChart() then initializeCurrencyChart() end"> <div class="container px-md-3 py-3"
_="init call initializeAccountChart() then initializeCurrencyChart() then initializeMonthlyDifferenceChart() end">
<div class="row gx-xl-4 gy-3 mb-4"> <div class="row gx-xl-4 gy-3 mb-4">
<div class="col-12 col-xl-5"> <div class="col-12 col-xl-5">
<div class="row row-cols-1 g-4"> <div class="row row-cols-1 g-4">
<div class="col"> <div class="col">
<c-ui.info-card color="yellow" icon="fa-solid fa-coins" title="{% trans 'By currency' %}" <c-ui.info-card color="yellow" icon="fa-solid fa-coins" title="{% trans 'By currency' %}"
title_css_classes="tw:cursor-pointer"
_="on click showAllDatasetsCurrency()"> _="on click showAllDatasetsCurrency()">
{% for currency in currency_net_worth.values %} {% for currency in currency_net_worth.values %}
<div class="d-flex justify-content-between mt-2"> <div class="d-flex justify-content-between mt-2">
<div class="d-flex align-items-baseline w-100"> <div class="d-flex align-items-baseline w-100">
<div class="currency-name text-start font-monospace tw:text-gray-300" <div class="currency-name text-start font-monospace tw:text-gray-300 tw:cursor-pointer"
_="on click showOnlyCurrencyDataset('{{ currency.currency.name }}')"> _="on click showOnlyCurrencyDataset('{{ currency.currency.name }}')">
{{ currency.currency.name }} {{ currency.currency.name }}
</div> </div>
@@ -91,10 +94,31 @@
</div> </div>
</div> </div>
<div class="col-12 col-xl-7"> <div class="col-12 col-xl-7">
<div class="chart-container position-relative tw:min-h-[40vh] tw:h-full"> <ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="evolution-tab" data-bs-toggle="tab"
data-bs-target="#evolution-tab-pane" type="button" role="tab" aria-controls="evolution-tab-pane"
aria-selected="true">{% trans 'Evolution' %}</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="diff-tab" data-bs-toggle="tab" data-bs-target="#diff-tab-pane" type="button"
role="tab" aria-controls="diff-tab-pane" aria-selected="false">{% trans 'Difference' %}</button>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="evolution-tab-pane" role="tabpanel"
aria-labelledby="evolution-tab" tabindex="0">
<div class="chart-container position-relative tw:min-h-[40vh] tw:h-full tw:w-full">
<canvas id="currencyBalanceChart"></canvas> <canvas id="currencyBalanceChart"></canvas>
</div> </div>
</div> </div>
<div class="tab-pane fade" id="diff-tab-pane" role="tabpanel" aria-labelledby="diff-tab" tabindex="0">
<div class="chart-container position-relative tw:min-h-[40vh] tw:h-full tw:w-full">
<canvas id="monthlyDifferenceChart"></canvas>
</div>
</div>
</div>
</div>
</div> </div>
<hr> <hr>
<div class="row gx-xl-4 gy-3 mt-4"> <div class="row gx-xl-4 gy-3 mt-4">
@@ -102,6 +126,7 @@
<div class="row row-cols-1 g-4"> <div class="row row-cols-1 g-4">
<div class="col"> <div class="col">
<c-ui.info-card color="blue" icon="fa-solid fa-wallet" title="{% trans 'By account' %}" <c-ui.info-card color="blue" icon="fa-solid fa-wallet" title="{% trans 'By account' %}"
title_css_classes="tw:cursor-pointer"
_="on click showAllDatasetsAccount()"> _="on click showAllDatasetsAccount()">
{% regroup account_net_worth.values by account.group as account_data %} {% regroup account_net_worth.values by account.group as account_data %}
{% for data in account_data %} {% for data in account_data %}
@@ -115,7 +140,7 @@
{% for account in data.list %} {% for account in data.list %}
<div class="d-flex justify-content-between mt-2"> <div class="d-flex justify-content-between mt-2">
<div class="d-flex align-items-baseline w-100"> <div class="d-flex align-items-baseline w-100">
<div class="account-name text-start font-monospace tw:text-gray-300" <div class="account-name text-start font-monospace tw:text-gray-300 tw:cursor-pointer"
_="on click showOnlyAccountDataset('{{ account.account.name }}')"> _="on click showOnlyAccountDataset('{{ account.account.name }}')">
<span class="hierarchy-line-icon"></span>{{ account.account.name }}</div> <span class="hierarchy-line-icon"></span>{{ account.account.name }}</div>
<div class="dotted-line flex-grow-1"></div> <div class="dotted-line flex-grow-1"></div>
@@ -143,7 +168,7 @@
{% for account in data.list %} {% for account in data.list %}
<div class="d-flex justify-content-between mt-2"> <div class="d-flex justify-content-between mt-2">
<div class="d-flex align-items-baseline w-100"> <div class="d-flex align-items-baseline w-100">
<div class="account-name text-start font-monospace tw:text-gray-300" <div class="account-name text-start font-monospace tw:text-gray-300 tw:cursor-pointer"
_="on click showOnlyAccountDataset('{{ account.account.name }}')"> _="on click showOnlyAccountDataset('{{ account.account.name }}')">
{{ account.account.name }} {{ account.account.name }}
</div> </div>
@@ -310,6 +335,54 @@
} }
</script> </script>
<script id="monthlyDifferenceChartScript">
var monthlyDifferenceChart;
function initializeMonthlyDifferenceChart() {
if (monthlyDifferenceChart) {
monthlyDifferenceChart.destroy();
}
var chartData = JSON.parse('{{ chart_data_monthly_difference_json|safe }}');
var ctx = document.getElementById('monthlyDifferenceChart').getContext('2d');
monthlyDifferenceChart = new Chart(ctx, {
type: 'bar',
data: chartData,
options: {
maintainAspectRatio: false,
responsive: true,
interaction: {
mode: 'index',
intersect: false,
},
plugins: {
title: {
display: true,
text: '{% translate "Difference" %}'
},
tooltip: {
mode: 'index',
intersect: false
}
},
scales: {
x: {
stacked: true,
},
y: {
stacked: true,
ticks: {
display: false,
format: {maximumFractionDigits: 40, minimumFractionDigits: 0}
},
}
}
}
});
}
</script>
<script type="text/hyperscript"> <script type="text/hyperscript">
def showOnlyAccountDataset(datasetName) def showOnlyAccountDataset(datasetName)
for dataset in accountChart.data.datasets for dataset in accountChart.data.datasets
@@ -325,6 +398,12 @@
call currencyChart.setDatasetVisibility(currencyChart.data.datasets.indexOf(dataset), isMatch) call currencyChart.setDatasetVisibility(currencyChart.data.datasets.indexOf(dataset), isMatch)
end end
call currencyChart.update() call currencyChart.update()
for dataset in monthlyDifferenceChart.data.datasets
set isMatch to dataset.label is datasetName
call monthlyDifferenceChart.setDatasetVisibility(monthlyDifferenceChart.data.datasets.indexOf(dataset), isMatch)
end
call monthlyDifferenceChart.update()
end end
def showAllDatasetsAccount() def showAllDatasetsAccount()
@@ -339,8 +418,13 @@
call currencyChart.setDatasetVisibility(currencyChart.data.datasets.indexOf(dataset), true) call currencyChart.setDatasetVisibility(currencyChart.data.datasets.indexOf(dataset), true)
end end
call currencyChart.update() call currencyChart.update()
for dataset in monthlyDifferenceChart.data.datasets
call monthlyDifferenceChart.setDatasetVisibility(monthlyDifferenceChart.data.datasets.indexOf(dataset), true)
end
call monthlyDifferenceChart.update()
end end
</script> </script>
</div> </div>
<c-ui.transactions_fab></c-ui.transactions_fab> <c-ui.transactions_fab></c-ui.transactions_fab>
{% endblock %} {% endblock %}
@@ -2,7 +2,7 @@
{% load i18n %} {% load i18n %}
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
{% block title %}{% translate 'Edit transaction rule' %}{% endblock %} {% block title %}{% trans 'Test' %} - {% trans 'Create' %}{% endblock %}
{% block body %} {% block body %}
<form hx-post="{% url 'transaction_rule_dry_run_created' pk=rule.id %}" hx-target="#generic-offcanvas" <form hx-post="{% url 'transaction_rule_dry_run_created' pk=rule.id %}" hx-target="#generic-offcanvas"
@@ -2,7 +2,7 @@
{% load i18n %} {% load i18n %}
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
{% block title %}{% translate 'Edit transaction rule' %}{% endblock %} {% block title %}{% trans 'Test' %} - {% trans 'Delete' %}{% endblock %}
{% block body %} {% block body %}
<form hx-post="{% url 'transaction_rule_dry_run_deleted' pk=rule.id %}" hx-target="#generic-offcanvas" <form hx-post="{% url 'transaction_rule_dry_run_deleted' pk=rule.id %}" hx-target="#generic-offcanvas"
@@ -2,7 +2,7 @@
{% load i18n %} {% load i18n %}
{% load crispy_forms_tags %} {% load crispy_forms_tags %}
{% block title %}{% translate 'Edit transaction rule' %}{% endblock %} {% block title %}{% trans 'Test' %} - {% trans 'Update' %}{% endblock %}
{% block body %} {% block body %}
<form hx-post="{% url 'transaction_rule_dry_run_updated' pk=rule.id %}" hx-target="#generic-offcanvas" <form hx-post="{% url 'transaction_rule_dry_run_updated' pk=rule.id %}" hx-target="#generic-offcanvas"
+1 -1
View File
@@ -9,7 +9,7 @@
{% block content %} {% block content %}
<div> <div>
<div class="container"> <div class="container">
<div class="row vh-100 d-flex justify-content-center align-items-center"> <div class="row tw:h-dvh d-flex justify-content-center align-items-center">
<div class="col-md-6 col-xl-4 col-12"> <div class="col-md-6 col-xl-4 col-12">
{% settings "DEMO" as demo_mode %} {% settings "DEMO" as demo_mode %}
{% if demo_mode %} {% if demo_mode %}