diff --git a/myproject/products/static/products/js/catalog.js b/myproject/products/static/products/js/catalog.js index bc40808..d48eebc 100644 --- a/myproject/products/static/products/js/catalog.js +++ b/myproject/products/static/products/js/catalog.js @@ -220,6 +220,187 @@ document.addEventListener('DOMContentLoaded', function() { }); }); + // ======================================== + // Inline-создание категорий + // ======================================== + + // Создание корневой категории + const addRootCategoryBtn = document.getElementById('add-root-category-btn'); + if (addRootCategoryBtn) { + addRootCategoryBtn.addEventListener('click', function(e) { + e.stopPropagation(); + showCategoryInput(null); + }); + } + + // Создание подкатегории (через иконку "+") + document.querySelectorAll('.category-add-child-btn').forEach(btn => { + btn.addEventListener('click', function(e) { + e.stopPropagation(); + const parentId = this.dataset.parentId; + const categoryNode = this.closest('.category-node'); + showCategoryInput(parentId, categoryNode); + }); + }); + + // Функция показа inline инпута для создания категории + function showCategoryInput(parentId = null, parentNode = null) { + // Проверяем, нет ли уже активного инпута + const existingInput = document.querySelector('.category-create-input'); + if (existingInput) { + existingInput.focus(); + return; + } + + // Создаем контейнер для нового инпута + const inputContainer = document.createElement('div'); + inputContainer.className = 'category-node'; + inputContainer.style.cssText = 'padding-left: ' + (parentId ? '1rem' : '0'); + + const inputHeader = document.createElement('div'); + inputHeader.className = 'category-header'; + inputHeader.style.cssText = 'background-color: #e7f1ff;'; + + // Иконка (точка для нового узла) + const dotIcon = document.createElement('i'); + dotIcon.className = 'bi bi-dot text-muted'; + inputHeader.appendChild(dotIcon); + + // Иконка папки + const folderIcon = document.createElement('i'); + folderIcon.className = 'bi bi-folder2 text-secondary'; + inputHeader.appendChild(folderIcon); + + // Создаем input + const input = document.createElement('input'); + input.type = 'text'; + input.placeholder = 'Название категории...'; + input.className = 'category-create-input flex-grow-1'; + input.style.cssText = 'border: 1px solid #198754; border-radius: 3px; padding: 2px 6px; font-size: 0.9rem; outline: none;'; + inputHeader.appendChild(input); + + inputContainer.appendChild(inputHeader); + + // Вставляем в нужное место + if (parentId && parentNode) { + // Подкатегория - вставляем в children контейнер + let childrenContainer = parentNode.querySelector('.category-children'); + + // Если контейнер не существует, создаем его + if (!childrenContainer) { + childrenContainer = document.createElement('div'); + childrenContainer.className = 'category-children'; + parentNode.appendChild(childrenContainer); + } + + // Раскрываем родителя, если свернут + if (childrenContainer.classList.contains('d-none')) { + childrenContainer.classList.remove('d-none'); + const toggle = parentNode.querySelector('.category-toggle'); + if (toggle) { + toggle.classList.remove('collapsed'); + } + } + + childrenContainer.insertBefore(inputContainer, childrenContainer.firstChild); + } else { + // Корневая категория - вставляем в начало дерева + const categoryTree = document.querySelector('.category-tree'); + categoryTree.insertBefore(inputContainer, categoryTree.firstChild); + } + + input.focus(); + + // Флаг для предотвращения повторного вызова + let isSubmitted = false; + let blurTimeoutId = null; + + // Функция создания категории + const createCategory = async () => { + // Проверяем, не была ли уже отправлена форма + if (isSubmitted) return; + + const name = input.value.trim(); + if (!name) { + inputContainer.remove(); + return; + } + + // Отменяем blur обработчик если он запланирован + if (blurTimeoutId) { + clearTimeout(blurTimeoutId); + blurTimeoutId = null; + } + + // Устанавливаем флаг + isSubmitted = true; + + // Показываем загрузку + input.disabled = true; + input.style.opacity = '0.5'; + + try { + const response = await fetch('/products/api/categories/create/', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': getCsrfToken() + }, + body: JSON.stringify({ + name: name, + parent_id: parentId + }) + }); + + const data = await response.json(); + + if (data.success) { + location.reload(); + } else { + // Удаляем input ДО alert + inputContainer.remove(); + alert(data.error || 'Ошибка при создании категории'); + } + } catch (error) { + console.error('Ошибка при создании категории:', error); + inputContainer.remove(); + alert('Ошибка сети'); + } + }; + + // Функция отмены + const cancelCreate = () => { + if (!isSubmitted) { + inputContainer.remove(); + } + }; + + // Enter - создать + input.addEventListener('keydown', function(e) { + if (e.key === 'Enter') { + e.preventDefault(); + createCategory(); + } else if (e.key === 'Escape') { + e.preventDefault(); + cancelCreate(); + } + }); + + // Потеря фокуса - сохранить или отменить + input.addEventListener('blur', function() { + // Небольшая задержка чтобы не конфликтовать с Enter + blurTimeoutId = setTimeout(() => { + if (!isSubmitted) { + if (input.value.trim()) { + createCategory(); + } else { + cancelCreate(); + } + } + }, 100); + }); + } + // Получение CSRF токена function getCsrfToken() { const cookieValue = document.cookie diff --git a/myproject/products/templates/products/catalog.html b/myproject/products/templates/products/catalog.html index 6923adf..bca7a44 100644 --- a/myproject/products/templates/products/catalog.html +++ b/myproject/products/templates/products/catalog.html @@ -118,8 +118,9 @@ .catalog-list .catalog-item .card-body .mt-1 { margin-top: 0 !important; } - /* Кнопка переименования категории */ - .category-rename-btn { + /* Кнопки управления категориями (переименование, добавление подкатегории) */ + .category-rename-btn, + .category-add-child-btn { opacity: 0; cursor: pointer; color: #6c757d; @@ -127,13 +128,18 @@ padding: 2px 4px; transition: opacity 0.2s, color 0.15s; } - .category-header:hover .category-rename-btn { + .category-header:hover .category-rename-btn, + .category-header:hover .category-add-child-btn { opacity: 0.5; } .category-rename-btn:hover { opacity: 1 !important; color: #0d6efd; } + .category-add-child-btn:hover { + opacity: 1 !important; + color: #198754; + } {% endblock %} @@ -145,9 +151,12 @@