diff --git a/shiftflow/templates/shiftflow/partials/_filter.html b/shiftflow/templates/shiftflow/partials/_filter.html index 60a9b31..76b0ff1 100644 --- a/shiftflow/templates/shiftflow/partials/_filter.html +++ b/shiftflow/templates/shiftflow/partials/_filter.html @@ -1,6 +1,6 @@
-
+ {% if user_role != 'operator' %} @@ -53,8 +53,21 @@
-
- +
+ + +
+ +
@@ -80,10 +93,12 @@ function saveFilters(){ if (!form) return; + const qEl = form.querySelector('input[name="q"]'); const data = { statuses: Array.from(form.querySelectorAll('input[name="statuses"]:checked')).map(i=>i.value), m_ids: Array.from(form.querySelectorAll('input[name="m_ids"]:checked')).map(i=>i.value), - start_date: s ? s.value : '' + start_date: s ? s.value : '', + q: qEl ? (qEl.value || '') : '' }; try { localStorage.setItem('registry_filters', JSON.stringify(data)); } catch(_){} } @@ -107,6 +122,9 @@ if (Array.isArray(data.m_ids)){ form.querySelectorAll('input[name="m_ids"]').forEach(i=>{ i.checked = data.m_ids.includes(i.value); }); } + const qEl = form.querySelector('input[name="q"]'); + if (qEl) qEl.value = data.q || ''; + if (s) s.value = data.start_date || weekAgo; if (e) e.value = today; const filtered = form.querySelector('input[name="filtered"]'); @@ -116,18 +134,31 @@ } if (form){ - form.addEventListener('change', saveFilters, true); + form.addEventListener('change', saveFilters, true); - const resetBtn = document.getElementById('registryResetBtn'); - if (resetBtn) { - resetBtn.addEventListener('click', function () { - try { localStorage.removeItem('registry_filters'); } catch(_) {} - }); + const resetBtn = document.getElementById('registryResetBtn'); + if (resetBtn) { + resetBtn.addEventListener('click', function () { + try { localStorage.removeItem('registry_filters'); } catch(_) {} + }); + } + + document.addEventListener('keydown', function (e) { + if (e.key !== 'Escape') return; + if (e.defaultPrevented) return; + const active = document.activeElement; + if (active && (active.tagName || '').toLowerCase() === 'textarea') return; + if (document.querySelector('.modal.show')) return; + + if (resetBtn) { + e.preventDefault(); + resetBtn.click(); + } + }, true); + + restoreFilters(); } - - restoreFilters(); - } - }); + });
\ No newline at end of file diff --git a/shiftflow/views.py b/shiftflow/views.py index c5c6b91..67d87ee 100644 --- a/shiftflow/views.py +++ b/shiftflow/views.py @@ -435,6 +435,8 @@ class RegistryView(LoginRequiredMixin, ListView): # Диапазон дат, задаваемый пользователем. Если фильтры не активны или явно указан reset=1 — используем дефолты start_date = self.request.GET.get('start_date') end_date = self.request.GET.get('end_date') + + q = (self.request.GET.get('q') or '').strip() # Дефолтный режим: последние 7 дней и только статус "В работе" is_default = (not filtered) or bool(reset) @@ -451,6 +453,16 @@ class RegistryView(LoginRequiredMixin, ListView): # Ограничения по ролям + if q: + queryset = queryset.filter( + Q(task__deal__number__icontains=q) + | Q(task__drawing_name__icontains=q) + | Q(task__entity__name__icontains=q) + | Q(task__entity__drawing_number__icontains=q) + | Q(task__material__name__icontains=q) + | Q(task__material__full_name__icontains=q) + ) + if role == 'operator': user_machines = profile.machines.all() if profile else Machine.objects.none() queryset = queryset.filter(machine__in=user_machines) @@ -470,6 +482,7 @@ class RegistryView(LoginRequiredMixin, ListView): context['user_role'] = role context['user_roles'] = sorted(roles) context['is_readonly'] = bool(getattr(profile, 'is_readonly', False)) if profile else False + context['q'] = (self.request.GET.get('q') or '').strip() allowed_ws = list(profile.allowed_workshops.values_list('id', flat=True)) if profile else [] context['allowed_workshop_ids'] = allowed_ws @@ -511,6 +524,16 @@ class RegistryView(LoginRequiredMixin, ListView): work_qs = WorkItem.objects.select_related('deal', 'deal__company', 'entity', 'entity__planned_material', 'operation', 'machine', 'workshop') + q = (self.request.GET.get('q') or '').strip() + if q: + work_qs = work_qs.filter( + Q(deal__number__icontains=q) + | Q(entity__name__icontains=q) + | Q(entity__drawing_number__icontains=q) + | Q(entity__planned_material__name__icontains=q) + | Q(entity__planned_material__full_name__icontains=q) + ) + m_ids = [int(i) for i in self.request.GET.getlist('m_ids') if str(i).isdigit()] if m_ids: work_qs = work_qs.filter(Q(machine_id__in=m_ids) | Q(machine_id__isnull=True)) diff --git a/static/css/style.css b/static/css/style.css index 23414a2..740abe9 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -161,6 +161,16 @@ body { width: 120px; } +.registry-filter-q { + width: 220px; +} + +@media (max-width: 575.98px) { + .registry-filter-q { + width: 100%; + } +} + /* Специальный класс для центрирования окна логина (вернем его только там) */ .sf-attention { animation: sfAttentionPulse 1.6s ease-in-out infinite;