Fix Sortable destroy error and add Group By filter
All checks were successful
Docker Build & Push / build-and-push (push) Successful in 18s
All checks were successful
Docker Build & Push / build-and-push (push) Successful in 18s
- Safely destroy Sortable instances before clearing innerHTML. - Added Group By dropdown (Kategorie, Hersteller, Lagerort) to Phase 1. - Increased image size in Lager grid from 130px to 160px.
This commit is contained in:
@@ -57,10 +57,11 @@ if ($current_user_household_id) {
|
||||
$placeholders = implode(',', array_fill(0, count($household_member_ids), '?'));
|
||||
$types = str_repeat('i', count($household_member_ids));
|
||||
|
||||
$sql_all_articles = "SELECT a.id, a.name, a.weight_grams, a.quantity_owned, a.product_designation, a.consumable, a.parent_article_id, a.image_url, c.name as category_name, m.name as manufacturer_name
|
||||
$sql_all_articles = "SELECT a.id, a.name, a.weight_grams, a.quantity_owned, a.product_designation, a.consumable, a.parent_article_id, a.image_url, c.name as category_name, m.name as manufacturer_name, sl.name as storage_location_name
|
||||
FROM articles a
|
||||
LEFT JOIN categories c ON a.category_id = c.id
|
||||
LEFT JOIN manufacturers m ON a.manufacturer_id = m.id
|
||||
LEFT JOIN storage_locations sl ON a.storage_location_id = sl.id
|
||||
WHERE a.user_id IN ($placeholders)";
|
||||
$all_params = $household_member_ids;
|
||||
$all_types = $types;
|
||||
@@ -150,7 +151,7 @@ $conn->close();
|
||||
/* Lager Grid */
|
||||
.lager-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 12px; padding: 5px;
|
||||
}
|
||||
.lager-card {
|
||||
@@ -160,7 +161,7 @@ $conn->close();
|
||||
}
|
||||
.lager-card:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0,0,0,0.05); }
|
||||
.lager-img-wrapper {
|
||||
height: 130px; margin-bottom: 8px; display: flex;
|
||||
height: 160px; margin-bottom: 8px; display: flex;
|
||||
align-items: center; justify-content: center;
|
||||
}
|
||||
.lager-img-wrapper img {
|
||||
@@ -222,8 +223,9 @@ $conn->close();
|
||||
<h5 class="mb-2"><i class="fas fa-boxes me-2"></i>Lagerbestand</h5>
|
||||
<div class="row g-2">
|
||||
<div class="col-12"><input type="text" id="filter-text" class="form-control form-control-sm" placeholder="Artikel suchen..."></div>
|
||||
<div class="col-6"><select id="filter-category" class="form-select form-select-sm"><option value="">-- Kategorien --</option><?php foreach($categories as $c) echo '<option>'.htmlspecialchars($c).'</option>'; ?></select></div>
|
||||
<div class="col-6"><select id="filter-manufacturer" class="form-select form-select-sm"><option value="">-- Hersteller --</option><?php foreach($manufacturers as $m) echo '<option>'.htmlspecialchars($m).'</option>'; ?></select></div>
|
||||
<div class="col-4"><select id="filter-category" class="form-select form-select-sm"><option value="">-- Kategorien --</option><?php foreach($categories as $c) echo '<option>'.htmlspecialchars($c).'</option>'; ?></select></div>
|
||||
<div class="col-4"><select id="filter-manufacturer" class="form-select form-select-sm"><option value="">-- Hersteller --</option><?php foreach($manufacturers as $m) echo '<option>'.htmlspecialchars($m).'</option>'; ?></select></div>
|
||||
<div class="col-4"><select id="group-by" class="form-select form-select-sm"><option value="">Keine Gruppe</option><option value="category_name">Kategorie</option><option value="manufacturer_name">Hersteller</option><option value="storage_location_name">Lagerort</option></select></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body pane-content" id="lager-container"></div>
|
||||
@@ -288,6 +290,7 @@ $conn->close();
|
||||
const tsOptions = { create: false, sortField: { field: "text", direction: "asc" }, allowEmptyOption: true, onChange: function() { this.input.dispatchEvent(new Event('change')); } };
|
||||
new TomSelect('#filter-category', tsOptions);
|
||||
new TomSelect('#filter-manufacturer', tsOptions);
|
||||
new TomSelect('#group-by', tsOptions);
|
||||
|
||||
const tooltip = document.getElementById('image-preview-tooltip');
|
||||
if (tooltip) {
|
||||
@@ -314,6 +317,7 @@ $conn->close();
|
||||
document.getElementById('filter-text').addEventListener(evt, renderLager);
|
||||
document.getElementById('filter-category').addEventListener(evt, renderLager);
|
||||
document.getElementById('filter-manufacturer').addEventListener(evt, renderLager);
|
||||
document.getElementById('group-by').addEventListener(evt, renderLager);
|
||||
});
|
||||
|
||||
document.getElementById('table-container').addEventListener('click', handlePackedItemActions);
|
||||
@@ -334,6 +338,7 @@ $conn->close();
|
||||
const filterText = document.getElementById('filter-text').value.toLowerCase();
|
||||
const filterCategory = document.getElementById('filter-category').value;
|
||||
const filterManufacturer = document.getElementById('filter-manufacturer').value;
|
||||
const groupBy = document.getElementById('group-by').value;
|
||||
|
||||
const tableQuantities = {};
|
||||
const backpackQuantities = {};
|
||||
@@ -349,7 +354,8 @@ $conn->close();
|
||||
}
|
||||
});
|
||||
|
||||
let html = '<div class="lager-grid">';
|
||||
let groups = {};
|
||||
|
||||
allArticles.forEach(article => {
|
||||
if (article.parent_article_id) return;
|
||||
|
||||
@@ -371,7 +377,7 @@ $conn->close();
|
||||
const imgUrl = article.image_url ? article.image_url : 'assets/images/keinbild.png';
|
||||
const plusBtnClass = qtyTable > 0 ? 'btn-success' : 'btn-outline-primary';
|
||||
|
||||
html += `
|
||||
const cardHtml = `
|
||||
<div class="lager-card">
|
||||
<div class="lager-img-wrapper">
|
||||
<img src="${imgUrl}" class="article-image-trigger" data-preview-url="${imgUrl}">
|
||||
@@ -386,9 +392,18 @@ $conn->close();
|
||||
${qtyTable} auf dem Tisch
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
let groupKey = groupBy ? (article[groupBy] || 'Ohne Zuordnung') : 'Alle Artikel';
|
||||
if (!groups[groupKey]) groups[groupKey] = '';
|
||||
groups[groupKey] += cardHtml;
|
||||
}
|
||||
});
|
||||
html += '</div>';
|
||||
|
||||
let html = '';
|
||||
Object.keys(groups).sort().forEach(key => {
|
||||
if (groupBy) html += `<h6 class="w-100 mt-3 mb-2 ps-2 border-bottom pb-1 text-secondary">${key}</h6>`;
|
||||
html += `<div class="lager-grid">${groups[key]}</div>`;
|
||||
});
|
||||
document.getElementById('lager-container').innerHTML = html;
|
||||
}
|
||||
|
||||
@@ -436,13 +451,22 @@ $conn->close();
|
||||
|
||||
function renderTable() {
|
||||
const container = document.getElementById('table-container');
|
||||
|
||||
Object.keys(sortableInstances).forEach(k => {
|
||||
if (k === 'table' || k.startsWith('table_nested_')) {
|
||||
if(sortableInstances[k]) {
|
||||
try { sortableInstances[k].destroy(); } catch(e){}
|
||||
}
|
||||
delete sortableInstances[k];
|
||||
}
|
||||
});
|
||||
|
||||
container.innerHTML = '';
|
||||
|
||||
const tableItems = packedItems.filter(item => item.carrier_user_id == null && !item.backpack_id && !item.backpack_compartment_id && !item.parent_packing_list_item_id);
|
||||
|
||||
renderRecursive(tableItems, container, packedItems);
|
||||
renderRecursive(tableItems, container, packedItems, 'table_');
|
||||
|
||||
if(sortableInstances['table']) sortableInstances['table'].destroy();
|
||||
sortableInstances['table'] = new Sortable(container, {
|
||||
group: 'nested',
|
||||
animation: 150,
|
||||
@@ -456,9 +480,17 @@ $conn->close();
|
||||
|
||||
function renderCarriersAndPackedItems() {
|
||||
const container = document.getElementById('carriers-container');
|
||||
container.innerHTML = '';
|
||||
|
||||
Object.keys(sortableInstances).forEach(k => { if(k !== 'table') sortableInstances[k].destroy(); });
|
||||
Object.keys(sortableInstances).forEach(k => {
|
||||
if (k.startsWith('carrier_')) {
|
||||
if(sortableInstances[k]) {
|
||||
try { sortableInstances[k].destroy(); } catch(e){}
|
||||
}
|
||||
delete sortableInstances[k];
|
||||
}
|
||||
});
|
||||
|
||||
container.innerHTML = '';
|
||||
|
||||
carriers.forEach(carrier => {
|
||||
const carrierId = carrier.id; // Sonstiges removed, so carrier.id is never null
|
||||
@@ -484,7 +516,7 @@ $conn->close();
|
||||
}
|
||||
});
|
||||
|
||||
renderRecursive(rootItems, carrierRootList, itemsById);
|
||||
renderRecursive(rootItems, carrierRootList, itemsById, 'carrier_');
|
||||
|
||||
sortableInstances['carrier_'+carrierId] = new Sortable(carrierRootList, {
|
||||
group: 'nested',
|
||||
@@ -498,7 +530,7 @@ $conn->close();
|
||||
});
|
||||
}
|
||||
|
||||
function renderRecursive(items, container, contextMap) {
|
||||
function renderRecursive(items, container, contextMap, prefix) {
|
||||
items.forEach(item => {
|
||||
let children = item.children || [];
|
||||
if (!item.children && Array.isArray(contextMap)) {
|
||||
@@ -509,10 +541,10 @@ $conn->close();
|
||||
container.appendChild(itemEl);
|
||||
const nestedContainer = itemEl.querySelector('.nested-sortable');
|
||||
if (children && children.length > 0) {
|
||||
renderRecursive(children, nestedContainer, contextMap);
|
||||
renderRecursive(children, nestedContainer, contextMap, prefix);
|
||||
}
|
||||
|
||||
sortableInstances['nested_'+item.id] = new Sortable(nestedContainer, {
|
||||
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(); }
|
||||
|
||||
Reference in New Issue
Block a user