Fix UI feedback on virtual table refactor
All checks were successful
Docker Build & Push / build-and-push (push) Successful in 12s
All checks were successful
Docker Build & Push / build-and-push (push) Successful in 12s
- 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.
This commit is contained in:
@@ -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; }
|
||||
</style>
|
||||
|
||||
<div class="card mb-3">
|
||||
@@ -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 = `
|
||||
<div class="${contentClass}">
|
||||
<i class="${iconClass}" style="cursor:grab;"></i>
|
||||
<i class="${iconClass}"></i>
|
||||
<span class="item-name ms-2">${nameDisplay} ${metaDisplay}</span>
|
||||
<div class="item-controls ms-auto d-flex align-items-center">
|
||||
${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) {
|
||||
|
||||
Reference in New Issue
Block a user