/** * Produktverwaltung - JavaScript * Baum auf-/zuklappen, Inline-Editing, AJAX-Aktionen */ document.addEventListener('DOMContentLoaded', function() { 'use strict'; // Only init on produktverwaltung pages if (!document.querySelector('.pv-tree, .pv-no-category')) return; var pvAjaxUrl = pvConfig ? pvConfig.ajaxUrl : ''; var pvToken = pvConfig ? pvConfig.token : ''; var pvHasMargin = pvConfig ? pvConfig.hasMargin : false; // === Toast Notification === function showToast(message, type) { var toast = document.createElement('div'); toast.className = 'pv-toast ' + (type || 'success'); toast.textContent = message; document.body.appendChild(toast); setTimeout(function() { toast.style.opacity = '0'; toast.style.transition = 'opacity 0.3s'; setTimeout(function() { toast.remove(); }, 300); }, 2500); } // === Baum auf-/zuklappen === function toggleCategory(header) { var toggle = header.querySelector('.pv-toggle'); var children = header.nextElementSibling; if (!children) return; if (children.classList.contains('collapsed')) { children.classList.remove('collapsed'); if (toggle) toggle.classList.remove('collapsed'); } else { children.classList.add('collapsed'); if (toggle) toggle.classList.add('collapsed'); } } // Alle aufklappen window.pvExpandAll = function() { document.querySelectorAll('.pv-category-children.collapsed').forEach(function(el) { el.classList.remove('collapsed'); }); document.querySelectorAll('.pv-toggle.collapsed').forEach(function(el) { el.classList.remove('collapsed'); }); // Also expand no-category section var ncBody = document.querySelector('.pv-no-category-body'); if (ncBody) ncBody.classList.remove('collapsed'); }; // Alle zuklappen window.pvCollapseAll = function() { document.querySelectorAll('.pv-category-children').forEach(function(el) { el.classList.add('collapsed'); }); document.querySelectorAll('.pv-toggle').forEach(function(el) { el.classList.add('collapsed'); }); }; // Category header click document.querySelectorAll('.pv-category-header').forEach(function(header) { header.addEventListener('click', function(e) { if (e.target.tagName === 'A' || e.target.tagName === 'BUTTON') return; toggleCategory(header); }); }); // No-category header toggle var ncHeader = document.querySelector('.pv-no-category-header'); if (ncHeader) { ncHeader.addEventListener('click', function() { var body = document.querySelector('.pv-no-category-body'); if (body) body.classList.toggle('collapsed'); }); } // === Ref-Schema toggle === var schemaBox = document.querySelector('.pv-ref-schema'); if (schemaBox) { var schemaHeader = schemaBox.querySelector('.pv-schema-header'); if (schemaHeader) { schemaHeader.addEventListener('click', function() { schemaBox.classList.toggle('collapsed'); }); } } // === Suche / Filter === var searchInput = document.querySelector('.pv-search input'); if (searchInput) { var searchTimeout; searchInput.addEventListener('input', function() { clearTimeout(searchTimeout); searchTimeout = setTimeout(function() { filterTree(searchInput.value.toLowerCase().trim()); }, 250); }); } function filterTree(query) { if (!query) { // Show all document.querySelectorAll('.pv-tree li, .pv-products tr').forEach(function(el) { el.style.display = ''; }); document.querySelectorAll('.pv-category-children').forEach(function(el) { el.classList.add('collapsed'); }); document.querySelectorAll('.pv-toggle').forEach(function(el) { el.classList.add('collapsed'); }); return; } // Search in product rows var anyMatch = false; document.querySelectorAll('.pv-products tbody tr').forEach(function(row) { var ref = (row.querySelector('.pv-col-ref') || {}).textContent || ''; var label = (row.querySelector('.pv-col-label') || {}).textContent || ''; var match = ref.toLowerCase().indexOf(query) >= 0 || label.toLowerCase().indexOf(query) >= 0; row.style.display = match ? '' : 'none'; if (match) anyMatch = true; }); // Expand categories that have visible products document.querySelectorAll('.pv-tree li').forEach(function(li) { var table = li.querySelector('.pv-products'); var hasVisible = false; if (table) { table.querySelectorAll('tbody tr').forEach(function(row) { if (row.style.display !== 'none') hasVisible = true; }); } // Check child categories too var childLis = li.querySelectorAll(':scope > .pv-category-children > li'); childLis.forEach(function(child) { if (child.style.display !== 'none') hasVisible = true; }); if (hasVisible) { li.style.display = ''; var children = li.querySelector('.pv-category-children'); if (children) children.classList.remove('collapsed'); var toggle = li.querySelector('.pv-toggle'); if (toggle) toggle.classList.remove('collapsed'); } else { // Check if category name matches var catLabel = li.querySelector('.pv-cat-label'); if (catLabel && catLabel.textContent.toLowerCase().indexOf(query) >= 0) { li.style.display = ''; hasVisible = true; } else { li.style.display = hasVisible ? '' : 'none'; } } }); // Also search in no-category section var ncBody = document.querySelector('.pv-no-category-body'); if (ncBody) { var ncMatch = false; ncBody.querySelectorAll('tbody tr').forEach(function(row) { var ref = (row.querySelector('.pv-col-ref') || {}).textContent || ''; var label = (row.querySelector('.pv-col-label') || {}).textContent || ''; var match = ref.toLowerCase().indexOf(query) >= 0 || label.toLowerCase().indexOf(query) >= 0; row.style.display = match ? '' : 'none'; if (match) ncMatch = true; }); if (ncMatch) ncBody.classList.remove('collapsed'); } } // === Inline Editing === document.querySelectorAll('.pv-editable').forEach(function(el) { el.addEventListener('dblclick', function(e) { e.stopPropagation(); startEditing(el); }); }); function startEditing(el) { if (el.querySelector('.pv-edit-input')) return; // Already editing var field = el.dataset.field; // 'ref', 'label', or 'description' var productId = el.dataset.productId; var currentValue = el.textContent.trim(); var input = document.createElement('input'); input.type = 'text'; input.className = 'pv-edit-input'; input.value = currentValue; if (field === 'ref') { input.style.textTransform = 'uppercase'; } el.textContent = ''; el.appendChild(input); input.focus(); input.select(); function save() { var newValue = input.value.trim(); if (field === 'ref') { newValue = newValue.toUpperCase(); } if (newValue === currentValue || newValue === '') { cancel(); return; } el.classList.add('pv-saving'); ajaxCall('update_' + field, { product_id: productId, value: newValue }, function(response) { el.classList.remove('pv-saving'); if (response.success) { el.textContent = newValue; showToast(pvLang.saveSuccess || 'Gespeichert', 'success'); } else { el.textContent = currentValue; showToast(response.error || pvLang.saveError || 'Fehler', 'error'); } }, function() { el.classList.remove('pv-saving'); el.textContent = currentValue; showToast(pvLang.saveError || 'Fehler beim Speichern', 'error'); }); } function cancel() { el.textContent = currentValue; } input.addEventListener('keydown', function(e) { if (e.key === 'Enter') { e.preventDefault(); save(); } else if (e.key === 'Escape') { cancel(); } }); input.addEventListener('blur', function() { // Small delay to allow click events setTimeout(function() { if (el.contains(input)) { save(); } }, 100); }); } // === Produkt löschen === window.pvDeleteProduct = function(productId, productRef) { var msg = (pvLang.confirmDelete || 'Produkt "%s" wirklich löschen?').replace('%s', productRef); if (!confirm(msg)) return; ajaxCall('delete_product', { product_id: productId }, function(response) { if (response.success) { // Remove row from table var row = document.querySelector('tr[data-product-id="' + productId + '"]'); if (row) row.remove(); showToast(pvLang.productDeleted || 'Produkt gelöscht', 'success'); } else { showToast(response.error || pvLang.deleteError || 'Fehler', 'error'); } }); }; // === Kategorie zuweisen === window.pvAssignCategory = function(productId, productRef) { // Load categories via AJAX ajaxCall('get_categories', {}, function(response) { if (!response.success || !response.categories) { showToast(response.error || 'Fehler beim Laden der Kategorien', 'error'); return; } showCategoryDialog(productId, productRef, response.categories); }, function() { showToast('AJAX-Fehler beim Laden der Kategorien', 'error'); }); }; function showCategoryDialog(productId, productRef, categories) { var overlay = document.createElement('div'); overlay.className = 'pv-dialog-overlay'; var dialog = document.createElement('div'); dialog.className = 'pv-dialog'; dialog.innerHTML = '
' + productRef + '
' + '' + ''; overlay.appendChild(dialog); document.body.appendChild(overlay); // Fill select var select = document.getElementById('pv-cat-select'); categories.forEach(function(cat) { var option = document.createElement('option'); option.value = cat.id; option.textContent = cat.fullpath; select.appendChild(option); }); // Assign button document.getElementById('pv-cat-assign-btn').addEventListener('click', function() { var catId = select.value; if (!catId) return; ajaxCall('add_to_category', { product_id: productId, category_id: catId }, function(response) { overlay.remove(); if (response.success) { showToast(pvLang.categoryAssigned || 'Kategorie zugewiesen', 'success'); // Reload page to reflect changes setTimeout(function() { location.reload(); }, 800); } else { showToast(response.error || 'Fehler', 'error'); } }); }); // Close on overlay click overlay.addEventListener('click', function(e) { if (e.target === overlay) overlay.remove(); }); } // === Aus Kategorie entfernen === window.pvRemoveFromCategory = function(productId, categoryId) { ajaxCall('remove_from_category', { product_id: productId, category_id: categoryId }, function(response) { if (response.success) { showToast(pvLang.categoryRemoved || 'Aus Kategorie entfernt', 'success'); setTimeout(function() { location.reload(); }, 800); } else { showToast(response.error || 'Fehler', 'error'); } }); }; // === Produkt bearbeiten (Dialog) === window.pvEditProduct = function(productId) { // Fetch current data from server ajaxCall('get_product', { product_id: productId }, function(response) { if (!response.success || !response.product) { showToast(response.error || 'Fehler', 'error'); return; } showEditDialog(response.product); }); }; function showEditDialog(product) { var overlay = document.createElement('div'); overlay.className = 'pv-dialog-overlay'; var doliUrl = (typeof pvDolibarrUrl !== 'undefined' ? pvDolibarrUrl : '') + '/product/card.php?id=' + product.id; var dialog = document.createElement('div'); dialog.className = 'pv-dialog pv-edit-dialog'; dialog.innerHTML = '