Compare commits

..

13 Commits
0.8.4 ... 0.8.6

Author SHA1 Message Date
Herculino Trotta
3190f3ae09 Merge pull request #128
fix: changing startpage to networth breaks homepage
2025-02-02 00:05:19 -03:00
Herculino Trotta
757f6647da fix: changing startpage to networth breaks homepage 2025-02-02 00:04:45 -03:00
Herculino Trotta
6721d9dfee Merge pull request #127
feat: indicate what paid/project button means
2025-02-01 19:06:23 -03:00
Herculino Trotta
9705441e2d feat: indicate what paid/project button means
Closes #122
2025-02-01 19:06:04 -03:00
Herculino Trotta
7123aefad0 Merge pull request #126 from eitchtee/dev
feat: indicate what paid/project button means
2025-02-01 15:05:26 -03:00
Herculino Trotta
712f5f428e feat: indicate what paid/project button means 2025-02-01 15:04:58 -03:00
Herculino Trotta
a2e97b4ba2 Merge pull request #125
fix: changing startpage from monthly breaks homepage
2025-02-01 15:00:22 -03:00
Herculino Trotta
60a694635b fix: changing startpage from monthly breaks homepage
Fixes #121
2025-02-01 14:59:55 -03:00
Herculino Trotta
877816b649 Merge pull request #120
feat: add trash can to see deleted transactions
2025-02-01 11:13:18 -03:00
Herculino Trotta
0a3e47819a feat: add trash can to see deleted transactions 2025-02-01 11:12:43 -03:00
Herculino Trotta
f9d299cb78 refactor: remove single 2025-02-01 09:43:48 -03:00
Herculino Trotta
52934124c1 Merge pull request #118 from eitchtee/dev
feat: add account and currency info to monthly view
2025-02-01 00:51:41 -03:00
Herculino Trotta
39c1f634b6 feat: add account and currency info to monthly view 2025-02-01 00:51:16 -03:00
33 changed files with 2350 additions and 1749 deletions

View File

@@ -19,4 +19,19 @@ urlpatterns = [
views.monthly_summary,
name="monthly_summary",
),
path(
"monthly/<int:month>/<int:year>/summary/accounts/",
views.monthly_account_summary,
name="monthly_account_summary",
),
path(
"monthly/<int:month>/<int:year>/summary/currencies/",
views.monthly_currency_summary,
name="monthly_currency_summary",
),
path(
"monthly/summary/select/<str:selected>/",
views.monthly_summary_select,
name="monthly_summary_select",
),
]

View File

@@ -2,6 +2,7 @@ from django.contrib.auth.decorators import login_required
from django.db.models import (
Q,
)
from django.http import HttpResponse
from django.shortcuts import render, redirect
from django.utils import timezone
from django.views.decorators.http import require_http_methods
@@ -16,6 +17,7 @@ from apps.transactions.models import Transaction
from apps.transactions.utils.calculations import (
calculate_currency_totals,
calculate_percentage_distribution,
calculate_account_totals,
)
from apps.transactions.utils.default_ordering import default_order
@@ -31,6 +33,7 @@ def index(request):
@require_http_methods(["GET"])
def monthly_overview(request, month: int, year: int):
order = request.session.get("monthly_transactions_order", "default")
summary_tab = request.session.get("monthly_summary_tab", "summary")
if month < 1 or month > 12:
from django.http import Http404
@@ -57,6 +60,7 @@ def monthly_overview(request, month: int, year: int):
"previous_year": previous_year,
"filter": f,
"order": order,
"summary_tab": summary_tab,
},
)
@@ -131,3 +135,61 @@ def monthly_summary(request, month: int, year: int):
"monthly_overview/fragments/monthly_summary.html",
context=context,
)
@only_htmx
@login_required
@require_http_methods(["GET"])
def monthly_account_summary(request, month: int, year: int):
# Base queryset with all required filters
base_queryset = Transaction.objects.filter(
reference_date__year=year,
reference_date__month=month,
).exclude(Q(category__mute=True) & ~Q(category=None))
account_data = calculate_account_totals(transactions_queryset=base_queryset.all())
account_percentages = calculate_percentage_distribution(account_data)
context = {
"account_data": account_data,
"account_percentages": account_percentages,
}
return render(
request,
"monthly_overview/fragments/monthly_account_summary.html",
context=context,
)
@only_htmx
@login_required
@require_http_methods(["GET"])
def monthly_currency_summary(request, month: int, year: int):
# Base queryset with all required filters
base_queryset = Transaction.objects.filter(
reference_date__year=year,
reference_date__month=month,
).exclude(Q(category__mute=True) & ~Q(category=None))
currency_data = calculate_currency_totals(base_queryset.all(), ignore_empty=True)
currency_percentages = calculate_percentage_distribution(currency_data)
context = {
"currency_data": currency_data,
"currency_percentages": currency_percentages,
}
return render(
request, "monthly_overview/fragments/monthly_currency_summary.html", context
)
@login_required
@require_http_methods(["GET"])
def monthly_summary_select(request, selected):
request.session["monthly_summary_tab"] = selected
return HttpResponse(
status=204,
)

View File

@@ -228,9 +228,12 @@ class Transaction(models.Model):
def delete(self, *args, **kwargs):
if settings.ENABLE_SOFT_DELETE:
self.deleted = True
self.deleted_at = timezone.now()
self.save()
if not self.deleted:
self.deleted = True
self.deleted_at = timezone.now()
self.save()
else:
super().delete(*args, **kwargs)
else:
super().delete(*args, **kwargs)

View File

