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

View File

@@ -36,7 +36,7 @@ class ArbitraryDecimalDisplayNumberInput(forms.TextInput):
{
"x-data": "",
"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'))",
}
)

View File

@@ -182,3 +182,29 @@ def calculate_historical_account_balance(queryset):
historical_account_balance[date_filter(end_date, "b Y")] = month_data
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

View File

@@ -8,6 +8,7 @@ from django.views.decorators.http import require_http_methods
from apps.net_worth.utils.calculate_net_worth import (
calculate_historical_currency_net_worth,
calculate_historical_account_balance,
calculate_monthly_net_worth_difference,
)
from apps.transactions.models import Transaction
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)
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(
queryset=transactions_account_queryset
)
@@ -140,6 +173,7 @@ def net_worth(request):
"chart_data_accounts_json": chart_data_accounts_json,
"accounts": accounts,
"type": view_type,
"chart_data_monthly_difference_json": chart_data_monthly_difference_json,
},
)

View File

@@ -95,9 +95,9 @@ class DryRunResults:
return
if isinstance(end_instance, Transaction):
start_instance = end_instance.deepcopy()
end_instance = end_instance.deepcopy()
elif isinstance(end_instance, dict):
start_instance = deepcopy(end_instance)
end_instance = deepcopy(end_instance)
result = {
"type": "update_or_create_transaction",
@@ -213,6 +213,16 @@ def check_for_transaction_rules(
f"{prefix}internal_id": transaction.internal_id,
f"{prefix}is_deleted": transaction.deleted,
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:
return {
@@ -256,6 +266,12 @@ def check_for_transaction_rules(
f"{prefix}internal_id": transaction.get("internal_id", ""),
f"{prefix}is_deleted": transaction.get("deleted", True),
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):
@@ -520,7 +536,8 @@ def check_for_transaction_rules(
return transaction
user = get_user_model().objects.get(id=user_id)
write_current_user(user)
if not dry_run:
write_current_user(user)
logs = [] if dry_run else None
dry_run_results = DryRunResults(dry_run=dry_run)
@@ -745,11 +762,12 @@ def check_for_transaction_rules(
"** Error while executing 'check_for_transaction_rules' task",
level="error",
)
delete_current_user()
if not dry_run:
delete_current_user()
raise e
delete_current_user()
if not dry_run:
delete_current_user()
return logs, dry_run_results.results

View File

@@ -90,4 +90,12 @@ def serialize_transaction(sender: Transaction, deleted: bool):
"internal_note": sender.internal_note,
"internal_id": sender.internal_id,
"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,
}

View File

@@ -1,3 +1,4 @@
import decimal
import logging
from copy import deepcopy
@@ -381,6 +382,9 @@ class Transaction(OwnedObject):
default_manager_name = "objects"
def clean_fields(self, *args, **kwargs):
if isinstance(self.amount, (str, int, float)):
self.amount = decimal.Decimal(str(self.amount))
self.amount = truncate_decimal(
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

View File

@@ -3,7 +3,7 @@
{% if icon %}<i class="{{ icon }}"></i>{% else %}<span class="fw-bold">{{ title.0 }}</span>{% endif %}
</div>
<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 }}
</div>
</div>

View File

@@ -354,6 +354,7 @@
display: false,
},
ticks: {
display: false,
format: { maximumFractionDigits: 40, minimumFractionDigits: 1 }
}
},
@@ -369,6 +370,7 @@
display: false,
},
ticks: {
display: false,
format: { maximumFractionDigits: 40, minimumFractionDigits: 1 }
}
}

View File

@@ -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="96x96" href="{% static 'img/favicon/favicon-96x96.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="theme-color" content="#ffffff">

View File

