From 2c2ef350b027b4851a9e3c1e58f9ab1be0fcab97 Mon Sep 17 00:00:00 2001 From: JKuijperM Date: Thu, 11 Jun 2026 11:15:52 +0200 Subject: [PATCH 1/3] Some improvements and fixes in dashboard --- .../expenses/static/expenses/css/base.css | 22 +- .../templates/expenses/dashboard.html | 212 +++++++++----- expenses_manager/expenses/views.py | 269 +++++++++--------- 3 files changed, 300 insertions(+), 203 deletions(-) diff --git a/expenses_manager/expenses/static/expenses/css/base.css b/expenses_manager/expenses/static/expenses/css/base.css index f224876..70ce213 100644 --- a/expenses_manager/expenses/static/expenses/css/base.css +++ b/expenses_manager/expenses/static/expenses/css/base.css @@ -99,9 +99,18 @@ a.danger { position: relative; } +.charts-container { + display: flex; + gap: 2rem; + align-items: flex-start; + flex-wrap: wrap; + justify-content: flex-start; + margin-bottom: 4rem; +} + .chart-box { height: 300px; - margin-bottom: 2rem; + margin-bottom: 3rem; } .canvas-wrapper { @@ -119,9 +128,9 @@ a.danger { .kpi-grid { display: grid; - grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; - margin: 1.5rem 0; + margin: 2rem 0; } .kpi-card { @@ -143,6 +152,13 @@ a.danger { margin-top: 0.25rem; } +.comparison { + background: #f9f9f9; + padding: 1rem; + margin-bottom: 2rem; + border-radius: 8px; +} + /* .comparison strong { color:#b91c1c; } */ diff --git a/expenses_manager/expenses/templates/expenses/dashboard.html b/expenses_manager/expenses/templates/expenses/dashboard.html index c02d22b..4e896ac 100644 --- a/expenses_manager/expenses/templates/expenses/dashboard.html +++ b/expenses_manager/expenses/templates/expenses/dashboard.html @@ -30,66 +30,78 @@

-
+ + {% if period %} + + {% endif %} + - + {% if not period %} + - + + {% endif %} - +
-
- +
-
Saldo actual {{ kpi_balance|floatformat:2 }}€ @@ -131,25 +142,78 @@ Categorías {{ kpi_categories }}
-
+ {% if compare_enabled %} +
+

📊 Resumen comparativo

+

+ Gastos periodo actual: {{ kpi_total|floatformat:2 }} €
+ Gastos periodo anterior: {{ kpi_previous_total|floatformat:2 }} €
+ Diferencia: + + {% if kpi_trend == "up" %}+{% endif %}{{ kpi_difference|floatformat:2 }}€ ({{ kpi_percentage|floatformat:1 }}%) + + + {% if kpi_trend == "up" %}⚠️ Has gastado más que el periodo pasado. {% else %} ¡Bien! Has reducido tus gastos. {% endif %} + +

+
+ {% endif %} + -
- -
+ +
+
+

Evolución de Gastos ({% if chart_type == 'day' %}Por día {% else %} Por Meses{% endif %})

+
+ +
+
+
+

Distribución por Categorías

+
+ +
+
+
+ +
+ +

Evolución anual por cuenta ({{ selected_year }})

-
{% for acc in accounts_charts %}
-

{{ acc.name }}

-

Saldo actual: {{ acc.current_balance|floatformat:2 }} €

- +

{{ acc.name }}

+

Saldo actual: {{ acc.current_balance|floatformat:2 }}€

@@ -160,7 +224,6 @@ - diff --git a/expenses_manager/expenses/views.py b/expenses_manager/expenses/views.py index 85ad52a..936c437 100644 --- a/expenses_manager/expenses/views.py +++ b/expenses_manager/expenses/views.py @@ -19,7 +19,7 @@ from django.db.models import Sum from django.contrib.auth import login from django.core.paginator import Paginator from django.utils.ipv6 import is_valid_ipv6_address -from django.db.models.functions import ExtractMonth, ExtractYear +from django.db.models.functions import ExtractMonth, ExtractYear, ExtractDay from django.contrib.auth.decorators import login_required from django.shortcuts import get_object_or_404, render, redirect @@ -287,177 +287,176 @@ def expense_delete(request, pk): @login_required def dashboard(request): - # ------------------ - # Filters - # ------------------ - year = _get_int(request.GET.get("year")) - month = _get_int(request.GET.get("month")) - period = request.GET.get("period") + today = date.today() + + period = request.GET.get("period", "") account_id = _get_int(request.GET.get("account")) compare_enabled = request.GET.get("compare") == "1" - - today = date.today() - current_year = today.year - - accounts = Account.objects.filter(owner=request.user, active=True) - selected_year = year or current_year - selected_month = month - - # ------------------ - # Queryset base - # ----------------- - expenses = Expense.objects.filter(owner=request.user) - - selected_account_obj = None - if account_id: - expenses = expenses.filter(account_id=account_id) - selected_account_obj = accounts.filter(id=account_id).first() - + + # Time presets if period == "this_month": selected_year, selected_month = today.year, today.month elif period == "last_month": selected_year, selected_month = sub_months(today.year, today.month, 1) elif period == "this_year": selected_year, selected_month = today.year, None - + else: + selected_year = _get_int(request.GET.get("year")) or _get_int(today.year) + selected_month = _get_int(request.GET.get("month")) + selected_month = _get_int(selected_month) if selected_month else None + + # Accounts + accounts = Account.objects.filter(owner=request.user, active=True) + selected_account_obj = None + if account_id: + selected_account_obj = accounts.filter(id=account_id).first() + + # Calculate the KPI if selected_account_obj: kpi_balance = selected_account_obj.current_balance() else: kpi_balance = sum(account.current_balance() for account in accounts) - + + # Filter by base expenses + expenses = Expense.objects.filter(owner=request.user) + if account_id: + expenses = expenses.filter(account_id=account_id) + expenses_filtered = expenses.filter(date__year=selected_year) if selected_month: expenses_filtered = expenses_filtered.filter(date__month=selected_month) - + + # Basic KPIs total_amount = expenses_filtered.aggregate(total=Sum("amount"))["total"] or 0 expense_count = expenses_filtered.count() category_count = expenses_filtered.values("category").distinct().count() - - # ------------------ - # Totals by category - # ----------------- - by_category = ( + + # Total by category + by_category_qs = by_category = ( expenses_filtered.values("category__name") .annotate(total=Sum("amount")) - .order_by("category__name") + .order_by("-total") ) - - # ------------------ - # Totals by month - # ----------------- - - by_month_qs = ( - expenses_filtered.annotate(month=ExtractMonth("date")) - .values("month") - .annotate(total=Sum("amount")) - ) - - month_totals = {row["month"]: float(row["total"]) for row in by_month_qs} - months = list(range(1, 13)) - by_month = [{"month": m, "total": month_totals.get(m, 0)}for m in months] - - # ------------------ - # Availables years - # ----------------- - + + CATEGORY_LIMIT = 10 # Top 10 + + by_category_chart = [] + for index, row in enumerate(by_category_qs): + if index >= CATEGORY_LIMIT: + break + + cat_name = row["category__name"] or "Sin categoría" + total_cat = float(row["total"]) + + by_category_chart.append({ + "category__name": cat_name, + "total": total_cat + }) + + #Graphic + if selected_month: + by_day_qs = ( + expenses_filtered.annotate(day=ExtractDay("date")) + .values("day") + .annotate(total=Sum("amount")) + ) + day_totals = {row["day"]: float(row["total"]) for row in by_day_qs} + + import calendar + num_days = calendar.monthrange(selected_year, selected_month)[1] + + chart_labels = list(range(1, num_days + 1)) + chart_totals = [day_totals.get(d, 0) for d in chart_labels] + chart_type = "day" + else: + by_month_qs = ( + expenses_filtered.annotate(month=ExtractMonth("date")) + .values("month") + .annotate(total=Sum("amount")) + ) + month_totals = {row["month"]: float(row["total"]) for row in by_month_qs} + chart_labels = ["Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"] + chart_totals = [month_totals.get(m, 0) for m in range(1, 13)] + chart_type = "month" + year_list = ( - expenses.annotate(year=ExtractYear("date")) + Expense.objects.filter(owner=request.user) + .annotate(year=ExtractYear("date")) .values_list("year", flat=True) .distinct() - .order_by("year") + .order_by("-year") ) - - # ------------------ - # Chart - # ----------------- - chart_labels = months - chart_totals = [row["total"] for row in by_month] - - # ------------------ - # Compare period - # ----------------- - - previous_total = None - kpi_difference = None - percentage = None - category_comparison = None - kpi_trend = None - kpi_difference_abs = None - + + # Comparison + previous_total = 0 + kpi_difference = 0 + percentage = 0 + kpi_trend = "equal" + category_comparison = [] + if compare_enabled: previous_expenses = Expense.objects.filter(owner=request.user) if account_id: previous_expenses = previous_expenses.filter(account_id=account_id) - + if selected_month: prev_year, prev_month = sub_months(selected_year, selected_month, 1) - previous_expenses = previous_expenses.filter( - date__year=prev_year, date__month=prev_month - ) + previous_expenses = previous_expenses.filter(date__year=prev_year, date__month=prev_month) else: - # Anual compare prev_year = selected_year - 1 previous_expenses = previous_expenses.filter(date__year=prev_year) - + previous_total = previous_expenses.aggregate(total=Sum("amount"))["total"] or 0 kpi_difference = total_amount - previous_total - - if previous_total: + + if previous_total > 0: percentage = (kpi_difference / previous_total) * 100 - - if kpi_difference is not None: - if kpi_difference > 0: - kpi_trend = "up" - elif kpi_difference < 0: - kpi_trend = "down" - else: - kpi_trend = "equal" - - kpi_difference_abs = abs(kpi_difference) if kpi_difference is not None else None - - # ------------------ - # Previous expenses by category - # ------------------ - + + if kpi_difference > 0: + kpi_trend = "up" + elif kpi_difference < 0: + kpi_trend = "down" + + # Comparison by category previous_by_category = previous_expenses.values("category__name").annotate(total=Sum("amount")) - - current_map = {row["category__name"]: row["total"] for row in by_category} - - previous_map = {row["category__name"]: row["total"] for row in previous_by_category} - all_categories = set(current_map) | set(previous_map) - - category_comparison = [] - for category in all_categories: - current_total = current_map.get(category, 0) - previous_total_cat = previous_map.get(category, 0) - difference = current_total - previous_total_cat - - category_comparison.append( - { - "category": category, - "current": current_total, - "previous": previous_total_cat, - "difference": difference, - "difference_abs": abs(difference), - } - ) - - # ------------------ - # Previous expenses by category - # ------------------ + current_map = {row["category__name"]: float(row["total"]) for row in by_category} + previous_map = {row["category__name"]: float(row["total"]) for row in previous_by_category} + + for cat in set(current_map) | set(previous_map): + cur_t = current_map.get(cat, 0) + prev_t = previous_map.get(cat, 0) + category_comparison.append({ + "category": cat, + "current": cur_t, + "previous": prev_t, + "difference": cur_t - prev_t, + "difference_abs": abs(cur_t - prev_t) + }) + + # Anual evolution by accounts accounts_charts = [] - for account in accounts: - monthly_data = account.monthly_balance(selected_year) - m_balance = [row["balance"] for row in monthly_data] - accounts_charts.append( - { - "id": account.id, - "name": account.name, - "data": m_balance, - "current_balance": account.current_balance(), - } - ) + for acc in accounts: + try: + monthly_data = acc.monthly_balance(selected_year) + m_balance = [float(row["balance"]) for row in monthly_data] + except: + m_balance = [0] * 12 + + if selected_year == today.year: + current_month_index = today.month - 1 + + if current_month_index < len(m_balance): + m_balance[current_month_index] = float(acc.current_balance()) + m_balance = m_balance[:today.month] + + accounts_charts.append({ + "id": acc.id, + "name": acc.name, + "data": m_balance, + "current_balance": acc.current_balance(), + }) + # Goals goals = Goal.objects.filter(owner=request.user) @@ -468,11 +467,11 @@ def dashboard(request): { "active_menu": "dashboard", "by_category": by_category, - "by_month": by_month, + "by_category_chart": by_category_chart, "chart_labels": chart_labels, "chart_data": chart_totals, "year_list": year_list, - "months": months, + "months": list(range(1, 13)), "selected_year": selected_year, "selected_month": selected_month, "kpi_total": total_amount, @@ -481,7 +480,7 @@ def dashboard(request): "compare_enabled": compare_enabled, "kpi_previous_total": previous_total, "kpi_difference": kpi_difference, - "kpi_difference_abs": kpi_difference_abs, + "kpi_difference_abs": abs(kpi_difference), "kpi_percentage": percentage, "category_comparison": category_comparison, "kpi_trend": kpi_trend, -- 2.45.2 From 53253bcc83120d7502c433f6620ad31ad97bffed Mon Sep 17 00:00:00 2001 From: JKuijperM Date: Thu, 11 Jun 2026 13:14:21 +0200 Subject: [PATCH 2/3] Created table in the dashboard with the last expenses --- .../templates/expenses/dashboard.html | 28 +++++++++++++++++++ expenses_manager/expenses/views.py | 4 +++ 2 files changed, 32 insertions(+) diff --git a/expenses_manager/expenses/templates/expenses/dashboard.html b/expenses_manager/expenses/templates/expenses/dashboard.html index 4e896ac..d31dad1 100644 --- a/expenses_manager/expenses/templates/expenses/dashboard.html +++ b/expenses_manager/expenses/templates/expenses/dashboard.html @@ -179,6 +179,34 @@
+ +
+

Gastos Recientes

+ + + + + + + + + + + + {% for exp in recent_expenses %} + + + + + + + + {% empty %} + + {% endfor %} + +
FechacuentaCategoríaConceptoImporte
{{ exp.date|date:"d/m/Y" }}{{ exp.account.name }}{{ exp.category.name }}{{ exp.description|default:"-" }}{{ exp.amount|floatformat:2 }}
No hay gastos recientes registrados
+
diff --git a/expenses_manager/expenses/views.py b/expenses_manager/expenses/views.py index 936c437..9194952 100644 --- a/expenses_manager/expenses/views.py +++ b/expenses_manager/expenses/views.py @@ -460,6 +460,9 @@ def dashboard(request): # Goals goals = Goal.objects.filter(owner=request.user) + # Recent expenses + recent_expenses = Expense.objects.filter(owner=request.user).order_by('-date', '-id')[:7] + # Send the data to the dashboard return render( request, @@ -491,6 +494,7 @@ def dashboard(request): "accounts_charts": accounts_charts, "period": period, "goals": goals, + "recent_expenses": recent_expenses, }, ) -- 2.45.2 From 14e0ee7a762128dd7f4ae058cae0447f15b0c092 Mon Sep 17 00:00:00 2001 From: JKuijperM Date: Thu, 11 Jun 2026 18:06:43 +0200 Subject: [PATCH 3/3] Added the comparative table --- .../templates/expenses/dashboard.html | 39 ++++++++++++++++++- expenses_manager/expenses/views.py | 17 +++++++- 2 files changed, 53 insertions(+), 3 deletions(-) diff --git a/expenses_manager/expenses/templates/expenses/dashboard.html b/expenses_manager/expenses/templates/expenses/dashboard.html index d31dad1..d49a356 100644 --- a/expenses_manager/expenses/templates/expenses/dashboard.html +++ b/expenses_manager/expenses/templates/expenses/dashboard.html @@ -142,11 +142,24 @@ Categorías {{ kpi_categories }} + +
+ Media diaria + {{ daily_average|floatformat:2 }}€ / día +
+ + {% if selected_month == today.month and selected_year == today.year %} +
+ Proyección fin de mes + {{ projected_end_of_month|floatformat:2 }}€ + Basado en tu ritmo de gasto actual +
+ {% endif %} {% if compare_enabled %}
-

📊 Resumen comparativo

+

Resumen comparativo

Gastos periodo actual: {{ kpi_total|floatformat:2 }} €
Gastos periodo anterior: {{ kpi_previous_total|floatformat:2 }} €
@@ -158,6 +171,30 @@ {% if kpi_trend == "up" %}⚠️ Has gastado más que el periodo pasado. {% else %} ¡Bien! Has reducido tus gastos. {% endif %}

+ +

Desglose de cambios por categoría

+ + + + + + + + + + + {% for cat in category_comparison %} + + + + + + + {% endfor %} + +
CategoríaPeriodo AnteriorPeriodo ActualVariación
{{ cat.category }}{{ cat.previous|floatformat:2 }}€{{ cat.current|floatformat:2 }}€ + {% if cat.difference > 0 %}+{% endif %}{{ cat.difference|floatformat:2 }}€ +
{% endif %} diff --git a/expenses_manager/expenses/views.py b/expenses_manager/expenses/views.py index 9194952..0f86762 100644 --- a/expenses_manager/expenses/views.py +++ b/expenses_manager/expenses/views.py @@ -354,6 +354,8 @@ def dashboard(request): }) #Graphic + daily_average = 0 + projected_end_of_month = 0 if selected_month: by_day_qs = ( expenses_filtered.annotate(day=ExtractDay("date")) @@ -364,6 +366,14 @@ def dashboard(request): import calendar num_days = calendar.monthrange(selected_year, selected_month)[1] + if selected_year == today.year and selected_month == today.month: + days_passed = today.day + else: + days_passed = calendar.monthrange(selected_year, selected_month)[1] + + daily_average = total_amount / days_passed if days_passed > 0 else 0 + total_days_in_month = calendar.monthrange(selected_year, selected_month)[1] + projected_end_of_month = daily_average * total_days_in_month chart_labels = list(range(1, num_days + 1)) chart_totals = [day_totals.get(d, 0) for d in chart_labels] @@ -378,7 +388,7 @@ def dashboard(request): chart_labels = ["Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"] chart_totals = [month_totals.get(m, 0) for m in range(1, 13)] chart_type = "month" - + year_list = ( Expense.objects.filter(owner=request.user) .annotate(year=ExtractYear("date")) @@ -462,13 +472,14 @@ def dashboard(request): # Recent expenses recent_expenses = Expense.objects.filter(owner=request.user).order_by('-date', '-id')[:7] - + # Send the data to the dashboard return render( request, "expenses/dashboard.html", { "active_menu": "dashboard", + "today": today, "by_category": by_category, "by_category_chart": by_category_chart, "chart_labels": chart_labels, @@ -495,6 +506,8 @@ def dashboard(request): "period": period, "goals": goals, "recent_expenses": recent_expenses, + "daily_average": daily_average, + "projected_end_of_month": projected_end_of_month, }, ) -- 2.45.2