Añadidas tandas: 4, 4b y 7 #33

Merged
jkuijperm merged 13 commits from dev into main 2026-09-09 14:29:29 +00:00
5 changed files with 123 additions and 55 deletions
Showing only changes of commit e814bc2d03 - Show all commits

View File

@ -367,8 +367,23 @@ button[type="submit"]:hover,
.filters-main {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
align-items: center;
gap: 0.5rem;
}
.filters-main select {
padding: 0.5rem 0.7rem;
font-family: inherit;
font-size: 0.95rem;
color: var(--color-text);
background: var(--color-surface);
border: 1px solid var(--color-border);
border-radius: 6px;
}
.filters-main select:focus {
outline: 2px solid var(--color-focus-ring);
outline-offset: 1px;
}
.filters-advanced {
@ -826,17 +841,14 @@ tr:hover {
transition: width 0.3s ease-in-out;
}
.progress-fill.low,
.progress-fill.danger {
background-color: var(--color-danger-accent);
}
.progress-fill.medium,
.progress-fill.warning {
background-color: var(--color-warning-accent);
}
.progress-fill.high,
.progress-fill.complete {
background-color: var(--color-success-accent);
}

View File

@ -428,11 +428,7 @@
aria-valuenow="{{ goal.percentage|unlocalize }}"
aria-label="Progreso de {{ goal.name }}"
aria-valuetext="{{ goal.progress|floatformat:1 }}€ de {{ goal.target_amount|floatformat:1 }}€ ({{ goal.percentage|floatformat:1 }}%)">
<div class="progress-fill
{% if goal.percentage < 50 %} low
{% elif goal.percentage < 80 %} medium
{% else %} high
{% endif %}"
<div class="progress-fill {{ goal.progress_state }}"
style="width: {{ goal.bar_width|unlocalize }}%"></div>
</div>

View File

@ -9,17 +9,38 @@
{% block content %}
<h1>Objetivos</h1>
<a class="btn btn-primary" href="{% url 'goal_create' %}"> Nuevo objetivo</a>
<div class="section-actions">
<a class="btn btn-primary" href="{% url 'goal_create' %}"> Nuevo objetivo</a>
</div>
<form method="get" class="filters">
<div class="filters-main">
<label class="sr-only" for="filterKind">Tipo</label>
<select name="kind" id="filterKind" onchange="this.form.submit()">
<option value="">Todos los tipos</option>
{% for value, label in kind_choices %}
<option value="{{ value }}" {% if selected_kind == value %}selected{% endif %}>
{{ label }}
</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-primary">Filtrar</button>
<a href="{% url 'goal_list' %}" class="btn btn-secondary">Limpiar</a>
</div>
</form>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>Nombre</th>
<th>Tipo</th>
<th>Progreso</th>
<th></th>
</tr>
</thead>
<tbody>
{% for goal in goals %}
<tr>
<td>{{ goal.name }}</td>
@ -53,12 +74,20 @@
</div>
</td>
<td>
<td class="table-actions">
<a href="{% url 'goal_edit' goal.id %}">Editar</a>
<a href="{% url 'goal_delete' goal.id %}" class="danger">Eliminar</a>
</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="empty-state">
<p>No hay objetivos</p>
<a href="{% url 'goal_create' %}">Añade el primero</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>

View File

@ -1,6 +1,7 @@
import pytest
from datetime import date
from decimal import Decimal
from django.urls import reverse
from expenses.models import Account, Category, Expense, Goal
from expenses.forms import GoalForm
@ -194,3 +195,23 @@ def test_goalform_valid_payment_saves_goal(user, category):
goal.save()
assert Goal.objects.filter(pk=goal.pk, name="Ahorro viaje", kind=Goal.KIND_PAYMENT, category=category).exists()
def test_goal_list_filters_by_kind(auth_client, user, category):
Goal.objects.create(owner=user, name="Pago", target_amount=Decimal("100"), kind=Goal.KIND_PAYMENT, category=category)
Goal.objects.create(owner=user, name="Presupuesto", target_amount=Decimal("200"), kind=Goal.KIND_BUDGET, category=category, period=Goal.PERIOD_MONTH)
response = auth_client.get(reverse('goal_list'), {"kind": Goal.KIND_BUDGET})
assert [g.name for g in response.context["goals"]] == ["Presupuesto"]
assert response.context["selected_kind"] == Goal.KIND_BUDGET
def test_goal_list_ignores_invalid_kind_param(auth_client, user, category):
Goal.objects.create(owner=user, name="Pago", target_amount=Decimal("100"), kind=Goal.KIND_PAYMENT, category=category)
response = auth_client.get(reverse('goal_list'), {"kind": "bogus"})
assert response.status_code == 200
assert [g.name for g in response.context["goals"]] == ["Pago"]
assert response.context["selected_kind"] == ""

View File

@ -1071,11 +1071,21 @@ def category_delete(request, pk):
def goal_list(request):
goals = Goal.objects.filter(owner=request.user)
selected_kind = request.GET.get("kind") or ""
if selected_kind in dict(Goal.KIND_CHOICES):
goals = goals.filter(kind=selected_kind)
else:
selected_kind = ""
goals = goals.order_by("name")
return render(
request,
"goals/list.html",
{
"goals": goals,
"kind_choices": Goal.KIND_CHOICES,
"selected_kind": selected_kind,
"active_menu": "settings",
},
)