Modificaciones en filtrado y creada opción para marcar que un gasto/ingreso es un traspaso de cuentas #34
@ -396,7 +396,8 @@ button[type="submit"]:hover,
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filters-main select {
|
.filters-main select,
|
||||||
|
.filters-main input[type="date"] {
|
||||||
padding: 0.5rem 0.7rem;
|
padding: 0.5rem 0.7rem;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
@ -406,11 +407,18 @@ button[type="submit"]:hover,
|
|||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filters-main select:focus {
|
.filters-main select:focus,
|
||||||
|
.filters-main input[type="date"]:focus {
|
||||||
outline: 2px solid var(--color-focus-ring);
|
outline: 2px solid var(--color-focus-ring);
|
||||||
outline-offset: 1px;
|
outline-offset: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.range-notice {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
.filters-advanced {
|
.filters-advanced {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,12 @@
|
|||||||
|
{% comment %}
|
||||||
|
Campos del rango de fechas libre. Se incluye dentro de un <form method="get">.
|
||||||
|
Espera date_from y date_to en el contexto.
|
||||||
|
{% endcomment %}
|
||||||
|
|
||||||
|
<label class="sr-only" for="dateFrom">Desde</label>
|
||||||
|
<input type="date" name="date_from" id="dateFrom"
|
||||||
|
value="{% if date_from %}{{ date_from|date:'Y-m-d' }}{% endif %}">
|
||||||
|
|
||||||
|
<label class="sr-only" for="dateTo">Hasta</label>
|
||||||
|
<input type="date" name="date_to" id="dateTo"
|
||||||
|
value="{% if date_to %}{{ date_to|date:'Y-m-d' }}{% endif %}">
|
||||||
@ -47,10 +47,20 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
{% include "expenses/_date_range.html" %}
|
||||||
|
|
||||||
<button type="submit" class="btn btn-primary">Filtrar</button>
|
<button type="submit" class="btn btn-primary">Filtrar</button>
|
||||||
<a href="{% url 'expense_list' %}" class="btn btn-secondary">Limpiar</a>
|
<a href="{% url 'expense_list' %}" class="btn btn-secondary">Limpiar</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{% if range_active %}
|
||||||
|
<p class="range-notice">
|
||||||
|
Mostrando el rango de fechas seleccionado; los filtros de año y mes no
|
||||||
|
se aplican.
|
||||||
|
<a href="{% url 'expense_list' %}">Quitar el rango</a>
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
<details class="filters-advanced"
|
<details class="filters-advanced"
|
||||||
|
|||||||
@ -31,6 +31,37 @@ def _get_int(value):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Umbral del eje del gráfico con rango libre: dos meses es donde un gráfico
|
||||||
|
# diario deja de leerse, así que por encima se agrupa por mes.
|
||||||
|
RANGE_DAY_CHART_MAX_DAYS = 62
|
||||||
|
|
||||||
|
|
||||||
|
def _get_date(value):
|
||||||
|
"""Parsea una fecha AAAA-MM-DD de la URL. None si falta o no es válida."""
|
||||||
|
try:
|
||||||
|
return datetime.strptime(value, "%Y-%m-%d").date()
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_date_range(request):
|
||||||
|
"""Lee el rango de fechas libre de la URL.
|
||||||
|
|
||||||
|
Devuelve (date_from, date_to); cualquiera de los dos puede ser None, porque
|
||||||
|
"desde el 1 de marzo" sin fecha final es un filtro válido.
|
||||||
|
|
||||||
|
Si el rango está invertido (desde > hasta) se ignoran LAS DOS fechas: devolver
|
||||||
|
una lista vacía sin explicación es peor que ignorar un filtro mal puesto.
|
||||||
|
"""
|
||||||
|
date_from = _get_date(request.GET.get("date_from"))
|
||||||
|
date_to = _get_date(request.GET.get("date_to"))
|
||||||
|
|
||||||
|
if date_from and date_to and date_from > date_to:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
return date_from, date_to
|
||||||
|
|
||||||
|
|
||||||
def _safe_next(request):
|
def _safe_next(request):
|
||||||
next_url = request.POST.get("next") or request.GET.get("next")
|
next_url = request.POST.get("next") or request.GET.get("next")
|
||||||
|
|
||||||
@ -336,11 +367,21 @@ def expense_list(request):
|
|||||||
if (t_id:= _get_int(t)) is not None
|
if (t_id:= _get_int(t)) is not None
|
||||||
]
|
]
|
||||||
|
|
||||||
if year:
|
date_from, date_to = _resolve_date_range(request)
|
||||||
expenses = expenses.filter(date__year=year)
|
range_active = bool(date_from or date_to)
|
||||||
|
|
||||||
if month:
|
# Un solo criterio de periodo a la vez: si hay rango, manda el rango y los
|
||||||
expenses = expenses.filter(date__month=month)
|
# selectores de año y mes no se aplican (la plantilla lo avisa).
|
||||||
|
if range_active:
|
||||||
|
if date_from:
|
||||||
|
expenses = expenses.filter(date__gte=date_from)
|
||||||
|
if date_to:
|
||||||
|
expenses = expenses.filter(date__lte=date_to)
|
||||||
|
else:
|
||||||
|
if year:
|
||||||
|
expenses = expenses.filter(date__year=year)
|
||||||
|
if month:
|
||||||
|
expenses = expenses.filter(date__month=month)
|
||||||
|
|
||||||
if category:
|
if category:
|
||||||
expenses = expenses.filter(category_id=category)
|
expenses = expenses.filter(category_id=category)
|
||||||
@ -407,6 +448,9 @@ def expense_list(request):
|
|||||||
"page_obj": page_obj,
|
"page_obj": page_obj,
|
||||||
"selected_year": year,
|
"selected_year": year,
|
||||||
"selected_month": month,
|
"selected_month": month,
|
||||||
|
"date_from": date_from,
|
||||||
|
"date_to": date_to,
|
||||||
|
"range_active": range_active,
|
||||||
"selected_category": category,
|
"selected_category": category,
|
||||||
"categories": categories,
|
"categories": categories,
|
||||||
"year_list": [y.year for y in year_list],
|
"year_list": [y.year for y in year_list],
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user