diff --git a/myproject/products/static/products/js/batch-selection.js b/myproject/products/static/products/js/batch-selection.js new file mode 100644 index 0000000..1ebf047 --- /dev/null +++ b/myproject/products/static/products/js/batch-selection.js @@ -0,0 +1,336 @@ +/** + * Batch Selection Manager for Product List + * Handles checkbox selection, row highlighting, and counter updates + */ + +(function() { + 'use strict'; + + // Selection state + let selectedItems = new Set(); + + // DOM elements + const selectAllCheckbox = document.getElementById('select-all-checkbox'); + const batchActionsBtn = document.getElementById('batch-actions-btn'); + const batchActionsDropdown = document.getElementById('batch-actions-dropdown'); + let selectionCountSpan = document.getElementById('selection-count'); + + /** + * Initialize the batch selection functionality + */ + function init() { + if (!selectAllCheckbox) { + console.warn('Batch selection: select-all checkbox not found'); + return; + } + + // Attach event listeners + selectAllCheckbox.addEventListener('change', handleSelectAllChange); + + // Attach listeners to all item checkboxes + const itemCheckboxes = document.querySelectorAll('.item-checkbox'); + itemCheckboxes.forEach(checkbox => { + checkbox.addEventListener('change', handleItemCheckboxChange); + }); + + // Initialize state + updateUI(); + } + + /** + * Handle "Select All" checkbox change + */ + function handleSelectAllChange(event) { + const isChecked = event.target.checked; + const itemCheckboxes = document.querySelectorAll('.item-checkbox'); + + itemCheckboxes.forEach(checkbox => { + checkbox.checked = isChecked; + const itemValue = checkbox.value; + + if (isChecked) { + selectedItems.add(itemValue); + highlightRow(checkbox, true); + } else { + selectedItems.delete(itemValue); + highlightRow(checkbox, false); + } + }); + + updateUI(); + } + + /** + * Handle individual item checkbox change + */ + function handleItemCheckboxChange(event) { + const checkbox = event.target; + const itemValue = checkbox.value; + const isChecked = checkbox.checked; + + if (isChecked) { + selectedItems.add(itemValue); + highlightRow(checkbox, true); + } else { + selectedItems.delete(itemValue); + highlightRow(checkbox, false); + } + + // Update "Select All" checkbox state + updateSelectAllCheckbox(); + + // Update UI + updateUI(); + } + + /** + * Highlight or unhighlight the row containing the checkbox + */ + function highlightRow(checkbox, highlight) { + const row = checkbox.closest('tr'); + if (row) { + if (highlight) { + row.classList.add('table-active'); + } else { + row.classList.remove('table-active'); + } + } + } + + /** + * Update the "Select All" checkbox state based on individual checkboxes + */ + function updateSelectAllCheckbox() { + const itemCheckboxes = document.querySelectorAll('.item-checkbox'); + const totalCheckboxes = itemCheckboxes.length; + const checkedCheckboxes = document.querySelectorAll('.item-checkbox:checked').length; + + if (checkedCheckboxes === 0) { + selectAllCheckbox.checked = false; + selectAllCheckbox.indeterminate = false; + } else if (checkedCheckboxes === totalCheckboxes) { + selectAllCheckbox.checked = true; + selectAllCheckbox.indeterminate = false; + } else { + selectAllCheckbox.checked = false; + selectAllCheckbox.indeterminate = true; + } + } + + /** + * Update UI elements based on selection state + */ + function updateUI() { + const count = selectedItems.size; + + // Обновляем счётчик (ищем заново на случай, если он был пересоздан) + selectionCountSpan = document.getElementById('selection-count'); + if (selectionCountSpan) { + selectionCountSpan.textContent = count; + } + + // Enable/disable batch actions button and restore text if needed + const shouldEnable = count > 0; + if (batchActionsBtn) { + batchActionsBtn.disabled = !shouldEnable; + + // Если в кнопке нет span#selection-count, значит текст был изменён - восстанавливаем + if (!batchActionsBtn.querySelector('#selection-count')) { + batchActionsBtn.innerHTML = ` Действия над выбранными (${count})`; + selectionCountSpan = document.getElementById('selection-count'); + } + } + if (batchActionsDropdown) { + batchActionsDropdown.disabled = !shouldEnable; + } + + // Показываем/скрываем dropdown для выбора всех + const selectAllDropdown = document.getElementById('select-all-dropdown-group'); + if (selectAllDropdown) { + // Показываем всегда, когда есть элементы на странице + const itemCheckboxes = document.querySelectorAll('.item-checkbox'); + if (itemCheckboxes.length > 0) { + selectAllDropdown.style.display = 'block'; + } + } + } + + /** + * Get currently selected items + * @returns {Array} Array of selected item identifiers + */ + function getSelectedItems() { + return Array.from(selectedItems); + } + + /** + * Clear all selections + */ + function clearSelection() { + selectedItems.clear(); + + // Uncheck all checkboxes + const itemCheckboxes = document.querySelectorAll('.item-checkbox'); + itemCheckboxes.forEach(checkbox => { + checkbox.checked = false; + highlightRow(checkbox, false); + }); + + if (selectAllCheckbox) { + selectAllCheckbox.checked = false; + selectAllCheckbox.indeterminate = false; + } + + updateUI(); + } + + /** + * Select all filtered items via AJAX request + */ + function selectAllFiltered() { + // Получаем текущие параметры фильтрации из URL + const urlParams = new URLSearchParams(window.location.search); + + // Строим URL для API запроса + const apiUrl = '/products/api/filtered-items-ids/?' + urlParams.toString(); + + // Показываем индикатор загрузки + if (batchActionsBtn) { + batchActionsBtn.disabled = true; + batchActionsBtn.innerHTML = ' Загрузка...'; + } + + // Выполняем AJAX запрос + fetch(apiUrl, { + method: 'GET', + headers: { + 'X-Requested-With': 'XMLHttpRequest' + }, + credentials: 'same-origin' + }) + .then(response => response.json()) + .then(data => { + if (data.success) { + // Очищаем текущий выбор + selectedItems.clear(); + + // Добавляем все отфильтрованные элементы + data.items.forEach(item => { + const itemValue = `${item.type}:${item.id}`; + selectedItems.add(itemValue); + }); + + // Отмечаем чекбоксы на текущей странице + const itemCheckboxes = document.querySelectorAll('.item-checkbox'); + itemCheckboxes.forEach(checkbox => { + const itemValue = checkbox.value; + if (selectedItems.has(itemValue)) { + checkbox.checked = true; + highlightRow(checkbox, true); + } + }); + + // Обновляем состояние "Select All" checkbox + updateSelectAllCheckbox(); + + // Обновляем UI + updateUI(); + + // Показываем уведомление + showNotification(`Выбрано ${data.count} элементов (включая элементы на других страницах)`, 'success'); + } else { + showNotification('Ошибка при загрузке списка элементов: ' + data.error, 'error'); + } + }) + .catch(error => { + console.error('Error fetching filtered items:', error); + showNotification('Ошибка при загрузке списка элементов', 'error'); + }) + .finally(() => { + // Восстанавливаем кнопку + updateUI(); + }); + } + + /** + * Show notification to user + */ + function showNotification(message, type = 'info') { + // Создаём toast уведомление (Bootstrap 5) + const toastContainer = document.querySelector('.toast-container') || createToastContainer(); + + const toastId = 'toast-' + Date.now(); + const bgClass = type === 'success' ? 'bg-success' : type === 'error' ? 'bg-danger' : 'bg-info'; + + const toastHTML = ` +
| + + | Фото | Название | Артикул | @@ -144,6 +215,14 @@
|---|---|---|---|
| + + |
{% if item.photos.all %}
{% with photo=item.photos.first %}
@@ -318,4 +397,78 @@
{% endif %}
+
+
+
+{% endblock %}
+
+{% block extra_js %}
+{% load static %}
+
+
{% endblock %}
diff --git a/myproject/products/urls.py b/myproject/products/urls.py
index b0960b6..7bb8119 100644
--- a/myproject/products/urls.py
+++ b/myproject/products/urls.py
@@ -11,11 +11,6 @@ urlpatterns = [
# Каталог с drag-n-drop
path('catalog/', views.CatalogView.as_view(), name='catalog'),
-
- # Legacy URLs for backward compatibility
- path('all/', views.CombinedProductListView.as_view(), name='all-products'),
- path('products/', views.ProductListView.as_view(), name='product-list-legacy'),
- path('kits/', views.ProductKitListView.as_view(), name='productkit-list'),
# CRUD URLs for Product
path('product/create/', views.ProductCreateView.as_view(), name='product-create'),
path('product/ |