From 8fea9e11a6626494650eef6292697ccf4dfa8f99 Mon Sep 17 00:00:00 2001 From: Gemini Agent Date: Mon, 11 May 2026 16:11:21 +0000 Subject: [PATCH] Fix UI feedback on virtual table refactor - Increased image size in Lager grid. - Allowed dragging by clicking anywhere on the item row. - Removed empty space/dropzone gaps on the table. - Added debounce to syncListState to prevent concurrent save errors during rapid drag & drop. --- src/manage_packing_list_items.php | 107 +++++++++++++++++------------- 1 file changed, 62 insertions(+), 45 deletions(-) diff --git a/src/manage_packing_list_items.php b/src/manage_packing_list_items.php index f0c85bf..ba4d73d 100644 --- a/src/manage_packing_list_items.php +++ b/src/manage_packing_list_items.php @@ -160,7 +160,7 @@ $conn->close(); } .lager-card:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.05); } .lager-img-wrapper { - height: 80px; margin-bottom: 8px; display: flex; + height: 130px; margin-bottom: 8px; display: flex; align-items: center; justify-content: center; } .lager-img-wrapper img { @@ -181,6 +181,21 @@ $conn->close(); .editor-pane { display: flex; flex-direction: column; height: 100%; } .pane-content { flex-grow: 1; overflow-y: auto; padding: 10px; } + + /* Remove empty dropzone on the table specifically */ + #table-container > .packed-item-container > .nested-sortable:empty { + min-height: 0; + border: none; + background: transparent; + margin: 0; + } + #table-container > .packed-item-container > .nested-sortable:empty::after { + display: none; + } + + /* Grab cursor for items */ + .packed-item-content { cursor: grab; } + .packed-item-content:active { cursor: grabbing; }
@@ -431,7 +446,7 @@ $conn->close(); sortableInstances['table'] = new Sortable(container, { group: 'nested', animation: 150, - handle: '.handle', + handle: '.packed-item-content', fallbackOnBody: true, swapThreshold: 0.65, ghostClass: 'sortable-ghost', @@ -474,7 +489,7 @@ $conn->close(); sortableInstances['carrier_'+carrierId] = new Sortable(carrierRootList, { group: 'nested', animation: 150, - handle: '.handle', + handle: '.packed-item-content', fallbackOnBody: true, swapThreshold: 0.65, ghostClass: 'sortable-ghost', @@ -485,8 +500,6 @@ $conn->close(); function renderRecursive(items, container, contextMap) { items.forEach(item => { - // For flat items array from table, build children tree if needed, but table shouldn't have nested items naturally unless dragged. - // But if they are dragged out, they might keep children. let children = item.children || []; if (!item.children && Array.isArray(contextMap)) { children = contextMap.filter(i => i.parent_packing_list_item_id === item.id); @@ -500,7 +513,7 @@ $conn->close(); } sortableInstances['nested_'+item.id] = new Sortable(nestedContainer, { - group: 'nested', animation: 150, handle: '.handle', + group: 'nested', animation: 150, handle: '.packed-item-content', fallbackOnBody: true, swapThreshold: 0.65, ghostClass: 'sortable-ghost', onEnd: function() { syncListState(); } }); @@ -538,7 +551,7 @@ $conn->close(); div.innerHTML = `
- + ${nameDisplay} ${metaDisplay}
${controls} @@ -549,47 +562,51 @@ $conn->close(); return div; } + let syncTimeout = null; function syncListState() { - const payload = { action: 'sync_list', list: [] }; - - function traverse(container, carrierId, parentId) { - Array.from(container.children).forEach(child => { - if (!child.classList.contains('packed-item-container')) return; - const pliId = child.dataset.itemId; - const articleId = child.dataset.articleId; - - payload.list.push({ - pli_id: pliId, - article_id: articleId, - carrier_id: carrierId, - parent_pli_id: parentId, - backpack_id: child.dataset.backpackId || null, - backpack_compartment_id: child.dataset.backpackCompartmentId || null + if (syncTimeout) clearTimeout(syncTimeout); + syncTimeout = setTimeout(() => { + const payload = { action: 'sync_list', list: [] }; + + function traverse(container, carrierId, parentId) { + Array.from(container.children).forEach(child => { + if (!child.classList.contains('packed-item-container')) return; + const pliId = child.dataset.itemId; + const articleId = child.dataset.articleId; + + payload.list.push({ + pli_id: pliId, + article_id: articleId, + carrier_id: carrierId, + parent_pli_id: parentId, + backpack_id: child.dataset.backpackId || null, + backpack_compartment_id: child.dataset.backpackCompartmentId || null + }); + + const nestedContainer = child.querySelector('.nested-sortable'); + if (nestedContainer) traverse(nestedContainer, carrierId, pliId); }); - - const nestedContainer = child.querySelector('.nested-sortable'); - if (nestedContainer) traverse(nestedContainer, carrierId, pliId); + } + + const tableList = document.getElementById('table-container'); + if (tableList) traverse(tableList, 'null', null); + + document.querySelectorAll('.carrier-list').forEach(rootList => { + const carrierId = rootList.dataset.carrierId; + traverse(rootList, carrierId, null); }); - } - - const tableList = document.getElementById('table-container'); - if (tableList) traverse(tableList, 'null', null); - - document.querySelectorAll('.carrier-list').forEach(rootList => { - const carrierId = rootList.dataset.carrierId; - traverse(rootList, carrierId, null); - }); - - sendApiRequest(payload).then(newItems => { - if(Array.isArray(newItems)) packedItems = newItems; - const panes = document.querySelectorAll('.pane-content'); - const scrollMap = Array.from(panes).map(p => p.scrollTop); - fullRender(); - Array.from(panes).forEach((p, i) => { if (scrollMap[i] !== undefined) p.scrollTop = scrollMap[i]; }); - }).catch(err => { - console.error("Sync failed:", err); - alert("Fehler beim Speichern! Bitte Seite neu laden."); - }); + + sendApiRequest(payload).then(newItems => { + if(Array.isArray(newItems)) packedItems = newItems; + const panes = document.querySelectorAll('.pane-content'); + const scrollMap = Array.from(panes).map(p => p.scrollTop); + fullRender(); + Array.from(panes).forEach((p, i) => { if (scrollMap[i] !== undefined) p.scrollTop = scrollMap[i]; }); + }).catch(err => { + console.error("Sync failed:", err); + alert("Fehler beim Speichern! Bitte Seite neu laden."); + }); + }, 300); } function handlePackedItemActions(e) {