Enhance Item Split Workflow and Modal interactions
All checks were successful
Docker Build & Push / build-and-push (push) Successful in 15s
All checks were successful
Docker Build & Push / build-and-push (push) Successful in 15s
- Reverted Phase 1 table addition to increment quantity of a single row rather than creating multiple rows. - Added a modal in Phase 2 that triggers when dragging an item with quantity > 1 into a backpack, asking how many items to move. - The remaining items are kept on the table. - Updated the 'return to table' functionality when deleting from a backpack to correctly merge quantities back onto the virtual table without affecting other compartments.
This commit is contained in:
@@ -101,7 +101,7 @@ try {
|
||||
// Die rekursive "traverse"-Funktion im JS garantiert dies (Pre-Order Traversal).
|
||||
$parent_pli_id = isset($item_data['parent_pli_id']) && isset($id_map[$item_data['parent_pli_id']]) ? $id_map[$item_data['parent_pli_id']] : null;
|
||||
|
||||
$quantity = isset($old_quantities[$pli_id_frontend]) ? intval($old_quantities[$pli_id_frontend]) : 1;
|
||||
$quantity = isset($item_data['quantity']) ? intval($item_data['quantity']) : (isset($old_quantities[$pli_id_frontend]) ? intval($old_quantities[$pli_id_frontend]) : 1);
|
||||
if ($quantity < 1) $quantity = 1;
|
||||
|
||||
$stmt_insert->bind_param("iiiiiiiis", $packing_list_id, $article_id_val, $quantity, $carrier_id, $index, $parent_pli_id, $backpack_id, $backpack_compartment_id, $name_val);
|
||||
@@ -185,27 +185,34 @@ try {
|
||||
$include_children = !empty($data['include_children']);
|
||||
|
||||
function adjust_single($conn, $packing_list_id, $art_id, $delta) {
|
||||
if ($delta > 0) {
|
||||
$idx_res = $conn->query("SELECT MAX(order_index) as max_idx FROM packing_list_items WHERE packing_list_id = $packing_list_id");
|
||||
$next_idx = ($idx_res->fetch_assoc()['max_idx'] ?? 0) + 1;
|
||||
$stmt_insert = $conn->prepare("INSERT INTO packing_list_items (packing_list_id, article_id, quantity, order_index) VALUES (?, ?, ?, ?)");
|
||||
$stmt_insert->bind_param("iiii", $packing_list_id, $art_id, $delta, $next_idx);
|
||||
$stmt_insert->execute();
|
||||
$stmt_insert->close();
|
||||
} else if ($delta < 0) {
|
||||
$stmt_find = $conn->prepare("SELECT id, quantity FROM packing_list_items WHERE packing_list_id = ? AND article_id = ? AND carrier_user_id IS NULL AND backpack_id IS NULL AND backpack_compartment_id IS NULL AND parent_packing_list_item_id IS NULL ORDER BY quantity ASC LIMIT 1");
|
||||
$stmt_find->bind_param("ii", $packing_list_id, $art_id);
|
||||
$stmt_find->execute();
|
||||
$res = $stmt_find->get_result();
|
||||
if ($row = $res->fetch_assoc()) {
|
||||
if ($row['quantity'] > 1) {
|
||||
$conn->query("UPDATE packing_list_items SET quantity = quantity - 1 WHERE id = " . $row['id']);
|
||||
} else {
|
||||
$conn->query("DELETE FROM packing_list_items WHERE id = " . $row['id']);
|
||||
}
|
||||
$stmt_find = $conn->prepare("SELECT id, quantity FROM packing_list_items WHERE packing_list_id = ? AND article_id = ? AND carrier_user_id IS NULL AND backpack_id IS NULL AND backpack_compartment_id IS NULL AND parent_packing_list_item_id IS NULL LIMIT 1");
|
||||
$stmt_find->bind_param("ii", $packing_list_id, $art_id);
|
||||
$stmt_find->execute();
|
||||
$res = $stmt_find->get_result();
|
||||
if ($row = $res->fetch_assoc()) {
|
||||
$new_quantity = $row['quantity'] + $delta;
|
||||
if ($new_quantity > 0) {
|
||||
$stmt_update = $conn->prepare("UPDATE packing_list_items SET quantity = ? WHERE id = ?");
|
||||
$stmt_update->bind_param("ii", $new_quantity, $row['id']);
|
||||
$stmt_update->execute();
|
||||
$stmt_update->close();
|
||||
} else {
|
||||
$stmt_del = $conn->prepare("DELETE FROM packing_list_items WHERE id = ?");
|
||||
$stmt_del->bind_param("i", $row['id']);
|
||||
$stmt_del->execute();
|
||||
$stmt_del->close();
|
||||
}
|
||||
} else {
|
||||
if ($delta > 0) {
|
||||
$idx_res = $conn->query("SELECT MAX(order_index) as max_idx FROM packing_list_items WHERE packing_list_id = $packing_list_id");
|
||||
$next_idx = ($idx_res->fetch_assoc()['max_idx'] ?? 0) + 1;
|
||||
$stmt_insert = $conn->prepare("INSERT INTO packing_list_items (packing_list_id, article_id, quantity, order_index) VALUES (?, ?, ?, ?)");
|
||||
$stmt_insert->bind_param("iiii", $packing_list_id, $art_id, $delta, $next_idx);
|
||||
$stmt_insert->execute();
|
||||
$stmt_insert->close();
|
||||
}
|
||||
$stmt_find->close();
|
||||
}
|
||||
$stmt_find->close();
|
||||
}
|
||||
|
||||
adjust_single($conn, $packing_list_id, $article_id, $delta);
|
||||
|
||||
@@ -325,6 +325,26 @@ $conn->close();
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="splitItemModal" tabindex="-1" aria-labelledby="splitItemModalLabel" aria-hidden="true" data-bs-backdrop="static" data-bs-keyboard="false">
|
||||
<div class="modal-dialog modal-sm">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title" id="splitItemModalLabel">Artikel aufteilen</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Wie viele möchtest du in dieses Fach packen?</p>
|
||||
<div class="d-flex align-items-center justify-content-center">
|
||||
<input type="number" id="split-qty-input" class="form-control text-center w-50" min="1" value="1">
|
||||
<span class="ms-2">von <span id="split-max-qty"></span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary w-100" id="btn-split-save">Packen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="save-feedback" class="save-feedback">Änderungen gespeichert!</div>
|
||||
<div id="image-preview-tooltip" class="image-preview-tooltip"></div>
|
||||
|
||||
@@ -336,6 +356,7 @@ $conn->close();
|
||||
let sortableInstances = {};
|
||||
let childrenModal = null;
|
||||
let pendingArticleIdForTable = null;
|
||||
let pendingSplit = null;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const tsOptions = { create: false, sortField: { field: "text", direction: "asc" }, allowEmptyOption: true, onChange: function() { this.input.dispatchEvent(new Event('change')); } };
|
||||
@@ -344,6 +365,24 @@ $conn->close();
|
||||
new TomSelect('#group-by', tsOptions);
|
||||
new TomSelect('#sort-by', { create: false, allowEmptyOption: false, onChange: function() { this.input.dispatchEvent(new Event('change')); } });
|
||||
|
||||
document.getElementById('btn-split-save').addEventListener('click', () => {
|
||||
const moveQty = parseInt(document.getElementById('split-qty-input').value, 10);
|
||||
if (moveQty < 1 || moveQty > pendingSplit.totalQty) return;
|
||||
|
||||
const remainQty = pendingSplit.totalQty - moveQty;
|
||||
pendingSplit.itemEl.querySelector('.quantity-input').value = moveQty;
|
||||
|
||||
if (remainQty > 0) {
|
||||
const clone = pendingSplit.itemEl.cloneNode(true);
|
||||
clone.dataset.itemId = 'new_' + Date.now();
|
||||
clone.querySelector('.quantity-input').value = remainQty;
|
||||
pendingSplit.fromEl.appendChild(clone);
|
||||
}
|
||||
|
||||
window.splitItemModalInstance.hide();
|
||||
syncListState();
|
||||
});
|
||||
|
||||
const tooltip = document.getElementById('image-preview-tooltip');
|
||||
if (tooltip) {
|
||||
document.body.addEventListener('mouseover', e => {
|
||||
@@ -545,6 +584,28 @@ $conn->close();
|
||||
});
|
||||
}
|
||||
|
||||
function handleSortableEnd(evt) {
|
||||
const itemEl = evt.item;
|
||||
const isFromTable = evt.from.id === 'table-container';
|
||||
const isToBackpack = evt.to.id !== 'table-container' && evt.to.classList.contains('nested-sortable');
|
||||
|
||||
if (isFromTable && isToBackpack) {
|
||||
const qtyInput = itemEl.querySelector('.quantity-input');
|
||||
const totalQty = parseInt(qtyInput ? qtyInput.value : 1, 10);
|
||||
if (totalQty > 1) {
|
||||
pendingSplit = { itemEl: itemEl, fromEl: evt.from, toEl: evt.to, totalQty: totalQty };
|
||||
document.getElementById('split-max-qty').textContent = totalQty;
|
||||
const splitInput = document.getElementById('split-qty-input');
|
||||
splitInput.value = totalQty;
|
||||
splitInput.max = totalQty;
|
||||
if (!window.splitItemModalInstance) window.splitItemModalInstance = new bootstrap.Modal(document.getElementById('splitItemModal'));
|
||||
window.splitItemModalInstance.show();
|
||||
return;
|
||||
}
|
||||
}
|
||||
syncListState();
|
||||
}
|
||||
|
||||
function renderTable() {
|
||||
const container = document.getElementById('table-container');
|
||||
|
||||
@@ -570,7 +631,7 @@ $conn->close();
|
||||
fallbackOnBody: true,
|
||||
swapThreshold: 0.65,
|
||||
ghostClass: 'sortable-ghost',
|
||||
onEnd: function() { syncListState(); }
|
||||
onEnd: handleSortableEnd
|
||||
});
|
||||
}
|
||||
|
||||
@@ -621,7 +682,7 @@ $conn->close();
|
||||
fallbackOnBody: true,
|
||||
swapThreshold: 0.65,
|
||||
ghostClass: 'sortable-ghost',
|
||||
onEnd: function() { syncListState(); }
|
||||
onEnd: handleSortableEnd
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -643,7 +704,7 @@ $conn->close();
|
||||
sortableInstances[prefix + 'nested_'+item.id] = new Sortable(nestedContainer, {
|
||||
group: 'nested', animation: 150, handle: '.packed-item-content',
|
||||
fallbackOnBody: true, swapThreshold: 0.65, ghostClass: 'sortable-ghost',
|
||||
onEnd: function() { syncListState(); }
|
||||
onEnd: handleSortableEnd
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -717,6 +778,7 @@ $conn->close();
|
||||
payload.list.push({
|
||||
pli_id: pliId,
|
||||
article_id: articleId,
|
||||
quantity: child.querySelector('.quantity-input') ? parseInt(child.querySelector('.quantity-input').value, 10) : 1,
|
||||
carrier_id: carrierId,
|
||||
parent_pli_id: parentId,
|
||||
backpack_id: child.dataset.backpackId || null,
|
||||
@@ -784,9 +846,16 @@ $conn->close();
|
||||
};
|
||||
document.getElementById('btn-remove-totable').onclick = () => {
|
||||
window.removeItemModalInstance.hide();
|
||||
const tableContainer = document.getElementById('table-container');
|
||||
tableContainer.appendChild(itemToRemoveEl);
|
||||
syncListState();
|
||||
const qtyInput = itemToRemoveEl.querySelector('.quantity-input');
|
||||
const qty = parseInt(qtyInput ? qtyInput.value : 1, 10);
|
||||
const articleId = itemToRemoveEl.dataset.articleId;
|
||||
itemToRemoveEl.remove();
|
||||
sendApiRequest({ action: 'adjust_table_quantity', article_id: articleId, delta: qty })
|
||||
.then(() => sendApiRequest({ action: 'delete_item', item_id: itemToRemoveId }))
|
||||
.then(newItems => {
|
||||
if(Array.isArray(newItems)) packedItems = newItems;
|
||||
fullRender();
|
||||
});
|
||||
};
|
||||
}
|
||||
window.removeItemModalInstance.show();
|
||||
|
||||
Reference in New Issue
Block a user