Fix work history filter — add validation, explicit form action, and visual feedback

- 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 <noreply@anthropic.com>
This commit is contained in:
Konrad du Plessis 2026-02-22 23:53:21 +02:00
parent b837932bb4
commit 0b3ef5395f
3 changed files with 77 additions and 6 deletions

View File

@ -200,3 +200,19 @@ from django.contrib.messages import constants as message_constants
MESSAGE_TAGS = { MESSAGE_TAGS = {
message_constants.ERROR: 'danger', message_constants.ERROR: 'danger',
} }
# === 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'

View File

@ -40,9 +40,9 @@
</div> </div>
{# === FILTER BAR === #} {# === FILTER BAR === #}
<div class="card shadow-sm border-0 mb-4"> <div class="card shadow-sm border-0 mb-4{% if has_active_filters %} border-start border-3{% endif %}" {% if has_active_filters %}style="border-left-color: var(--accent, #10b981) !important;"{% endif %}>
<div class="card-body py-3"> <div class="card-body py-3">
<form method="GET" class="row g-2 align-items-end"> <form method="GET" action="{% url 'work_history' %}" class="row g-2 align-items-end">
{# Preserve current view mode when filtering #} {# Preserve current view mode when filtering #}
<input type="hidden" name="view" value="{{ view_mode }}"> <input type="hidden" name="view" value="{{ view_mode }}">
{% if view_mode == 'calendar' %} {% if view_mode == 'calendar' %}
@ -87,16 +87,48 @@
</select> </select>
</div> </div>
{# Filter Button #} {# Filter + Clear Buttons #}
<div class="col-md-3 d-flex gap-2"> <div class="col-md-3 d-flex gap-2">
<button type="submit" class="btn btn-sm btn-accent"> <button type="submit" class="btn btn-sm btn-accent">
<i class="fas fa-filter me-1"></i> Filter <i class="fas fa-filter me-1"></i> Filter
</button> </button>
<a href="{% url 'work_history' %}?view={{ view_mode }}" class="btn btn-sm btn-outline-secondary"> {% if has_active_filters %}
<a href="{% url 'work_history' %}?view={{ view_mode }}" class="btn btn-sm btn-outline-danger">
<i class="fas fa-times me-1"></i> Clear <i class="fas fa-times me-1"></i> Clear
</a> </a>
{% endif %}
</div> </div>
</form> </form>
{# === Active Filter Feedback === #}
{# Shows a results counter when filters are active so the user can see the filter is working #}
{% if has_active_filters %}
<div class="mt-2 d-flex align-items-center flex-wrap gap-2">
<small class="text-muted">
<i class="fas fa-info-circle me-1"></i>
Showing <strong>{{ filtered_log_count }}</strong> of {{ total_log_count }} work log{{ total_log_count|pluralize }}
</small>
{# Show which filters are active as small badges #}
{% if selected_worker %}
<span class="badge bg-primary bg-opacity-10 text-primary border border-primary border-opacity-25">
<i class="fas fa-user fa-xs me-1"></i>
{% for w in filter_workers %}{% if w.id|stringformat:"d" == selected_worker %}{{ w.name }}{% endif %}{% endfor %}
</span>
{% endif %}
{% if selected_project %}
<span class="badge bg-success bg-opacity-10 text-success border border-success border-opacity-25">
<i class="fas fa-project-diagram fa-xs me-1"></i>
{% for p in filter_projects %}{% if p.id|stringformat:"d" == selected_project %}{{ p.name }}{% endif %}{% endfor %}
</span>
{% endif %}
{% if selected_status %}
<span class="badge bg-warning bg-opacity-10 text-dark border border-warning border-opacity-25">
<i class="fas fa-tag fa-xs me-1"></i>
{{ selected_status|capfirst }}
</span>
{% endif %}
</div>
{% endif %}
</div> </div>
</div> </div>

View File

@ -375,11 +375,25 @@ def work_history(request):
).distinct() ).distinct()
# --- Filters --- # --- 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', '') worker_filter = request.GET.get('worker', '')
project_filter = request.GET.get('project', '') project_filter = request.GET.get('project', '')
status_filter = request.GET.get('status', '') 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: if worker_filter:
logs = logs.filter(workers__id=worker_filter).distinct() logs = logs.filter(workers__id=worker_filter).distinct()
@ -393,6 +407,12 @@ def work_history(request):
# "Unpaid" = has no PayrollRecord linked # "Unpaid" = has no PayrollRecord linked
logs = logs.filter(payroll_records__isnull=True) 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) # Add related data and order by date (newest first)
logs = logs.select_related( logs = logs.select_related(
'project', 'supervisor' 'project', 'supervisor'
@ -435,6 +455,9 @@ def work_history(request):
'is_admin': is_admin(user), 'is_admin': is_admin(user),
'view_mode': view_mode, 'view_mode': view_mode,
'filter_params': filter_params, 'filter_params': filter_params,
'has_active_filters': has_active_filters,
'total_log_count': total_log_count,
'filtered_log_count': filtered_log_count,
} }
# === CALENDAR MODE === # === CALENDAR MODE ===