Добавлена система фильтрации клиентов с универсальным поиском
- Реализован универсальный поиск по имени, email и телефону в одной строке - Добавлен счетчик общего количества клиентов - Поиск работает по нажатию Enter или кнопке 'Поиск' - Удалены неиспользуемые фильтры django-filter - Упрощен интерфейс списка клиентов - Добавлена кнопка 'Очистить' для сброса поиска
This commit is contained in:
@@ -8,7 +8,12 @@
|
||||
<div class="col-12">
|
||||
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h1>Клиенты</h1>
|
||||
<div>
|
||||
<h1>Клиенты</h1>
|
||||
<p class="text-muted mb-0">
|
||||
Всего клиентов: <strong>{{ total_customers }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
<div class="btn-group" role="group">
|
||||
<a href="{% url 'customers:customer-import' %}" class="btn btn-outline-success">
|
||||
<i class="bi bi-upload"></i> Импорт
|
||||
@@ -22,99 +27,66 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search Form -->
|
||||
<div class="card mb-4">
|
||||
<!-- Поиск -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<form method="get" class="row g-3" id="search-form">
|
||||
<div class="col-md-6">
|
||||
<input type="text" class="form-control" name="q"
|
||||
value="{{ query|default:'' }}" placeholder="Поиск по имени, email или телефону (минимум 3 символа)..." id="search-input">
|
||||
<small class="form-text text-muted" id="search-hint" style="display: none; color: #dc3545 !important;">
|
||||
Введите минимум 3 символа для поиска
|
||||
</small>
|
||||
<form method="get" class="row g-2">
|
||||
<div class="col-md-8">
|
||||
<input type="text" class="form-control" name="q" value="{{ query }}"
|
||||
placeholder="Поиск по имени, email или телефону..."
|
||||
autofocus>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button type="submit" class="btn btn-outline-primary" id="search-btn">Поиск</button>
|
||||
{% if query %}
|
||||
<a href="{% url 'customers:customer-list' %}" class="btn btn-outline-secondary">Очистить</a>
|
||||
{% endif %}
|
||||
<div class="col-md-4">
|
||||
<div class="btn-group w-100" role="group">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-search"></i> Поиск
|
||||
</button>
|
||||
{% if query %}
|
||||
<a href="{% url 'customers:customer-list' %}" class="btn btn-outline-secondary">
|
||||
<i class="bi bi-x-circle"></i> Очистить
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<script>
|
||||
document.getElementById('search-form').addEventListener('submit', function(e) {
|
||||
const searchInput = document.getElementById('search-input');
|
||||
const searchValue = searchInput.value.trim();
|
||||
const searchHint = document.getElementById('search-hint');
|
||||
|
||||
// Если поле пусто или содержит менее 3 символов, не отправляем форму
|
||||
if (searchValue && searchValue.length < 3) {
|
||||
e.preventDefault();
|
||||
searchHint.style.display = 'block';
|
||||
searchInput.classList.add('is-invalid');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Если поле пусто, тоже не отправляем (это будет просто пусто)
|
||||
if (!searchValue) {
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Все хорошо, отправляем
|
||||
searchHint.style.display = 'none';
|
||||
searchInput.classList.remove('is-invalid');
|
||||
});
|
||||
|
||||
// Убираем ошибку при вводе
|
||||
document.getElementById('search-input').addEventListener('input', function() {
|
||||
const searchValue = this.value.trim();
|
||||
const searchHint = document.getElementById('search-hint');
|
||||
|
||||
if (searchValue.length >= 3) {
|
||||
searchHint.style.display = 'none';
|
||||
this.classList.remove('is-invalid');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Customers Table -->
|
||||
<!-- Таблица клиентов -->
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
|
||||
{% if page_obj %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Имя</th>
|
||||
<th>Email</th>
|
||||
<th>Телефон</th>
|
||||
<th class="text-end">Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for customer in page_obj %}
|
||||
<tr
|
||||
style="cursor:pointer"
|
||||
onclick="window.location='{% url 'customers:customer-detail' customer.pk %}'"
|
||||
>
|
||||
<td class="fw-semibold">{{ customer.full_name }}</td>
|
||||
<td>{{ customer.email|default:'—' }}</td>
|
||||
<td>{{ customer.phone|default:'—' }}</td>
|
||||
<table class="table table-hover align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Имя</th>
|
||||
<th>Email</th>
|
||||
<th>Телефон</th>
|
||||
<th class="text-end">Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for customer in page_obj %}
|
||||
<tr
|
||||
style="cursor:pointer"
|
||||
onclick="window.location='{% url 'customers:customer-detail' customer.pk %}'"
|
||||
>
|
||||
<td class="fw-semibold">{{ customer.full_name }}</td>
|
||||
<td>{{ customer.email|default:'—' }}</td>
|
||||
<td>{{ customer.phone|default:'—' }}</td>
|
||||
|
||||
<td class="text-end" onclick="event.stopPropagation();">
|
||||
<a href="{% url 'customers:customer-detail' customer.pk %}"
|
||||
class="btn btn-sm btn-outline-primary">👁</a>
|
||||
<a href="{% url 'customers:customer-update' customer.pk %}"
|
||||
class="btn btn-sm btn-outline-secondary">✎</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<td class="text-end" onclick="event.stopPropagation();">
|
||||
<a href="{% url 'customers:customer-detail' customer.pk %}"
|
||||
class="btn btn-sm btn-outline-primary">👁</a>
|
||||
<a href="{% url 'customers:customer-update' customer.pk %}"
|
||||
class="btn btn-sm btn-outline-secondary">✎</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if page_obj.has_other_pages %}
|
||||
|
||||
Reference in New Issue
Block a user