snapshot: preserve dashboard filters and realtime search

This commit is contained in:
Md Bayazid Bostame
2026-03-26 00:20:59 +01:00
parent e0231a6cca
commit 37c2cddf41
5 changed files with 260 additions and 102 deletions

View File

@@ -425,18 +425,37 @@
.search-form {
display: grid;
gap: 12px;
gap: 10px;
}
.filter-grid {
display: grid;
grid-template-columns: repeat(5, minmax(0, 1fr));
gap: 8px;
}
.filter-field {
display: grid;
gap: 4px;
}
.filter-field label {
font-size: 11px;
font-weight: 800;
color: #294465;
}
.search-box {
position: relative;
}
.search-box input {
.search-box input,
.filter-field select,
.filter-field input[type="date"] {
width: 100%;
border: 1px solid var(--line-strong);
border-radius: 16px;
padding: 14px 16px;
border-radius: 14px;
padding: 11px 13px;
font: inherit;
font-weight: 600;
color: var(--ink);
@@ -444,7 +463,9 @@
box-shadow: inset 0 1px 0 rgba(255,255,255,0.98);
}
.search-box input:focus {
.search-box input:focus,
.filter-field select:focus,
.filter-field input[type="date"]:focus {
outline: none;
border-color: rgba(0, 0, 120, 0.3);
box-shadow: 0 0 0 4px rgba(0, 0, 120, 0.08);
@@ -488,7 +509,7 @@
}
.table-controls {
padding: 14px 18px 14px;
padding: 12px 18px 12px;
border-bottom: 1px solid var(--line);
background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(244,248,255,0.96));
}
@@ -496,16 +517,16 @@
.table-controls-grid {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 14px;
gap: 10px;
align-items: stretch;
}
.control-stack {
display: grid;
gap: 12px;
padding: 14px;
gap: 10px;
padding: 12px;
border: 1px solid rgba(217, 227, 238, 0.95);
border-radius: 18px;
border-radius: 16px;
background: linear-gradient(180deg, rgba(255,255,255,0.98), rgba(249,251,255,0.95));
box-shadow: inset 0 1px 0 rgba(255,255,255,0.9);
}
@@ -848,6 +869,10 @@
.table-controls-grid {
grid-template-columns: 1fr;
}
.filter-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 760px) {
@@ -860,4 +885,5 @@
.topbar { flex-direction: column; }
.quick-actions { justify-content: flex-start; }
.table-wrap { padding-left: 12px; padding-right: 12px; }
.filter-grid { grid-template-columns: 1fr; }
}

View File

