From 0b3ef5395f55e6fe60325b6c38e2953267128298 Mon Sep 17 00:00:00 2001 From: Konrad du Plessis Date: Sun, 22 Feb 2026 23:53:21 +0200 Subject: [PATCH] =?UTF-8?q?Fix=20work=20history=20filter=20=E2=80=94=20add?= =?UTF-8?q?=20validation,=20explicit=20form=20action,=20and=20visual=20fee?= =?UTF-8?q?dback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add explicit action="{% url 'work_history' %}" to filter form (prevents potential URL mismatch on Flatlogic proxy) - Add numeric validation for worker/project GET params (prevents 500 errors) - Add results counter: "Showing X of Y work logs" when filters are active - Add active filter badges showing worker name, project name, and status - Add green left border indicator on filter card when filters are active - Make Clear button conditional (red, only appears with active filters) - Add SQLite dev toggle in settings.py for local testing without MariaDB Co-Authored-By: Claude Opus 4.6 --- config/settings.py | 18 +++++++++++- core/templates/core/work_history.html | 40 ++++++++++++++++++++++++--- core/views.py | 25 ++++++++++++++++- 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/config/settings.py b/config/settings.py index d906642..b50255f 100644 --- a/config/settings.py +++ b/config/settings.py @@ -199,4 +199,20 @@ LOGOUT_REDIRECT_URL = 'login' from django.contrib.messages import constants as message_constants MESSAGE_TAGS = { message_constants.ERROR: 'danger', -} \ No newline at end of file +} + +# === LOCAL DEVELOPMENT: SQLite override === +# Set USE_SQLITE=true in environment to use SQLite instead of MariaDB. +# This lets you test locally without a MySQL/MariaDB server. +if os.getenv('USE_SQLITE', 'false').lower() == 'true': + DATABASES = { + 'default': { + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': BASE_DIR / 'db.sqlite3', + } + } + # Disable secure cookies for local http:// testing + SESSION_COOKIE_SECURE = False + CSRF_COOKIE_SECURE = False + SESSION_COOKIE_SAMESITE = 'Lax' + CSRF_COOKIE_SAMESITE = 'Lax' \ No newline at end of file diff --git a/core/templates/core/work_history.html b/core/templates/core/work_history.html index 765b19c..f647e28 100644 --- a/core/templates/core/work_history.html +++ b/core/templates/core/work_history.html @@ -40,9 +40,9 @@ {# === FILTER BAR === #} -
+
-
+ {# Preserve current view mode when filtering #} {% if view_mode == 'calendar' %} @@ -87,16 +87,48 @@
- {# Filter Button #} + {# Filter + Clear Buttons #} + + {# === Active Filter Feedback === #} + {# Shows a results counter when filters are active so the user can see the filter is working #} + {% if has_active_filters %} +
+ + + Showing {{ filtered_log_count }} of {{ total_log_count }} work log{{ total_log_count|pluralize }} + + {# Show which filters are active as small badges #} + {% if selected_worker %} + + + {% for w in filter_workers %}{% if w.id|stringformat:"d" == selected_worker %}{{ w.name }}{% endif %}{% endfor %} + + {% endif %} + {% if selected_project %} + + + {% for p in filter_projects %}{% if p.id|stringformat:"d" == selected_project %}{{ p.name }}{% endif %}{% endfor %} + + {% endif %} + {% if selected_status %} + + + {{ selected_status|capfirst }} + + {% endif %} +
+ {% endif %}
diff --git a/core/views.py b/core/views.py index 73e5420..6726dc0 100644 --- a/core/views.py +++ b/core/views.py @@ -375,11 +375,25 @@ def work_history(request): ).distinct() # --- Filters --- - # Read filter values from the URL query string + # Read filter values from the URL query string. + # Validate numeric params to prevent 500 errors from bad/malformed URLs. worker_filter = request.GET.get('worker', '') project_filter = request.GET.get('project', '') status_filter = request.GET.get('status', '') + # Validate: worker and project must be numeric IDs (or empty) + try: + worker_filter = str(int(worker_filter)) if worker_filter else '' + except (ValueError, TypeError): + worker_filter = '' + try: + project_filter = str(int(project_filter)) if project_filter else '' + except (ValueError, TypeError): + project_filter = '' + + # Count total logs BEFORE filtering (so we can show "X of Y" to the user) + total_log_count = logs.count() + if worker_filter: logs = logs.filter(workers__id=worker_filter).distinct() @@ -393,6 +407,12 @@ def work_history(request): # "Unpaid" = has no PayrollRecord linked logs = logs.filter(payroll_records__isnull=True) + # Track whether any filter is active (for showing feedback in the template) + has_active_filters = bool(worker_filter or project_filter or status_filter) + + # Count filtered results BEFORE adding joins (more efficient SQL) + filtered_log_count = logs.count() if has_active_filters else 0 + # Add related data and order by date (newest first) logs = logs.select_related( 'project', 'supervisor' @@ -435,6 +455,9 @@ def work_history(request): 'is_admin': is_admin(user), 'view_mode': view_mode, 'filter_params': filter_params, + 'has_active_filters': has_active_filters, + 'total_log_count': total_log_count, + 'filtered_log_count': filtered_log_count, } # === CALENDAR MODE ===