Добавить функционал массового изменения категорий товаров

- Добавлен UI для пакетного выбора товаров с чекбоксами
- Реализована возможность выбора всех товаров на странице
- Реализована возможность выбора всех отфильтрованных товаров
- Добавлено модальное окно для массового управления категориями
- Добавлены API эндпоинты: get_filtered_items_ids, bulk_update_categories
- Реализованы три режима работы с категориями: добавление, замена, очистка
- Добавлен селектор количества элементов на странице (20/50/100)
- Улучшена информативность о количестве выбранных элементов
This commit is contained in:
2026-01-07 09:15:53 +03:00
parent d5c1ed1e4b
commit 161f65e6c3
6 changed files with 1297 additions and 13 deletions

View File

@@ -108,7 +108,17 @@
{% endif %}
</div>
<div class="col-12 col-md-6">
<!-- Показывать по -->
<div class="col-12 col-md-2">
<label for="per_page" class="form-label"><i class="bi bi-list-ol"></i> Показывать по:</label>
<select class="form-select" id="per_page" name="per_page" onchange="this.form.submit()">
<option value="20" {% if filters.current.per_page == '20' %}selected{% endif %}>20</option>
<option value="50" {% if filters.current.per_page == '50' %}selected{% endif %}>50</option>
<option value="100" {% if filters.current.per_page == '100' %}selected{% endif %}>100</option>
</select>
</div>
<div class="col-12 col-md-4">
<label class="form-label d-none d-md-block">&nbsp;</label>
<div class="d-flex gap-2">
<button type="submit" class="btn btn-primary">
@@ -125,10 +135,71 @@
</div>
{% if items %}
<!-- Инструментальная панель для пакетных действий -->
<div class="d-flex justify-content-between align-items-center mb-3">
<div class="d-flex align-items-center gap-3">
{% if is_paginated %}
<span class="text-muted">
Показано {{ page_obj.start_index }}{{ page_obj.end_index }} из {{ page_obj.paginator.count }}
</span>
{% else %}
<span class="text-muted">Всего: {{ items|length }}</span>
{% endif %}
<!-- Dropdown для выбора всех отфильтрованных -->
<div class="btn-group" id="select-all-dropdown-group" style="display: none;">
<button type="button" class="btn btn-sm btn-outline-primary dropdown-toggle"
data-bs-toggle="dropdown" aria-expanded="false">
<i class="bi bi-check2-square"></i> Выбрать
</button>
<ul class="dropdown-menu">
<li>
<a class="dropdown-item" href="#" onclick="document.getElementById('select-all-checkbox').click(); return false;">
<i class="bi bi-check2"></i> Все на странице
</a>
</li>
{% if is_paginated %}
<li>
<a class="dropdown-item" href="#" onclick="window.BatchSelection.selectAllFiltered(); return false;">
<i class="bi bi-check2-all"></i> Все отфильтрованные ({{ page_obj.paginator.count }})
</a>
</li>
{% endif %}
<li><hr class="dropdown-divider"></li>
<li>
<a class="dropdown-item" href="#" onclick="window.BatchSelection.clearSelection(); return false;">
<i class="bi bi-x-square"></i> Снять выбор
</a>
</li>
</ul>
</div>
</div>
<div class="btn-group">
<button type="button" class="btn btn-outline-secondary" id="batch-actions-btn" disabled>
<i class="bi bi-gear-fill"></i> Действия над выбранными (<span id="selection-count">0</span>)
</button>
<button type="button" class="btn btn-outline-secondary dropdown-toggle dropdown-toggle-split"
id="batch-actions-dropdown" data-bs-toggle="dropdown" aria-expanded="false" disabled>
<span class="visually-hidden">Открыть меню</span>
</button>
<ul class="dropdown-menu dropdown-menu-end" aria-labelledby="batch-actions-dropdown">
<li>
<a class="dropdown-item" href="#" id="bulk-category-action">
<i class="bi bi-bookmark-fill"></i> Изменить категории
</a>
</li>
</ul>
</div>
</div>
<div class="table-responsive">
<table class="table table-striped table-hover align-middle">
<thead class="table-dark">
<tr>
<th style="width: 50px;">
<input type="checkbox" id="select-all-checkbox" class="form-check-input"
title="Выбрать все на странице">
</th>
<th style="width: 80px;">Фото</th>
<th>Название</th>
<th style="width: 120px;">Артикул</th>
@@ -144,6 +215,14 @@
<tbody>
{% for item in items %}
<tr>
<td>
<input type="checkbox" name="selected_items"
value="{{ item.item_type }}:{{ item.pk }}"
class="form-check-input item-checkbox"
data-item-type="{{ item.item_type }}"
data-item-id="{{ item.pk }}"
data-item-name="{{ item.name }}">
</td>
<td>
{% if item.photos.all %}
{% with photo=item.photos.first %}
@@ -318,4 +397,78 @@
</div>
{% endif %}
</div>
<!-- Модальное окно для массового изменения категорий -->
<div class="modal fade" id="bulkCategoryModal" tabindex="-1" aria-labelledby="bulkCategoryModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="bulkCategoryModalLabel">
<i class="bi bi-bookmark-fill"></i> Изменить категории для выбранных товаров
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div>
<div class="modal-body">
<!-- Информация о выбранных товарах -->
<div class="alert alert-info mb-3">
<i class="bi bi-info-circle"></i> <strong>Выбрано:</strong> <span id="selectedItemsCount">0</span> товаров
<span id="selectedItemsBreakdown" class="ms-2 text-muted"></span>
</div>
<!-- Опции применения -->
<div class="mb-4">
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" id="clearExistingCategoriesToggle" role="switch">
<label class="form-check-label" for="clearExistingCategoriesToggle">
<i class="bi bi-trash"></i> Очистить существующие категории перед сохранением
</label>
<div class="form-text text-warning">
<i class="bi bi-exclamation-triangle"></i>
При включении все текущие категории товаров будут удалены, а затем применены только выбранные ниже
</div>
</div>
</div>
<!-- Выбор категорий -->
<div class="mb-3">
<label class="form-label fw-bold">Выберите категории:</label>
<!-- Поиск по категориям -->
<input type="text" class="form-control mb-2" id="categorySearchInput"
placeholder="Поиск категорий...">
<!-- Список категорий -->
<div id="categoryListContainer" style="max-height: 300px; overflow-y: auto; border: 1px solid #dee2e6; border-radius: 0.25rem; padding: 0.5rem;">
<div class="text-center py-3">
<div class="spinner-border spinner-border-sm" role="status">
<span class="visually-hidden">Загрузка...</span>
</div>
<div class="mt-2 text-muted">Загрузка категорий...</div>
</div>
</div>
</div>
<!-- Сообщения об ошибках -->
<div id="bulkCategoryError" class="alert alert-danger d-none" role="alert"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger me-auto" id="clearAllCategoriesBtn">
<i class="bi bi-trash3"></i> Очистить все категории
</button>
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<i class="bi bi-x-circle"></i> Отмена
</button>
<button type="button" class="btn btn-primary" id="applyBulkCategoriesBtn" disabled>
<i class="bi bi-check2-circle"></i> Применить
</button>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
{% load static %}
<script src="{% static 'products/js/batch-selection.js' %}?v=1.1"></script>
<script src="{% static 'products/js/bulk-category-modal.js' %}?v=1.0"></script>
{% endblock %}