Merge pull request 'Dashboard' (#10) from dev into main
Reviewed-on: #10
This commit is contained in:
commit
27544863c9
@ -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;
|
||||
} */
|
||||
|
||||
@ -30,66 +30,78 @@
|
||||
|
||||
<section class="dashboard-filters">
|
||||
<div class="dashboard-presets">
|
||||
<a href="{% url 'dashboard' %}" data-period="this_month" class="preset {% if period == 'this_month' %}active{% endif %}">
|
||||
<a href="{% url 'dashboard' %}?period=this_month" class="preset {% if period == 'this_month' %}active{% endif %}">
|
||||
Este mes
|
||||
</a>
|
||||
|
||||
<a href="{% url 'dashboard' %}" data-period="last_month" class="preset {% if period == 'last_month' %}active{% endif %}">
|
||||
<a href="{% url 'dashboard' %}?period=last_month" class="preset {% if period == 'last_month' %}active{% endif %}">
|
||||
Mes anterior
|
||||
</a>
|
||||
|
||||
<a href="{% url 'dashboard' %}" data-period="this_year" class="preset {% if period == 'this_year' %}active{% endif %}">
|
||||
<a href="{% url 'dashboard' %}?period=this_year" class="preset {% if period == 'this_year' %}active{% endif %}">
|
||||
Este año
|
||||
</a>
|
||||
{% if period %}
|
||||
<a href="{% url 'dashboard' %}" style="color: red; margin-left: 10px;">❌ Quitar filtros temporales</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<form method="get">
|
||||
<form method="get" action="{% url 'dashboard' %}" id="filtered_form">
|
||||
{% if period %}
|
||||
<input type="hidden" name="period" value="{{ period }}" id="hiddenPeriod">
|
||||
{% endif %}
|
||||
|
||||
<select name="account" id="account">
|
||||
<option value="">Todas las cuentas</option>
|
||||
{% for acc in accounts %}
|
||||
<option value="{{ acc.id }}"
|
||||
{% if selected_account == acc.id %}selected{% endif %}
|
||||
>
|
||||
<option value="{{ acc.id }}" {% if selected_account == acc.id %}selected{% endif %}>
|
||||
{{ acc.name }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<select name="year">
|
||||
{% for y in year_list %}
|
||||
<option value="{{ y }}"
|
||||
{% if y == selected_year %}selected{% endif %}
|
||||
>
|
||||
{{ y }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% if not period %}
|
||||
<select name="year" id="yearSelect">
|
||||
{% for y in year_list %}
|
||||
<option value="{{ y }}" {% if y == selected_year %}selected{% endif %}>
|
||||
{{ y }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
||||
<select name="month">
|
||||
<option value="">Todo el año</option>
|
||||
{% for m in months %}
|
||||
<option value="{{ m }}"
|
||||
{% if m == selected_month %}selected{% endif %}
|
||||
>
|
||||
{{ m }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<select name="month" id="monthSelect">
|
||||
<option value="">Todo el año</option>
|
||||
{% for m in months %}
|
||||
<option value="{{ m }}"{% if m == selected_month %}selected{% endif %}>
|
||||
{{ m }}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% endif %}
|
||||
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" name="compare" value="1"
|
||||
{% if compare_enabled %}checked{% endif %}>
|
||||
Comparar
|
||||
<input type="checkbox" name="compare" value="1" {% if compare_enabled %}checked{% endif %}>
|
||||
Comparar periodo anterior
|
||||
</label>
|
||||
|
||||
<button type="submit">Aplicar</button>
|
||||
<button type="submit">Aplicar Filtros</button>
|
||||
</form>
|
||||
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const yearSelect = document.getElementById('yearSelect');
|
||||
const monthSelect = document.getElementById('monthSelect');
|
||||
const hiddenPeriod = document.getElementById('hiddenPeriod');
|
||||
|
||||
function clearPeriodPreset() {
|
||||
if (hiddenPeriod) {
|
||||
hiddenPeriod.value = ""
|
||||
}
|
||||
document.querySelectorAll('.preset').forEach(btn => btn.classList.remove('active'));
|
||||
}
|
||||
</script>
|
||||
<!-- <script>
|
||||
document.querySelectorAll('.dashboard-presets a').forEach(link => {
|
||||
link.addEventListener('click', e => {
|
||||
e.preventDefault();
|
||||
@ -104,14 +116,13 @@
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</script> -->
|
||||
|
||||
<!-- ========================= -->
|
||||
<!-- KPIs -->
|
||||
<!-- ========================= -->
|
||||
|
||||
<section class="kpi-grid">
|
||||
|
||||
<div class="kpi-card">
|
||||
<span class="kpi-label">Saldo actual</span>
|
||||
<span class="kpi-value">{{ kpi_balance|floatformat:2 }}€</span>
|
||||
@ -132,24 +143,142 @@
|
||||
<span class="kpi-value">{{ kpi_categories }}</span>
|
||||
</div>
|
||||
|
||||
<div class="kpi-card">
|
||||
<span class="kpi-label"> Media diaria</span>
|
||||
<span class="kpi-value">{{ daily_average|floatformat:2 }}€ / día</span>
|
||||
</div>
|
||||
|
||||
{% if selected_month == today.month and selected_year == today.year %}
|
||||
<div class="kpi-card" style="border-left: 4px solid #ff9f43;">
|
||||
<span class="kpi-label">Proyección fin de mes</span>
|
||||
<span class="kpi-value">{{ projected_end_of_month|floatformat:2 }}€</span>
|
||||
<small style="color: gray; display:block;">Basado en tu ritmo de gasto actual</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
</section>
|
||||
|
||||
{% if compare_enabled %}
|
||||
<section class="comparison">
|
||||
<h3>Resumen comparativo</h3>
|
||||
<p>
|
||||
Gastos periodo actual: <strong>{{ kpi_total|floatformat:2 }} €</strong><br>
|
||||
Gastos periodo anterior: <strong>{{ kpi_previous_total|floatformat:2 }} €</strong><br>
|
||||
Diferencia:
|
||||
<strong style="color: {% if kpi_trend == 'up' %}#d9534f{% else %}#5cb85c{% endif %};">
|
||||
{% if kpi_trend == "up" %}+{% endif %}{{ kpi_difference|floatformat:2 }}€ ({{ kpi_percentage|floatformat:1 }}%)
|
||||
</strong>
|
||||
<small style="display:block; color:gray;">
|
||||
{% if kpi_trend == "up" %}⚠️ Has gastado más que el periodo pasado. {% else %} ¡Bien! Has reducido tus gastos. {% endif %}
|
||||
</small>
|
||||
</p>
|
||||
|
||||
<h3>Desglose de cambios por categoría</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Categoría</th>
|
||||
<th>Periodo Anterior</th>
|
||||
<th>Periodo Actual</th>
|
||||
<th>Variación</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for cat in category_comparison %}
|
||||
<tr>
|
||||
<td>{{ cat.category }}</td>
|
||||
<td>{{ cat.previous|floatformat:2 }}€</td>
|
||||
<td>{{ cat.current|floatformat:2 }}€</td>
|
||||
<td style="font-weight: bold; color: {% if cat.difference > 0 %}#d9534f{% else %}#5cb85c{% endif %};">
|
||||
{% if cat.difference > 0 %}+{% endif %}{{ cat.difference|floatformat:2 }}€
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
{% endif %}
|
||||
|
||||
<!-- ========================= -->
|
||||
<!-- Charts -->
|
||||
<!-- ========================= -->
|
||||
<section class="chart-box">
|
||||
<canvas id="mainChart"></canvas>
|
||||
</section>
|
||||
|
||||
<div class="charts-container">
|
||||
<section class="chart-box" style="width: 45%; min-width: 300px; max-width: 550px;">
|
||||
<h3>Evolución de Gastos ({% if chart_type == 'day' %}Por día {% else %} Por Meses{% endif %})</h3>
|
||||
<div style="position: relative; width: 100%; height: 350px; padding-bottom: 20px;">
|
||||
<canvas id="mainChart"></canvas>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="chart-box" style="width: 30%; min-width: 280px; max-width: 400px;">
|
||||
<h3>Distribución por Categorías</h3>
|
||||
<div style="position: relative; width: 100%; height: 350px; display: flex; align-items: center; justify-content: center">
|
||||
<canvas id="categoryChart"></canvas>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3>Gastos Recientes</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Fecha</th>
|
||||
<th>cuenta</th>
|
||||
<th>Categoría</th>
|
||||
<th>Concepto</th>
|
||||
<th>Importe</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for exp in recent_expenses %}
|
||||
<tr>
|
||||
<td>{{ exp.date|date:"d/m/Y" }}</td>
|
||||
<td>{{ exp.account.name }}</td>
|
||||
<td>{{ exp.category.name }}</td>
|
||||
<td>{{ exp.description|default:"-" }}</td>
|
||||
<td style="color: #d9534f; font-weight: bold;">{{ exp.amount|floatformat:2 }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="7">No hay gastos recientes registrados</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div style="clear: both; margin-top: 2rem;"></div>
|
||||
|
||||
<script>
|
||||
const catLabels = [{% for row in by_category_chart %}"{{ row.category__name }}",{% endfor %}];
|
||||
const catData = [{% for row in by_category_chart %}{{ row.total|unlocalize }},{% endfor %}];
|
||||
|
||||
new Chart(document.getElementById('categoryChart'), {
|
||||
type: 'pie',
|
||||
data: {
|
||||
labels: catLabels,
|
||||
datasets: [{
|
||||
data: catData,
|
||||
backgroundColor: [
|
||||
'#ff6384', '#36a2eb', '#cc65fe', '#ffce56', '#4bc0c0', '#ffa1b5'
|
||||
]
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: { position: 'right' }
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<h3>Evolución anual por cuenta ({{ selected_year }})</h3>
|
||||
|
||||
<div class="dashboard-grid">
|
||||
{% for acc in accounts_charts %}
|
||||
<div class="card card-chart">
|
||||
<h3>{{ acc.name }}</h3>
|
||||
<p><strong>Saldo actual:</strong> {{ acc.current_balance|floatformat:2 }} €</p>
|
||||
|
||||
<h4>{{ acc.name }}</h4>
|
||||
<p><strong>Saldo actual:</strong> {{ acc.current_balance|floatformat:2 }}€</p>
|
||||
<div class="canvas-wrapper">
|
||||
<canvas id="accountChart{{ acc.id }}"></canvas>
|
||||
</div>
|
||||
@ -160,7 +289,6 @@
|
||||
<script>
|
||||
const labels = {{ chart_labels|safe }};
|
||||
const data = {{ chart_data|safe }};
|
||||
|
||||
const ctx = document.getElementById('mainChart');
|
||||
|
||||
new Chart(ctx, {
|
||||
@ -168,35 +296,54 @@
|
||||
data: {
|
||||
labels: labels,
|
||||
datasets:[{
|
||||
label: 'Gastos',
|
||||
label: 'Gastos (€)',
|
||||
data: data,
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.6)',
|
||||
backgroundColor: 'rgba(54, 162, 235, 1)',
|
||||
borderWidth: 1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: { y: { beginAtZero: true } }
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
const monthLabels = {{ chart_labels|safe }};
|
||||
|
||||
const allMonths = ["Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"];
|
||||
|
||||
{% for acc in accounts_charts %}
|
||||
new Chart(
|
||||
document.getElementById('accountChart{{ acc.id }}'),
|
||||
{
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: monthLabels,
|
||||
datasets: [{
|
||||
label: 'Saldo',
|
||||
data: {{ acc.data|safe }},
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false
|
||||
(function() {
|
||||
const dataAccount = {{ acc.data|safe }};
|
||||
const filteredLabels = allMonths.slice(0, dataAccount.length);
|
||||
|
||||
new Chart(
|
||||
document.getElementById('accountChart{{ acc.id }}'),
|
||||
{
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: allMonths,
|
||||
datasets: [{
|
||||
label: 'Saldo Real (€)',
|
||||
data: dataAccount,
|
||||
fill: false,
|
||||
borderColor: 'rgba(54, 162, 235, 1)',
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.2)',
|
||||
tension: 0.1
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
})();
|
||||
{% endfor %}
|
||||
</script>
|
||||
|
||||
|
||||
@ -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,192 +287,205 @@ 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
|
||||
daily_average = 0
|
||||
projected_end_of_month = 0
|
||||
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]
|
||||
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]
|
||||
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)
|
||||
|
||||
# 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_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 +494,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,
|
||||
@ -492,6 +505,9 @@ def dashboard(request):
|
||||
"accounts_charts": accounts_charts,
|
||||
"period": period,
|
||||
"goals": goals,
|
||||
"recent_expenses": recent_expenses,
|
||||
"daily_average": daily_average,
|
||||
"projected_end_of_month": projected_end_of_month,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user