Merge pull request #164

feat: insights page
This commit is contained in:
Herculino Trotta
2025-02-16 00:14:56 -03:00
committed by GitHub
26 changed files with 1205 additions and 114 deletions

View File

@@ -49,4 +49,5 @@ urlpatterns = [
path("", include("apps.dca.urls")), path("", include("apps.dca.urls")),
path("", include("apps.mini_tools.urls")), path("", include("apps.mini_tools.urls")),
path("", include("apps.import_app.urls")), path("", include("apps.import_app.urls")),
path("", include("apps.insights.urls")),
] ]

View File

@@ -227,3 +227,56 @@ class AirMonthYearPickerInput(AirDatePickerInput):
except (ValueError, KeyError): except (ValueError, KeyError):
return None return None
return None return None
class AirYearPickerInput(AirDatePickerInput):
def __init__(self, attrs=None, format=None, *args, **kwargs):
super().__init__(attrs=attrs, format=format, *args, **kwargs)
# Store the display format for AirDatepicker
self.display_format = "yyyy"
# Store the Python format for internal use
self.python_format = "%Y"
def build_attrs(self, base_attrs, extra_attrs=None):
attrs = super().build_attrs(base_attrs, extra_attrs)
# Add data attributes for AirDatepicker configuration
attrs["data-now-button-txt"] = _("Today")
attrs["data-date-format"] = "yyyy"
return attrs
def format_value(self, value):
"""Format the value for display in the widget."""
if value:
self.attrs["data-value"] = (
value # We use this to dynamically select the initial date on AirDatePicker
)
if value is None:
return ""
if isinstance(value, str):
try:
value = datetime.datetime.strptime(value, "%Y-%m-%d").date()
except ValueError:
return value
if isinstance(value, (datetime.datetime, datetime.date)):
# Use Django's date translation
return f"{value.year}"
return value
def value_from_datadict(self, data, files, name):
"""Convert the value from the widget format back to a format Django can handle."""
value = super().value_from_datadict(data, files, name)
if value:
try:
# Split the value into month name and year
year_str = value
year = int(year_str)
if year:
# Return the first day of the month in Django's expected format
return datetime.date(year, 1, 1).strftime("%Y-%m-%d")
except (ValueError, KeyError):
return None
return None

View File

View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class InsightsConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "apps.insights"

110
app/apps/insights/forms.py Normal file
View File

