Files
octopus/myproject/customers/templates/customers/customer_list.html
Andrey Smakotin 7d82d67b5f Добавлена frontend валидация поиска на странице списка клиентов
Реализовано:
- Frontend валидация: минимум 3 символа для запуска поиска
- Динамическое отображение подсказки при попытке отправить поиск < 3 символов
- Визуальное выделение ошибки (is-invalid класс для input)
- Автоматическое скрытие ошибки при вводе 3+ символов

Backend уже использует ту же логику оптимизированного поиска:
- Те же стратегии поиска (name_only, universal, email и т.д.)
- Тот же SQL LIKE запрос для поиска по цифрам телефона
- Тот же API эндпоинт api_search_customers()

Теперь обе страницы (создание заказа и список клиентов) используют
единую оптимизированную логику поиска и требуют минимум 3 символа.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-11 01:35:31 +03:00

174 lines
9.5 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block title %}Клиенты{% endblock %}
{% block content %}
<div class="container-fluid">
<div class="row">
<div class="col-12">
<div class="d-flex justify-content-between align-items-center mb-3">
<h1>Клиенты</h1>
<a href="{% url 'customers:customer-create' %}" class="btn btn-primary">Добавить клиента</a>
</div>
<!-- Search Form -->
<div class="card mb-4">
<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>
</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>
</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>Уровень лояльности</th>
<th>Сумма покупок</th>
<th>VIP</th>
<th class="text-end">Действия</th>
</tr>
</thead>
<tbody>
{% for customer in page_obj %}
<tr
class="{% if customer.is_vip %}table-warning{% endif %}"
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>
<span class="badge
{% if customer.loyalty_tier == 'no_discount' %}bg-light text-muted
{% elif customer.loyalty_tier == 'bronze' %}bg-secondary text-white
{% elif customer.loyalty_tier == 'silver' %}bg-info text-dark
{% elif customer.loyalty_tier == 'gold' %}bg-warning text-dark
{% elif customer.loyalty_tier == 'platinum' %}bg-primary text-white
{% endif %}
">
{{ customer.get_loyalty_tier_display }}
</span>
</td>
<td>{{ customer.total_spent|default:0|floatformat:2 }} руб.</td>
<td>
{% if customer.is_vip %}
<span class="badge bg-success">Да</span>
{% else %}
<span class="badge bg-secondary text-white">Нет</span>
{% endif %}
</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>
<!-- Pagination -->
{% if page_obj.has_other_pages %}
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
{% if page_obj.has_previous %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if query %}&q={{ query }}{% endif %}">Предыдущая</a>
</li>
{% endif %}
{% for num in page_obj.paginator.page_range %}
{% if page_obj.number == num %}
<li class="page-item active"><span class="page-link">{{ num }}</span></li>
{% elif num > page_obj.number|add:'-3' and num < page_obj.number|add:'3' %}
<li class="page-item"><a class="page-link" href="?page={{ num }}{% if query %}&q={{ query }}{% endif %}">{{ num }}</a></li>
{% endif %}
{% endfor %}
{% if page_obj.has_next %}
<li class="page-item">
<a class="page-link" href="?page={{ page_obj.next_page_number }}{% if query %}&q={{ query }}{% endif %}">Следующая</a>
</li>
{% endif %}
</ul>
</nav>
{% endif %}
{% else %}
<p>Клиенты не найдены.</p>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endblock %}