Anade un rango de fechas libre al listado de gastos
Los selectores de ano y mes solo saben expresar periodos naturales completos. "Del 15 de marzo al 3 de abril" o "desde que empece el trabajo nuevo" no se pueden pedir, y son justo las preguntas que uno le hace a un gestor de gastos. Se anaden dos parametros date_from y date_to, opcionales por separado: "desde el 1 de marzo" sin fecha final es un filtro perfectamente valido. Si el rango viene invertido se ignoran las dos fechas, porque devolver una lista vacia sin explicacion es peor que descartar un filtro mal puesto. Cuando hay rango, manda el rango y los selectores de ano y mes no se aplican: combinar ambos criterios da intersecciones que el usuario no ha pedido y no puede ver. La plantilla lo avisa con un mensaje y un enlace para quitarlo, en vez de dejar los selectores mostrando un periodo que no es el que se esta viendo. Los campos de fecha comparten estilo con el resto de filtros, incluido el anillo de foco, para que no desentonen con los selectores de al lado. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
bb5ade4cc9
commit
c395c638e9
@ -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