@@ -0,0 +1,110 @@
from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Field, Row, Column
from django import forms
from django.utils.translation import gettext_lazy as _
from apps.common.widgets.datepicker import (
AirMonthYearPickerInput,
AirYearPickerInput,
AirDatePickerInput,
)
class SingleMonthForm(forms.Form):
month = forms.DateField(
widget=AirMonthYearPickerInput(clear_button=False), label="", required=False
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.disable_csrf = True
self.helper.layout = Layout(Field("month"))
class SingleYearForm(forms.Form):
year = forms.DateField(
widget=AirYearPickerInput(clear_button=False), label="", required=False
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.disable_csrf = True
self.helper.layout = Layout(Field("year"))
class MonthRangeForm(forms.Form):
month_from = forms.DateField(
widget=AirMonthYearPickerInput(clear_button=False), label="", required=False
)
month_to = forms.DateField(
widget=AirMonthYearPickerInput(clear_button=False), label="", required=False
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.disable_csrf = True
self.helper.layout = Layout(
Row(
Column("month_from", css_class="form-group col-md-6"),
Column("month_to", css_class="form-group col-md-6"),
),
)
class YearRangeForm(forms.Form):
year_from = forms.DateField(
widget=AirYearPickerInput(clear_button=False), label="", required=False
)
year_to = forms.DateField(
widget=AirYearPickerInput(clear_button=False), label="", required=False
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.disable_csrf = True
self.helper.layout = Layout(
Row(
Column("year_from", css_class="form-group col-md-6"),
Column("year_to", css_class="form-group col-md-6"),
),
)
class DateRangeForm(forms.Form):
date_from = forms.DateField(
widget=AirDatePickerInput(clear_button=False), label="", required=False
)
date_to = forms.DateField(
widget=AirDatePickerInput(clear_button=False), label="", required=False
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.disable_csrf = True
self.helper.layout = Layout(
Row(
Column("date_from", css_class="form-group col-md-6"),
Column("date_to", css_class="form-group col-md-6"),
css_class="mb-0",
),
)

View File

View File

@@ -0,0 +1,3 @@
from django.db import models
# Create your models here.

View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

17
app/apps/insights/urls.py Normal file
View File

@@ -0,0 +1,17 @@
from django.urls import path
from . import views
urlpatterns = [
path("insights/", views.index, name="insights_index"),
path(
"insights/sankey/account/",
views.sankey_by_account,
name="insights_sankey_by_account",
),
path(
"insights/sankey/currency/",
views.sankey_by_currency,
name="insights_sankey_by_currency",
),
]

View File

View File

@@ -0,0 +1,248 @@
from django.utils.translation import gettext_lazy as _
from decimal import Decimal
from typing import Dict, List, TypedDict
class SankeyNode(TypedDict):
name: str
class SankeyFlow(TypedDict):
from_node: str
to_node: str
flow: float
currency: Dict
original_amount: float
percentage: float
def generate_sankey_data_by_account(transactions_queryset):
"""
Generates Sankey diagram data from transaction queryset using account as intermediary.
"""
nodes: Dict[str, Dict] = {}
flows: List[SankeyFlow] = []
# Aggregate transactions
income_data = {} # {(category, currency, account) -> amount}
expense_data = {} # {(category, currency, account) -> amount}
total_income_by_currency = {} # {currency -> amount}
total_expense_by_currency = {} # {currency -> amount}
total_volume_by_currency = {} # {currency -> amount}
for transaction in transactions_queryset:
currency = transaction.account.currency
account = transaction.account
category = transaction.category or _("Uncategorized")
key = (category, currency, account)
amount = transaction.amount
if transaction.type == "IN":
income_data[key] = income_data.get(key, Decimal("0")) + amount
total_income_by_currency[currency] = (
total_income_by_currency.get(currency, Decimal("0")) + amount
)
else:
expense_data[key] = expense_data.get(key, Decimal("0")) + amount
total_expense_by_currency[currency] = (
total_expense_by_currency.get(currency, Decimal("0")) + amount
)
total_volume_by_currency[currency] = (
total_volume_by_currency.get(currency, Decimal("0")) + amount
)
def get_node_id(node_type: str, name: str, account_id: int) -> str:
"""Generate unique node ID."""
return f"{node_type}_{name}_{account_id}".lower().replace(" ", "_")
def add_node(node_id: str, display_name: str) -> None:
"""Add node with both ID and display name."""
nodes[node_id] = {"id": node_id, "name": display_name}
def add_flow(
from_node_id: str, to_node_id: str, amount: Decimal, currency, is_income: bool
) -> None:
"""
Add flow with percentage based on total transaction volume for the specific currency.
"""
total_volume = total_volume_by_currency.get(currency, Decimal("0"))
percentage = (amount / total_volume) * 100 if total_volume else 0
scaled_flow = percentage / 100
flows.append(
{
"from_node": from_node_id,
"to_node": to_node_id,
"flow": float(scaled_flow),
"currency": {
"code": currency.code,
"prefix": currency.prefix,
"suffix": currency.suffix,
"decimal_places": currency.decimal_places,
},
"original_amount": float(amount),
"percentage": float(percentage),
}
)
# Process income
for (category, currency, account), amount in income_data.items():
category_node_id = get_node_id("income", category, account.id)
account_node_id = get_node_id("account", account.name, account.id)
add_node(category_node_id, str(category))
add_node(account_node_id, account.name)
add_flow(category_node_id, account_node_id, amount, currency, is_income=True)
# Process expenses
for (category, currency, account), amount in expense_data.items():
category_node_id = get_node_id("expense", category, account.id)
account_node_id = get_node_id("account", account.name, account.id)
add_node(category_node_id, str(category))
add_node(account_node_id, account.name)
add_flow(account_node_id, category_node_id, amount, currency, is_income=False)
# Calculate and add savings flows
savings_data = {} # {(account, currency) -> amount}
for (category, currency, account), amount in income_data.items():
key = (account, currency)
savings_data[key] = savings_data.get(key, Decimal("0")) + amount
for (category, currency, account), amount in expense_data.items():
key = (account, currency)
savings_data[key] = savings_data.get(key, Decimal("0")) - amount
for (account, currency), amount in savings_data.items():
if amount > 0:
account_node_id = get_node_id("account", account.name, account.id)
savings_node_id = get_node_id("savings", _("Saved"), account.id)
add_node(savings_node_id, str(_("Saved")))
add_flow(account_node_id, savings_node_id, amount, currency, is_income=True)
# Calculate total across all currencies (for reference only)
total_amount = sum(float(amount) for amount in total_income_by_currency.values())
return {
"nodes": list(nodes.values()),
"flows": flows,
"total_amount": total_amount,
"total_by_currency": {
curr.code: float(amount)
for curr, amount in total_income_by_currency.items()
},
}
def generate_sankey_data_by_currency(transactions_queryset):
"""
Generates Sankey diagram data from transaction queryset, using currency as intermediary.
"""
nodes: Dict[str, Dict] = {}
flows: List[SankeyFlow] = []
# Aggregate transactions
income_data = {} # {(category, currency) -> amount}
expense_data = {} # {(category, currency) -> amount}
total_income_by_currency = {} # {currency -> amount}
total_expense_by_currency = {} # {currency -> amount}
total_volume_by_currency = {} # {currency -> amount}
for transaction in transactions_queryset:
currency = transaction.account.currency
category = transaction.category or _("Uncategorized")
key = (category, currency)
amount = transaction.amount
if transaction.type == "IN":
income_data[key] = income_data.get(key, Decimal("0")) + amount
total_income_by_currency[currency] = (
total_income_by_currency.get(currency, Decimal("0")) + amount
)
else:
expense_data[key] = expense_data.get(key, Decimal("0")) + amount
total_expense_by_currency[currency] = (
total_expense_by_currency.get(currency, Decimal("0")) + amount
)
total_volume_by_currency[currency] = (
total_volume_by_currency.get(currency, Decimal("0")) + amount
)
def get_node_id(node_type: str, name: str, currency_id: int) -> str:
"""Generate unique node ID including currency information."""
return f"{node_type}_{name}_{currency_id}".lower().replace(" ", "_")
def add_node(node_id: str, display_name: str) -> None:
"""Add node with both ID and display name."""
nodes[node_id] = {"id": node_id, "name": display_name}
def add_flow(
from_node_id: str, to_node_id: str, amount: Decimal, currency, is_income: bool
) -> None:
"""
Add flow with percentage based on total transaction volume for the specific currency.
"""
total_volume = total_volume_by_currency.get(currency, Decimal("0"))
percentage = (amount / total_volume) * 100 if total_volume else 0
scaled_flow = percentage / 100
flows.append(
{
"from_node": from_node_id,
"to_node": to_node_id,
"flow": float(scaled_flow),
"currency": {
"code": currency.code,
"name": currency.name,
"prefix": currency.prefix,
"suffix": currency.suffix,
"decimal_places": currency.decimal_places,
},
"original_amount": float(amount),
"percentage": float(percentage),
}
)
# Process income
for (category, currency), amount in income_data.items():
category_node_id = get_node_id("income", category, currency.id)
currency_node_id = get_node_id("currency", currency.name, currency.id)
add_node(category_node_id, str(category))
add_node(currency_node_id, currency.name)
add_flow(category_node_id, currency_node_id, amount, currency, is_income=True)
# Process expenses
for (category, currency), amount in expense_data.items():
category_node_id = get_node_id("expense", category, currency.id)
currency_node_id = get_node_id("currency", currency.name, currency.id)
add_node(category_node_id, str(category))
add_node(currency_node_id, currency.name)
add_flow(currency_node_id, category_node_id, amount, currency, is_income=False)
# Calculate and add savings flows
savings_data = {} # {currency -> amount}
for (category, currency), amount in income_data.items():
savings_data[currency] = savings_data.get(currency, Decimal("0")) + amount
for (category, currency), amount in expense_data.items():
savings_data[currency] = savings_data.get(currency, Decimal("0")) - amount
for currency, amount in savings_data.items():
if amount > 0:
currency_node_id = get_node_id("currency", currency.name, currency.id)
savings_node_id = get_node_id("savings", _("Saved"), currency.id)
add_node(savings_node_id, str(_("Saved")))
add_flow(
currency_node_id, savings_node_id, amount, currency, is_income=True
)
# Calculate total across all currencies (for reference only)
total_amount = sum(float(amount) for amount in total_income_by_currency.values())
return {
"nodes": list(nodes.values()),
"flows": flows,
"total_amount": total_amount,
"total_by_currency": {
curr.name: float(amount)
for curr, amount in total_income_by_currency.items()
},
}

View File

@@ -0,0 +1,96 @@
from django.db.models import Q
from django.utils import timezone
from dateutil.relativedelta import relativedelta
from apps.transactions.models import Transaction
from apps.insights.forms import (
SingleMonthForm,
SingleYearForm,
MonthRangeForm,
YearRangeForm,
DateRangeForm,
)
def get_transactions(request, include_unpaid=True, include_silent=False):
transactions = Transaction.objects.all()
filter_type = request.GET.get("type", None)
if filter_type is not None:
if filter_type == "month":
form = SingleMonthForm(request.GET)
if form.is_valid():
month = form.cleaned_data["month"].replace(day=1)
else:
month = timezone.localdate(timezone.now()).replace(day=1)
transactions = transactions.filter(
reference_date__month=month.month, reference_date__year=month.year
)
elif filter_type == "year":
form = SingleYearForm(request.GET)
if form.is_valid():
year = form.cleaned_data["year"].replace(day=1, month=1)
else:
year = timezone.localdate(timezone.now()).replace(day=1, month=1)
transactions = transactions.filter(reference_date__year=year.year)
elif filter_type == "month-range":
form = MonthRangeForm(request.GET)
if form.is_valid():
month_from = form.cleaned_data["month_from"].replace(day=1)
month_to = form.cleaned_data["month_to"].replace(day=1)
else:
month_from = timezone.localdate(timezone.now()).replace(day=1)
month_to = (
timezone.localdate(timezone.now()) + relativedelta(months=1)
).replace(day=1)
transactions = transactions.filter(
reference_date__gte=month_from,
reference_date__lte=month_to,
)
elif filter_type == "year-range":
form = YearRangeForm(request.GET)
if form.is_valid():
year_from = form.cleaned_data["year_from"].replace(day=1, month=1)
year_to = form.cleaned_data["year_to"].replace(day=31, month=12)
else:
year_from = timezone.localdate(timezone.now()).replace(day=1, month=1)
year_to = (
timezone.localdate(timezone.now()) + relativedelta(years=1)
).replace(day=31, month=12)
transactions = transactions.filter(
reference_date__gte=year_from,
reference_date__lte=year_to,
)
elif filter_type == "date-range":
form = DateRangeForm(request.GET)
if form.is_valid():
date_from = form.cleaned_data["date_from"]
date_to = form.cleaned_data["date_to"]
else:
date_from = timezone.localdate(timezone.now())
date_to = timezone.localdate(timezone.now()) + relativedelta(months=1)
transactions = transactions.filter(
date__gte=date_from,
date__lte=date_to,
)
else: # Default to current month
month = timezone.localdate(timezone.now())
transactions = transactions.filter(
reference_date__month=month.month, reference_date__year=month.year
)
if not include_unpaid:
transactions = transactions.filter(is_paid=True)
if not include_silent:
transactions = transactions.exclude(Q(category__mute=True) & ~Q(category=None))
return transactions

View File

@@ -0,0 +1,94 @@
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from django.utils import timezone
from django.views.decorators.http import require_http_methods
from dateutil.relativedelta import relativedelta
from apps.transactions.models import Transaction
from apps.insights.utils.sankey import (
generate_sankey_data_by_account,
generate_sankey_data_by_currency,
)
from apps.insights.forms import (
SingleMonthForm,
SingleYearForm,
MonthRangeForm,
YearRangeForm,
DateRangeForm,
)
from apps.common.decorators.htmx import only_htmx
from apps.insights.utils.transactions import get_transactions
@login_required
@require_http_methods(["GET"])
def index(request):
date = timezone.localdate(timezone.now())
month_form = SingleMonthForm(initial={"month": date.replace(day=1)})
year_form = SingleYearForm(initial={"year": date.replace(day=1)})
month_range_form = MonthRangeForm(
initial={
"month_from": date.replace(day=1),
"month_to": date.replace(day=1) + relativedelta(months=1),
}
)
year_range_form = YearRangeForm(
initial={
"year_from": date.replace(day=1, month=1),
"year_to": date.replace(day=1, month=1) + relativedelta(years=1),
}
)
date_range_form = DateRangeForm(
initial={
"date_from": date,
"date_to": date + relativedelta(months=1),
}
)
return render(
request,
"insights/pages/index.html",
context={
"month_form": month_form,
"year_form": year_form,
"month_range_form": month_range_form,
"year_range_form": year_range_form,
"date_range_form": date_range_form,
},
)
@only_htmx
@login_required
@require_http_methods(["GET", "POST"])
def sankey_by_account(request):
# Get filtered transactions
transactions = get_transactions(request)
# Generate Sankey data
sankey_data = generate_sankey_data_by_account(transactions)
return render(
request,
"insights/fragments/sankey.html",
{"sankey_data": sankey_data, "type": "account"},
)
@only_htmx
@login_required
@require_http_methods(["GET", "POST"])
def sankey_by_currency(request):
# Get filtered transactions
transactions = get_transactions(request)
# Generate Sankey data
sankey_data = generate_sankey_data_by_currency(transactions)
return render(
request,
"insights/fragments/sankey.html",
{"sankey_data": sankey_data, "type": "currency"},
)

View File

@@ -555,6 +555,4 @@ def get_recent_transactions(request, filter_type=None):
for t in queryset: for t in queryset:
data.append({"text": str(t), "value": str(t.id)}) data.append({"text": str(t), "value": str(t.id)})
print(data)
return JsonResponse(data, safe=False) return JsonResponse(data, safe=False)

View File

@@ -2,13 +2,13 @@
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-15 00:38-0300\n" "POT-Creation-Date: 2025-02-16 00:03-0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -85,7 +85,7 @@ msgstr ""
#: apps/transactions/forms.py:315 apps/transactions/forms.py:471 #: apps/transactions/forms.py:315 apps/transactions/forms.py:471
#: apps/transactions/forms.py:716 apps/transactions/models.py:209 #: apps/transactions/forms.py:716 apps/transactions/models.py:209
#: apps/transactions/models.py:380 apps/transactions/models.py:562 #: apps/transactions/models.py:380 apps/transactions/models.py:562
#: templates/includes/navbar.html:105 templates/tags/fragments/list.html:5 #: templates/includes/navbar.html:108 templates/tags/fragments/list.html:5
#: templates/tags/pages/index.html:4 #: templates/tags/pages/index.html:4
msgid "Tags" msgid "Tags"
msgstr "" msgstr ""
@@ -114,7 +114,7 @@ msgstr ""
#: apps/accounts/models.py:13 templates/account_groups/fragments/list.html:5 #: apps/accounts/models.py:13 templates/account_groups/fragments/list.html:5
#: templates/account_groups/pages/index.html:4 #: templates/account_groups/pages/index.html:4
#: templates/includes/navbar.html:115 #: templates/includes/navbar.html:118
msgid "Account Groups" msgid "Account Groups"
msgstr "" msgstr ""
@@ -162,8 +162,8 @@ msgstr ""
#: apps/accounts/models.py:60 apps/transactions/filters.py:53 #: apps/accounts/models.py:60 apps/transactions/filters.py:53
#: templates/accounts/fragments/list.html:5 #: templates/accounts/fragments/list.html:5
#: templates/accounts/pages/index.html:4 templates/includes/navbar.html:111 #: templates/accounts/pages/index.html:4 templates/includes/navbar.html:114
#: templates/includes/navbar.html:113 #: templates/includes/navbar.html:116
#: templates/monthly_overview/pages/overview.html:94 #: templates/monthly_overview/pages/overview.html:94
#: templates/transactions/fragments/summary.html:12 #: templates/transactions/fragments/summary.html:12
#: templates/transactions/pages/transactions.html:72 #: templates/transactions/pages/transactions.html:72
@@ -332,6 +332,7 @@ msgid "Cache cleared successfully"
msgstr "" msgstr ""
#: apps/common/widgets/datepicker.py:47 apps/common/widgets/datepicker.py:186 #: apps/common/widgets/datepicker.py:47 apps/common/widgets/datepicker.py:186
#: apps/common/widgets/datepicker.py:244
msgid "Today" msgid "Today"
msgstr "" msgstr ""
@@ -386,8 +387,8 @@ msgstr ""
#: apps/currencies/models.py:40 apps/transactions/filters.py:60 #: apps/currencies/models.py:40 apps/transactions/filters.py:60
#: templates/currencies/fragments/list.html:5 #: templates/currencies/fragments/list.html:5
#: templates/currencies/pages/index.html:4 templates/includes/navbar.html:119 #: templates/currencies/pages/index.html:4 templates/includes/navbar.html:122
#: templates/includes/navbar.html:121 #: templates/includes/navbar.html:124
#: templates/monthly_overview/pages/overview.html:81 #: templates/monthly_overview/pages/overview.html:81
#: templates/transactions/fragments/summary.html:8 #: templates/transactions/fragments/summary.html:8
#: templates/transactions/pages/transactions.html:59 #: templates/transactions/pages/transactions.html:59
@@ -416,7 +417,7 @@ msgstr ""
#: apps/currencies/models.py:74 templates/exchange_rates/fragments/list.html:6 #: apps/currencies/models.py:74 templates/exchange_rates/fragments/list.html:6
#: templates/exchange_rates/pages/index.html:4 #: templates/exchange_rates/pages/index.html:4
#: templates/includes/navbar.html:123 #: templates/includes/navbar.html:126
msgid "Exchange Rates" msgid "Exchange Rates"
msgstr "" msgstr ""
@@ -676,7 +677,7 @@ msgstr ""
#: apps/import_app/forms.py:61 #: apps/import_app/forms.py:61
#: templates/import_app/fragments/profiles/list.html:62 #: templates/import_app/fragments/profiles/list.html:62
#: templates/includes/navbar.html:131 #: templates/includes/navbar.html:134
msgid "Import" msgid "Import"
msgstr "" msgstr ""
@@ -744,6 +745,15 @@ msgstr ""
msgid "Run deleted successfully" msgid "Run deleted successfully"
msgstr "" msgstr ""
#: apps/insights/utils/sankey.py:37 apps/insights/utils/sankey.py:154
msgid "Uncategorized"
msgstr ""
#: apps/insights/utils/sankey.py:118 apps/insights/utils/sankey.py:119
#: apps/insights/utils/sankey.py:234 apps/insights/utils/sankey.py:235
msgid "Saved"
msgstr ""
#: apps/rules/forms.py:20 #: apps/rules/forms.py:20
msgid "Run on creation" msgid "Run on creation"
msgstr "" msgstr ""
@@ -760,7 +770,7 @@ msgstr ""
msgid "Set field" msgid "Set field"
msgstr "" msgstr ""
#: apps/rules/forms.py:65 #: apps/rules/forms.py:65 templates/insights/fragments/sankey.html:84
msgid "To" msgid "To"
msgstr "" msgstr ""
@@ -801,7 +811,7 @@ msgstr ""
#: apps/rules/forms.py:165 apps/rules/forms.py:178 apps/rules/models.py:29 #: apps/rules/forms.py:165 apps/rules/forms.py:178 apps/rules/models.py:29
#: apps/rules/models.py:256 apps/transactions/models.py:192 #: apps/rules/models.py:256 apps/transactions/models.py:192
#: apps/transactions/models.py:551 #: apps/transactions/models.py:551 templates/insights/fragments/sankey.html:85
msgid "Amount" msgid "Amount"
msgstr "" msgstr ""
@@ -828,7 +838,7 @@ msgstr ""
#: apps/transactions/forms.py:731 apps/transactions/models.py:161 #: apps/transactions/forms.py:731 apps/transactions/models.py:161
#: apps/transactions/models.py:214 apps/transactions/models.py:383 #: apps/transactions/models.py:214 apps/transactions/models.py:383
#: apps/transactions/models.py:565 templates/entities/fragments/list.html:5 #: apps/transactions/models.py:565 templates/entities/fragments/list.html:5
#: templates/entities/pages/index.html:4 templates/includes/navbar.html:107 #: templates/entities/pages/index.html:4 templates/includes/navbar.html:110
msgid "Entities" msgid "Entities"
msgstr "" msgstr ""
@@ -982,7 +992,7 @@ msgid "Transaction Type"
msgstr "" msgstr ""
#: apps/transactions/filters.py:67 templates/categories/fragments/list.html:5 #: apps/transactions/filters.py:67 templates/categories/fragments/list.html:5
#: templates/categories/pages/index.html:4 templates/includes/navbar.html:103 #: templates/categories/pages/index.html:4 templates/includes/navbar.html:106
msgid "Categories" msgid "Categories"
msgstr "" msgstr ""
@@ -1119,8 +1129,8 @@ msgstr ""
msgid "Transaction" msgid "Transaction"
msgstr "" msgstr ""
#: apps/transactions/models.py:256 templates/includes/navbar.html:54 #: apps/transactions/models.py:256 templates/includes/navbar.html:57
#: templates/includes/navbar.html:101 #: templates/includes/navbar.html:104
#: templates/recurring_transactions/fragments/list_transactions.html:5 #: templates/recurring_transactions/fragments/list_transactions.html:5
#: templates/recurring_transactions/fragments/table.html:37 #: templates/recurring_transactions/fragments/table.html:37
#: templates/transactions/pages/transactions.html:5 #: templates/transactions/pages/transactions.html:5
@@ -1184,7 +1194,7 @@ msgstr ""
msgid "Installment Amount" msgid "Installment Amount"
msgstr "" msgstr ""
#: apps/transactions/models.py:391 templates/includes/navbar.html:69 #: apps/transactions/models.py:391 templates/includes/navbar.html:72
#: templates/installment_plans/fragments/list.html:5 #: templates/installment_plans/fragments/list.html:5
#: templates/installment_plans/pages/index.html:4 #: templates/installment_plans/pages/index.html:4
msgid "Installment Plans" msgid "Installment Plans"
@@ -1227,7 +1237,7 @@ msgstr ""
msgid "Last Generated Reference Date" msgid "Last Generated Reference Date"
msgstr "" msgstr ""
#: apps/transactions/models.py:592 templates/includes/navbar.html:71 #: apps/transactions/models.py:592 templates/includes/navbar.html:74
#: templates/recurring_transactions/fragments/list.html:5 #: templates/recurring_transactions/fragments/list.html:5
#: templates/recurring_transactions/pages/index.html:4 #: templates/recurring_transactions/pages/index.html:4
msgid "Recurring Transactions" msgid "Recurring Transactions"
@@ -2070,7 +2080,7 @@ msgid "Edit exchange rate"
msgstr "" msgstr ""
#: templates/exchange_rates/fragments/list.html:25 #: templates/exchange_rates/fragments/list.html:25
#: templates/includes/navbar.html:58 #: templates/includes/navbar.html:61
#: templates/installment_plans/fragments/list.html:21 #: templates/installment_plans/fragments/list.html:21
#: templates/yearly_overview/pages/overview_by_account.html:92 #: templates/yearly_overview/pages/overview_by_account.html:92
#: templates/yearly_overview/pages/overview_by_currency.html:94 #: templates/yearly_overview/pages/overview_by_currency.html:94
@@ -2100,7 +2110,7 @@ msgstr ""
#: templates/exchange_rates_services/fragments/list.html:6 #: templates/exchange_rates_services/fragments/list.html:6
#: templates/exchange_rates_services/pages/index.html:4 #: templates/exchange_rates_services/pages/index.html:4
#: templates/includes/navbar.html:133 #: templates/includes/navbar.html:136
msgid "Automatic Exchange Rates" msgid "Automatic Exchange Rates"
msgstr "" msgstr ""
@@ -2237,52 +2247,56 @@ msgstr ""
msgid "Current" msgid "Current"
msgstr "" msgstr ""
#: templates/includes/navbar.html:63 #: templates/includes/navbar.html:50
msgid "Insights"
msgstr ""
#: templates/includes/navbar.html:66
msgid "Trash Can" msgid "Trash Can"
msgstr "" msgstr ""
#: templates/includes/navbar.html:79 #: templates/includes/navbar.html:82
msgid "Tools" msgid "Tools"
msgstr "" msgstr ""
#: templates/includes/navbar.html:83 #: templates/includes/navbar.html:86
msgid "Dollar Cost Average Tracker" msgid "Dollar Cost Average Tracker"
msgstr "" msgstr ""
#: templates/includes/navbar.html:86 #: templates/includes/navbar.html:89
#: templates/mini_tools/unit_price_calculator.html:5 #: templates/mini_tools/unit_price_calculator.html:5
#: templates/mini_tools/unit_price_calculator.html:10 #: templates/mini_tools/unit_price_calculator.html:10
msgid "Unit Price Calculator" msgid "Unit Price Calculator"
msgstr "" msgstr ""
#: templates/includes/navbar.html:89 #: templates/includes/navbar.html:92
#: templates/mini_tools/currency_converter/currency_converter.html:8 #: templates/mini_tools/currency_converter/currency_converter.html:8
#: templates/mini_tools/currency_converter/currency_converter.html:15 #: templates/mini_tools/currency_converter/currency_converter.html:15
msgid "Currency Converter" msgid "Currency Converter"
msgstr "" msgstr ""
#: templates/includes/navbar.html:98 #: templates/includes/navbar.html:101
msgid "Management" msgid "Management"
msgstr "" msgstr ""
#: templates/includes/navbar.html:127 #: templates/includes/navbar.html:130
msgid "Automation" msgid "Automation"
msgstr "" msgstr ""
#: templates/includes/navbar.html:129 templates/rules/fragments/list.html:5 #: templates/includes/navbar.html:132 templates/rules/fragments/list.html:5
#: templates/rules/pages/index.html:4 #: templates/rules/pages/index.html:4
msgid "Rules" msgid "Rules"
msgstr "" msgstr ""
#: templates/includes/navbar.html:143 #: templates/includes/navbar.html:146
msgid "Only use this if you know what you're doing" msgid "Only use this if you know what you're doing"
msgstr "" msgstr ""
#: templates/includes/navbar.html:144 #: templates/includes/navbar.html:147
msgid "Django Admin" msgid "Django Admin"
msgstr "" msgstr ""
#: templates/includes/navbar.html:153 #: templates/includes/navbar.html:156
msgid "Calculator" msgid "Calculator"
msgstr "" msgstr ""
@@ -2314,6 +2328,44 @@ msgstr ""
msgid "Confirm" msgid "Confirm"
msgstr "" msgstr ""
#: templates/insights/fragments/sankey.html:83
msgid "From"
msgstr ""
#: templates/insights/fragments/sankey.html:86
msgid "Percentage"
msgstr ""
#: templates/insights/pages/index.html:33
msgid "Month"
msgstr ""
#: templates/insights/pages/index.html:36
#: templates/yearly_overview/pages/overview_by_account.html:61
#: templates/yearly_overview/pages/overview_by_currency.html:63
msgid "Year"
msgstr ""
#: templates/insights/pages/index.html:39
msgid "Month Range"
msgstr ""
#: templates/insights/pages/index.html:42
msgid "Year Range"
msgstr ""
#: templates/insights/pages/index.html:45
msgid "Date Range"
msgstr ""
#: templates/insights/pages/index.html:74
msgid "Account Flow"
msgstr ""
#: templates/insights/pages/index.html:81
msgid "Currency Flow"
msgstr ""
#: templates/installment_plans/fragments/add.html:5 #: templates/installment_plans/fragments/add.html:5
msgid "Add installment plan" msgid "Add installment plan"
msgstr "" msgstr ""
@@ -2678,8 +2730,3 @@ msgstr ""
#: templates/yearly_overview/pages/overview_by_currency.html:9 #: templates/yearly_overview/pages/overview_by_currency.html:9
msgid "Yearly Overview" msgid "Yearly Overview"
msgstr "" msgstr ""
#: templates/yearly_overview/pages/overview_by_account.html:61
#: templates/yearly_overview/pages/overview_by_currency.html:63
msgid "Year"
msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-15 00:42-0300\n" "POT-Creation-Date: 2025-02-16 00:03-0300\n"
"PO-Revision-Date: 2025-02-12 06:58+0100\n" "PO-Revision-Date: 2025-02-12 06:58+0100\n"
"Last-Translator: Dimitri Decrock <dimitri@fam-decrock.eu>\n" "Last-Translator: Dimitri Decrock <dimitri@fam-decrock.eu>\n"
"Language-Team: \n" "Language-Team: \n"
@@ -86,7 +86,7 @@ msgstr "Categorie"
#: apps/transactions/forms.py:315 apps/transactions/forms.py:471 #: apps/transactions/forms.py:315 apps/transactions/forms.py:471
#: apps/transactions/forms.py:716 apps/transactions/models.py:209 #: apps/transactions/forms.py:716 apps/transactions/models.py:209
#: apps/transactions/models.py:380 apps/transactions/models.py:562 #: apps/transactions/models.py:380 apps/transactions/models.py:562
#: templates/includes/navbar.html:105 templates/tags/fragments/list.html:5 #: templates/includes/navbar.html:108 templates/tags/fragments/list.html:5
#: templates/tags/pages/index.html:4 #: templates/tags/pages/index.html:4
msgid "Tags" msgid "Tags"
msgstr "Labels" msgstr "Labels"
@@ -115,7 +115,7 @@ msgstr "Accountgroep"
#: apps/accounts/models.py:13 templates/account_groups/fragments/list.html:5 #: apps/accounts/models.py:13 templates/account_groups/fragments/list.html:5
#: templates/account_groups/pages/index.html:4 #: templates/account_groups/pages/index.html:4
#: templates/includes/navbar.html:115 #: templates/includes/navbar.html:118
msgid "Account Groups" msgid "Account Groups"
msgstr "Accountgroepen" msgstr "Accountgroepen"
@@ -167,8 +167,8 @@ msgstr "Rekening"
#: apps/accounts/models.py:60 apps/transactions/filters.py:53 #: apps/accounts/models.py:60 apps/transactions/filters.py:53
#: templates/accounts/fragments/list.html:5 #: templates/accounts/fragments/list.html:5
#: templates/accounts/pages/index.html:4 templates/includes/navbar.html:111 #: templates/accounts/pages/index.html:4 templates/includes/navbar.html:114
#: templates/includes/navbar.html:113 #: templates/includes/navbar.html:116
#: templates/monthly_overview/pages/overview.html:94 #: templates/monthly_overview/pages/overview.html:94
#: templates/transactions/fragments/summary.html:12 #: templates/transactions/fragments/summary.html:12
#: templates/transactions/pages/transactions.html:72 #: templates/transactions/pages/transactions.html:72
@@ -338,6 +338,7 @@ msgid "Cache cleared successfully"
msgstr "Categorie succesvol bijgewerkt" msgstr "Categorie succesvol bijgewerkt"
#: apps/common/widgets/datepicker.py:47 apps/common/widgets/datepicker.py:186 #: apps/common/widgets/datepicker.py:47 apps/common/widgets/datepicker.py:186
#: apps/common/widgets/datepicker.py:244
msgid "Today" msgid "Today"
msgstr "Vandaag" msgstr "Vandaag"
@@ -392,8 +393,8 @@ msgstr "Cijfers na de komma"
#: apps/currencies/models.py:40 apps/transactions/filters.py:60 #: apps/currencies/models.py:40 apps/transactions/filters.py:60
#: templates/currencies/fragments/list.html:5 #: templates/currencies/fragments/list.html:5
#: templates/currencies/pages/index.html:4 templates/includes/navbar.html:119 #: templates/currencies/pages/index.html:4 templates/includes/navbar.html:122
#: templates/includes/navbar.html:121 #: templates/includes/navbar.html:124
#: templates/monthly_overview/pages/overview.html:81 #: templates/monthly_overview/pages/overview.html:81
#: templates/transactions/fragments/summary.html:8 #: templates/transactions/fragments/summary.html:8
#: templates/transactions/pages/transactions.html:59 #: templates/transactions/pages/transactions.html:59
@@ -422,7 +423,7 @@ msgstr "Datum en Tijd"
#: apps/currencies/models.py:74 templates/exchange_rates/fragments/list.html:6 #: apps/currencies/models.py:74 templates/exchange_rates/fragments/list.html:6
#: templates/exchange_rates/pages/index.html:4 #: templates/exchange_rates/pages/index.html:4
#: templates/includes/navbar.html:123 #: templates/includes/navbar.html:126
msgid "Exchange Rates" msgid "Exchange Rates"
msgstr "Wisselkoersen" msgstr "Wisselkoersen"
@@ -696,7 +697,7 @@ msgstr "Selecteer een bestand"
#: apps/import_app/forms.py:61 #: apps/import_app/forms.py:61
#: templates/import_app/fragments/profiles/list.html:62 #: templates/import_app/fragments/profiles/list.html:62
#: templates/includes/navbar.html:131 #: templates/includes/navbar.html:134
msgid "Import" msgid "Import"
msgstr "Importeer" msgstr "Importeer"
@@ -764,6 +765,19 @@ msgstr "Importrun met succes in de wachtrij geplaatst"
msgid "Run deleted successfully" msgid "Run deleted successfully"
msgstr "Run met succes verwijderd" msgstr "Run met succes verwijderd"
#: apps/insights/utils/sankey.py:37 apps/insights/utils/sankey.py:154
#, fuzzy
#| msgid "Categories"
msgid "Uncategorized"
msgstr "Categorieën"
#: apps/insights/utils/sankey.py:118 apps/insights/utils/sankey.py:119
#: apps/insights/utils/sankey.py:234 apps/insights/utils/sankey.py:235
#, fuzzy
#| msgid "Save"
msgid "Saved"
msgstr "Opslaan"
#: apps/rules/forms.py:20 #: apps/rules/forms.py:20
msgid "Run on creation" msgid "Run on creation"
msgstr "Uitvoeren na het aanmaken" msgstr "Uitvoeren na het aanmaken"
@@ -780,7 +794,7 @@ msgstr "Als..."
msgid "Set field" msgid "Set field"
msgstr "Veld instellen" msgstr "Veld instellen"
#: apps/rules/forms.py:65 #: apps/rules/forms.py:65 templates/insights/fragments/sankey.html:84
msgid "To" msgid "To"
msgstr "Naar" msgstr "Naar"
@@ -821,7 +835,7 @@ msgstr "Referentiedatum"
#: apps/rules/forms.py:165 apps/rules/forms.py:178 apps/rules/models.py:29 #: apps/rules/forms.py:165 apps/rules/forms.py:178 apps/rules/models.py:29
#: apps/rules/models.py:256 apps/transactions/models.py:192 #: apps/rules/models.py:256 apps/transactions/models.py:192
#: apps/transactions/models.py:551 #: apps/transactions/models.py:551 templates/insights/fragments/sankey.html:85
msgid "Amount" msgid "Amount"
msgstr "Bedrag" msgstr "Bedrag"
@@ -848,7 +862,7 @@ msgstr "Interne ID"
#: apps/transactions/forms.py:731 apps/transactions/models.py:161 #: apps/transactions/forms.py:731 apps/transactions/models.py:161
#: apps/transactions/models.py:214 apps/transactions/models.py:383 #: apps/transactions/models.py:214 apps/transactions/models.py:383
#: apps/transactions/models.py:565 templates/entities/fragments/list.html:5 #: apps/transactions/models.py:565 templates/entities/fragments/list.html:5
#: templates/entities/pages/index.html:4 templates/includes/navbar.html:107 #: templates/entities/pages/index.html:4 templates/includes/navbar.html:110
msgid "Entities" msgid "Entities"
msgstr "Bedrijven" msgstr "Bedrijven"
@@ -1005,7 +1019,7 @@ msgid "Transaction Type"
msgstr "Soort transactie" msgstr "Soort transactie"
#: apps/transactions/filters.py:67 templates/categories/fragments/list.html:5 #: apps/transactions/filters.py:67 templates/categories/fragments/list.html:5
#: templates/categories/pages/index.html:4 templates/includes/navbar.html:103 #: templates/categories/pages/index.html:4 templates/includes/navbar.html:106
msgid "Categories" msgid "Categories"
msgstr "Categorieën" msgstr "Categorieën"
@@ -1148,8 +1162,8 @@ msgstr "Verwijderd Op"
msgid "Transaction" msgid "Transaction"
msgstr "Verrichting" msgstr "Verrichting"
#: apps/transactions/models.py:256 templates/includes/navbar.html:54 #: apps/transactions/models.py:256 templates/includes/navbar.html:57
#: templates/includes/navbar.html:101 #: templates/includes/navbar.html:104
#: templates/recurring_transactions/fragments/list_transactions.html:5 #: templates/recurring_transactions/fragments/list_transactions.html:5
#: templates/recurring_transactions/fragments/table.html:37 #: templates/recurring_transactions/fragments/table.html:37
#: templates/transactions/pages/transactions.html:5 #: templates/transactions/pages/transactions.html:5
@@ -1217,7 +1231,7 @@ msgstr "Terugkeerpatroon"
msgid "Installment Amount" msgid "Installment Amount"
msgstr "Termijnbedrag" msgstr "Termijnbedrag"
#: apps/transactions/models.py:391 templates/includes/navbar.html:69 #: apps/transactions/models.py:391 templates/includes/navbar.html:72
#: templates/installment_plans/fragments/list.html:5 #: templates/installment_plans/fragments/list.html:5
#: templates/installment_plans/pages/index.html:4 #: templates/installment_plans/pages/index.html:4
msgid "Installment Plans" msgid "Installment Plans"
@@ -1260,7 +1274,7 @@ msgstr "Laatste Gegenereerde Datum"
msgid "Last Generated Reference Date" msgid "Last Generated Reference Date"
msgstr "Laatste Gegenereerde Referentiedatum" msgstr "Laatste Gegenereerde Referentiedatum"
#: apps/transactions/models.py:592 templates/includes/navbar.html:71 #: apps/transactions/models.py:592 templates/includes/navbar.html:74
#: templates/recurring_transactions/fragments/list.html:5 #: templates/recurring_transactions/fragments/list.html:5
#: templates/recurring_transactions/pages/index.html:4 #: templates/recurring_transactions/pages/index.html:4
msgid "Recurring Transactions" msgid "Recurring Transactions"
@@ -2103,7 +2117,7 @@ msgid "Edit exchange rate"
msgstr "Wisselkoers bewerken" msgstr "Wisselkoers bewerken"
#: templates/exchange_rates/fragments/list.html:25 #: templates/exchange_rates/fragments/list.html:25
#: templates/includes/navbar.html:58 #: templates/includes/navbar.html:61
#: templates/installment_plans/fragments/list.html:21 #: templates/installment_plans/fragments/list.html:21
#: templates/yearly_overview/pages/overview_by_account.html:92 #: templates/yearly_overview/pages/overview_by_account.html:92
#: templates/yearly_overview/pages/overview_by_currency.html:94 #: templates/yearly_overview/pages/overview_by_currency.html:94
@@ -2133,7 +2147,7 @@ msgstr "Paginanavigatie"
#: templates/exchange_rates_services/fragments/list.html:6 #: templates/exchange_rates_services/fragments/list.html:6
#: templates/exchange_rates_services/pages/index.html:4 #: templates/exchange_rates_services/pages/index.html:4
#: templates/includes/navbar.html:133 #: templates/includes/navbar.html:136
msgid "Automatic Exchange Rates" msgid "Automatic Exchange Rates"
msgstr "Automatische Wisselkoersen" msgstr "Automatische Wisselkoersen"
@@ -2271,52 +2285,56 @@ msgstr "Netto Waarde"
msgid "Current" msgid "Current"
msgstr "Huidige" msgstr "Huidige"
#: templates/includes/navbar.html:63 #: templates/includes/navbar.html:50
msgid "Insights"
msgstr ""
#: templates/includes/navbar.html:66
msgid "Trash Can" msgid "Trash Can"
msgstr "Prullenbak" msgstr "Prullenbak"
#: templates/includes/navbar.html:79 #: templates/includes/navbar.html:82
msgid "Tools" msgid "Tools"
msgstr "Hulpmiddelen" msgstr "Hulpmiddelen"
#: templates/includes/navbar.html:83 #: templates/includes/navbar.html:86
msgid "Dollar Cost Average Tracker" msgid "Dollar Cost Average Tracker"
msgstr "Dollar Kostgemiddelde Tracker" msgstr "Dollar Kostgemiddelde Tracker"
#: templates/includes/navbar.html:86 #: templates/includes/navbar.html:89
#: templates/mini_tools/unit_price_calculator.html:5 #: templates/mini_tools/unit_price_calculator.html:5
#: templates/mini_tools/unit_price_calculator.html:10 #: templates/mini_tools/unit_price_calculator.html:10
msgid "Unit Price Calculator" msgid "Unit Price Calculator"
msgstr "Eenheidsprijs berekenen" msgstr "Eenheidsprijs berekenen"
#: templates/includes/navbar.html:89 #: templates/includes/navbar.html:92
#: templates/mini_tools/currency_converter/currency_converter.html:8 #: templates/mini_tools/currency_converter/currency_converter.html:8
#: templates/mini_tools/currency_converter/currency_converter.html:15 #: templates/mini_tools/currency_converter/currency_converter.html:15
msgid "Currency Converter" msgid "Currency Converter"
msgstr "Valuta omrekenen" msgstr "Valuta omrekenen"
#: templates/includes/navbar.html:98 #: templates/includes/navbar.html:101
msgid "Management" msgid "Management"
msgstr "Beheer" msgstr "Beheer"
#: templates/includes/navbar.html:127 #: templates/includes/navbar.html:130
msgid "Automation" msgid "Automation"
msgstr "Automatisatie" msgstr "Automatisatie"
#: templates/includes/navbar.html:129 templates/rules/fragments/list.html:5 #: templates/includes/navbar.html:132 templates/rules/fragments/list.html:5
#: templates/rules/pages/index.html:4 #: templates/rules/pages/index.html:4
msgid "Rules" msgid "Rules"
msgstr "Regels" msgstr "Regels"
#: templates/includes/navbar.html:143 #: templates/includes/navbar.html:146
msgid "Only use this if you know what you're doing" msgid "Only use this if you know what you're doing"
msgstr "Gebruik dit alleen als je weet wat je doet" msgstr "Gebruik dit alleen als je weet wat je doet"
#: templates/includes/navbar.html:144 #: templates/includes/navbar.html:147
msgid "Django Admin" msgid "Django Admin"
msgstr "Django Beheerder" msgstr "Django Beheerder"
#: templates/includes/navbar.html:153 #: templates/includes/navbar.html:156
msgid "Calculator" msgid "Calculator"
msgstr "Rekenmachine" msgstr "Rekenmachine"
@@ -2350,6 +2368,54 @@ msgstr "Annuleer"
msgid "Confirm" msgid "Confirm"
msgstr "Bevestig" msgstr "Bevestig"
#: templates/insights/fragments/sankey.html:83
msgid "From"
msgstr ""
#: templates/insights/fragments/sankey.html:86
msgid "Percentage"
msgstr ""
#: templates/insights/pages/index.html:33
#, fuzzy
#| msgid "Monthly"
msgid "Month"
msgstr "Maandelijks"
#: templates/insights/pages/index.html:36
#: templates/yearly_overview/pages/overview_by_account.html:61
#: templates/yearly_overview/pages/overview_by_currency.html:63
msgid "Year"
msgstr "Jaar"
#: templates/insights/pages/index.html:39
#, fuzzy
#| msgid "Unchanged"
msgid "Month Range"
msgstr "Ongewijzigd"
#: templates/insights/pages/index.html:42
msgid "Year Range"
msgstr ""
#: templates/insights/pages/index.html:45
#, fuzzy
#| msgid "Date and Time"
msgid "Date Range"
msgstr "Datum en Tijd"
#: templates/insights/pages/index.html:74
#, fuzzy
#| msgid "Account"
msgid "Account Flow"
msgstr "Rekening"
#: templates/insights/pages/index.html:81
#, fuzzy
#| msgid "Currency Code"
msgid "Currency Flow"
msgstr "Munteenheids Code"
#: templates/installment_plans/fragments/add.html:5 #: templates/installment_plans/fragments/add.html:5
msgid "Add installment plan" msgid "Add installment plan"
msgstr "Afbetalingsplan toevoegen" msgstr "Afbetalingsplan toevoegen"
@@ -2723,11 +2789,6 @@ msgstr "Bedragen tonen"
msgid "Yearly Overview" msgid "Yearly Overview"
msgstr "Jaaroverzicht" msgstr "Jaaroverzicht"
#: templates/yearly_overview/pages/overview_by_account.html:61
#: templates/yearly_overview/pages/overview_by_currency.html:63
msgid "Year"
msgstr "Jaar"
#, fuzzy #, fuzzy
#~| msgid "Start Date" #~| msgid "Start Date"
#~ msgid "Search Date" #~ msgid "Search Date"

View File

@@ -8,8 +8,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-02-15 00:38-0300\n" "POT-Creation-Date: 2025-02-16 00:03-0300\n"
"PO-Revision-Date: 2025-02-15 00:39-0300\n" "PO-Revision-Date: 2025-02-16 00:04-0300\n"
"Last-Translator: Herculino Trotta\n" "Last-Translator: Herculino Trotta\n"
"Language-Team: \n" "Language-Team: \n"
"Language: pt_BR\n" "Language: pt_BR\n"
@@ -86,7 +86,7 @@ msgstr "Categoria"
#: apps/transactions/forms.py:315 apps/transactions/forms.py:471 #: apps/transactions/forms.py:315 apps/transactions/forms.py:471
#: apps/transactions/forms.py:716 apps/transactions/models.py:209 #: apps/transactions/forms.py:716 apps/transactions/models.py:209
#: apps/transactions/models.py:380 apps/transactions/models.py:562 #: apps/transactions/models.py:380 apps/transactions/models.py:562
#: templates/includes/navbar.html:105 templates/tags/fragments/list.html:5 #: templates/includes/navbar.html:108 templates/tags/fragments/list.html:5
#: templates/tags/pages/index.html:4 #: templates/tags/pages/index.html:4
msgid "Tags" msgid "Tags"
msgstr "Tags" msgstr "Tags"
@@ -115,7 +115,7 @@ msgstr "Grupo da Conta"
#: apps/accounts/models.py:13 templates/account_groups/fragments/list.html:5 #: apps/accounts/models.py:13 templates/account_groups/fragments/list.html:5
#: templates/account_groups/pages/index.html:4 #: templates/account_groups/pages/index.html:4
#: templates/includes/navbar.html:115 #: templates/includes/navbar.html:118
msgid "Account Groups" msgid "Account Groups"
msgstr "Grupos da Conta" msgstr "Grupos da Conta"
@@ -166,8 +166,8 @@ msgstr "Conta"
#: apps/accounts/models.py:60 apps/transactions/filters.py:53 #: apps/accounts/models.py:60 apps/transactions/filters.py:53
#: templates/accounts/fragments/list.html:5 #: templates/accounts/fragments/list.html:5
#: templates/accounts/pages/index.html:4 templates/includes/navbar.html:111 #: templates/accounts/pages/index.html:4 templates/includes/navbar.html:114
#: templates/includes/navbar.html:113 #: templates/includes/navbar.html:116
#: templates/monthly_overview/pages/overview.html:94 #: templates/monthly_overview/pages/overview.html:94
#: templates/transactions/fragments/summary.html:12 #: templates/transactions/fragments/summary.html:12
#: templates/transactions/pages/transactions.html:72 #: templates/transactions/pages/transactions.html:72
@@ -336,6 +336,7 @@ msgid "Cache cleared successfully"
msgstr "Cache limpo com sucesso" msgstr "Cache limpo com sucesso"
#: apps/common/widgets/datepicker.py:47 apps/common/widgets/datepicker.py:186 #: apps/common/widgets/datepicker.py:47 apps/common/widgets/datepicker.py:186
#: apps/common/widgets/datepicker.py:244
msgid "Today" msgid "Today"
msgstr "Hoje" msgstr "Hoje"
@@ -390,8 +391,8 @@ msgstr "Casas Decimais"
#: apps/currencies/models.py:40 apps/transactions/filters.py:60 #: apps/currencies/models.py:40 apps/transactions/filters.py:60
#: templates/currencies/fragments/list.html:5 #: templates/currencies/fragments/list.html:5
#: templates/currencies/pages/index.html:4 templates/includes/navbar.html:119 #: templates/currencies/pages/index.html:4 templates/includes/navbar.html:122
#: templates/includes/navbar.html:121 #: templates/includes/navbar.html:124
#: templates/monthly_overview/pages/overview.html:81 #: templates/monthly_overview/pages/overview.html:81
#: templates/transactions/fragments/summary.html:8 #: templates/transactions/fragments/summary.html:8
#: templates/transactions/pages/transactions.html:59 #: templates/transactions/pages/transactions.html:59
@@ -420,7 +421,7 @@ msgstr "Data e Tempo"
#: apps/currencies/models.py:74 templates/exchange_rates/fragments/list.html:6 #: apps/currencies/models.py:74 templates/exchange_rates/fragments/list.html:6
#: templates/exchange_rates/pages/index.html:4 #: templates/exchange_rates/pages/index.html:4
#: templates/includes/navbar.html:123 #: templates/includes/navbar.html:126
msgid "Exchange Rates" msgid "Exchange Rates"
msgstr "Taxas de Câmbio" msgstr "Taxas de Câmbio"
@@ -690,7 +691,7 @@ msgstr "Selecione um arquivo"
#: apps/import_app/forms.py:61 #: apps/import_app/forms.py:61
#: templates/import_app/fragments/profiles/list.html:62 #: templates/import_app/fragments/profiles/list.html:62
#: templates/includes/navbar.html:131 #: templates/includes/navbar.html:134
msgid "Import" msgid "Import"
msgstr "Importar" msgstr "Importar"
@@ -758,6 +759,15 @@ msgstr "Importação adicionada à fila com sucesso"
msgid "Run deleted successfully" msgid "Run deleted successfully"
msgstr "Importação apagada com sucesso" msgstr "Importação apagada com sucesso"
#: apps/insights/utils/sankey.py:37 apps/insights/utils/sankey.py:154
msgid "Uncategorized"
msgstr "Sem categoria"
#: apps/insights/utils/sankey.py:118 apps/insights/utils/sankey.py:119
#: apps/insights/utils/sankey.py:234 apps/insights/utils/sankey.py:235
msgid "Saved"
msgstr "Salvo"
#: apps/rules/forms.py:20 #: apps/rules/forms.py:20
msgid "Run on creation" msgid "Run on creation"
msgstr "Rodar na criação" msgstr "Rodar na criação"
@@ -774,7 +784,7 @@ msgstr "Se..."
msgid "Set field" msgid "Set field"
msgstr "Definir campo" msgstr "Definir campo"
#: apps/rules/forms.py:65 #: apps/rules/forms.py:65 templates/insights/fragments/sankey.html:84
msgid "To" msgid "To"
msgstr "Para" msgstr "Para"
@@ -815,7 +825,7 @@ msgstr "Data de Referência"
#: apps/rules/forms.py:165 apps/rules/forms.py:178 apps/rules/models.py:29 #: apps/rules/forms.py:165 apps/rules/forms.py:178 apps/rules/models.py:29
#: apps/rules/models.py:256 apps/transactions/models.py:192 #: apps/rules/models.py:256 apps/transactions/models.py:192
#: apps/transactions/models.py:551 #: apps/transactions/models.py:551 templates/insights/fragments/sankey.html:85
msgid "Amount" msgid "Amount"
msgstr "Quantia" msgstr "Quantia"
@@ -842,7 +852,7 @@ msgstr "ID Interna"
#: apps/transactions/forms.py:731 apps/transactions/models.py:161 #: apps/transactions/forms.py:731 apps/transactions/models.py:161
#: apps/transactions/models.py:214 apps/transactions/models.py:383 #: apps/transactions/models.py:214 apps/transactions/models.py:383
#: apps/transactions/models.py:565 templates/entities/fragments/list.html:5 #: apps/transactions/models.py:565 templates/entities/fragments/list.html:5
#: templates/entities/pages/index.html:4 templates/includes/navbar.html:107 #: templates/entities/pages/index.html:4 templates/includes/navbar.html:110
msgid "Entities" msgid "Entities"
msgstr "Entidades" msgstr "Entidades"
@@ -998,7 +1008,7 @@ msgid "Transaction Type"
msgstr "Tipo de Transação" msgstr "Tipo de Transação"
#: apps/transactions/filters.py:67 templates/categories/fragments/list.html:5 #: apps/transactions/filters.py:67 templates/categories/fragments/list.html:5
#: templates/categories/pages/index.html:4 templates/includes/navbar.html:103 #: templates/categories/pages/index.html:4 templates/includes/navbar.html:106
msgid "Categories" msgid "Categories"
msgstr "Categorias" msgstr "Categorias"
@@ -1140,8 +1150,8 @@ msgstr "Apagado Em"
msgid "Transaction" msgid "Transaction"
msgstr "Transação" msgstr "Transação"
#: apps/transactions/models.py:256 templates/includes/navbar.html:54 #: apps/transactions/models.py:256 templates/includes/navbar.html:57
#: templates/includes/navbar.html:101 #: templates/includes/navbar.html:104
#: templates/recurring_transactions/fragments/list_transactions.html:5 #: templates/recurring_transactions/fragments/list_transactions.html:5
#: templates/recurring_transactions/fragments/table.html:37 #: templates/recurring_transactions/fragments/table.html:37
#: templates/transactions/pages/transactions.html:5 #: templates/transactions/pages/transactions.html:5
@@ -1205,7 +1215,7 @@ msgstr "Recorrência"
msgid "Installment Amount" msgid "Installment Amount"
msgstr "Valor da Parcela" msgstr "Valor da Parcela"
#: apps/transactions/models.py:391 templates/includes/navbar.html:69 #: apps/transactions/models.py:391 templates/includes/navbar.html:72
#: templates/installment_plans/fragments/list.html:5 #: templates/installment_plans/fragments/list.html:5
#: templates/installment_plans/pages/index.html:4 #: templates/installment_plans/pages/index.html:4
msgid "Installment Plans" msgid "Installment Plans"
@@ -1248,7 +1258,7 @@ msgstr "Última data gerada"
msgid "Last Generated Reference Date" msgid "Last Generated Reference Date"
msgstr "Última data de referência gerada" msgstr "Última data de referência gerada"
#: apps/transactions/models.py:592 templates/includes/navbar.html:71 #: apps/transactions/models.py:592 templates/includes/navbar.html:74
#: templates/recurring_transactions/fragments/list.html:5 #: templates/recurring_transactions/fragments/list.html:5
#: templates/recurring_transactions/pages/index.html:4 #: templates/recurring_transactions/pages/index.html:4
msgid "Recurring Transactions" msgid "Recurring Transactions"
@@ -2092,7 +2102,7 @@ msgid "Edit exchange rate"
msgstr "Editar taxa de câmbio" msgstr "Editar taxa de câmbio"
#: templates/exchange_rates/fragments/list.html:25 #: templates/exchange_rates/fragments/list.html:25
#: templates/includes/navbar.html:58 #: templates/includes/navbar.html:61
#: templates/installment_plans/fragments/list.html:21 #: templates/installment_plans/fragments/list.html:21
#: templates/yearly_overview/pages/overview_by_account.html:92 #: templates/yearly_overview/pages/overview_by_account.html:92
#: templates/yearly_overview/pages/overview_by_currency.html:94 #: templates/yearly_overview/pages/overview_by_currency.html:94
@@ -2122,7 +2132,7 @@ msgstr "Navegação por página"
#: templates/exchange_rates_services/fragments/list.html:6 #: templates/exchange_rates_services/fragments/list.html:6
#: templates/exchange_rates_services/pages/index.html:4 #: templates/exchange_rates_services/pages/index.html:4
#: templates/includes/navbar.html:133 #: templates/includes/navbar.html:136
msgid "Automatic Exchange Rates" msgid "Automatic Exchange Rates"
msgstr "Taxas de Câmbio Automáticas" msgstr "Taxas de Câmbio Automáticas"
@@ -2261,52 +2271,56 @@ msgstr "Patrimônio"
msgid "Current" msgid "Current"
msgstr "Atual" msgstr "Atual"
#: templates/includes/navbar.html:63 #: templates/includes/navbar.html:50
msgid "Insights"
msgstr "Insights"
#: templates/includes/navbar.html:66
msgid "Trash Can" msgid "Trash Can"
msgstr "Lixeira" msgstr "Lixeira"
#: templates/includes/navbar.html:79 #: templates/includes/navbar.html:82
msgid "Tools" msgid "Tools"
msgstr "Ferramentas" msgstr "Ferramentas"
#: templates/includes/navbar.html:83 #: templates/includes/navbar.html:86
msgid "Dollar Cost Average Tracker" msgid "Dollar Cost Average Tracker"
msgstr "Rastreador de Custo Médio Ponderado" msgstr "Rastreador de Custo Médio Ponderado"
#: templates/includes/navbar.html:86 #: templates/includes/navbar.html:89
#: templates/mini_tools/unit_price_calculator.html:5 #: templates/mini_tools/unit_price_calculator.html:5
#: templates/mini_tools/unit_price_calculator.html:10 #: templates/mini_tools/unit_price_calculator.html:10
msgid "Unit Price Calculator" msgid "Unit Price Calculator"
msgstr "Calculadora de preço unitário" msgstr "Calculadora de preço unitário"
#: templates/includes/navbar.html:89 #: templates/includes/navbar.html:92
#: templates/mini_tools/currency_converter/currency_converter.html:8 #: templates/mini_tools/currency_converter/currency_converter.html:8
#: templates/mini_tools/currency_converter/currency_converter.html:15 #: templates/mini_tools/currency_converter/currency_converter.html:15
msgid "Currency Converter" msgid "Currency Converter"
msgstr "Conversor de Moeda" msgstr "Conversor de Moeda"
#: templates/includes/navbar.html:98 #: templates/includes/navbar.html:101
msgid "Management" msgid "Management"
msgstr "Gerenciar" msgstr "Gerenciar"
#: templates/includes/navbar.html:127 #: templates/includes/navbar.html:130
msgid "Automation" msgid "Automation"
msgstr "Automação" msgstr "Automação"
#: templates/includes/navbar.html:129 templates/rules/fragments/list.html:5 #: templates/includes/navbar.html:132 templates/rules/fragments/list.html:5
#: templates/rules/pages/index.html:4 #: templates/rules/pages/index.html:4
msgid "Rules" msgid "Rules"
msgstr "Regras" msgstr "Regras"
#: templates/includes/navbar.html:143 #: templates/includes/navbar.html:146
msgid "Only use this if you know what you're doing" msgid "Only use this if you know what you're doing"
msgstr "Só use isso se você souber o que está fazendo" msgstr "Só use isso se você souber o que está fazendo"
#: templates/includes/navbar.html:144 #: templates/includes/navbar.html:147
msgid "Django Admin" msgid "Django Admin"
msgstr "Django Admin" msgstr "Django Admin"
#: templates/includes/navbar.html:153 #: templates/includes/navbar.html:156
msgid "Calculator" msgid "Calculator"
msgstr "Calculadora" msgstr "Calculadora"
@@ -2339,6 +2353,44 @@ msgstr "Cancelar"
msgid "Confirm" msgid "Confirm"
msgstr "Confirmar" msgstr "Confirmar"
#: templates/insights/fragments/sankey.html:83
msgid "From"
msgstr "De"
#: templates/insights/fragments/sankey.html:86
msgid "Percentage"
msgstr "Porcentagem"
#: templates/insights/pages/index.html:33
msgid "Month"
msgstr "Mês"
#: templates/insights/pages/index.html:36
#: templates/yearly_overview/pages/overview_by_account.html:61
#: templates/yearly_overview/pages/overview_by_currency.html:63
msgid "Year"
msgstr "Ano"
#: templates/insights/pages/index.html:39
msgid "Month Range"
msgstr "Intervalo de Mês"
#: templates/insights/pages/index.html:42
msgid "Year Range"
msgstr "Intervalo de Ano"
#: templates/insights/pages/index.html:45
msgid "Date Range"
msgstr "Intervalo de Data"
#: templates/insights/pages/index.html:74
msgid "Account Flow"
msgstr "Fluxo de Conta"
#: templates/insights/pages/index.html:81
msgid "Currency Flow"
msgstr "Fluxo de Moeda"
#: templates/installment_plans/fragments/add.html:5 #: templates/installment_plans/fragments/add.html:5
msgid "Add installment plan" msgid "Add installment plan"
msgstr "Adicionar parcelamento" msgstr "Adicionar parcelamento"
@@ -2709,11 +2761,6 @@ msgstr "Mostrar valores"
msgid "Yearly Overview" msgid "Yearly Overview"
msgstr "Visão Anual" msgstr "Visão Anual"
#: templates/yearly_overview/pages/overview_by_account.html:61
#: templates/yearly_overview/pages/overview_by_currency.html:63
msgid "Year"
msgstr "Ano"
#, fuzzy #, fuzzy
#~| msgid "Tags" #~| msgid "Tags"
#~ msgid "No Tags" #~ msgid "No Tags"
@@ -2913,9 +2960,6 @@ msgstr "Ano"
#~ msgid "Is an asset account?" #~ msgid "Is an asset account?"
#~ msgstr "É uma conta de ativos?" #~ msgstr "É uma conta de ativos?"
#~ msgid "Month"
#~ msgstr "Mês"
#~ msgid "" #~ msgid ""
#~ "This transaction is part of a Installment Plan, you can't delete it " #~ "This transaction is part of a Installment Plan, you can't delete it "
#~ "directly." #~ "directly."

View File

@@ -46,8 +46,11 @@
href="{% url 'net_worth_projected' %}">{% translate 'Projected' %}</a></li> href="{% url 'net_worth_projected' %}">{% translate 'Projected' %}</a></li>
</ul> </ul>
</li> </li>
<li class="nav-item">
<a class="nav-link {% active_link views='insights_index' %}" href="{% url 'insights_index' %}">{% trans 'Insights' %}</a>
</li>
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle {% active_link views='installment_plans_index||recurring_trasanctions_index||transactions_all_index' %}" <a class="nav-link dropdown-toggle {% active_link views='installment_plans_index||recurring_trasanctions_index||transactions_all_index||transactions_trash_index' %}"
href="#" role="button" href="#" role="button"
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
aria-expanded="false"> aria-expanded="false">
@@ -91,7 +94,7 @@
</ul> </ul>
</li> </li>
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle {% active_link views='tags_index||entities_index||categories_index||accounts_index||account_groups_index||currencies_index||exchange_rates_index||rules_index' %}" <a class="nav-link dropdown-toggle {% 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' %}"
href="#" role="button" href="#" role="button"
data-bs-toggle="dropdown" data-bs-toggle="dropdown"
aria-expanded="false"> aria-expanded="false">

View File

@@ -17,6 +17,10 @@
for x in datepickers for x in datepickers
MonthYearPicker(it) MonthYearPicker(it)
end end
set datepickers to <.airyearpickerinput/> in me
for x in datepickers
YearPicker(it)
end
end end
end end
</script> </script>

View File

@@ -0,0 +1,120 @@
{% load i18n %}
{% if type == 'account' %}
<div class="show-loading" hx-get="{% url 'insights_sankey_by_account' %}" hx-trigger="updated from:window" hx-swap="outerHTML" hx-include="#picker-form, #picker-type">
{% else %}
<div class="show-loading" hx-get="{% url 'insights_sankey_by_currency' %}" hx-trigger="updated from:window" hx-swap="outerHTML" hx-include="#picker-form, #picker-type">
{% endif %}
<div class="chart-container position-relative tw-min-h-[60vh] tw-max-h-[60vh] tw-h-full tw-w-full"
id="sankeyContainer"
_="init call setupSankeyChart() end">
<canvas id="sankeyChart"></canvas>
</div>
</div>
<script>
var data = {{ sankey_data|safe }};
function setupSankeyChart(chartId = 'sankeyChart') {
function formatCurrency(value, currency) {
return new Intl.NumberFormat(undefined, {
minimumFractionDigits: currency.decimal_places,
maximumFractionDigits: currency.decimal_places
}).format(value);
}
// Create labels object mapping node IDs to display names
const labels = data.nodes.reduce((acc, node) => {
acc[node.id] = node.name;
return acc;
}, {});
// Define colors for each node based on its type
const colors = {};
data.nodes.forEach(node => {
if (node.id.startsWith('income_')) {
colors[node.id] = '#4dde80'; // Green for income
} else if (node.id.startsWith('expense_')) {
colors[node.id] = '#f87171'; // Red for expenses
} else {
colors[node.id] = '#fbb700'; // Primary for others
}
});
// Color getter functions
const getColor = (nodeId) => colors[nodeId];
const getHover = (nodeId) => colors[nodeId];
// Format data for Chart.js
const chartData = {
datasets: [{
data: data.flows.map(flow => ({
from: flow.from_node,
to: flow.to_node,
flow: flow.flow
})),
labels: labels,
colorFrom: (c) => getColor(c.dataset.data[c.dataIndex].from),
colorTo: (c) => getColor(c.dataset.data[c.dataIndex].to),
hoverColorFrom: (c) => getHover(c.dataset.data[c.dataIndex].from),
hoverColorTo: (c) => getHover(c.dataset.data[c.dataIndex].to),
colorMode: 'gradient',
alpha: 0.5,
size: 'max',
color: "white"
}]
};
const config = {
type: 'sankey',
data: chartData,
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
tooltip: {
callbacks: {
label: (context) => {
const flow = data.flows[context.dataIndex];
const fromNode = data.nodes.find(n => n.id === flow.from_node);
const toNode = data.nodes.find(n => n.id === flow.to_node);
const formattedValue = formatCurrency(flow.original_amount, flow.currency);
return [
`{% trans 'From' %}: ${fromNode.name}`,
`{% trans 'To' %}: ${toNode.name}`,
`{% trans 'Amount' %}: ${flow.currency.prefix}${formattedValue}${flow.currency.suffix}`,
`{% trans 'Percentage' %}: ${flow.percentage.toFixed(2)}%`
];
}
}
},
legend: {
display: false
},
title: {
display: false,
}
}
}
};
// Destroy existing chart if it exists
const existingChart = Chart.getChart(chartId);
if (existingChart) {
existingChart.destroy();
}
// Create new chart
var chart = new Chart(
document.getElementById(chartId),
config
);
window.addEventListener('resize', () => {
chart.resize();
});
document.addEventListener('fullscreenchange', function () {
console.log('oi');
chart.resize();
});
}
</script>

View File

@@ -0,0 +1,94 @@
{% extends "layouts/base.html" %}
{% load crispy_forms_tags %}
{% load i18n %}
{% block content %}
<div class="container-fluid">
<div class="row my-3">
<div class="col-lg-2 col-md-3 mb-3 mb-md-0">
<div class="">
<div class="mb-2 w-100 d-lg-inline-flex d-grid gap-2 flex-wrap justify-content-lg-center" role="group"
_="on change
set type to event.target.value
add .tw-hidden to <#picker-form > div:not(.tw-hidden)/>
if type == 'month'
remove .tw-hidden from #month-form
end
if type == 'year'
remove .tw-hidden from #year-form
end
if type == 'month-range'
remove .tw-hidden from #month-range-form
end
if type == 'year-range'
remove .tw-hidden from #year-range-form
end
if type == 'date-range'
remove .tw-hidden from #date-range-form
end
then trigger updated"
id="picker-type">
<input type="radio" class="btn-check" name="type" value="month" id="monthradio" autocomplete="off" checked>
<label class="btn btn-sm btn-outline-primary flex-grow-1" for="monthradio">{% translate 'Month' %}</label>
<input type="radio" class="btn-check" name="type" value="year" id="yearradio" autocomplete="off">
<label class="btn btn-sm btn-outline-primary flex-grow-1" for="yearradio">{% translate 'Year' %}</label>
<input type="radio" class="btn-check" name="type" value="month-range" id="monthrangeradio" autocomplete="off">
<label class="btn btn-sm btn-outline-primary flex-grow-1" for="monthrangeradio">{% translate 'Month Range' %}</label>
<input type="radio" class="btn-check" name="type" value="year-range" id="yearrangeradio" autocomplete="off">
<label class="btn btn-sm btn-outline-primary flex-grow-1" for="yearrangeradio">{% translate 'Year Range' %}</label>
<input type="radio" class="btn-check" name="type" value="date-range" id="daterangeradio" autocomplete="off">
<label class="btn btn-sm btn-outline-primary flex-grow-1" for="daterangeradio">{% translate 'Date Range' %}</label>
</div>
<form id="picker-form"
_="install init_datepicker
on change trigger updated">
<div id="month-form" class="">
{% crispy month_form %}
</div>
<div id="year-form" class="tw-hidden">
{% crispy year_form %}
</div>
<div id="month-range-form" class="tw-hidden">
{% crispy month_range_form %}
</div>
<div id="year-range-form" class="tw-hidden">
{% crispy year_range_form %}
</div>
<div id="date-range-form" class="tw-hidden">
{% crispy date_range_form %}
</div>
</form>
</div>
<hr class="mt-0">
<div class="nav flex-column nav-pills" id="v-pills-tab" role="tablist"
aria-orientation="vertical">
<button class="nav-link" id="v-pills-tab" data-bs-toggle="pill" data-bs-target="#v-pills-content"
type="button" role="tab" aria-controls="v-pills-content" aria-selected="false"
hx-get="{% url 'insights_sankey_by_account' %}" hx-include="#picker-form, #picker-type"
hx-indicator="#tab-content"
hx-target="#tab-content">{% trans 'Account Flow' %}
</button>
<button class="nav-link" id="v-pills-tab" data-bs-toggle="pill" data-bs-target="#v-pills-content"
type="button" role="tab" aria-controls="v-pills-content" aria-selected="false"
hx-get="{% url 'insights_sankey_by_currency' %}"
hx-include="#picker-form, #picker-type"
hx-indicator="#tab-content"
hx-target="#tab-content">{% trans 'Currency Flow' %}
</button>
</div>
</div>
<div class="col-md-9 col-lg-10">
<div class="tab-content w-100" id="v-pills-tabContent">
<div class="tab-pane fade" id="v-pills-content" role="tabpanel" tabindex="0">
<div id="tab-content" class="show-loading"></div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -24,6 +24,7 @@
"babel-loader": "^8.2.3", "babel-loader": "^8.2.3",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"chart.js": "^4.4.6", "chart.js": "^4.4.6",
"chartjs-chart-sankey": "^0.14.0",
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^11.0.0", "copy-webpack-plugin": "^11.0.0",
"core-js": "^3.20.3", "core-js": "^3.20.3",
@@ -3235,6 +3236,15 @@
"pnpm": ">=8" "pnpm": ">=8"
} }
}, },
"node_modules/chartjs-chart-sankey": {
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/chartjs-chart-sankey/-/chartjs-chart-sankey-0.14.0.tgz",
"integrity": "sha512-MrU3lE73TE9kALy4MjWFlfcwf4R1EN/DBvhHxmv9n4AHap//JLKjlJTLIZwHsUjDsYo0B8PuMkrJODwfirEZUA==",
"license": "MIT",
"peerDependencies": {
"chart.js": ">=3.3.0"
}
},
"node_modules/chokidar": { "node_modules/chokidar": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",

View File

@@ -37,6 +37,7 @@
"babel-loader": "^8.2.3", "babel-loader": "^8.2.3",
"bootstrap": "^5.3.3", "bootstrap": "^5.3.3",
"chart.js": "^4.4.6", "chart.js": "^4.4.6",
"chartjs-chart-sankey": "^0.14.0",
"clean-webpack-plugin": "^4.0.0", "clean-webpack-plugin": "^4.0.0",
"copy-webpack-plugin": "^11.0.0", "copy-webpack-plugin": "^11.0.0",
"core-js": "^3.20.3", "core-js": "^3.20.3",

View File

@@ -1,2 +1,5 @@
import Chart from 'chart.js/auto'; import Chart from 'chart.js/auto';
import {SankeyController, Flow} from 'chartjs-chart-sankey';
Chart.register(SankeyController, Flow);
window.Chart = Chart; window.Chart = Chart;

View File

@@ -168,3 +168,75 @@ window.MonthYearPicker = function createDynamicDatePicker(element) {
} }
return new AirDatepicker(element, opts); return new AirDatepicker(element, opts);
}; };
window.YearPicker = function createDynamicDatePicker(element) {
let todayButton = {
content: element.dataset.nowButtonTxt,
onClick: (dp) => {
let date = new Date();
dp.selectDate(date, {updateTime: true});
dp.setViewDate(date);
}
}
let isOnMobile = isMobile();
let baseOpts = {
isMobile: isOnMobile,
view: 'years',
minView: 'years',
dateFormat: 'yyyy',
autoClose: element.dataset.autoClose === 'true',
buttons: element.dataset.clearButton === 'true' ? ['clear', todayButton] : [todayButton],
locale: locales[element.dataset.language],
onSelect: ({date, formattedDate, datepicker}) => {
const _event = new CustomEvent("change", {
bubbles: true,
});
datepicker.$el.dispatchEvent(_event);
}
};
const positionConfig = !isOnMobile ? {
position({$datepicker, $target, $pointer, done}) {
let popper = createPopper($target, $datepicker, {
placement: 'bottom',
modifiers: [
{
name: 'flip',
options: {
padding: {
top: 64
}
}
},
{
name: 'offset',
options: {
offset: [0, 20]
}
},
{
name: 'arrow',
options: {
element: $pointer
}
}
]
});
return function completeHide() {
popper.destroy();
done();
};
}
} : {};
let opts = {...baseOpts, ...positionConfig};
if (element.dataset.value) {
opts["selectedDates"] = [new Date(element.dataset.value + "T00:00:00")];
opts["startDate"] = [new Date(element.dataset.value + "T00:00:00")];
}
return new AirDatepicker(element, opts);
};