fix: убрана двойная загрузка данных на странице списка сотрудников
- Упрощен employee_list view - НЕ загружает данные сотрудников - Сервер отдает только справочники для фильтров - Убран серверный рендеринг карточек из шаблона - Добавлен spinner во время загрузки - Данные загружаются один раз через JavaScript - Производительность улучшена - нет двойного запроса
This commit is contained in:
parent
785a82f54b
commit
e31972b7aa
@ -206,46 +206,37 @@ def employee_list(request):
|
||||
Список сотрудников.
|
||||
Если ?format=json — вернуть JSON (для автокомплита):
|
||||
/employees/list/?format=json&q=...&exclude=1,2,3&limit=12
|
||||
|
||||
Для HTML страницы - данные загружаются через JavaScript на клиенте.
|
||||
"""
|
||||
api = get_api_client(request)
|
||||
|
||||
page = request.GET.get("page") or 1
|
||||
ordering = request.GET.get("o") or "last_name"
|
||||
q = (request.GET.get("q") or "").strip()
|
||||
department = request.GET.get("department")
|
||||
position = request.GET.get("position")
|
||||
is_active = request.GET.get("is_active")
|
||||
|
||||
params = {"page": page, "ordering": ordering}
|
||||
if q:
|
||||
params["search"] = q
|
||||
if department:
|
||||
params["department"] = department
|
||||
if position:
|
||||
params["position"] = position
|
||||
if is_active:
|
||||
params["is_active"] = is_active
|
||||
|
||||
resp = api.get("v1/employees/", params=params)
|
||||
if not resp.ok:
|
||||
if request.GET.get("format") == "json":
|
||||
return JsonResponse({"results": [], "count": 0}, status=resp.status or 500)
|
||||
messages.error(request, f"Ошибка API ({resp.status})")
|
||||
return render(
|
||||
request,
|
||||
"employees/employees_list.html",
|
||||
{"employees": [], "q": q, "o": ordering, "page": page},
|
||||
)
|
||||
|
||||
payload = resp.json or {}
|
||||
items, count, next_url, prev_url = _extract_items(payload)
|
||||
|
||||
# Преобразуем API URLs в frontend URLs
|
||||
next_url = _convert_api_url_to_frontend(next_url, request.path)
|
||||
prev_url = _convert_api_url_to_frontend(prev_url, request.path)
|
||||
|
||||
# JSON-ветка для автокомплита
|
||||
if request.GET.get("format") == "json":
|
||||
page = request.GET.get("page") or 1
|
||||
ordering = request.GET.get("o") or "last_name"
|
||||
q = (request.GET.get("q") or "").strip()
|
||||
department = request.GET.get("department")
|
||||
position = request.GET.get("position")
|
||||
is_active = request.GET.get("is_active")
|
||||
|
||||
params = {"page": page, "ordering": ordering}
|
||||
if q:
|
||||
params["search"] = q
|
||||
if department:
|
||||
params["department"] = department
|
||||
if position:
|
||||
params["position"] = position
|
||||
if is_active:
|
||||
params["is_active"] = is_active
|
||||
|
||||
resp = api.get("v1/employees/", params=params)
|
||||
if not resp.ok:
|
||||
return JsonResponse({"results": [], "count": 0}, status=resp.status or 500)
|
||||
|
||||
payload = resp.json or {}
|
||||
items, count, next_url, prev_url = _extract_items(payload)
|
||||
|
||||
# exclude: "1,2,3"
|
||||
raw_ex = (request.GET.get("exclude") or "").strip()
|
||||
try:
|
||||
@ -268,34 +259,18 @@ def employee_list(request):
|
||||
items = [e for e in items if _id(e) not in ex_ids][:limit]
|
||||
return JsonResponse({"results": items, "count": len(items)})
|
||||
|
||||
# HTML-ветка (как было)
|
||||
# Проверяем права на создание сотрудников
|
||||
create_url = None
|
||||
has_perm = (
|
||||
request.user.has_perm('employees.add_employee')
|
||||
or request.user.is_staff
|
||||
or request.user.is_superuser
|
||||
)
|
||||
if has_perm:
|
||||
from django.urls import reverse
|
||||
create_url = reverse('employees:employee_create')
|
||||
|
||||
# Если фильтрация по отделу, меняем label
|
||||
count_label = "Всего:"
|
||||
if department:
|
||||
count_label = "В отделе:"
|
||||
|
||||
# Получаем списки для фильтров
|
||||
# HTML-ветка - НЕ загружаем данные сотрудников, они загрузятся через JavaScript
|
||||
# Получаем только справочники для фильтров
|
||||
departments_list = []
|
||||
positions_list = []
|
||||
try:
|
||||
# Получаем все отделы
|
||||
# Получаем все отделы для фильтров
|
||||
dept_resp = api.get("v1/departments/", params={"page_size": 1000})
|
||||
if dept_resp.ok:
|
||||
dept_data = dept_resp.json or {}
|
||||
departments_list = dept_data.get("results", [])
|
||||
|
||||
# Получаем все должности
|
||||
# Получаем все должности для фильтров
|
||||
pos_resp = api.get("v1/positions/", params={"page_size": 1000})
|
||||
if pos_resp.ok:
|
||||
pos_data = pos_resp.json or {}
|
||||
@ -304,15 +279,7 @@ def employee_list(request):
|
||||
pass
|
||||
|
||||
context = {
|
||||
"employees": items,
|
||||
"count": count,
|
||||
"q": q,
|
||||
"o": ordering,
|
||||
"page": page,
|
||||
"next_url": next_url,
|
||||
"prev_url": prev_url,
|
||||
"create_url": create_url,
|
||||
"count_label": count_label,
|
||||
"employees": [], # Пустой список - данные загрузятся через JS
|
||||
"departments_list": departments_list,
|
||||
"positions_list": positions_list,
|
||||
}
|
||||
|
||||
@ -23,81 +23,11 @@
|
||||
<div class="card mb-3">
|
||||
<div id="empList" class="list-view card-list">
|
||||
{# Список загружается через JavaScript из employeesList.js #}
|
||||
{% if employees %}
|
||||
{% for emp in employees %}
|
||||
<article class="emp-row list-row"
|
||||
data-name="{{ emp.last_name }} {{ emp.first_name }} {{ emp.patronymic }} {% firstof emp.position.name emp.position_label '' %}"
|
||||
data-depts="{% if emp.departments %}{% for d in emp.departments %}{% if d.name %}{{ d.name }}{% if not forloop.last %}, {% endif %}{% endif %}{% endfor %}{% elif is_filtered_by_department and current_department %}{{ current_department.name }}{% endif %}">
|
||||
<header class="card-header">
|
||||
<div class="card-icon">
|
||||
{% if emp.avatar %}
|
||||
<img src="{{ emp.avatar }}" alt="{{ emp.last_name }} {{ emp.first_name }}">
|
||||
{% else %}
|
||||
<i class="bi-person"></i>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="card-meta flex-grow-1">
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
{% if emp.id %}
|
||||
<a href="{% url 'employees:employee_detail' emp.id %}" class="card-title text-decoration-none">
|
||||
{{ emp.last_name }} {{ emp.first_name }}{% if emp.patronymic %} {{ emp.patronymic }}{% endif %}
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="card-title">
|
||||
{{ emp.last_name }} {{ emp.first_name }}{% if emp.patronymic %} {{ emp.patronymic }}{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
|
||||
{% if emp.position and emp.position.name %}
|
||||
<span class="emp-pos">— {{ emp.position.name }}</span>
|
||||
{% elif emp.position_label %}
|
||||
<span class="emp-pos">— {{ emp.position_label }}</span>
|
||||
{% elif emp.position %}
|
||||
<span class="emp-pos">— Должность #{{ emp.position }}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if emp.departments %}
|
||||
<div class="card-subtitle">
|
||||
{% for d in emp.departments %}
|
||||
{% if d.name %}
|
||||
{% if d.id %}
|
||||
<a href="{% url 'employees:department_detail' d.id %}">{{ d.name }}</a>
|
||||
{% else %}
|
||||
{{ d.name }}
|
||||
{% endif %}
|
||||
{% if not forloop.last %}, {% endif %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% elif is_filtered_by_department and current_department %}
|
||||
<div class="card-subtitle">{{ current_department.name }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="ms-auto d-flex align-items-center gap-2">
|
||||
{% if emp.id and user.id != emp.id %}
|
||||
{% if can_manage_roles and is_filtered_by_department and current_department and emp.id != current_department.head_id %}
|
||||
<a href="{% url 'employees:edit_department_role' current_department.id emp.id %}?next={{ request.get_full_path|urlencode }}"
|
||||
class="btn btn-ghost action-btn" title="Изменить роль">
|
||||
<i class="bi-pencil"></i><span class="d-none d-sm-inline ms-1">Роль</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
<a href="{% url 'communications:start_private_chat' emp.id %}"
|
||||
class="btn btn-ghost action-btn" title="Чат">
|
||||
<i class="bi-chat-dots"></i><span class="d-none d-sm-inline ms-1">Чат</span>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="card-body"></div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="alert alert-info shadow-sm">Нет сотрудников.</div>
|
||||
{% endif %}
|
||||
<div class="text-center py-4">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Загрузка...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user