@@ -10,18 +10,10 @@
<nav
id="sidebar"
hx-boost="true"
hx-swap="transition: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">
{# <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">
<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"/>
@@ -154,16 +146,16 @@
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" %}">
<i class="fa-solid fa-toolbox fa-fw"></i>
<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">
{% translate 'Management' %}
</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">
{% translate 'Management' %}
</span>
<i class="fa-solid fa-chevron-right fa-fw ms-auto pe-2"></i>
</div>
</ul>
<div class="mt-auto p-2 w-100">
<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:justify-between tw:items-center tw:p-4 tw:border-b tw:border-gray-600 sidebar-submenu-header">

View File

@@ -9,338 +9,422 @@
{% block title %}{% if type == "current" %}{% translate 'Current Net Worth' %}{% else %}{% translate 'Projected Net Worth' %}{% endif %}{% endblock %}
{% block content %}
<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="btn-group gap-3" role="group" id="view-type" _="on change trigger updated">
<input type="radio" class="btn-check"
name="view_type"
id="current-view"
autocomplete="off"
value="current"
{% if type == "current" %}checked{% endif %}>
<label class="btn btn-outline-primary rounded-5" for="current-view"><i
class="fa-solid fa-sack-dollar fa-fw me-2"></i>{% trans 'Current' %}</label>
<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="btn-group gap-3" role="group" id="view-type" _="on change trigger updated">
<input type="radio" class="btn-check"
name="view_type"
id="current-view"
autocomplete="off"
value="current"
{% if type == "current" %}checked{% endif %}>
<label class="btn btn-outline-primary rounded-5" for="current-view"><i
class="fa-solid fa-sack-dollar fa-fw me-2"></i>{% trans 'Current' %}</label>
<input type="radio"
class="btn-check"
name="view_type"
id="projected-view"
autocomplete="off"
value="projected"
{% if type == "projected" %}checked{% endif %}>
<label class="btn btn-outline-primary rounded-5" for="projected-view"><i
class="fa-solid fa-rocket fa-fw me-2"></i>{% trans 'Projected' %}</label>
<input type="radio"
class="btn-check"
name="view_type"
id="projected-view"
autocomplete="off"
value="projected"
{% if type == "projected" %}checked{% endif %}>
<label class="btn btn-outline-primary rounded-5" for="projected-view"><i
class="fa-solid fa-rocket fa-fw me-2"></i>{% trans 'Projected' %}</label>
</div>
</div>
</div>
<div class="container px-md-3 py-3" _="init call initializeAccountChart() then initializeCurrencyChart() end">
<div class="row gx-xl-4 gy-3 mb-4">
<div class="col-12 col-xl-5">
<div class="row row-cols-1 g-4">
<div class="col">
<c-ui.info-card color="yellow" icon="fa-solid fa-coins" title="{% trans 'By currency' %}"
_="on click showAllDatasetsCurrency()">
{% for currency in currency_net_worth.values %}
<div class="d-flex justify-content-between mt-2">
<div class="d-flex align-items-baseline w-100">
<div class="currency-name text-start font-monospace tw:text-gray-300"
_="on click showOnlyCurrencyDataset('{{ currency.currency.name }}')">
{{ currency.currency.name }}
<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="col-12 col-xl-5">
<div class="row row-cols-1 g-4">
<div class="col">
<c-ui.info-card color="yellow" icon="fa-solid fa-coins" title="{% trans 'By currency' %}"
title_css_classes="tw:cursor-pointer"
_="on click showAllDatasetsCurrency()">
{% for currency in currency_net_worth.values %}
<div class="d-flex justify-content-between mt-2">
<div class="d-flex align-items-baseline w-100">
<div class="currency-name text-start font-monospace tw:text-gray-300 tw:cursor-pointer"
_="on click showOnlyCurrencyDataset('{{ currency.currency.name }}')">
{{ currency.currency.name }}
</div>
<div class="dotted-line flex-grow-1"></div>
<div>
<c-amount.display
:amount="currency.total_final"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="{% if currency.total_final > 0 %}green{% elif currency.total_final < 0 %}red{% endif %}"
text-end></c-amount.display>
</div>
</div>
<div class="dotted-line flex-grow-1"></div>
</div>
{% if currency.exchanged and currency.exchanged.total_final %}
<div>
<c-amount.display
:amount="currency.total_final"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="{% if currency.total_final > 0 %}green{% elif currency.total_final < 0 %}red{% endif %}"
text-end></c-amount.display>
:amount="currency.exchanged.total_final"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
text-end
color="grey"></c-amount.display>
</div>
</div>
</div>
{% if currency.exchanged and currency.exchanged.total_final %}
<div>
<c-amount.display
:amount="currency.exchanged.total_final"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
text-end
color="grey"></c-amount.display>
</div>
{% endif %}
{% if currency.consolidated and currency.consolidated.total_final != currency.total_final %}
<div class="d-flex align-items-baseline w-100">
<div class="account-name text-start font-monospace tw:text-gray-300">
<span class="hierarchy-line-icon"></span>{% trans 'Consolidated' %}</div>
<div class="dotted-line flex-grow-1"></div>
<div class="">
<c-amount.display
{% endif %}
{% if currency.consolidated and currency.consolidated.total_final != currency.total_final %}
<div class="d-flex align-items-baseline w-100">
<div class="account-name text-start font-monospace tw:text-gray-300">
<span class="hierarchy-line-icon"></span>{% trans 'Consolidated' %}</div>
<div class="dotted-line flex-grow-1"></div>
<div class="">
<c-amount.display
:amount="currency.consolidated.total_final"
:prefix="currency.consolidated.currency.prefix"
:suffix="currency.consolidated.currency.suffix"
:decimal_places="currency.consolidated.currency.decimal_places"
color="{% if currency.consolidated.total_final > 0 %}green{% elif currency.consolidated.total_final < 0 %}red{% endif %}"
text-end></c-amount.display>
</div>
</div>
{% endif %}
{% endfor %}
</c-ui.info-card>
</div>
{% endif %}
{% endfor %}
</c-ui.info-card>
</div>
</div>
</div>
<div class="col-12 col-xl-7">
<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>
</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 class="col-12 col-xl-7">
<div class="chart-container position-relative tw:min-h-[40vh] tw:h-full">
<canvas id="currencyBalanceChart"></canvas>
</div>
</div>
</div>
<hr>
<div class="row gx-xl-4 gy-3 mt-4">
<div class="col-12 col-xl-5">
<div class="row row-cols-1 g-4">
<div class="col">
<c-ui.info-card color="blue" icon="fa-solid fa-wallet" title="{% trans 'By account' %}"
_="on click showAllDatasetsAccount()">
{% regroup account_net_worth.values by account.group as account_data %}
{% for data in account_data %}
{% if data.grouper %}
<div class="d-flex justify-content-between mt-2">
<div class="d-flex align-items-baseline w-100">
<div class="text-start font-monospace tw:text-gray-300"><span class="badge text-bg-primary">
<hr>
<div class="row gx-xl-4 gy-3 mt-4">
<div class="col-12 col-xl-5">
<div class="row row-cols-1 g-4">
<div class="col">
<c-ui.info-card color="blue" icon="fa-solid fa-wallet" title="{% trans 'By account' %}"
title_css_classes="tw:cursor-pointer"
_="on click showAllDatasetsAccount()">
{% regroup account_net_worth.values by account.group as account_data %}
{% for data in account_data %}
{% if data.grouper %}
<div class="d-flex justify-content-between mt-2">
<div class="d-flex align-items-baseline w-100">
<div class="text-start font-monospace tw:text-gray-300"><span class="badge text-bg-primary">
{{ data.grouper }}</span></div>
</div>
</div>
{% for account in data.list %}
<div class="d-flex justify-content-between mt-2">
<div class="d-flex align-items-baseline w-100">
<div class="account-name text-start font-monospace tw:text-gray-300"
_="on click showOnlyAccountDataset('{{ account.account.name }}')">
<span class="hierarchy-line-icon"></span>{{ account.account.name }}</div>
<div class="dotted-line flex-grow-1"></div>
<div class="">
<c-amount.display
:amount="account.total_final"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_final > 0 %}green{% elif account.total_final < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
</div>
{% if account.exchanged and account.exchanged.total_final %}
<c-amount.display
:amount="account.exchanged.total_final"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="grey"
text-end></c-amount.display>
{% endif %}
{% endfor %}
{% else %}
{% for account in data.list %}
<div class="d-flex justify-content-between mt-2">
<div class="d-flex align-items-baseline w-100">
<div class="account-name text-start font-monospace tw:text-gray-300"
_="on click showOnlyAccountDataset('{{ account.account.name }}')">
{{ account.account.name }}
</div>
<div class="dotted-line flex-grow-1"></div>
<div>
<c-amount.display
:amount="account.total_final"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_final > 0 %}green{% elif account.total_final < 0 %}red{% endif %}"></c-amount.display>
{% for account in data.list %}
<div class="d-flex justify-content-between mt-2">
<div class="d-flex align-items-baseline w-100">
<div class="account-name text-start font-monospace tw:text-gray-300 tw:cursor-pointer"
_="on click showOnlyAccountDataset('{{ account.account.name }}')">
<span class="hierarchy-line-icon"></span>{{ account.account.name }}</div>
<div class="dotted-line flex-grow-1"></div>
<div class="">
<c-amount.display
:amount="account.total_final"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_final > 0 %}green{% elif account.total_final < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
</div>
</div>
{% if account.exchanged and account.exchanged.total_final %}
<c-amount.display
:amount="account.exchanged.total_final"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="grey"
text-end></c-amount.display>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
</c-ui.info-card>
{% if account.exchanged and account.exchanged.total_final %}
<c-amount.display
:amount="account.exchanged.total_final"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="grey"
text-end></c-amount.display>
{% endif %}
{% endfor %}
{% else %}
{% for account in data.list %}
<div class="d-flex justify-content-between mt-2">
<div class="d-flex align-items-baseline w-100">
<div class="account-name text-start font-monospace tw:text-gray-300 tw:cursor-pointer"
_="on click showOnlyAccountDataset('{{ account.account.name }}')">
{{ account.account.name }}
</div>
<div class="dotted-line flex-grow-1"></div>
<div>
<c-amount.display
:amount="account.total_final"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_final > 0 %}green{% elif account.total_final < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
</div>
{% if account.exchanged and account.exchanged.total_final %}
<c-amount.display
:amount="account.exchanged.total_final"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"
color="grey"
text-end></c-amount.display>
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}
</c-ui.info-card>
</div>
</div>
</div>
<div class="col-12 col-xl-7">
<div class="chart-container position-relative tw:min-h-[40vh] tw:h-full">
<canvas id="accountBalanceChart"></canvas>
</div>
</div>
</div>
<div class="col-12 col-xl-7">
<div class="chart-container position-relative tw:min-h-[40vh] tw:h-full">
<canvas id="accountBalanceChart"></canvas>
</div>
</div>
</div>
<script>
var currencyChart;
function initializeCurrencyChart() {
// Destroy existing chart if it exists
if (currencyChart) {
currencyChart.destroy();
}
var chartData = JSON.parse('{{ chart_data_currency_json|safe }}');
var currencies = {{ currencies|safe }};
var ctx = document.getElementById('currencyBalanceChart').getContext('2d');
currencyChart = new Chart(ctx, {
type: 'line',
data: chartData,
options: {
maintainAspectRatio: false,
responsive: true,
interaction: {
mode: 'index',
intersect: false,
},
plugins: {
title: {
display: true,
text: '{% translate 'Evolution by currency' %}'
},
tooltip: {
mode: 'index',
intersect: false
}
},
scales: {
x: {
display: true,
title: {
display: false,
}
},
...Object.fromEntries(currencies.map((currency, i) => [
`y${i}`,
{
type: 'linear',
display: true,
grid: {
drawOnChartArea: i === 0,
},
ticks: {
display: false,
format: {maximumFractionDigits: 40, minimumFractionDigits: 0}
},
border: {
display: false
}
}
]))
}
}
});
}
</script>
<script id="accountBalanceChartScript">
var accountChart;
function initializeAccountChart() {
// Destroy existing chart if it exists
if (accountChart) {
accountChart.destroy();
}
var chartData = JSON.parse('{{ chart_data_accounts_json|safe }}');
var accounts = {{ accounts|safe }};
var ctx = document.getElementById('accountBalanceChart').getContext('2d');
accountChart = new Chart(ctx, {
type: 'line',
data: chartData,
options: {
maintainAspectRatio: false,
responsive: true,
interaction: {
mode: 'index',
intersect: false,
},
stacked: false,
plugins: {
title: {
display: true,
text: '{% translate "Evolution by account" %}'
},
tooltip: {
mode: 'index',
intersect: false
}
},
scales: {
x: {
display: true,
title: {
display: false,
}
},
...Object.fromEntries(accounts.map((account, i) => [
`y-axis-${i}`,
{
type: 'linear',
display: true,
position: i % 2 === 0 ? 'left' : 'right',
grid: {
drawOnChartArea: i === 0,
},
ticks: {
display: false,
format: {maximumFractionDigits: 40, minimumFractionDigits: 0}
},
border: {
display: false
}
}
]))
}
}
});
}
</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">
def showOnlyAccountDataset(datasetName)
for dataset in accountChart.data.datasets
set isMatch to dataset.label is datasetName
call accountChart.setDatasetVisibility(accountChart.data.datasets.indexOf(dataset), isMatch)
end
call accountChart.update()
end
def showOnlyCurrencyDataset(datasetName)
for dataset in currencyChart.data.datasets
set isMatch to dataset.label is datasetName
call currencyChart.setDatasetVisibility(currencyChart.data.datasets.indexOf(dataset), isMatch)
end
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
def showAllDatasetsAccount()
for dataset in accountChart.data.datasets
call accountChart.setDatasetVisibility(accountChart.data.datasets.indexOf(dataset), true)
end
call accountChart.update()
end
def showAllDatasetsCurrency()
for dataset in currencyChart.data.datasets
call currencyChart.setDatasetVisibility(currencyChart.data.datasets.indexOf(dataset), true)
end
call currencyChart.update()
for dataset in monthlyDifferenceChart.data.datasets
call monthlyDifferenceChart.setDatasetVisibility(monthlyDifferenceChart.data.datasets.indexOf(dataset), true)
end
call monthlyDifferenceChart.update()
end
</script>
</div>
<script>
var currencyChart;
function initializeCurrencyChart() {
// Destroy existing chart if it exists
if (currencyChart) {
currencyChart.destroy();
}
var chartData = JSON.parse('{{ chart_data_currency_json|safe }}');
var currencies = {{ currencies|safe }};
var ctx = document.getElementById('currencyBalanceChart').getContext('2d');
currencyChart = new Chart(ctx, {
type: 'line',
data: chartData,
options: {
maintainAspectRatio: false,
responsive: true,
interaction: {
mode: 'index',
intersect: false,
},
plugins: {
title: {
display: true,
text: '{% translate 'Evolution by currency' %}'
},
tooltip: {
mode: 'index',
intersect: false
}
},
scales: {
x: {
display: true,
title: {
display: false,
}
},
...Object.fromEntries(currencies.map((currency, i) => [
`y${i}`,
{
type: 'linear',
display: true,
grid: {
drawOnChartArea: i === 0,
},
ticks: {
display: false,
format: {maximumFractionDigits: 40, minimumFractionDigits: 0}
},
border: {
display: false
}
}
]))
}
}
});
}
</script>
<script id="accountBalanceChartScript">
var accountChart;
function initializeAccountChart() {
// Destroy existing chart if it exists
if (accountChart) {
accountChart.destroy();
}
var chartData = JSON.parse('{{ chart_data_accounts_json|safe }}');
var accounts = {{ accounts|safe }};
var ctx = document.getElementById('accountBalanceChart').getContext('2d');
accountChart = new Chart(ctx, {
type: 'line',
data: chartData,
options: {
maintainAspectRatio: false,
responsive: true,
interaction: {
mode: 'index',
intersect: false,
},
stacked: false,
plugins: {
title: {
display: true,
text: '{% translate "Evolution by account" %}'
},
tooltip: {
mode: 'index',
intersect: false
}
},
scales: {
x: {
display: true,
title: {
display: false,
}
},
...Object.fromEntries(accounts.map((account, i) => [
`y-axis-${i}`,
{
type: 'linear',
display: true,
position: i % 2 === 0 ? 'left' : 'right',
grid: {
drawOnChartArea: i === 0,
},
ticks: {
display: false,
format: {maximumFractionDigits: 40, minimumFractionDigits: 0}
},
border: {
display: false
}
}
]))
}
}
});
}
</script>
<script type="text/hyperscript">
def showOnlyAccountDataset(datasetName)
for dataset in accountChart.data.datasets
set isMatch to dataset.label is datasetName
call accountChart.setDatasetVisibility(accountChart.data.datasets.indexOf(dataset), isMatch)
end
call accountChart.update()
end
def showOnlyCurrencyDataset(datasetName)
for dataset in currencyChart.data.datasets
set isMatch to dataset.label is datasetName
call currencyChart.setDatasetVisibility(currencyChart.data.datasets.indexOf(dataset), isMatch)
end
call currencyChart.update()
end
def showAllDatasetsAccount()
for dataset in accountChart.data.datasets
call accountChart.setDatasetVisibility(accountChart.data.datasets.indexOf(dataset), true)
end
call accountChart.update()
end
def showAllDatasetsCurrency()
for dataset in currencyChart.data.datasets
call currencyChart.setDatasetVisibility(currencyChart.data.datasets.indexOf(dataset), true)
end
call currencyChart.update()
end
</script>
</div>
<c-ui.transactions_fab></c-ui.transactions_fab>
<c-ui.transactions_fab></c-ui.transactions_fab>
{% endblock %}