@@ -1,4 +1,34 @@
(function () {
const filterForm = document.querySelector('.search-form');
if (filterForm) {
const searchInput = filterForm.querySelector('input[name="q"]');
const immediateFields = Array.from(
filterForm.querySelectorAll('select[name], input[type="date"][name]')
);
let debounceTimer = null;
const submitFilters = function () {
if (debounceTimer) {
window.clearTimeout(debounceTimer);
debounceTimer = null;
}
filterForm.requestSubmit();
};
if (searchInput) {
searchInput.addEventListener('input', function () {
if (debounceTimer) {
window.clearTimeout(debounceTimer);
}
debounceTimer = window.setTimeout(submitFilters, 280);
});
}
immediateFields.forEach(function (field) {
field.addEventListener('change', submitFilters);
});
}
const selectAll = document.getElementById('select-all');
const rowChecks = Array.from(document.querySelectorAll('.row-select'));
const selectedCount = document.getElementById('selected-count');

View File

@@ -112,7 +112,7 @@
{% endif %}
<section class="content-grid">
<section class="table-card">
<section class="table-card" id="vorgaenge">
<div class="table-head">
<div>
<h2>{% trans "Vorgänge" %}</h2>
@@ -123,14 +123,50 @@
<div class="table-controls">
<div class="table-controls-grid">
<div class="control-stack">
<form method="get" action="/requests/" class="search-form">
<form method="get" action="/requests/#vorgaenge" class="search-form">
<div class="search-box">
<input type="search" name="q" value="{{ search_query }}" placeholder="{% trans "Nach Name oder E-Mail suchen" %}" aria-label="{% trans "Nach Name oder E-Mail suchen" %}" />
</div>
<div class="filter-grid">
<div class="filter-field">
<label for="type-filter">{% trans "Typ" %}</label>
<select id="type-filter" name="type">
<option value="">{% trans "Alle" %}</option>
<option value="onboarding" {% if selected_type == 'onboarding' %}selected{% endif %}>{% trans "Onboarding" %}</option>
<option value="offboarding" {% if selected_type == 'offboarding' %}selected{% endif %}>{% trans "Offboarding" %}</option>
</select>
</div>
<div class="filter-field">
<label for="status-filter">{% trans "Status" %}</label>
<select id="status-filter" name="status">
<option value="">{% trans "Alle" %}</option>
{% for choice in status_choices %}
<option value="{{ choice.value }}" {% if selected_status == choice.value %}selected{% endif %}>{{ choice.label }}</option>
{% endfor %}
</select>
</div>
<div class="filter-field">
<label for="department-filter">{% trans "Abteilung" %}</label>
<select id="department-filter" name="department">
<option value="">{% trans "Alle" %}</option>
{% for department in departments %}
<option value="{{ department }}" {% if selected_department == department %}selected{% endif %}>{{ department }}</option>
{% endfor %}
</select>
</div>
<div class="filter-field">
<label for="date-from-filter">{% trans "Von" %}</label>
<input id="date-from-filter" type="date" name="date_from" value="{{ date_from }}" />
</div>
<div class="filter-field">
<label for="date-to-filter">{% trans "Bis" %}</label>
<input id="date-to-filter" type="date" name="date_to" value="{{ date_to }}" />
</div>
</div>
<div class="intro-actions">
<button class="btn btn-primary" type="submit">{% trans "Suchen" %}</button>
{% if search_query %}
<a class="btn btn-secondary" href="/requests/">{% trans "Zurücksetzen" %}</a>
{% if has_filters %}
<a class="btn btn-secondary" href="/requests/#vorgaenge">{% trans "Zurücksetzen" %}</a>
{% endif %}
</div>
</form>

View File

@@ -557,11 +557,37 @@ def requests_dashboard(request):
return redirect('requests_dashboard')
search_query = request.GET.get('q', '').strip()
type_filter = (request.GET.get('type') or '').strip().lower()
status_filter = (request.GET.get('status') or '').strip().lower()
department_filter = (request.GET.get('department') or '').strip()
date_from = (request.GET.get('date_from') or '').strip()
date_to = (request.GET.get('date_to') or '').strip()
onboarding_qs = OnboardingRequest.objects.order_by('-created_at')
offboarding_qs = OffboardingRequest.objects.order_by('-created_at')
all_onboarding = OnboardingRequest.objects.all()
all_offboarding = OffboardingRequest.objects.all()
if search_query:
onboarding_qs = onboarding_qs.filter(Q(full_name__icontains=search_query) | Q(work_email__icontains=search_query))
offboarding_qs = offboarding_qs.filter(Q(full_name__icontains=search_query) | Q(work_email__icontains=search_query))
if status_filter in {'submitted', 'processing', 'completed', 'failed'}:
onboarding_qs = onboarding_qs.filter(processing_status=status_filter)
offboarding_qs = offboarding_qs.filter(processing_status=status_filter)
if department_filter:
onboarding_qs = onboarding_qs.filter(department=department_filter)
offboarding_qs = offboarding_qs.filter(department=department_filter)
if date_from:
onboarding_qs = onboarding_qs.filter(created_at__date__gte=date_from)
offboarding_qs = offboarding_qs.filter(created_at__date__gte=date_from)
if date_to:
onboarding_qs = onboarding_qs.filter(created_at__date__lte=date_to)
offboarding_qs = offboarding_qs.filter(created_at__date__lte=date_to)
if type_filter == 'onboarding':
offboarding_qs = offboarding_qs.none()
elif type_filter == 'offboarding':
onboarding_qs = onboarding_qs.none()
onboarding_items = onboarding_qs[:50]
offboarding_items = offboarding_qs[:50]
@@ -649,12 +675,36 @@ def requests_dashboard(request):
onboarding_total = onboarding_qs.count()
offboarding_total = offboarding_qs.count()
departments = sorted(
{
value.strip()
for value in list(all_onboarding.exclude(department='').values_list('department', flat=True))
+ list(all_offboarding.exclude(department='').values_list('department', flat=True))
if value and value.strip()
},
key=str.lower,
)
status_choices = [
{'value': 'submitted', 'label': _request_status_label('submitted', language_code)},
{'value': 'processing', 'label': _request_status_label('processing', language_code)},
{'value': 'completed', 'label': _request_status_label('completed', language_code)},
{'value': 'failed', 'label': _request_status_label('failed', language_code)},
]
has_filters = any([search_query, type_filter, status_filter, department_filter, date_from, date_to])
return render(
request,
'workflows/requests_dashboard.html',
{
'rows': rows[:60],
'search_query': search_query,
'selected_type': type_filter,
'selected_status': status_filter,
'selected_department': department_filter,
'date_from': date_from,
'date_to': date_to,
'departments': departments,
'status_choices': status_choices,
'has_filters': has_filters,
'onboarding_total': onboarding_total,
'offboarding_total': offboarding_total,
'combined_total': onboarding_total + offboarding_total,