mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-01-11 11:50:27 +01:00
feat(transactions:filter): make montlhy summary filter-aware
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
# apps/dca_tracker/views.py
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Sum, Avg
|
||||
@@ -234,7 +233,7 @@ def strategy_entry_add(request, strategy_id):
|
||||
if request.method == "POST":
|
||||
form = DCAEntryForm(request.POST, strategy=strategy)
|
||||
if form.is_valid():
|
||||
entry = form.save()
|
||||
form.save()
|
||||
messages.success(request, _("Entry added successfully"))
|
||||
|
||||
return HttpResponse(
|
||||
|
||||
@@ -2,7 +2,8 @@ from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import (
|
||||
Q,
|
||||
)
|
||||
from django.http import HttpResponse
|
||||
from django.http import HttpResponse, Http404
|
||||
|
||||
from django.shortcuts import render, redirect
|
||||
from django.utils import timezone
|
||||
from django.views.decorators.http import require_http_methods
|
||||
@@ -36,8 +37,6 @@ def monthly_overview(request, month: int, year: int):
|
||||
summary_tab = request.session.get("monthly_summary_tab", "summary")
|
||||
|
||||
if month < 1 or month > 12:
|
||||
from django.http import Http404
|
||||
|
||||
raise Http404("Month is out of range")
|
||||
|
||||
next_month = 1 if month == 12 else month + 1
|
||||
@@ -107,17 +106,48 @@ def transactions_list(request, month: int, year: int):
|
||||
@require_http_methods(["GET"])
|
||||
def monthly_summary(request, month: int, year: int):
|
||||
# Base queryset with all required filters
|
||||
base_queryset = (
|
||||
Transaction.objects.filter(
|
||||
reference_date__year=year,
|
||||
reference_date__month=month,
|
||||
account__is_asset=False,
|
||||
)
|
||||
.exclude(Q(Q(category__mute=True) & ~Q(category=None)) | Q(mute=True))
|
||||
.exclude(account__in=request.user.untracked_accounts.all())
|
||||
base_queryset = Transaction.objects.filter(
|
||||
reference_date__year=year,
|
||||
reference_date__month=month,
|
||||
)
|
||||
|
||||
data = calculate_currency_totals(base_queryset, ignore_empty=True)
|
||||
# Apply filters and check if any are active
|
||||
f = TransactionsFilter(request.GET, queryset=base_queryset)
|
||||
|
||||
# Check if any filter has a non-default value
|
||||
# Default values are: type=['IN', 'EX'], is_paid=['1', '0'], everything else empty
|
||||
has_active_filter = False
|
||||
if f.form.is_valid():
|
||||
for name, value in f.form.cleaned_data.items():
|
||||
# Skip fields with default/empty values
|
||||
if not value:
|
||||
continue
|
||||
# Skip type if it has both default values
|
||||
if name == "type" and set(value) == {"IN", "EX"}:
|
||||
continue
|
||||
# Skip is_paid if it has both default values (values are strings)
|
||||
if name == "is_paid" and set(value) == {"1", "0"}:
|
||||
continue
|
||||
# Skip mute_status if it has both default values
|
||||
if name == "mute_status" and set(value) == {"active", "muted"}:
|
||||
continue
|
||||
# If we get here, there's an active filter
|
||||
has_active_filter = True
|
||||
break
|
||||
|
||||
if has_active_filter:
|
||||
queryset = f.qs
|
||||
else:
|
||||
queryset = (
|
||||
base_queryset.exclude(
|
||||
Q(Q(category__mute=True) & ~Q(category=None)) | Q(mute=True)
|
||||
)
|
||||
.exclude(account__in=request.user.untracked_accounts.all())
|
||||
.exclude(account__is_asset=True)
|
||||
)
|
||||
|
||||
data = calculate_currency_totals(queryset, ignore_empty=True)
|
||||
|
||||
percentages = calculate_percentage_distribution(data)
|
||||
|
||||
context = {
|
||||
@@ -132,6 +162,7 @@ def monthly_summary(request, month: int, year: int):
|
||||
currency_totals=data, month=month, year=year
|
||||
),
|
||||
"percentages": percentages,
|
||||
"has_active_filter": has_active_filter,
|
||||
}
|
||||
|
||||
return render(
|
||||
@@ -149,9 +180,38 @@ def monthly_account_summary(request, month: int, year: int):
|
||||
base_queryset = Transaction.objects.filter(
|
||||
reference_date__year=year,
|
||||
reference_date__month=month,
|
||||
).exclude(Q(Q(category__mute=True) & ~Q(category=None)) | Q(mute=True))
|
||||
)
|
||||
|
||||
account_data = calculate_account_totals(transactions_queryset=base_queryset.all())
|
||||
# Apply filters and check if any are active
|
||||
f = TransactionsFilter(request.GET, queryset=base_queryset)
|
||||
|
||||
# Check if any filter has a non-default value
|
||||
has_active_filter = False
|
||||
if f.form.is_valid():
|
||||
for name, value in f.form.cleaned_data.items():
|
||||
if not value:
|
||||
continue
|
||||
if name == "type" and set(value) == {"IN", "EX"}:
|
||||
continue
|
||||
if name == "is_paid" and set(value) == {"1", "0"}:
|
||||
continue
|
||||
if name == "mute_status" and set(value) == {"active", "muted"}:
|
||||
continue
|
||||
has_active_filter = True
|
||||
break
|
||||
|
||||
if has_active_filter:
|
||||
queryset = f.qs
|
||||
else:
|
||||
queryset = (
|
||||
base_queryset.exclude(
|
||||
Q(Q(category__mute=True) & ~Q(category=None)) | Q(mute=True)
|
||||
)
|
||||
.exclude(account__in=request.user.untracked_accounts.all())
|
||||
.exclude(account__is_asset=True)
|
||||
)
|
||||
|
||||
account_data = calculate_account_totals(transactions_queryset=queryset.all())
|
||||
account_percentages = calculate_percentage_distribution(account_data)
|
||||
|
||||
context = {
|
||||
@@ -171,16 +231,41 @@ def monthly_account_summary(request, month: int, year: int):
|
||||
@require_http_methods(["GET"])
|
||||
def monthly_currency_summary(request, month: int, year: int):
|
||||
# Base queryset with all required filters
|
||||
base_queryset = (
|
||||
Transaction.objects.filter(
|
||||
reference_date__year=year,
|
||||
reference_date__month=month,
|
||||
)
|
||||
.exclude(Q(Q(category__mute=True) & ~Q(category=None)) | Q(mute=True))
|
||||
.exclude(account__in=request.user.untracked_accounts.all())
|
||||
base_queryset = Transaction.objects.filter(
|
||||
reference_date__year=year,
|
||||
reference_date__month=month,
|
||||
)
|
||||
|
||||
currency_data = calculate_currency_totals(base_queryset.all(), ignore_empty=True)
|
||||
# Apply filters and check if any are active
|
||||
f = TransactionsFilter(request.GET, queryset=base_queryset)
|
||||
|
||||
# Check if any filter has a non-default value
|
||||
has_active_filter = False
|
||||
if f.form.is_valid():
|
||||
for name, value in f.form.cleaned_data.items():
|
||||
if not value:
|
||||
continue
|
||||
if name == "type" and set(value) == {"IN", "EX"}:
|
||||
continue
|
||||
if name == "is_paid" and set(value) == {"1", "0"}:
|
||||
continue
|
||||
if name == "mute_status" and set(value) == {"active", "muted"}:
|
||||
continue
|
||||
has_active_filter = True
|
||||
break
|
||||
|
||||
if has_active_filter:
|
||||
queryset = f.qs
|
||||
else:
|
||||
queryset = (
|
||||
base_queryset.exclude(
|
||||
Q(Q(category__mute=True) & ~Q(category=None)) | Q(mute=True)
|
||||
)
|
||||
.exclude(account__in=request.user.untracked_accounts.all())
|
||||
.exclude(account__is_asset=True)
|
||||
)
|
||||
|
||||
currency_data = calculate_currency_totals(queryset.all(), ignore_empty=True)
|
||||
currency_percentages = calculate_percentage_distribution(currency_data)
|
||||
|
||||
context = {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{% load i18n %}
|
||||
{% load currency_display %}
|
||||
<div class="grid grid-cols-1 gap-4 mt-1 mb-3">
|
||||
{% if not has_active_filter %}
|
||||
{# Daily Spending#}
|
||||
<div>
|
||||
<c-ui.info-card color="yellow" icon="fa-solid fa-calendar-day" title="{% trans 'Daily Spending Allowance' %}" help_text={% trans "This is the final total divided by the remaining days in the month" %}>
|
||||
@@ -34,6 +35,7 @@
|
||||
</div>
|
||||
</c-ui.info-card>
|
||||
</div>
|
||||
{% endif %}
|
||||
{# Income#}
|
||||
<div>
|
||||
<c-ui.info-card color="green" icon="fa-solid fa-arrow-right-to-bracket" title="{% trans 'Income' %}">
|
||||
|
||||
@@ -50,12 +50,13 @@
|
||||
role="tab"
|
||||
{% if summary_tab == 'summary' or not summary_tab %}checked="checked"{% endif %}
|
||||
_="on click fetch {% url 'monthly_summary_select' selected='summary' %}"
|
||||
aria-controls="summary-tab-pane" />
|
||||
aria-controls="summary-tab-pane"/>
|
||||
<div class="tab-content" id="summary-tab-pane" role="tabpanel">
|
||||
<div id="summary"
|
||||
hx-get="{% url 'monthly_summary' month=month year=year %}"
|
||||
class="show-loading"
|
||||
hx-trigger="load, updated from:window, selective_update from:window, every 10m">
|
||||
hx-trigger="load, updated from:window, selective_update from:window, every 10m"
|
||||
hx-include="#filter">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -68,7 +69,8 @@
|
||||
<div id="currency-summary"
|
||||
hx-get="{% url 'monthly_currency_summary' month=month year=year %}"
|
||||
class="show-loading"
|
||||
hx-trigger="load, updated from:window, selective_update from:window, every 10m">
|
||||
hx-trigger="load, updated from:window, selective_update from:window, every 10m"
|
||||
hx-include="#filter">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -81,7 +83,8 @@
|
||||
<div id="account-summary"
|
||||
hx-get="{% url 'monthly_account_summary' month=month year=year %}"
|
||||
class="show-loading"
|
||||
hx-trigger="load, updated from:window, selective_update from:window, every 10m">
|
||||
hx-trigger="load, updated from:window, selective_update from:window, every 10m"
|
||||
hx-include="#filter">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -100,11 +103,112 @@
|
||||
{# Main control bar with filter, search, and ordering #}
|
||||
<div class="join w-full">
|
||||
|
||||
<button class="btn btn-secondary join-item relative" type="button"
|
||||
<button class="btn btn-secondary join-item relative z-1" type="button"
|
||||
@click="filterOpen = !filterOpen"
|
||||
:aria-expanded="filterOpen" id="filter-button"
|
||||
title="{% translate 'Filter transactions' %}">
|
||||
title="{% translate 'Filter transactions' %}"
|
||||
_="on load or change from #filter
|
||||
-- Check if any filter has a non-default value
|
||||
set hasActiveFilter to false
|
||||
|
||||
-- Check type (default is both IN and EX checked)
|
||||
set typeInputs to <input[name='type']:checked/> in #filter
|
||||
if typeInputs.length is not 2
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check is_paid (default is both 1 and 0 checked)
|
||||
set isPaidInputs to <input[name='is_paid']:checked/> in #filter
|
||||
if isPaidInputs.length is not 2
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check mute_status (default is both active and muted checked)
|
||||
set muteStatusInputs to <input[name='mute_status']:checked/> in #filter
|
||||
if muteStatusInputs.length is not 2
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check description
|
||||
set descInput to #id_description
|
||||
if descInput exists and descInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check date_start
|
||||
set dateStartInput to #id_date_start
|
||||
if dateStartInput exists and dateStartInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check date_end
|
||||
set dateEndInput to #id_date_end
|
||||
if dateEndInput exists and dateEndInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check reference_date_start
|
||||
set refDateStartInput to #id_reference_date_start
|
||||
if refDateStartInput exists and refDateStartInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check reference_date_end
|
||||
set refDateEndInput to #id_reference_date_end
|
||||
if refDateEndInput exists and refDateEndInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check from_amount
|
||||
set fromAmountInput to #id_from_amount
|
||||
if fromAmountInput exists and fromAmountInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check to_amount
|
||||
set toAmountInput to #id_to_amount
|
||||
if toAmountInput exists and toAmountInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check account (TomSelect stores values differently)
|
||||
set accountInput to #id_account
|
||||
if accountInput exists and accountInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check currency
|
||||
set currencyInput to #id_currency
|
||||
if currencyInput exists and currencyInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check category
|
||||
set categoryInput to #id_category
|
||||
if categoryInput exists and categoryInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check tags
|
||||
set tagsInput to #id_tags
|
||||
if tagsInput exists and tagsInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check entities
|
||||
set entitiesInput to #id_entities
|
||||
if entitiesInput exists and entitiesInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Show or hide the indicator
|
||||
if hasActiveFilter
|
||||
remove .hidden from #filter-active-indicator
|
||||
else
|
||||
add .hidden to #filter-active-indicator
|
||||
end">
|
||||
<i class="fa-solid fa-filter fa-fw"></i>
|
||||
<span id="filter-active-indicator" class="absolute -top-1 -right-1 w-3 h-3 bg-error rounded-full hidden z-10"></span>
|
||||
</button>
|
||||
|
||||
{# Search box #}
|
||||
|
||||
@@ -52,11 +52,112 @@
|
||||
{# Main control bar with filter, search, and ordering #}
|
||||
<div class="join w-full">
|
||||
|
||||
<button class="btn btn-secondary join-item relative" type="button"
|
||||
<button class="btn btn-secondary join-item relative z-1" type="button"
|
||||
@click="filterOpen = !filterOpen"
|
||||
:aria-expanded="filterOpen" id="filter-button"
|
||||
title="{% translate 'Filter transactions' %}">
|
||||
title="{% translate 'Filter transactions' %}"
|
||||
_="on load or change from #filter
|
||||
-- Check if any filter has a non-default value
|
||||
set hasActiveFilter to false
|
||||
|
||||
-- Check type (default is both IN and EX checked)
|
||||
set typeInputs to <input[name='type']:checked/> in #filter
|
||||
if typeInputs.length is not 2
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check is_paid (default is both 1 and 0 checked)
|
||||
set isPaidInputs to <input[name='is_paid']:checked/> in #filter
|
||||
if isPaidInputs.length is not 2
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check mute_status (default is both active and muted checked)
|
||||
set muteStatusInputs to <input[name='mute_status']:checked/> in #filter
|
||||
if muteStatusInputs.length is not 2
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check description
|
||||
set descInput to #id_description
|
||||
if descInput exists and descInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check date_start
|
||||
set dateStartInput to #id_date_start
|
||||
if dateStartInput exists and dateStartInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check date_end
|
||||
set dateEndInput to #id_date_end
|
||||
if dateEndInput exists and dateEndInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check reference_date_start
|
||||
set refDateStartInput to #id_reference_date_start
|
||||
if refDateStartInput exists and refDateStartInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check reference_date_end
|
||||
set refDateEndInput to #id_reference_date_end
|
||||
if refDateEndInput exists and refDateEndInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check from_amount
|
||||
set fromAmountInput to #id_from_amount
|
||||
if fromAmountInput exists and fromAmountInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check to_amount
|
||||
set toAmountInput to #id_to_amount
|
||||
if toAmountInput exists and toAmountInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check account (TomSelect stores values differently)
|
||||
set accountInput to #id_account
|
||||
if accountInput exists and accountInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check currency
|
||||
set currencyInput to #id_currency
|
||||
if currencyInput exists and currencyInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check category
|
||||
set categoryInput to #id_category
|
||||
if categoryInput exists and categoryInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check tags
|
||||
set tagsInput to #id_tags
|
||||
if tagsInput exists and tagsInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Check entities
|
||||
set entitiesInput to #id_entities
|
||||
if entitiesInput exists and entitiesInput.value is not ''
|
||||
set hasActiveFilter to true
|
||||
end
|
||||
|
||||
-- Show or hide the indicator
|
||||
if hasActiveFilter
|
||||
remove .hidden from #filter-active-indicator
|
||||
else
|
||||
add .hidden to #filter-active-indicator
|
||||
end">
|
||||
<i class="fa-solid fa-filter fa-fw"></i>
|
||||
<span id="filter-active-indicator" class="absolute -top-1 -right-1 w-3 h-3 bg-error rounded-full hidden z-10"></span>
|
||||
</button>
|
||||
|
||||
{# Search box #}
|
||||
|
||||
Reference in New Issue
Block a user