@@ -6,11 +6,36 @@ urlpatterns = [
path(
"transactions/list/", views.transaction_all_list, name="transactions_all_list"
),
path(
"transactions/trash/",
views.transactions_trash_can_index,
name="transactions_trash_index",
),
path(
"transactions/trash/list/",
views.transactions_trash_can_list,
name="transactions_trash_list",
),
path(
"transactions/summary/",
views.transaction_all_summary,
name="transactions_all_summary",
),
path(
"transactions/summary/account/",
views.transaction_all_account_summary,
name="transaction_all_account_summary",
),
path(
"transactions/summary/currency/",
views.transaction_all_currency_summary,
name="transaction_all_currency_summary",
),
path(
"transactions/summary/select/<str:selected>/",
views.transaction_all_summary_select,
name="transaction_all_summary_select",
),
path(
"transactions/actions/pay/",
views.bulk_pay_transactions,
@@ -26,6 +51,11 @@ urlpatterns = [
views.bulk_delete_transactions,
name="transactions_bulk_delete",
),
path(
"transactions/actions/undelete/",
views.bulk_undelete_transactions,
name="transactions_bulk_undelete",
),
path(
"transactions/actions/duplicate/",
views.bulk_clone_transactions,
@@ -41,6 +71,11 @@ urlpatterns = [
views.transaction_delete,
name="transaction_delete",
),
path(
"transaction/<int:transaction_id>/undelete/",
views.transaction_undelete,
name="transaction_undelete",
),
path(
"transaction/<int:transaction_id>/edit/",
views.transaction_edit,

View File

@@ -72,8 +72,12 @@ def calculate_currency_totals(transactions_queryset, ignore_empty=False):
.order_by()
)
# Process the results and calculate additional totals
# First pass: Process basic totals and store all currency data
result = {}
currencies_using_exchange = (
{}
) # Track which currencies use which exchange currencies
for total in currency_totals:
# Skip empty currencies if ignore_empty is True
if ignore_empty and all(
@@ -91,7 +95,6 @@ def calculate_currency_totals(transactions_queryset, ignore_empty=False):
total_current = total["income_current"] - total["expense_current"]
total_projected = total["income_projected"] - total["expense_projected"]
total_final = total_current + total_projected
currency_id = total["account__currency"]
from_currency = Currency.objects.get(id=currency_id)
exchange_currency = (
@@ -120,8 +123,6 @@ def calculate_currency_totals(transactions_queryset, ignore_empty=False):
# Add exchanged values if exchange_currency exists
if exchange_currency:
exchanged = {}
# Convert each value
for field in [
"expense_current",
"expense_projected",
@@ -136,7 +137,6 @@ def calculate_currency_totals(transactions_queryset, ignore_empty=False):
from_currency=from_currency,
to_currency=exchange_currency,
)
if amount is not None:
exchanged[field] = amount
if "currency" not in exchanged:
@@ -148,12 +148,48 @@ def calculate_currency_totals(transactions_queryset, ignore_empty=False):
"name": exchange_currency.name,
}
# Only add exchanged data if at least one conversion was successful
if exchanged:
currency_data["exchanged"] = exchanged
# Track which currencies are using which exchange currencies
if exchange_currency.id not in currencies_using_exchange:
currencies_using_exchange[exchange_currency.id] = []
currencies_using_exchange[exchange_currency.id].append(
{"currency_id": currency_id, "exchanged": exchanged}
)
result[currency_id] = currency_data
# Second pass: Add consolidated totals for currencies that are used as exchange currencies
for currency_id, currency_data in result.items():
if currency_id in currencies_using_exchange:
consolidated = {
"currency": currency_data["currency"].copy(),
"expense_current": currency_data["expense_current"],
"expense_projected": currency_data["expense_projected"],
"income_current": currency_data["income_current"],
"income_projected": currency_data["income_projected"],
"total_current": currency_data["total_current"],
"total_projected": currency_data["total_projected"],
"total_final": currency_data["total_final"],
}
# Add exchanged values from all currencies using this as exchange currency
for using_currency in currencies_using_exchange[currency_id]:
exchanged = using_currency["exchanged"]
for field in [
"expense_current",
"expense_projected",
"income_current",
"income_projected",
"total_current",
"total_projected",
"total_final",
]:
if field in exchanged:
consolidated[field] += exchanged[field]
result[currency_id]["consolidated"] = consolidated
return result

View File

@@ -61,7 +61,7 @@ def bulk_unpay_transactions(request):
@login_required
def bulk_delete_transactions(request):
selected_transactions = request.GET.getlist("transactions", [])
transactions = Transaction.objects.filter(id__in=selected_transactions)
transactions = Transaction.all_objects.filter(id__in=selected_transactions)
count = transactions.count()
transactions.delete()
@@ -81,6 +81,30 @@ def bulk_delete_transactions(request):
)
@only_htmx
@login_required
def bulk_undelete_transactions(request):
selected_transactions = request.GET.getlist("transactions", [])
transactions = Transaction.deleted_objects.filter(id__in=selected_transactions)
count = transactions.count()
transactions.update(deleted=False, deleted_at=None)
messages.success(
request,
ngettext_lazy(
"%(count)s transaction restored successfully",
"%(count)s transactions restored successfully",
count,
)
% {"count": count},
)
return HttpResponse(
status=204,
headers={"HX-Trigger": "updated"},
)
@only_htmx
@login_required
def bulk_clone_transactions(request):

View File

@@ -244,7 +244,7 @@ def transaction_clone(request, transaction_id, **kwargs):
@login_required
@require_http_methods(["DELETE"])
def transaction_delete(request, transaction_id, **kwargs):
transaction = get_object_or_404(Transaction, id=transaction_id)
transaction = get_object_or_404(Transaction.all_objects, id=transaction_id)
transaction.delete()
@@ -256,6 +256,24 @@ def transaction_delete(request, transaction_id, **kwargs):
)
@only_htmx
@login_required
@require_http_methods(["GET"])
def transaction_undelete(request, transaction_id, **kwargs):
transaction = get_object_or_404(Transaction.deleted_objects, id=transaction_id)
transaction.deleted = False
transaction.deleted_at = None
transaction.save()
messages.success(request, _("Transaction restored successfully"))
return HttpResponse(
status=204,
headers={"HX-Trigger": "updated"},
)
@only_htmx
@login_required
@require_http_methods(["GET", "POST"])
@@ -314,9 +332,13 @@ def transaction_pay(request, transaction_id):
@require_http_methods(["GET"])
def transaction_all_index(request):
order = request.session.get("all_transactions_order", "default")
summary_tab = request.session.get("transaction_all_summary_tab", "currency")
f = TransactionsFilter(request.GET)
return render(
request, "transactions/pages/transactions.html", {"filter": f, "order": order}
request,
"transactions/pages/transactions.html",
{"filter": f, "order": order, "summary_tab": summary_tab},
)
@@ -382,16 +404,98 @@ def transaction_all_summary(request):
account_percentages = calculate_percentage_distribution(account_data)
context = {
"income_current": remove_falsey_entries(currency_data, "income_current"),
"income_projected": remove_falsey_entries(currency_data, "income_projected"),
"expense_current": remove_falsey_entries(currency_data, "expense_current"),
"expense_projected": remove_falsey_entries(currency_data, "expense_projected"),
"total_current": remove_falsey_entries(currency_data, "total_current"),
"total_final": remove_falsey_entries(currency_data, "total_final"),
"total_projected": remove_falsey_entries(currency_data, "total_projected"),
"currency_data": currency_data,
"currency_percentages": currency_percentages,
"account_data": account_data,
"account_percentages": account_percentages,
}
return render(request, "transactions/fragments/summary.html", context)
@only_htmx
@login_required
@require_http_methods(["GET"])
def transaction_all_account_summary(request):
transactions = Transaction.objects.prefetch_related(
"account",
"account__group",
"category",
"tags",
"account__exchange_currency",
"account__currency",
"installment_plan",
).all()
f = TransactionsFilter(request.GET, queryset=transactions)
account_data = calculate_account_totals(transactions_queryset=f.qs.all())
account_percentages = calculate_percentage_distribution(account_data)
context = {
"account_data": account_data,
"account_percentages": account_percentages,
}
return render(request, "transactions/fragments/all_account_summary.html", context)
@only_htmx
@login_required
@require_http_methods(["GET"])
def transaction_all_currency_summary(request):
transactions = Transaction.objects.prefetch_related(
"account",
"account__group",
"category",
"tags",
"account__exchange_currency",
"account__currency",
"installment_plan",
).all()
f = TransactionsFilter(request.GET, queryset=transactions)
currency_data = calculate_currency_totals(f.qs.all(), ignore_empty=True)
currency_percentages = calculate_percentage_distribution(currency_data)
context = {
"currency_data": currency_data,
"currency_percentages": currency_percentages,
}
return render(request, "transactions/fragments/all_currency_summary.html", context)
@login_required
@require_http_methods(["GET"])
def transaction_all_summary_select(request, selected):
request.session["transaction_all_summary_tab"] = selected
return HttpResponse(
status=204,
)
@login_required
@require_http_methods(["GET"])
def transactions_trash_can_index(request):
return render(request, "transactions/pages/trash.html")
def transactions_trash_can_list(request):
transactions = Transaction.deleted_objects.prefetch_related(
"account",
"account__group",
"category",
"tags",
"account__exchange_currency",
"account__currency",
"installment_plan",
).all()
return render(
request,
"transactions/fragments/trash_list.html",
{"transactions": transactions},
)

View File

@@ -0,0 +1,18 @@
# Generated by Django 5.1.5 on 2025-02-02 02:56
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('users', '0017_usersettings_number_format'),
]
operations = [
migrations.AlterField(
model_name='usersettings',
name='start_page',
field=models.CharField(choices=[('MONTHLY_OVERVIEW', 'Monthly'), ('YEARLY_OVERVIEW_CURRENCY', 'Yearly by currency'), ('YEARLY_OVERVIEW_ACCOUNT', 'Yearly by account'), ('NETWORTH_CURRENT', 'Current Net Worth'), ('NETWORTH_PROJECTED', 'Projected Net Worth'), ('ALL_TRANSACTIONS', 'All Transactions'), ('CALENDAR', 'Calendar')], default='MONTHLY_OVERVIEW', max_length=255, verbose_name='Start page'),
),
]

View File

@@ -26,7 +26,8 @@ class UserSettings(models.Model):
MONTHLY = "MONTHLY_OVERVIEW", _("Monthly")
YEARLY_CURRENCY = "YEARLY_OVERVIEW_CURRENCY", _("Yearly by currency")
YEARLY_ACCOUNT = "YEARLY_OVERVIEW_ACCOUNT", _("Yearly by account")
NETWORTH = "NETWORTH", _("Net Worth")
NETWORTH_CURRENT = "NETWORTH_CURRENT", _("Current Net Worth")
NETWORTH_PROJECTED = "NETWORTH_PROJECTED", _("Projected Net Worth")
ALL_TRANSACTIONS = "ALL_TRANSACTIONS", _("All Transactions")
CALENDAR = "CALENDAR", _("Calendar")

View File

@@ -26,10 +26,14 @@ def logout_view(request):
def index(request):
if request.user.settings.start_page == UserSettings.StartPage.MONTHLY:
return redirect(reverse("monthly_index"))
elif request.user.settings.start_page == UserSettings.StartPage.YEARLY:
return redirect(reverse("yearly_index"))
elif request.user.settings.start_page == UserSettings.StartPage.NETWORTH:
return redirect(reverse("net_worth"))
elif request.user.settings.start_page == UserSettings.StartPage.YEARLY_ACCOUNT:
return redirect(reverse("yearly_index_account"))
elif request.user.settings.start_page == UserSettings.StartPage.YEARLY_CURRENCY:
return redirect(reverse("yearly_index_currency"))
elif request.user.settings.start_page == UserSettings.StartPage.NETWORTH_CURRENT:
return redirect(reverse("net_worth_current"))
elif request.user.settings.start_page == UserSettings.StartPage.NETWORTH_PROJECTED:
return redirect(reverse("net_worth_projected"))
elif request.user.settings.start_page == UserSettings.StartPage.ALL_TRANSACTIONS:
return redirect(reverse("transactions_all_index"))
elif request.user.settings.start_page == UserSettings.StartPage.CALENDAR:

View File

@@ -89,7 +89,6 @@ def yearly_overview_by_currency(request, year: int):
"year": year,
"totals": data,
"percentages": percentages,
"single": True if currency else False,
},
)
@@ -159,6 +158,5 @@ def yearly_overview_by_account(request, year: int):
"year": year,
"totals": data,
"percentages": percentages,
"single": True if account else False,
},
)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,83 +2,95 @@
{% load i18n %}
<div class="transaction d-flex my-1 {% if transaction.type == "EX" %}expense{% else %}income{% endif %}">
{% if not disable_selection %}
<label class="px-3 d-flex align-items-center justify-content-center">
<input class="form-check-input" type="checkbox" name="transactions" value="{{ transaction.id }}" id="check-{{ transaction.id }}" aria-label="{% translate 'Select' %}" hx-preserve>
</label>
<label class="px-3 d-flex align-items-center justify-content-center">
<input class="form-check-input" type="checkbox" name="transactions" value="{{ transaction.id }}"
id="check-{{ transaction.id }}" aria-label="{% translate 'Select' %}" hx-preserve>
</label>
{% endif %}
<div class="tw-border-s-6 tw-border-e-0 tw-border-t-0 tw-border-b-0 border-bottom
hover:tw-bg-zinc-900 p-2 {% if transaction.account.is_asset %}tw-border-dashed{% else %}tw-border-solid{% endif %}
{% if transaction.type == "EX" %}tw-border-red-500{% else %}tw-border-green-500{% endif %} tw-relative
w-100 transaction-item"
_="on mouseover remove .tw-invisible from the first .transaction-actions in me end
_="on mouseover remove .tw-invisible from the first .transaction-actions in me end
on mouseout add .tw-invisible to the first .transaction-actions in me end">
<div class="row font-monospace tw-text-sm align-items-center">
<div class="col-lg-1 col-12 d-flex align-items-center tw-text-2xl lg:tw-text-xl text-lg-center text-center">
<a class="text-decoration-none my-lg-3 mx-lg-3 mx-2 my-2 tw-text-gray-500"
role="button"
hx-get="{% url 'transaction_pay' transaction_id=transaction.id %}"
hx-target="closest .transaction"
hx-swap="outerHTML">
<div class="col-lg-1 col-12 d-flex align-items-center tw-text-2xl lg:tw-text-xl text-lg-center text-center">
{% if not transaction.deleted %}
<a class="text-decoration-none my-lg-3 mx-lg-3 mx-2 my-2 tw-text-gray-500"
title="{% if transaction.is_paid %}{% trans 'Paid' %}{% else %}{% trans 'Projected' %}{% endif %}"
role="button"
hx-get="{% url 'transaction_pay' transaction_id=transaction.id %}"
hx-target="closest .transaction"
hx-swap="outerHTML">
{% if transaction.is_paid %}<i class="fa-regular fa-circle-check"></i>{% else %}<i
class="fa-regular fa-circle"></i>{% endif %}
</a>
</div>
<div class="col-lg-8 col-12">
{# Date#}
<div class="row mb-2 mb-lg-1 tw-text-gray-400">
</a>
{% else %}
<div class="text-decoration-none my-lg-3 mx-lg-3 mx-2 my-2 tw-text-gray-500"
title="{% if transaction.is_paid %}{% trans 'Paid' %}{% else %}{% trans 'Projected' %}{% endif %}">
{% if transaction.is_paid %}<i class="fa-regular fa-circle-check"></i>{% else %}<i
class="fa-regular fa-circle"></i>{% endif %}
</div>
{% endif %}
</div>
<div class="col-lg-8 col-12">
{# Date#}
<div class="row mb-2 mb-lg-1 tw-text-gray-400">
<div class="col-auto pe-1"><i class="fa-solid fa-calendar fa-fw me-1 fa-xs"></i></div>
<div class="col ps-0">{{ transaction.date|date:"SHORT_DATE_FORMAT" }} • {{ transaction.reference_date|date:"b/Y" }}</div>
<div
class="col ps-0">{{ transaction.date|date:"SHORT_DATE_FORMAT" }} • {{ transaction.reference_date|date:"b/Y" }}</div>
</div>
{# Description#}
<div class="mb-2 mb-lg-1 text-white tw-text-base">
{% spaceless %}
<span>{{ transaction.description }}</span>
{% if transaction.installment_plan and transaction.installment_id %}
<span
class="badge text-bg-secondary ms-2">{{ transaction.installment_id }}/{{ transaction.installment_plan.installment_total_number }}</span>
{% endif %}
{% if transaction.recurring_transaction %}
<span class="text-primary tw-text-xs ms-2"><i class="fa-solid fa-arrows-rotate fa-fw"></i></span>
{% endif %}
{% endspaceless %}
</div>
<div class="tw-text-gray-400 tw-text-sm">
{# Entities #}
{% with transaction.entities.all as entities %}
{% if entities %}
<div class="row mb-2 mb-lg-1 tw-text-gray-400">
<div class="col-auto pe-1"><i class="fa-solid fa-user-group fa-fw me-1 fa-xs"></i></div>
<div class="col ps-0">{{ entities|join:", " }}</div>
</div>
{% endif %}
{% endwith %}
{# Notes#}
{% if transaction.notes %}
<div class="row mb-2 mb-lg-1 tw-text-gray-400">
<div class="col-auto pe-1"><i class="fa-solid fa-align-left fa-fw me-1 fa-xs"></i></div>
<div class="col ps-0">{{ transaction.notes | limited_markdown | linebreaksbr }}</div>
</div>
{% endif %}
{# Category#}
{% if transaction.category %}
<div class="row mb-2 mb-lg-1 tw-text-gray-400">
<div class="col-auto pe-1"><i class="fa-solid fa-icons fa-fw me-1 fa-xs"></i></div>
<div class="col ps-0">{{ transaction.category.name }}</div>
</div>
{% endif %}
{# Tags#}
{% with transaction.tags.all as tags %}
{% if tags %}
<div class="row mb-2 mb-lg-1 tw-text-gray-400">
<div class="col-auto pe-1"><i class="fa-solid fa-hashtag fa-fw me-1 fa-xs"></i></div>
<div class="col ps-0">{{ tags|join:", " }}</div>
</div>
{% endif %}
{% endwith %}
</div>
</div>
{# Description#}
<div class="mb-2 mb-lg-1 text-white tw-text-base">
{% spaceless %}
<span>{{ transaction.description }}</span>
{% if transaction.installment_plan and transaction.installment_id %}
<span class="badge text-bg-secondary ms-2">{{ transaction.installment_id }}/{{ transaction.installment_plan.installment_total_number }}</span>
{% endif %}
{% if transaction.recurring_transaction %}
<span class="text-primary tw-text-xs ms-2"><i class="fa-solid fa-arrows-rotate fa-fw"></i></span>
{% endif %}
{% endspaceless %}
</div>
<div class="tw-text-gray-400 tw-text-sm">
{# Entities #}
{% with transaction.entities.all as entities %}
{% if entities %}
<div class="row mb-2 mb-lg-1 tw-text-gray-400">
<div class="col-auto pe-1"><i class="fa-solid fa-user-group fa-fw me-1 fa-xs"></i></div>
<div class="col ps-0">{{ entities|join:", " }}</div>
</div>
{% endif %}
{% endwith %}
{# Notes#}
{% if transaction.notes %}
<div class="row mb-2 mb-lg-1 tw-text-gray-400">
<div class="col-auto pe-1"><i class="fa-solid fa-align-left fa-fw me-1 fa-xs"></i></div>
<div class="col ps-0">{{ transaction.notes | limited_markdown | linebreaksbr }}</div>
</div>
{% endif %}
{# Category#}
{% if transaction.category %}
<div class="row mb-2 mb-lg-1 tw-text-gray-400">
<div class="col-auto pe-1"><i class="fa-solid fa-icons fa-fw me-1 fa-xs"></i></div>
<div class="col ps-0">{{ transaction.category.name }}</div>
</div>
{% endif %}
{# Tags#}
{% with transaction.tags.all as tags %}
{% if tags %}
<div class="row mb-2 mb-lg-1 tw-text-gray-400">
<div class="col-auto pe-1"><i class="fa-solid fa-hashtag fa-fw me-1 fa-xs"></i></div>
<div class="col ps-0">{{ tags|join:", " }}</div>
</div>
{% endif %}
{% endwith %}
</div>
</div>
<div class="col-lg-3 col-12 text-lg-end align-self-end">
<div class="col-lg-3 col-12 text-lg-end align-self-end">
<div class="main-amount mb-2 mb-lg-0">
<c-amount.display
<c-amount.display
:amount="transaction.amount"
:prefix="transaction.account.currency.prefix"
:suffix="transaction.account.currency.suffix"
@@ -87,53 +99,77 @@
</div>
{# Exchange Rate#}
{% with exchanged=transaction.exchanged_amount %}
{% if exchanged %}
<div class="exchanged-amount mb-2 mb-lg-0">
<c-amount.display
:amount="exchanged.amount"
:prefix="exchanged.prefix"
:suffix="exchanged.suffix"
:decimal_places="exchanged.decimal_places"
color="grey"></c-amount.display>
</div>
{% endif %}
{% if exchanged %}
<div class="exchanged-amount mb-2 mb-lg-0">
<c-amount.display
:amount="exchanged.amount"
:prefix="exchanged.prefix"
:suffix="exchanged.suffix"
:decimal_places="exchanged.decimal_places"
color="grey"></c-amount.display>
</div>
{% endif %}
{% endwith %}
<div>{% if transaction.account.group %}{{ transaction.account.group.name }} • {% endif %}{{ transaction.account.name }}</div>
</div>
<div>
{# Item actions#}
<div class="transaction-actions !tw-absolute tw-left-1/2 tw-top-0 tw--translate-x-1/2 tw--translate-y-1/2 tw-invisible d-flex flex-row card">
<div class="card-body p-1 shadow-lg">
<a class="btn btn-secondary btn-sm transaction-action"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Edit" %}"
hx-get="{% url 'transaction_edit' transaction_id=transaction.id %}"
hx-target="#generic-offcanvas" hx-swap="innerHTML">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm transaction-action"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Duplicate" %}"
hx-get="{% url 'transaction_clone' transaction_id=transaction.id %}"
_="on click if event.ctrlKey set @hx-get to `{% url 'transaction_clone' transaction_id=transaction.id %}?edit=true` then call htmx.process(me) end then trigger ready"
hx-trigger="ready" >
<i class="fa-solid fa-clone fa-fw"></i></a>
<a class="btn btn-secondary btn-sm transaction-action"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Delete" %}"
hx-delete="{% url 'transaction_delete' transaction_id=transaction.id %}"
hx-trigger='confirmed'
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw text-danger"></i>
</a>
<div>
{% if transaction.account.group %}{{ transaction.account.group.name }} • {% endif %}{{ transaction.account.name }}
</div>
</div>
<div>
{# Item actions#}
<div
class="transaction-actions !tw-absolute tw-left-1/2 tw-top-0 tw--translate-x-1/2 tw--translate-y-1/2 tw-invisible d-flex flex-row card">
<div class="card-body p-1 shadow-lg">
{% if not transaction.deleted %}
<a class="btn btn-secondary btn-sm transaction-action"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Edit" %}"
hx-get="{% url 'transaction_edit' transaction_id=transaction.id %}"
hx-target="#generic-offcanvas" hx-swap="innerHTML">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm transaction-action"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Duplicate" %}"
hx-get="{% url 'transaction_clone' transaction_id=transaction.id %}"
_="on click if event.ctrlKey set @hx-get to `{% url 'transaction_clone' transaction_id=transaction.id %}?edit=true` then call htmx.process(me) end then trigger ready"
hx-trigger="ready">
<i class="fa-solid fa-clone fa-fw"></i></a>
<a class="btn btn-secondary btn-sm transaction-action"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Delete" %}"
hx-delete="{% url 'transaction_delete' transaction_id=transaction.id %}"
hx-trigger='confirmed'
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw text-danger"></i>
</a>
{% else %}
<a class="btn btn-secondary btn-sm transaction-action"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Restore" %}"
hx-get="{% url 'transaction_undelete' transaction_id=transaction.id %}"><i
class="fa-solid fa-trash-arrow-up"></i></a>
<a class="btn btn-secondary btn-sm transaction-action"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Delete" %}"
hx-delete="{% url 'transaction_delete' transaction_id=transaction.id %}"
hx-trigger='confirmed'
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw text-danger"></i>
</a>
{% endif %}
</div>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,184 @@
{% load tools %}
{% load i18n %}
<div class="col card shadow">
<div class="card-body">
{% if account.account.group %}
<div class="tw-text-sm mb-2">
<span class="badge text-bg-primary ">{{ account.account.group }}</span>
</div>
{% endif %}
<h5 class="card-title">
{{ account.account.name }}
</h5>
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected income' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace tw-text-green-400">
<c-amount.display
:amount="account.income_projected"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display>
</div>
</div>
{% if account.exchanged and account.exchanged.income_projected %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.currency.income_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected expenses' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div>
<div class="text-end font-monospace tw-text-red-400">
<c-amount.display
:amount="account.expense_projected"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display>
</div>
</div>
</div>
{% if account.exchanged and account.exchanged.expense_projected %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.expense_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected total' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div
class="text-end font-monospace">
<c-amount.display
:amount="account.total_projected"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_projected > 0 %}green{% elif account.total_projected < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if account.exchanged.total_projected and account.exchanged.total_projected %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.total_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<hr class="my-3">
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current income' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace tw-text-green-400">
<c-amount.display
:amount="account.income_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display>
</div>
</div>
{% if account.exchanged and account.exchanged.income_current %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.income_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current expenses' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace tw-text-red-400">
<c-amount.display
:amount="account.expense_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display>
</div>
</div>
{% if account.exchanged and account.exchanged.expense_current %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.expense_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current total' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div
class="text-end font-monospace">
<c-amount.display
:amount="account.total_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_current > 0 %}green{% elif account.total_current < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if account.exchanged and account.exchanged.total_current %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.total_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div>
<hr class="my-3">
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'final total' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace">
<c-amount.display
:amount="account.total_final"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_final > 0 %}green{% elif account.total_final < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if account.exchanged and account.exchanged.total_final %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.total_final"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
</div>
{% with p=percentages|get_dict_item:account_id %}
<div class="my-3">
<c-ui.percentage-distribution :percentage="p"></c-ui.percentage-distribution>
</div>
{% endwith %}
</div>
</div>

View File

@@ -0,0 +1,182 @@
{% load tools %}
{% load i18n %}
<div class="col card shadow">
<div class="card-body">
<div class="tw-text-sm mb-2">
<span class="badge text-bg-primary">{{ currency.currency.code }}</span>
</div>
<h5 class="card-title">
{{ currency.currency.name }}
</h5>
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected income' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace tw-text-green-400">
<c-amount.display
:amount="currency.income_projected"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"></c-amount.display>
</div>
</div>
{% if currency.exchanged and currency.exchanged.income_projected %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="currency.exchanged.currency.income_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected expenses' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div>
<div class="text-end font-monospace tw-text-red-400">
<c-amount.display
:amount="currency.expense_projected"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"></c-amount.display>
</div>
</div>
</div>
{% if currency.exchanged and currency.exchanged.expense_projected %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="currency.exchanged.expense_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected total' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div
class="text-end font-monospace">
<c-amount.display
:amount="currency.total_projected"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="{% if currency.total_projected > 0 %}green{% elif currency.total_projected < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if currency.exchanged.total_projected and currency.exchanged.total_projected %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="currency.exchanged.total_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<hr class="my-3">
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current income' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace tw-text-green-400">
<c-amount.display
:amount="currency.income_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"></c-amount.display>
</div>
</div>
{% if currency.exchanged and currency.exchanged.income_current %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="currency.exchanged.income_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current expenses' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace tw-text-red-400">
<c-amount.display
:amount="currency.expense_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"></c-amount.display>
</div>
</div>
{% if currency.exchanged and currency.exchanged.expense_current %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="currency.exchanged.expense_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current total' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div
class="text-end font-monospace">
<c-amount.display
:amount="currency.total_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="{% if currency.total_current > 0 %}green{% elif currency.total_current < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if currency.exchanged and currency.exchanged.total_current %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="currency.exchanged.total_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div>
<hr class="my-3">
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'final total' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace">
<c-amount.display
:amount="currency.total_final"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="{% if currency.total_final > 0 %}green{% elif currency.total_final < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if currency.exchanged and currency.exchanged.total_final %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="currency.exchanged.total_final"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
</div>
{% with p=percentages|get_dict_item:currency_id %}
<div class="my-3">
<c-ui.percentage-distribution :percentage="p"></c-ui.percentage-distribution>
</div>
{% endwith %}
</div>
</div>

View File

@@ -0,0 +1,243 @@
{% load i18n %}
<div class="tw-sticky tw-bottom-4 tw-left-0 tw-right-0 tw-z-50 tw-hidden mx-auto tw-w-fit" id="actions-bar"
_="on change from #transactions-list or htmx:afterSettle from window
if #actions-bar then
if no <input[type='checkbox']:checked/> in #transactions-list
if #actions-bar
add .slide-in-bottom-reverse then settle
then add .tw-hidden to #actions-bar
then remove .slide-in-bottom-reverse
end
else
if #actions-bar
remove .tw-hidden from #actions-bar
then trigger selected_transactions_updated
end
end
end
end">
<div class="card slide-in-bottom">
<div class="card-body p-2 d-flex justify-content-between align-items-center gap-3">
{% spaceless %}
<div class="dropdown">
<button class="btn btn-secondary btn-sm dropdown-toggle" type="button" data-bs-toggle="dropdown"
aria-expanded="false">
<i class="fa-regular fa-square-check fa-fw"></i>
</button>
<ul class="dropdown-menu">
<li>
<div class="dropdown-item px-3 tw-cursor-pointer"
_="on click set <#transactions-list input[type='checkbox']/>'s checked to true then call me.blur() then trigger change">
<i class="fa-regular fa-square-check tw-text-green-400 me-3"></i>{% translate 'Select All' %}
</div>
</li>
<li>
<div class="dropdown-item px-3 tw-cursor-pointer"
_="on click set <#transactions-list input[type='checkbox']/>'s checked to false then call me.blur() then trigger change">
<i class="fa-regular fa-square tw-text-red-400 me-3"></i>{% translate 'Unselect All' %}
</div>
</li>
</ul>
</div>
<div class="vr tw-align-middle"></div>
<button class="btn btn-secondary btn-sm"
hx-get="{% url 'transactions_bulk_undelete' %}"
hx-include=".transaction"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Restore' %}">
<i class="fa-solid fa-trash-arrow-up fa-fw"></i>
</button>
<button class="btn btn-secondary btn-sm"
hx-get="{% url 'transactions_bulk_delete' %}"
hx-include=".transaction"
hx-trigger="confirmed"
data-bs-toggle="tooltip"
data-bs-title="{% translate 'Delete' %}"
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete them!" %}"
_="install prompt_swal">
<i class="fa-solid fa-trash text-danger"></i>
</button>
<div class="vr tw-align-middle"></div>
<div class="btn-group"
_="on selected_transactions_updated from #actions-bar
set realTotal to math.bignumber(0)
set flatTotal to math.bignumber(0)
set transactions to <.transaction:has(input[name='transactions']:checked)/>
set flatAmountValues to []
set realAmountValues to []
for transaction in transactions
set amt to first <.main-amount .amount/> in transaction
set amountValue to parseFloat(amt.getAttribute('data-amount'))
append amountValue to flatAmountValues
if not isNaN(amountValue)
set flatTotal to math.chain(flatTotal).add(amountValue)
if transaction match .income
append amountValue to realAmountValues
set realTotal to math.chain(realTotal).add(amountValue)
else
append -amountValue to realAmountValues
set realTotal to math.chain(realTotal).subtract(amountValue)
end
end
end
set mean to flatTotal.divide(flatAmountValues.length).done().toNumber()
set realTotal to realTotal.done().toNumber()
set flatTotal to flatTotal.done().toNumber()
put realTotal.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #real-total-front's innerText
put realTotal.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-real-total's innerText
put flatTotal.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-flat-total's innerText
put Math.max.apply(Math, realAmountValues).toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-max's innerText
put Math.min.apply(Math, realAmountValues).toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-min's innerText
put mean.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-mean's innerText
put flatAmountValues.length.toLocaleString(undefined, {minimumFractionDigits: 0, maximumFractionDigits: 40}) into #calc-menu-count's innerText
end">
<button class="btn btn-secondary btn-sm" _="on click
set original_value to #real-total-front's innerText
writeText(original_value) on navigator.clipboard
put '{% translate "copied!" %}' into #real-total-front's innerText
wait 1s
put original_value into #real-total-front's innerText
end">
<i class="fa-solid fa-plus fa-fw me-md-2 text-primary"></i>
<span class="d-none d-md-inline-block" id="real-total-front">0</span>
</button>
<button type="button" class="btn btn-sm btn-secondary dropdown-toggle dropdown-toggle-split"
data-bs-toggle="dropdown" aria-expanded="false" data-bs-auto-close="outside">
<span class="visually-hidden">{% trans "Toggle Dropdown" %}</span>
</button>
<ul class="dropdown-menu">
<li>
<div class="dropdown-item-text p-0">
<div>
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
{% trans "Flat Total" %}
</div>
<div class="dropdown-item px-3 tw-cursor-pointer"
id="calc-menu-flat-total"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
{% trans "Real Total" %}
</div>
<div class="dropdown-item px-3 tw-cursor-pointer"
id="calc-menu-real-total"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
{% trans "Mean" %}
</div>
<div class="dropdown-item px-3 tw-cursor-pointer"
id="calc-menu-mean"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
{% trans "Max" %}
</div>
<div class="dropdown-item px-3 tw-cursor-pointer"
id="calc-menu-max"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
{% trans "Min" %}
</div>
<div class="dropdown-item px-3 tw-cursor-pointer"
id="calc-menu-min"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</div>
</div>
</div>
</li>
<li>
<div class="dropdown-item-text p-0">
<div>
<div class="text-body-secondary tw-text-xs tw-font-medium px-3">
{% trans "Count" %}
</div>
<div class="dropdown-item px-3 tw-cursor-pointer"
id="calc-menu-count"
_="on click
set original_value to my innerText
writeText(my innerText) on navigator.clipboard
put '{% translate "copied!" %}' into me
wait 1s
put original_value into me
end">
0
</div>
</div>
</div>
</li>
</ul>
</div>
{% endspaceless %}
</div>
</div>
</div>

View File

@@ -1,16 +1,18 @@
{% load i18n %}
<div class="tw-sticky tw-bottom-4 tw-left-0 tw-right-0 tw-z-50 tw-hidden mx-auto tw-w-fit" id="actions-bar"
_="on change from #transactions-list or htmx:afterSettle from window
if no <input[type='checkbox']:checked/> in #transactions-list
if #actions-bar
add .slide-in-bottom-reverse then settle
then add .tw-hidden to #actions-bar
then remove .slide-in-bottom-reverse
end
else
if #actions-bar
remove .tw-hidden from #actions-bar
then trigger selected_transactions_updated
if #actions-bar then
if no <input[type='checkbox']:checked/> in #transactions-list
if #actions-bar
add .slide-in-bottom-reverse then settle
then add .tw-hidden to #actions-bar
then remove .slide-in-bottom-reverse
end
else
if #actions-bar
remove .tw-hidden from #actions-bar
then trigger selected_transactions_updated
end
end
end
end">

View File

@@ -1,3 +1,4 @@
{% load settings %}
{% load static %}
{% load i18n %}
{% load active_link %}
@@ -56,7 +57,13 @@
<li><a class="dropdown-item {% active_link views='transactions_all_index' %}"
href="{% url 'transactions_all_index' %}">{% translate 'All' %}</a></li>
<li>
<hr class="dropdown-divider">
{% settings "ENABLE_SOFT_DELETE" as enable_soft_delete %}
{% if enable_soft_delete %}
<li><a class="dropdown-item {% active_link views='transactions_trash_index' %}"
href="{% url 'transactions_trash_index' %}">{% translate 'Trash Can' %}</a></li>
<li>
{% endif %}
<hr class="dropdown-divider">
</li>
<li><a class="dropdown-item {% active_link views='installment_plans_index' %}"
href="{% url 'installment_plans_index' %}">{% translate 'Installment Plans' %}</a></li>

View File

@@ -0,0 +1,17 @@
{% load tools %}
{% load i18n %}
{% load currency_display %}
<div class="row row-cols-1 g-4 mt-1 mb-3">
{% for account_id, account in account_data.items %}
<div class="col">
<c-ui.account_card :account="account" :account_id="account_id"
:percentages="account_percentages"></c-ui.account_card>
</div>
{% empty %}
<div class="col">
<c-msg.empty
title="{% translate "No information to display" %}"></c-msg.empty>
{% endfor %}
</div>
</div>

View File

@@ -0,0 +1,16 @@
{% load tools %}
{% load i18n %}
{% load currency_display %}
<div class="row row-cols-1 g-4 mt-1 mb-3">
{% for currency_id, currency in currency_data.items %}
<div class="col">
<c-ui.currency_card :currency="currency" :currency_id="currency_id"
:percentages="currency_percentages"></c-ui.currency_card>
</div>
{% empty %}
<div class="col">
<c-msg.empty
title="{% translate "No information to display" %}"></c-msg.empty>
</div>
{% endfor %}
</div>

View File

@@ -1,6 +1,6 @@
{% load i18n %}
{% load currency_display %}
<div class="row row-cols-1 g-4 mb-3">
<div class="row row-cols-1 g-4 mt-1 mb-3">
{# Daily Spending#}
<div class="col">
<c-ui.info-card color="yellow" icon="fa-solid fa-calendar-day" title="{% trans 'Daily Spending Allowance' %}" help_text={% trans "This is the final total divided by the remaining days in the month" %}>
@@ -252,6 +252,7 @@
</div>
</c-ui.info-card>
</div>
{% if percentages %}
<div class="col">
<c-ui.info-card color="yellow" icon="fa-solid fa-percent" title="{% trans 'Distribution' %}">
{% for p in percentages.values %}
@@ -260,4 +261,5 @@
{% endfor %}
</c-ui.info-card>
</div>
{% endif %}
</div>

View File

@@ -13,89 +13,172 @@
{% endblock %}
{% block content %}
<div class="container px-md-3 py-3 column-gap-5">
<div class="row mb-3 gx-xl-4 gy-3 mb-4">
{# Date picker#}
<div class="col-12 col-xl-4 flex-row align-items-center d-flex">
<div class="tw-text-base h-100 align-items-center d-flex">
<a role="button"
class="pe-4 py-2"
hx-boost="true"
hx-trigger="click, previous_month from:window"
href="{% url 'monthly_overview' month=previous_month year=previous_year %}"><i
class="fa-solid fa-chevron-left"></i></a>
</div>
<div class="tw-text-3xl fw-bold font-monospace tw-w-full text-center"
hx-get="{% url 'month_year_picker' %}"
hx-target="#generic-offcanvas-left"
hx-trigger="click, date_picker from:window"
hx-vals='{"month": {{ month }}, "year": {{ year }}, "for": "monthly_overview", "field": "reference_date"}' role="button">
{{ month|month_name }} {{ year }}
</div>
<div class="tw-text-base mx-2 h-100 align-items-center d-flex">
<a role="button"
class="ps-3 py-2"
hx-boost="true"
hx-trigger="click, next_month from:window"
href="{% url 'monthly_overview' month=next_month year=next_year %}">
<i class="fa-solid fa-chevron-right"></i>
</a>
</div>
</div>
{# Action buttons#}
<div class="col-12 col-xl-8">
<c-ui.quick-transactions-buttons
:year="year"
:month="month"
></c-ui.quick-transactions-buttons>
</div>
</div>
{# Monthly summary#}
<div class="row gx-xl-4 gy-3">
<div class="col-12 col-xl-4 order-0 order-xl-2">
<div id="summary" hx-get="{% url 'monthly_summary' month=month year=year %}" class="show-loading"
hx-trigger="load, updated from:window, selective_update from:window">
</div>
</div>
<div class="col-12 col-xl-8 order-2 order-xl-1">
<div class="row mb-1">
<div class="col-sm-6 col-12">
{# Filter transactions button #}
<button type="button" class="btn btn-sm btn-outline-primary dropdown-toggle" type="button" data-bs-toggle="collapse" data-bs-target="#collapse-filter" aria-expanded="false" aria-controls="collapse-filter">
<i class="fa-solid fa-filter fa-fw me-2"></i>{% translate 'Filter transactions' %}
</button>
<div class="container px-md-3 py-3 column-gap-5">
<div class="row mb-3 gx-xl-4 gy-3 mb-4">
{# Date picker#}
<div class="col-12 col-xl-4 flex-row align-items-center d-flex">
<div class="tw-text-base h-100 align-items-center d-flex">
<a role="button"
class="pe-4 py-2"
hx-boost="true"
hx-trigger="click, previous_month from:window"
href="{% url 'monthly_overview' month=previous_month year=previous_year %}"><i
class="fa-solid fa-chevron-left"></i></a>
</div>
{# Ordering button#}
<div class="col-sm-6 col-12 tw-content-center my-3 my-sm-0">
<div class="text-sm-end" _="on change trigger updated on window">
<label for="order">{% translate "Order by" %}</label>
<select class="tw-border-0 focus-visible:tw-outline-0 w-full pe-2 tw-leading-normal text-bg-tertiary tw-font-medium rounded" name="order" id="order">
<option value="default" {% if order == 'default' %}selected{% endif %}>{% translate 'Default' %}</option>
<option value="older" {% if order == 'older' %}selected{% endif %}>{% translate 'Oldest first' %}</option>
<option value="newer" {% if order == 'newer' %}selected{% endif %}>{% translate 'Newest first' %}</option>
</select>
<div class="tw-text-3xl fw-bold font-monospace tw-w-full text-center"
hx-get="{% url 'month_year_picker' %}"
hx-target="#generic-offcanvas-left"
hx-trigger="click, date_picker from:window"
hx-vals='{"month": {{ month }}, "year": {{ year }}, "for": "monthly_overview", "field": "reference_date"}'
role="button">
{{ month|month_name }} {{ year }}
</div>
<div class="tw-text-base mx-2 h-100 align-items-center d-flex">
<a role="button"
class="ps-3 py-2"
hx-boost="true"
hx-trigger="click, next_month from:window"
href="{% url 'monthly_overview' month=next_month year=next_year %}">
<i class="fa-solid fa-chevron-right"></i>
</a>
</div>
</div>
{# Action buttons#}
<div class="col-12 col-xl-8">
<c-ui.quick-transactions-buttons
:year="year"
:month="month"
></c-ui.quick-transactions-buttons>
</div>
</div>
{# Monthly summary#}
<div class="row gx-xl-4 gy-3">
<div class="col-12 col-xl-4 order-0 order-xl-2">
<ul class="nav nav-tabs" id="monthly-summary" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link {% if summary_tab == 'summary' %}active{% endif %}"
id="summary-tab"
data-bs-toggle="tab"
data-bs-target="#summary-tab-pane"
type="button"
role="tab"
aria-controls="summary-tab-pane"
_="on click fetch {% url 'monthly_summary_select' selected='summary' %}"
aria-selected="{% if summary_tab == 'summary' or not summary_tab %}true{% else %}false{% endif %}">
{% trans 'Summary' %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link {% if summary_tab == 'currency' %}active{% endif %}"
id="currency-tab"
data-bs-toggle="tab"
data-bs-target="#currency-tab-pane"
type="button"
role="tab"
aria-controls="currency-tab-pane"
_="on click fetch {% url 'monthly_summary_select' selected='currency' %}"
aria-selected="{% if summary_tab == 'currency' %}true{% else %}false{% endif %}">
{% trans 'Currencies' %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link {% if summary_tab == 'account' %}active{% endif %}"
id="account-tab"
data-bs-toggle="tab"
data-bs-target="#account-tab-pane"
type="button"
role="tab"
aria-controls="account-tab-pane"
_="on click fetch {% url 'monthly_summary_select' selected='account' %}"
aria-selected="{% if summary_tab == 'account' %}true{% else %}false{% endif %}">
{% trans 'Accounts' %}
</button>
</li>
</ul>
<div class="tab-content" id="monthly-summary-content">
<div class="tab-pane fade {% if summary_tab == 'summary' %}show active{% endif %}"
id="summary-tab-pane"
role="tabpanel"
aria-labelledby="summary-tab"
tabindex="0">
<div id="summary"
hx-get="{% url 'monthly_summary' month=month year=year %}"
class="show-loading"
hx-trigger="load, updated from:window, selective_update from:window">
</div>
</div>
<div class="tab-pane fade {% if summary_tab == 'currency' %}show active{% endif %}"
id="currency-tab-pane"
role="tabpanel"
aria-labelledby="currency-tab"
tabindex="0">
<div id="currency-summary"
hx-get="{% url 'monthly_currency_summary' month=month year=year %}"
class="show-loading"
hx-trigger="load, updated from:window, selective_update from:window">
</div>
</div>
<div class="tab-pane fade {% if summary_tab == 'account' %}show active{% endif %}"
id="account-tab-pane"
role="tabpanel"
aria-labelledby="account-tab"
tabindex="0">
<div id="account-summary"
hx-get="{% url 'monthly_account_summary' month=month year=year %}"
class="show-loading"
hx-trigger="load, updated from:window, selective_update from:window">
</div>
</div>
</div>
</div>
{# Filter transactions form#}
<div class="collapse" id="collapse-filter">
<div class="card card-body">
<form _="on change or submit or search trigger updated on window end
<div class="col-12 col-xl-8 order-2 order-xl-1">
<div class="row mb-1">
<div class="col-sm-6 col-12">
{# Filter transactions button #}
<button type="button" class="btn btn-sm btn-outline-primary dropdown-toggle" type="button"
data-bs-toggle="collapse" data-bs-target="#collapse-filter" aria-expanded="false"
aria-controls="collapse-filter">
<i class="fa-solid fa-filter fa-fw me-2"></i>{% translate 'Filter transactions' %}
</button>
</div>
{# Ordering button#}
<div class="col-sm-6 col-12 tw-content-center my-3 my-sm-0">
<div class="text-sm-end" _="on change trigger updated on window">
<label for="order">{% translate "Order by" %}</label>
<select
class="tw-border-0 focus-visible:tw-outline-0 w-full pe-2 tw-leading-normal text-bg-tertiary tw-font-medium rounded"
name="order" id="order">
<option value="default"
{% if order == 'default' %}selected{% endif %}>{% translate 'Default' %}</option>
<option value="older"
{% if order == 'older' %}selected{% endif %}>{% translate 'Oldest first' %}</option>
<option value="newer"
{% if order == 'newer' %}selected{% endif %}>{% translate 'Newest first' %}</option>
</select>
</div>
</div>
</div>
{# Filter transactions form#}
<div class="collapse" id="collapse-filter">
<div class="card card-body">
<form _="on change or submit or search trigger updated on window end
install init_tom_select
install init_datepicker"
id="filter">
{% crispy filter.form %}
</form>
<button class="btn btn-outline-danger btn-sm" _="on click call #filter.reset() then trigger change on #filter">{% translate 'Clear' %}</button>
id="filter">
{% crispy filter.form %}
</form>
<button class="btn btn-outline-danger btn-sm"
_="on click call #filter.reset() then trigger change on #filter">{% translate 'Clear' %}</button>
</div>
</div>
{# Transactions list#}
<div id="transactions"
class="show-loading"
hx-get="{% url 'monthly_transactions_list' month=month year=year %}"
hx-trigger="load, updated from:window" hx-include="#filter, #order">
</div>
</div>
{# Transactions list#}
<div id="transactions"
class="show-loading"
hx-get="{% url 'monthly_transactions_list' month=month year=year %}"
hx-trigger="load, updated from:window" hx-include="#filter, #order">
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -46,6 +46,22 @@
color="grey"></c-amount.display>
</div>
{% endif %}
{% if currency.consolidated %}
<div class="d-flex align-items-baseline w-100">
<div class="account-name text-start font-monospace tw-text-gray-300">
<span class="hierarchy-line-icon"></span>{% trans 'Consolidated' %}</div>
<div class="dotted-line flex-grow-1"></div>
<div class="">
<c-amount.display
:amount="currency.consolidated.total_final"
:prefix="currency.consolidated.currency.prefix"
:suffix="currency.consolidated.currency.suffix"
:decimal_places="currency.consolidated.currency.decimal_places"
color="{% if currency.total_final > 0 %}green{% elif currency.total_final < 0 %}red{% endif %}"
text-end></c-amount.display>
</div>
</div>
{% endif %}
{% endfor %}
</c-ui.info-card>
</div>

View File

@@ -0,0 +1,17 @@
{% load tools %}
{% load i18n %}
{% load currency_display %}
<div class="row row-cols-1 g-4 mt-1 mb-3">
{% for account_id, account in account_data.items %}
<div class="col">
<c-ui.account_card :account="account" :account_id="account_id"
:percentages="account_percentages"></c-ui.account_card>
</div>
{% empty %}
<div class="col">
<c-msg.empty
title="{% translate "No information to display" %}"></c-msg.empty>
{% endfor %}
</div>
</div>

View File

@@ -0,0 +1,16 @@
{% load tools %}
{% load i18n %}
{% load currency_display %}
<div class="row row-cols-1 g-4 mt-1 mb-3">
{% for currency_id, currency in currency_data.items %}
<div class="col">
<c-ui.currency_card :currency="currency" :currency_id="currency_id"
:percentages="currency_percentages"></c-ui.currency_card>
</div>
{% empty %}
<div class="col">
<c-msg.empty
title="{% translate "No information to display" %}"></c-msg.empty>
</div>
{% endfor %}
</div>

View File

@@ -1,431 +1,47 @@
{% load tools %}
{% load i18n %}
{% load currency_display %}
<ul class="nav nav-tabs" id="myTab" role="tablist">
<ul class="nav nav-tabs" id="all-trasactions-tab" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="currency-tab" data-bs-toggle="tab" data-bs-target="#currency-tab-pane" type="button" role="tab" aria-controls="currency-tab-pane" aria-selected="true">{% trans 'Currencies' %}</button>
<button class="nav-link active" id="currency-tab" data-bs-toggle="tab" data-bs-target="#currency-tab-pane"
type="button" role="tab" aria-controls="currency-tab-pane"
aria-selected="true">{% trans 'Currencies' %}</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="account-tab" data-bs-toggle="tab" data-bs-target="#account-tab-pane" type="button" role="tab" aria-controls="account-tab-pane" aria-selected="false">{% trans 'Accounts' %}</button>
<button class="nav-link" id="account-tab" data-bs-toggle="tab" data-bs-target="#account-tab-pane" type="button"
role="tab" aria-controls="account-tab-pane" aria-selected="false">{% trans 'Accounts' %}</button>
</li>
</ul>
<div class="tab-content" id="myTabContent">
<div class="tab-pane fade show active" id="currency-tab-pane" role="tabpanel" aria-labelledby="currency-tab" tabindex="0">
<div class="tab-content" id="all-transactions-content">
<div class="tab-pane fade show active" id="currency-tab-pane" role="tabpanel" aria-labelledby="currency-tab"
tabindex="0">
<div class="row row-cols-1 g-4 mt-2">
{# Income#}
<div class="col">
<c-ui.info-card color="green" icon="fa-solid fa-arrow-right-to-bracket" title="{% trans 'Income' %}">
<div class="d-flex justify-content-between mt-3">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current' %}</div>
</div>
<div class="text-end font-monospace">
{% for currency in income_current.values %}
<div>
<c-amount.display
:amount="currency.income_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="green"></c-amount.display>
</div>
{% if currency.exchanged %}
<div>
<c-amount.display
:amount="currency.exchanged.income_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
<hr class="my-1">
<div class="d-flex justify-content-between">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected' %}</div>
</div>
<div class="text-end font-monospace">
{% for currency in income_projected.values %}
<div>
<c-amount.display
:amount="currency.income_projected"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="green"></c-amount.display>
</div>
{% if currency.exchanged %}
<div>
<c-amount.display
:amount="currency.exchanged.income_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
</c-ui.info-card>
</div>
{# Expenses#}
<div class="col">
<c-ui.info-card color="red" icon="fa-solid fa-arrow-right-from-bracket" title="{% trans 'Expenses' %}">
<div class="d-flex justify-content-between mt-3">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current' %}</div>
</div>
<div class="text-end font-monospace">
{% for currency in expense_current.values %}
<div>
<c-amount.display
:amount="currency.expense_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="red"></c-amount.display>
</div>
{% if currency.exchanged %}
<div>
<c-amount.display
:amount="currency.exchanged.expense_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
<hr class="my-1">
<div class="d-flex justify-content-between">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected' %}</div>
</div>
<div class="text-end font-monospace">
{% for currency in expense_projected.values %}
<div>
<c-amount.display
:amount="currency.expense_projected"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="red"></c-amount.display>
</div>
{% if currency.exchanged %}
<div>
<c-amount.display
:amount="currency.exchanged.expense_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
</c-ui.info-card>
</div>
{# Total#}
<div class="col">
<c-ui.info-card color="blue" icon="fa-solid fa-scale-balanced" title="{% trans 'Total' %}">
<div class="d-flex justify-content-between mt-3">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current' %}</div>
</div>
<div class="text-end font-monospace">
{% for currency in total_current.values %}
<div>
<c-amount.display
:amount="currency.total_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="{% if currency.total_current > 0 %}green{% elif currency.total_current < 0 %}red{% endif %}"></c-amount.display>
</div>
{% if currency.exchanged %}
<div>
<c-amount.display
:amount="currency.exchanged.total_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
<div class="d-flex justify-content-between mt-3">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected' %}</div>
</div>
<div class="text-end font-monospace">
{% for currency in total_projected.values %}
<div>
<c-amount.display
:amount="currency.total_projected"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="{% if currency.total_projected > 0 %}green{% elif currency.total_projected < 0 %}red{% endif %}"></c-amount.display>
</div>
{% if currency.exchanged %}
<div>
<c-amount.display
:amount="currency.exchanged.total_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
<hr class="my-1">
<div class="d-flex justify-content-end">
<div class="text-end font-monospace">
{% for currency in total_final.values %}
<div>
<c-amount.display
:amount="currency.total_final"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="{% if currency.total_final > 0 %}green{% elif currency.total_final < 0 %}red{% endif %}"></c-amount.display>
</div>
{% if currency.exchanged %}
<div>
<c-amount.display
:amount="currency.exchanged.total_final"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"
color="gray"></c-amount.display>
</div>
{% endif %}
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
</c-ui.info-card>
</div>
<div class="col">
<c-ui.info-card color="yellow" icon="fa-solid fa-percent" title="{% trans 'Distribution' %}">
{% for p in currency_percentages.values %}
<p class="tw-text-gray-400 mb-2 {% if not forloop.first %}mt-3{% endif %}">{{ p.currency.name }} ({{ p.currency.code }})</p>
<c-ui.percentage-distribution :percentage="p"></c-ui.percentage-distribution>
{% endfor %}
</c-ui.info-card>
</div>
</div>
</div>
<div class="tab-pane fade" id="account-tab-pane" role="tabpanel" aria-labelledby="account-tab" tabindex="0">
<div class="row row-cols-1 g-4 mt-2">
<div class="col">
{% for account_id, account in account_data.items %}
{% if not single %}
<div class="tw-text-xl {% if not forloop.first %}mt-4 mb-3{% endif %}">
{% if account.account.group %}
<span class="badge text-bg-primary me-2">{{ account.account.group }}</span>{% endif %}{{ account.account.name }}
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected income' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace tw-text-green-400">
<c-amount.display
:amount="account.income_projected"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display>
</div>
{% for currency_id, currency in currency_data.items %}
<div class="col">
<c-ui.currency_card :currency="currency" :currency_id="currency_id"
:percentages="currency_percentages"></c-ui.currency_card>
</div>
{% if account.exchanged and account.exchanged.income_projected %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.currency.income_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected expenses' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div>
<div class="text-end font-monospace tw-text-red-400">
<c-amount.display
:amount="account.expense_projected"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display>
</div>
</div>
</div>
{% if account.exchanged and account.exchanged.expense_projected %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.expense_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected total' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div
class="text-end font-monospace">
<c-amount.display
:amount="account.total_projected"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_projected > 0 %}green{% elif account.total_projected < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if account.exchanged.total_projected and account.exchanged.total_projected %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.total_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<hr class="my-3">
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current income' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace tw-text-green-400">
<c-amount.display
:amount="account.income_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display>
</div>
</div>
{% if account.exchanged and account.exchanged.income_current %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.income_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current expenses' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace tw-text-red-400">
<c-amount.display
:amount="account.expense_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display>
</div>
</div>
{% if account.exchanged and account.exchanged.expense_current %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.expense_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current total' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div
class="text-end font-monospace">
<c-amount.display
:amount="account.total_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_current > 0 %}green{% elif account.total_current < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if account.exchanged and account.exchanged.total_current %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.total_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div>
<hr class="my-3">
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'final total' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace">
<c-amount.display
:amount="account.total_final"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_final > 0 %}green{% elif account.total_final < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if account.exchanged and account.exchanged.total_final %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.total_final"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
</div>
{% with p=account_percentages|get_dict_item:account_id %}
<div class="my-3">
<c-ui.percentage-distribution :percentage="p"></c-ui.percentage-distribution>
</div>
{% endwith %}
<hr>
{% empty %}
<div class="col">
<c-msg.empty
title="{% translate "No information to display" %}"></c-msg.empty>
{% endfor %}
</div>
</div>
</div>
<div class="tab-pane fade" id="account-tab-pane" role="tabpanel" aria-labelledby="account-tab" tabindex="0">
<div class="row row-cols-1 g-4 mt-2">
{% for account_id, account in account_data.items %}
<div class="col">
<c-ui.account_card :account="account" :account_id="account_id"
:percentages="account_percentages"></c-ui.account_card>
</div>
{% empty %}
<div class="col">
<c-msg.empty
title="{% translate "No information to display" %}"></c-msg.empty>
{% endfor %}
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,11 @@
{% load i18n %}
<div class="trash-list-container" id="transactions-list">
{% for transaction in transactions %}
<c-transaction.item :transaction="transaction"></c-transaction.item>
{% empty %}
<c-msg.empty
title="{% translate "No deleted transactions to show" %}"></c-msg.empty>
{% endfor %}
{# Floating bar #}
<c-ui.deleted-transactions-action-bar></c-ui.deleted-transactions-action-bar>
</div>

View File

@@ -45,10 +45,59 @@
</div>
</div>
<div class="col-12 col-xl-3 order-1 order-xl-2">
<div id="transactions"
class="show-loading"
hx-get="{% url 'transactions_all_summary' %}"
hx-trigger="load, updated from:window, change from:#filter, submit from:#filter, search from:#filter" hx-include="#filter">
<ul class="nav nav-tabs" id="all-transactions-summary" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link {% if summary_tab == 'currency' %}active{% endif %}"
id="currency-tab"
data-bs-toggle="tab"
data-bs-target="#currency-tab-pane"
type="button"
role="tab"
aria-controls="currency-tab-pane"
_="on click fetch {% url 'transaction_all_summary_select' selected='currency' %}"
aria-selected="{% if summary_tab == 'currency' %}true{% else %}false{% endif %}">
{% trans 'Currencies' %}
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link {% if summary_tab == 'account' %}active{% endif %}"
id="account-tab"
data-bs-toggle="tab"
data-bs-target="#account-tab-pane"
type="button"
role="tab"
aria-controls="account-tab-pane"
_="on click fetch {% url 'transaction_all_summary_select' selected='account' %}"
aria-selected="{% if summary_tab == 'account' %}true{% else %}false{% endif %}">
{% trans 'Accounts' %}
</button>
</li>
</ul>
<div class="tab-content" id="all-transactions-content">
<div class="tab-pane fade {% if summary_tab == 'currency' %}show active{% endif %}"
id="currency-tab-pane"
role="tabpanel"
aria-labelledby="currency-tab"
tabindex="0">
<div id="currency-summary"
hx-get="{% url 'transaction_all_currency_summary' %}"
class="show-loading"
hx-trigger="load, selective_update from:window, updated from:window, change from:#filter, submit from:#filter, search from:#filter"
hx-include="#filter">
</div>
</div>
<div class="tab-pane fade {% if summary_tab == 'account' %}show active{% endif %}"
id="account-tab-pane"
role="tabpanel"
aria-labelledby="account-tab"
tabindex="0">
<div id="account-summary"
hx-get="{% url 'transaction_all_account_summary' %}"
class="show-loading"
hx-trigger="load, selective_update from:window, updated from:window, change from:#filter, submit from:#filter, search from:#filter"
hx-include="#filter">
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,14 @@
{% extends "layouts/base.html" %}
{% load i18n %}
{% block title %}{% translate 'Deleted transactions' %}{% endblock %}
{% block content %}
<div class="container px-md-3 py-3 column-gap-5">
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
<div>{% translate 'Deleted transactions' %}</div>
</div>
<div hx-get="{% url 'transactions_trash_list' %}" hx-trigger="load, updated from:window" class="show-loading"></div>
</div>
{% endblock %}

View File

@@ -1,188 +1,15 @@
{% load tools %}
{% load i18n %}
<div class="row row-cols-1 g-4 mb-3">
{% for account_id, account in totals.items %}
<div class="col">
{% for account_id, account in totals.items %}
{% if not single %}
<div class="tw-text-xl {% if not forloop.first %}mt-4 mb-3{% endif %}">
{% if account.account.group %}
<span class="badge text-bg-primary me-2">{{ account.account.group }}</span>{% endif %}{{ account.account.name }}
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected income' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace tw-text-green-400">
<c-amount.display
:amount="account.income_projected"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display>
</div>
</div>
{% if account.exchanged and account.exchanged.income_projected %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.currency.income_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected expenses' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div>
<div class="text-end font-monospace tw-text-red-400">
<c-amount.display
:amount="account.expense_projected"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display>
</div>
</div>
</div>
{% if account.exchanged and account.exchanged.expense_projected %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.expense_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected total' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div
class="text-end font-monospace">
<c-amount.display
:amount="account.total_projected"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_projected > 0 %}green{% elif account.total_projected < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if account.exchanged.total_projected and account.exchanged.total_projected %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.total_projected"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<hr class="my-3">
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current income' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace tw-text-green-400">
<c-amount.display
:amount="account.income_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display>
</div>
</div>
{% if account.exchanged and account.exchanged.income_current %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.income_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current expenses' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace tw-text-red-400">
<c-amount.display
:amount="account.expense_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"></c-amount.display>
</div>
</div>
{% if account.exchanged and account.exchanged.expense_current %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.expense_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current total' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div
class="text-end font-monospace">
<c-amount.display
:amount="account.total_current"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_current > 0 %}green{% elif account.total_current < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if account.exchanged and account.exchanged.total_current %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.total_current"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div>
<hr class="my-3">
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'final total' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace">
<c-amount.display
:amount="account.total_final"
:prefix="account.currency.prefix"
:suffix="account.currency.suffix"
:decimal_places="account.currency.decimal_places"
color="{% if account.total_final > 0 %}green{% elif account.total_final < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if account.exchanged and account.exchanged.total_final %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="account.exchanged.total_final"
:prefix="account.exchanged.currency.prefix"
:suffix="account.exchanged.currency.suffix"
:decimal_places="account.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
</div>
{% with p=percentages|get_dict_item:account_id %}
<div class="my-3">
<c-ui.percentage-distribution :percentage="p"></c-ui.percentage-distribution>
</div>
{% endwith %}
<hr>
{% empty %}
<c-msg.empty
title="{% translate "No information to display" %}"></c-msg.empty>
{% endfor %}
<c-ui.account_card :account="account" :account_id="account_id"
:percentages="percentages"></c-ui.account_card>
</div>
{% empty %}
<div class="col">
<c-msg.empty
title="{% translate "No information to display" %}"></c-msg.empty>
</div>
{% endfor %}
</div>

View File

@@ -1,187 +1,15 @@
{% load tools %}
{% load month_name %}
{% load i18n %}
<div class="row row-cols-1 g-4 mb-3">
{% for currency_id, currency in totals.items %}
{% for currency_id, currency in totals.items %}
<div class="col">
{% if not single %}
<div class="tw-text-xl {% if not forloop.first %}mt-4 mb-3{% endif %}">
{{ currency.currency.name }} ({{ currency.currency.code }})
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected income' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace tw-text-green-400">
<c-amount.display
:amount="currency.income_projected"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"></c-amount.display>
</div>
</div>
{% if currency.exchanged and currency.exchanged.income_projected %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="currency.exchanged.currency.income_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected expenses' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div>
<div class="text-end font-monospace tw-text-red-400">
<c-amount.display
:amount="currency.expense_projected"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"></c-amount.display>
</div>
</div>
</div>
{% if currency.exchanged and currency.exchanged.expense_projected %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="currency.exchanged.expense_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected total' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div
class="text-end font-monospace">
<c-amount.display
:amount="currency.total_projected"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="{% if currency.total_projected > 0 %}green{% elif currency.total_projected < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if currency.exchanged.total_projected and currency.exchanged.total_projected %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="currency.exchanged.total_projected"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<hr class="my-3">
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current income' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace tw-text-green-400">
<c-amount.display
:amount="currency.income_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"></c-amount.display>
</div>
</div>
{% if currency.exchanged and currency.exchanged.income_current %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="currency.exchanged.income_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current expenses' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace tw-text-red-400">
<c-amount.display
:amount="currency.expense_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"></c-amount.display>
</div>
</div>
{% if currency.exchanged and currency.exchanged.expense_current %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="currency.exchanged.expense_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current total' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div
class="text-end font-monospace">
<c-amount.display
:amount="currency.total_current"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="{% if currency.total_current > 0 %}green{% elif currency.total_current < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if currency.exchanged and currency.exchanged.total_current %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="currency.exchanged.total_current"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
<div>
<hr class="my-3">
<div class="d-flex justify-content-between align-items-baseline mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'final total' %}</div>
</div>
<div class="dotted-line flex-grow-1"></div>
<div class="text-end font-monospace">
<c-amount.display
:amount="currency.total_final"
:prefix="currency.currency.prefix"
:suffix="currency.currency.suffix"
:decimal_places="currency.currency.decimal_places"
color="{% if currency.total_final > 0 %}green{% elif currency.total_final < 0 %}red{% endif %}"></c-amount.display>
</div>
</div>
{% if currency.exchanged and currency.exchanged.total_final %}
<div class="text-end font-monospace tw-text-gray-500">
<c-amount.display
:amount="currency.exchanged.total_final"
:prefix="currency.exchanged.currency.prefix"
:suffix="currency.exchanged.currency.suffix"
:decimal_places="currency.exchanged.currency.decimal_places"></c-amount.display>
</div>
{% endif %}
</div>
{% with p=percentages|get_dict_item:currency_id %}
<div class="my-3">
<c-ui.percentage-distribution :percentage="p"></c-ui.percentage-distribution>
</div>
{% endwith %}
{% empty %}
<c-msg.empty
title="{% translate "No information to display" %}"></c-msg.empty>
<c-ui.currency_card :currency="currency" :currency_id="currency_id"
:percentages="percentages"></c-ui.currency_card>
</div>
{% empty %}
<div class="col">
<c-msg.empty
title="{% translate "No information to display" %}"></c-msg.empty>
</div>
{% endfor %}
</div>