feat: add better layout for yearly_overview_by_currency

This commit is contained in:
Herculino Trotta
2024-11-01 23:20:40 -03:00
parent fcbce21cfa
commit b3869a0ff2
4 changed files with 259 additions and 173 deletions

View File

@@ -7,9 +7,14 @@ urlpatterns = [
path("yearly/account/", views.index_by_account, name="yearly_index_account"),
path(
"yearly/currency/<int:year>/",
views.yearly_overview_by_currency,
views.index_yearly_overview_by_currency,
name="yearly_overview_currency",
),
path(
"yearly-overview/<int:year>/data/",
views.yearly_overview_by_currency,
name="yearly_overview_currency_data",
),
path(
"yearly/account/<int:year>/",
views.yearly_overview_by_account,

View File

@@ -2,16 +2,16 @@ from datetime import date
from decimal import Decimal
from django.contrib.auth.decorators import login_required
from django.db.models import Sum, F, Value, DecimalField
from django.db.models.expressions import Case, When
from django.db.models.functions import TruncMonth, Coalesce, TruncYear
from django.http import Http404
from django.shortcuts import render, redirect
from django.utils import timezone
from django.db.models import Sum, F, Q, Value, CharField, DecimalField
from django.db.models.functions import TruncMonth, Coalesce
from django.db.models.expressions import Case, When
from django.db.models.functions import Concat
from apps.transactions.models import Transaction
from apps.currencies.utils.convert import convert
from apps.currencies.models import Currency
from apps.currencies.utils.convert import convert
from apps.transactions.models import Transaction
@login_required
@@ -29,16 +29,56 @@ def index_by_account(request):
@login_required
def yearly_overview_by_currency(request, year: int):
def index_yearly_overview_by_currency(request, year: int):
next_year = year + 1
previous_year = year - 1
transactions = Transaction.objects.filter(
reference_date__year=year, account__is_archived=False
month_options = range(1, 13)
currency_options = Currency.objects.all()
return render(
request,
"yearly_overview/pages/overview_by_currency.html",
context={
"year": year,
"next_year": next_year,
"previous_year": previous_year,
"months": month_options,
"currencies": currency_options,
},
)
@login_required
def yearly_overview_by_currency(request, year: int):
next_year = year + 1
previous_year = year - 1
month = request.GET.get("month")
currency = request.GET.get("currency")
# Base query filter
filter_params = {"reference_date__year": year, "account__is_archived": False}
# Add month filter if provided
if month:
month = int(month)
if not 1 <= month <= 12:
raise Http404("Invalid month")
filter_params["reference_date__month"] = month
# Add currency filter if provided
if currency:
filter_params["account__currency_id"] = int(currency)
transactions = Transaction.objects.filter(**filter_params)
if month:
date_trunc = TruncMonth("reference_date")
else:
date_trunc = TruncYear("reference_date")
monthly_data = (
transactions.annotate(month=TruncMonth("reference_date"))
transactions.annotate(month=date_trunc)
.values(
"month",
"account__currency__code",
@@ -117,26 +157,19 @@ def yearly_overview_by_currency(request, year: int):
.order_by("month", "account__currency__code")
)
# Create a list of all months in the year
all_months = [date(year, month, 1) for month in range(1, 13)]
# Create a dictionary to store the final result
result = {
month: {
"income_paid": [],
"expense_paid": [],
"income_unpaid": [],
"expense_unpaid": [],
"balance_unpaid": [],
"balance_paid": [],
"balance_total": [],
}
for month in all_months
"income_paid": [],
"expense_paid": [],
"income_unpaid": [],
"expense_unpaid": [],
"balance_unpaid": [],
"balance_paid": [],
"balance_total": [],
}
# Fill in the data
for entry in monthly_data:
month = entry["month"]
currency_code = entry["account__currency__code"]
prefix = entry["account__currency__prefix"]
suffix = entry["account__currency__suffix"]
@@ -152,7 +185,7 @@ def yearly_overview_by_currency(request, year: int):
"balance_total",
]:
if entry[field] != 0:
result[month][field].append(
result[field].append(
{
"code": currency_code,
"prefix": prefix,
@@ -162,26 +195,13 @@ def yearly_overview_by_currency(request, year: int):
}
)
# Fill in missing months with empty lists
for month in all_months:
if not any(result[month].values()):
result[month] = {
"income_paid": [],
"expense_paid": [],
"income_unpaid": [],
"expense_unpaid": [],
"balance_unpaid": [],
"balance_paid": [],
"balance_total": [],
}
print(result)
return render(
request,
"yearly_overview/pages/overview_by_currency.html",
"yearly_overview/fragments/currency_data.html",
context={
"year": year,
"next_year": next_year,
"previous_year": previous_year,
"totals": result,
},
)

View File

@@ -0,0 +1,124 @@
{% load month_name %}
{% load i18n %}
<div class="col">
<div class="d-flex justify-content-between">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected income' %}</div>
</div>
<div class="text-end font-monospace">
{% for entry in totals.income_unpaid %}
<c-amount.display
:amount="entry.amount"
:prefix="entry.prefix"
:suffix="entry.suffix"
:decimal_places="entry.decimal_places"></c-amount.display>
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
<div class="d-flex justify-content-between mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected expenses' %}</div>
</div>
<div class="text-end font-monospace">
{% for entry in totals.expense_unpaid %}
<c-amount.display
:amount="entry.amount"
:prefix="entry.prefix"
:suffix="entry.suffix"
:decimal_places="entry.decimal_places"></c-amount.display>
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
<div class="d-flex justify-content-between mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected total' %}</div>
</div>
<div class="text-end font-monospace">
{% for entry in totals.balance_unpaid %}
<div class="{% if entry.amount > 0 %}tw-text-green-400{% elif entry.amount < 0 %}tw-text-red-400{% endif %}">
<c-amount.display
:amount="entry.amount"
:prefix="entry.prefix"
:suffix="entry.suffix"
:decimal_places="entry.decimal_places"></c-amount.display>
</div>
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
<hr class="my-1">
<div class="d-flex justify-content-between mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current income' %}</div>
</div>
<div class="text-end font-monospace">
{% for entry in totals.income_paid %}
<c-amount.display
:amount="entry.amount"
:prefix="entry.prefix"
:suffix="entry.suffix"
:decimal_places="entry.decimal_places"></c-amount.display>
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
<div class="d-flex justify-content-between mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current expenses' %}</div>
</div>
<div class="text-end font-monospace">
{% for entry in totals.expense_paid %}
<c-amount.display
:amount="entry.amount"
:prefix="entry.prefix"
:suffix="entry.suffix"
:decimal_places="entry.decimal_places"></c-amount.display>
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
<div class="d-flex justify-content-between mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current total' %}</div>
</div>
<div class="text-end font-monospace tw-text-yellow-400">
{% for entry in totals.balance_paid %}
<div class="{% if entry.amount > 0 %}tw-text-green-400{% elif entry.amount < 0 %}tw-text-red-400{% endif %}">
<c-amount.display
:amount="entry.amount"
:prefix="entry.prefix"
:suffix="entry.suffix"
:decimal_places="entry.decimal_places"></c-amount.display>
</div>
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
<hr class="my-1">
<div class="d-flex justify-content-between mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'final total' %}</div>
</div>
<div class="text-end font-monospace">
{% for entry in totals.balance_total %}
<div class="{% if entry.amount > 0 %}tw-text-green-400{% elif entry.amount < 0 %}tw-text-red-400{% endif %}">
<c-amount.display
:amount="entry.amount"
:prefix="entry.prefix"
:suffix="entry.suffix"
:decimal_places="entry.decimal_places"></c-amount.display>
</div>
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
</div>

View File

@@ -90,140 +90,77 @@
</div>
</div>
</div>
<div class="row row-cols-1 row-cols-xl-3 g-4 mb-3">
{% for date, x in totals.items %}
<div class="col">
<div class="card tw-relative h-100 shadow">
<div class="tw-absolute tw-h-8 tw-w-8 tw-right-2 tw-top-2 tw-bg-yellow-300 tw-text-yellow-800 text-center
align-items-center d-flex justify-content-center rounded-2 tw-font-bold">
{{ forloop.counter }}
</div>
<div class="card-body">
<h5 class="tw-text-yellow-400 fw-bold">{{ date.month|month_name }}</h5>
<div class="d-flex justify-content-between mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected income' %}</div>
</div>
<div class="text-end font-monospace">
{% for entry in x.income_unpaid %}
<c-amount.display
:amount="entry.amount"
:prefix="entry.prefix"
:suffix="entry.suffix"
:decimal_places="entry.decimal_places"></c-amount.display>
{% empty %}
<div>-</div>
<div class="row">
<div class="col-lg-2">
<div class="nav flex-column nav-pills" id="month-pills" role="tablist" aria-orientation="vertical">
<input type="hidden" name="month" value="">
<button class="nav-link active"
role="tab"
data-bs-toggle="pill"
hx-get="{% url 'yearly_overview_currency_data' year=year %}"
hx-target="#data-content"
hx-trigger="click"
hx-include="[name='currency'], [name='month']"
hx-swap="innerHTML"
onclick="document.querySelector('[name=month]').value = ''">
{% translate 'Year' %}
</button>
{% for month in months %}
<button class="nav-link"
role="tab"
data-bs-toggle="pill"
hx-get="{% url 'yearly_overview_currency_data' year=year %}"
hx-target="#data-content"
hx-trigger="click"
hx-include="[name='currency'], [name='month']"
hx-swap="innerHTML"
onclick="document.querySelector('[name=month]').value = '{{ month }}'">
{{ month|month_name }}
</button>
{% endfor %}
</div>
</div>
<div class="d-flex justify-content-between mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected expenses' %}</div>
</div>
<div class="text-end font-monospace">
{% for entry in x.expense_unpaid %}
<c-amount.display
:amount="entry.amount"
:prefix="entry.prefix"
:suffix="entry.suffix"
:decimal_places="entry.decimal_places"></c-amount.display>
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
<div class="d-flex justify-content-between mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'projected total' %}</div>
</div>
<div class="text-end font-monospace">
{% for entry in x.balance_unpaid %}
<div class="{% if entry.amount > 0 %}tw-text-green-400{% elif entry.amount < 0 %}tw-text-red-400{% endif %}">
<c-amount.display
:amount="entry.amount"
:prefix="entry.prefix"
:suffix="entry.suffix"
:decimal_places="entry.decimal_places"></c-amount.display>
</div>
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
<hr class="my-1">
<div class="d-flex justify-content-between mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current income' %}</div>
</div>
<div class="text-end font-monospace">
{% for entry in x.income_paid %}
<c-amount.display
:amount="entry.amount"
:prefix="entry.prefix"
:suffix="entry.suffix"
:decimal_places="entry.decimal_places"></c-amount.display>
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
<div class="d-flex justify-content-between mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current expenses' %}</div>
</div>
<div class="text-end font-monospace">
{% for entry in x.expense_paid %}
<c-amount.display
:amount="entry.amount"
:prefix="entry.prefix"
:suffix="entry.suffix"
:decimal_places="entry.decimal_places"></c-amount.display>
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
<div class="d-flex justify-content-between mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'current total' %}</div>
</div>
<div class="text-end font-monospace tw-text-yellow-400">
{% for entry in x.balance_paid %}
<div class="{% if entry.amount > 0 %}tw-text-green-400{% elif entry.amount < 0 %}tw-text-red-400{% endif %}">
<c-amount.display
:amount="entry.amount"
:prefix="entry.prefix"
:suffix="entry.suffix"
:decimal_places="entry.decimal_places"></c-amount.display>
</div>
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
<hr class="my-1">
<div class="d-flex justify-content-between mt-2">
<div class="text-end font-monospace">
<div class="tw-text-gray-400">{% translate 'final total' %}</div>
</div>
<div class="text-end font-monospace">
{% for entry in x.balance_total %}
<div class="{% if entry.amount > 0 %}tw-text-green-400{% elif entry.amount < 0 %}tw-text-red-400{% endif %}">
<c-amount.display
:amount="entry.amount"
:prefix="entry.prefix"
:suffix="entry.suffix"
:decimal_places="entry.decimal_places"></c-amount.display>
</div>
{% empty %}
<div>-</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
<hr class="my-4 d-block d-lg-none">
<div class="col-lg-3">
<div class="nav flex-column nav-pills" id="currency-pills" role="tablist" aria-orientation="vertical">
<input type="hidden" name="currency" value="">
<button class="nav-link active"
role="tab"
data-bs-toggle="pill"
hx-get="{% url 'yearly_overview_currency_data' year=year %}"
hx-target="#data-content"
hx-trigger="click"
hx-include="[name='currency'], [name='month']"
hx-swap="innerHTML"
onclick="document.querySelector('[name=currency]').value = ''">
{% translate 'All' %}
</button>
{% for currency in currencies %}
<button class="nav-link"
role="tab"
data-bs-toggle="pill"
hx-get="{% url 'yearly_overview_currency_data' year=year %}"
hx-target="#data-content"
hx-trigger="click"
hx-include="[name='currency'], [name='month']"
hx-swap="innerHTML"
onclick="document.querySelector('[name=currency]').value = '{{ currency.id }}'">
{{ currency.name }}
</button>
{% endfor %}
</div>
</div>
<div class="col-lg-7">
<div id="data-content"
class="show-loading"
hx-get="{% url 'yearly_overview_currency_data' year=year %}"
hx-trigger="load"
hx-include="[name='currency'], [name='month']"
hx-swap="innerHTML">
</div>
</div>
</div>
</div>
{% endblock %}