feat: insight page

This commit is contained in:
Herculino Trotta
2025-02-16 00:14:23 -03:00
parent daf1f68b82
commit 195a8a68d6
15 changed files with 1062 additions and 262 deletions
+53
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
+110
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",
),
)
+10 -1
View File
@@ -4,5 +4,14 @@ from . import views
urlpatterns = [ urlpatterns = [
path("insights/", views.index, name="insights_index"), path("insights/", views.index, name="insights_index"),
path("insights/sankey/", views.sankey, name="sankey"), 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",
),
] ]
+176 -30
View File
@@ -1,4 +1,3 @@
from django.db.models import Sum
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from decimal import Decimal from decimal import Decimal
from typing import Dict, List, TypedDict from typing import Dict, List, TypedDict
@@ -17,41 +16,64 @@ class SankeyFlow(TypedDict):
percentage: float percentage: float
def generate_sankey_data(transactions_queryset): def generate_sankey_data_by_account(transactions_queryset):
""" """
Generates Sankey diagram data from transaction queryset. Generates Sankey diagram data from transaction queryset using account as intermediary.
Uses a 1-5 scale for flows based on percentages.
""" """
nodes: Dict[str, SankeyNode] = {} nodes: Dict[str, Dict] = {}
flows: List[SankeyFlow] = [] flows: List[SankeyFlow] = []
# Aggregate transactions # Aggregate transactions
income_data = {} # {(category, currency, account) -> amount} income_data = {} # {(category, currency, account) -> amount}
expense_data = {} # {(category, currency, account) -> amount} expense_data = {} # {(category, currency, account) -> amount}
total_amount = Decimal("0") total_income_by_currency = {} # {currency -> amount}
total_expense_by_currency = {} # {currency -> amount}
total_volume_by_currency = {} # {currency -> amount}
for transaction in transactions_queryset: for transaction in transactions_queryset:
currency = transaction.account.currency currency = transaction.account.currency
account = transaction.account account = transaction.account
category = transaction.category or _("Uncategorized") category = transaction.category or _("Uncategorized")
key = (category, currency, account) key = (category, currency, account)
amount = transaction.amount
if transaction.type == "IN": if transaction.type == "IN":
income_data[key] = income_data.get(key, Decimal("0")) + transaction.amount 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: else:
expense_data[key] = expense_data.get(key, Decimal("0")) + transaction.amount 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_amount += transaction.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
# Function to add flow
def add_flow(from_node, to_node, amount, currency):
percentage = (amount / total_amount) * 100 if total_amount else 0
scaled_flow = 1 + min(percentage / 20, 4) # Scale 1-5, capping at 100%
flows.append( flows.append(
{ {
"from_node": from_node, "from_node": from_node_id,
"to_node": to_node, "to_node": to_node_id,
"flow": float(scaled_flow), "flow": float(scaled_flow),
"currency": { "currency": {
"code": currency.code, "code": currency.code,
@@ -63,40 +85,164 @@ def generate_sankey_data(transactions_queryset):
"percentage": float(percentage), "percentage": float(percentage),
} }
) )
nodes[from_node] = {"name": from_node}
nodes[to_node] = {"name": to_node}
# Process income # Process income
for (category, currency, account), amount in income_data.items(): for (category, currency, account), amount in income_data.items():
category_name = f"{category} ({currency.code})" category_node_id = get_node_id("income", category, account.id)
account_name = f"{account.name} ({currency.code})" account_node_id = get_node_id("account", account.name, account.id)
add_flow(category_name, account_name, amount, currency) 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 # Process expenses
for (category, currency, account), amount in expense_data.items(): for (category, currency, account), amount in expense_data.items():
category_name = f"{category} ({currency.code})" category_node_id = get_node_id("expense", category, account.id)
account_name = f"{account.name} ({currency.code})" account_node_id = get_node_id("account", account.name, account.id)
add_flow(account_name, category_name, amount, currency) 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 # Calculate and add savings flows
savings_data = {} # {(account, currency) -> amount} savings_data = {} # {(account, currency) -> amount}
for (category, currency, account), amount in income_data.items(): for (category, currency, account), amount in income_data.items():
key = (account, currency) key = (account, currency)
savings_data[key] = savings_data.get(key, Decimal("0")) + amount savings_data[key] = savings_data.get(key, Decimal("0")) + amount
for (category, currency, account), amount in expense_data.items(): for (category, currency, account), amount in expense_data.items():
key = (account, currency) key = (account, currency)
savings_data[key] = savings_data.get(key, Decimal("0")) - amount savings_data[key] = savings_data.get(key, Decimal("0")) - amount
for (account, currency), amount in savings_data.items(): for (account, currency), amount in savings_data.items():
if amount > 0: if amount > 0:
account_name = f"{account.name} ({currency.code})" account_node_id = get_node_id("account", account.name, account.id)
savings_name = f"{_('Savings')} ({currency.code})" savings_node_id = get_node_id("savings", _("Saved"), account.id)
add_flow(account_name, savings_name, amount, currency) 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 { return {
"nodes": list(nodes.values()), "nodes": list(nodes.values()),
"flows": flows, "flows": flows,
"total_amount": float(total_amount), "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()
},
} }
+96
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
+85 -12
View File
@@ -1,21 +1,94 @@
from django.contrib.auth.decorators import login_required
from django.shortcuts import render 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.transactions.models import Transaction
from apps.insights.utils.sankey import generate_sankey_data 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): def index(request):
return render(request, "insights/pages/index.html") date = timezone.localdate(timezone.now())
month_form = SingleMonthForm(initial={"month": date.replace(day=1)})
year_form = SingleYearForm(initial={"year": date.replace(day=1)})
def sankey(request): month_range_form = MonthRangeForm(
# Get filtered transactions initial={
transactions = Transaction.objects.filter(date__year=2025) "month_from": date.replace(day=1),
"month_to": date.replace(day=1) + relativedelta(months=1),
# Generate Sankey data }
sankey_data = generate_sankey_data(transactions) )
print(sankey_data) 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( return render(
request, "insights/fragments/sankey.html", {"sankey_data": sankey_data} 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"},
) )
@@ -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)
+83 -36
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 ""
+96 -35
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"
+83 -39
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."
+5 -2
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">
@@ -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>
+106 -93
View File
@@ -1,107 +1,120 @@
{% extends "layouts/base.html" %} {% load i18n %}
{% block content %} {% if type == 'account' %}
<canvas id="sankeyChart" height="379"></canvas> <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> <script>
function setupSankeyChart(data, chartId = 'sankeyChart') { var data = {{ sankey_data|safe }};
// Format currency value
function formatCurrency(value, currency) {
return new Intl.NumberFormat(undefined, {
minimumFractionDigits: currency.decimal_places,
maximumFractionDigits: currency.decimal_places
}).format(value);
}
const nodeIndices = {}; function setupSankeyChart(chartId = 'sankeyChart') {
data.nodes.forEach((node, index) => { function formatCurrency(value, currency) {
nodeIndices[node.name] = index; return new Intl.NumberFormat(undefined, {
}); minimumFractionDigits: currency.decimal_places,
maximumFractionDigits: currency.decimal_places
}).format(value);
}
// Format data for Chart.js // Create labels object mapping node IDs to display names
const chartData = { const labels = data.nodes.reduce((acc, node) => {
datasets: [{ acc[node.id] = node.name;
data: data.flows.map(flow => ({ return acc;
from: flow.from_node, }, {});
to: flow.to_node,
flow: flow.flow
})),
colorMode: 'to',
labels: data.nodes.map(node => node.name),
size: 'max',
}]
};
// Chart configuration // Define colors for each node based on its type
const config = { const colors = {};
type: 'sankey', data.nodes.forEach(node => {
data: chartData, if (node.id.startsWith('income_')) {
options: { colors[node.id] = '#4dde80'; // Green for income
responsive: true, } else if (node.id.startsWith('expense_')) {
maintainAspectRatio: false, colors[node.id] = '#f87171'; // Red for expenses
plugins: { } else {
tooltip: { colors[node.id] = '#fbb700'; // Primary for others
callbacks: { }
label: (context) => { });
const flow = data.flows[context.dataIndex];
const formattedValue = formatCurrency(flow.original_amount, flow.currency); // Color getter functions
return [ const getColor = (nodeId) => colors[nodeId];
`De: ${flow.from_node}`, const getHover = (nodeId) => colors[nodeId];
`Para: ${flow.to_node}`,
`Valor: ${flow.currency.prefix}${formattedValue}${flow.currency.suffix}`, // Format data for Chart.js
`Porcentagem: ${flow.percentage.toFixed(2)}%` 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,
} }
},
legend: {
display: false
},
title: {
display: true,
text: 'Fluxo de Transações',
font: {
size: 16
}
}
},
layout: {
padding: {
top: 20,
right: 20,
bottom: 20,
left: 20
} }
} }
};
// Destroy existing chart if it exists
const existingChart = Chart.getChart(chartId);
if (existingChart) {
existingChart.destroy();
} }
};
// Destroy existing chart if it exists // Create new chart
const existingChart = Chart.getChart(chartId); var chart = new Chart(
if (existingChart) { document.getElementById(chartId),
existingChart.destroy(); config
);
window.addEventListener('resize', () => {
chart.resize();
});
document.addEventListener('fullscreenchange', function () {
console.log('oi');
chart.resize();
});
} }
// Create new chart
return new Chart(
document.getElementById(chartId),
config
);
}
// Usage in Django template or JavaScript file
document.addEventListener('DOMContentLoaded', function() {
// Assuming you have the Sankey data in a variable called sankeyData
// For Django template:
const sankeyData = {{ sankey_data|safe }};
console.log(sankeyData);
const chart = setupSankeyChart(sankeyData);
// Optional: Handle resize
window.addEventListener('resize', () => {
chart.resize();
});
});
</script> </script>
{% endblock %}
+83 -12
View File
@@ -1,23 +1,94 @@
{% extends "layouts/base.html" %} {% extends "layouts/base.html" %}
{% load crispy_forms_tags %}
{% load i18n %} {% load i18n %}
{% block content %} {% block content %}
<div class="container-fluid"> <div class="container-fluid">
<div class="row mx-3 mt-3"> <div class="row my-3">
<div class="card shadow w-auto"> <div class="col-lg-2 col-md-3 mb-3 mb-md-0">
<div class="card-body"> <div class="">
<div class="btn-group" role="group" aria-label="Basic radio toggle button group" _="on change log 'oi'"> <div class="mb-2 w-100 d-lg-inline-flex d-grid gap-2 flex-wrap justify-content-lg-center" role="group"
<input type="radio" class="btn-check" name="btnradio" id="btnradio1" autocomplete="off" checked> _="on change
<label class="btn btn-sm btn-outline-primary" for="btnradio1">{% translate 'Month' %}</label> set type to event.target.value
add .tw-hidden to <#picker-form > div:not(.tw-hidden)/>
<input type="radio" class="btn-check" name="btnradio" id="btnradio2" autocomplete="off"> if type == 'month'
<label class="btn btn-sm btn-outline-primary" for="btnradio2">{% translate 'Year' %}</label> 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="btnradio" id="btnradio3" autocomplete="off"> <input type="radio" class="btn-check" name="type" value="year" id="yearradio" autocomplete="off">
<label class="btn btn-sm btn-outline-primary" for="btnradio3">{% translate 'Range' %}</label> <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>
</div> </div>
</div> </div>
</div>
{% endblock %} {% endblock %}
+72
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);
};