View File

@@ -2,7 +2,7 @@
{% load i18n %}
{% load crispy_forms_tags %}
{% block title %}{% translate 'Edit transaction rule' %}{% endblock %}
{% block title %}{% trans 'Test' %} - {% trans 'Create' %}{% endblock %}
{% block body %}
<form hx-post="{% url 'transaction_rule_dry_run_created' pk=rule.id %}" hx-target="#generic-offcanvas"

View File

@@ -2,7 +2,7 @@
{% load i18n %}
{% load crispy_forms_tags %}
{% block title %}{% translate 'Edit transaction rule' %}{% endblock %}
{% block title %}{% trans 'Test' %} - {% trans 'Delete' %}{% endblock %}
{% block body %}
<form hx-post="{% url 'transaction_rule_dry_run_deleted' pk=rule.id %}" hx-target="#generic-offcanvas"

View File

@@ -2,7 +2,7 @@
{% load i18n %}
{% load crispy_forms_tags %}
{% block title %}{% translate 'Edit transaction rule' %}{% endblock %}
{% block title %}{% trans 'Test' %} - {% trans 'Update' %}{% endblock %}
{% block body %}
<form hx-post="{% url 'transaction_rule_dry_run_updated' pk=rule.id %}" hx-target="#generic-offcanvas"

View File

@@ -9,7 +9,7 @@
{% block content %}
<div>
<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">
{% settings "DEMO" as demo_mode %}
{% if demo_mode %}