Implement supermarket walking path (Laufwege & Regale), presets, and 1-tap shelf reassignment
This commit is contained in:
@@ -250,12 +250,103 @@
|
||||
background: rgba(30, 77, 59, 0.05);
|
||||
}
|
||||
|
||||
/* Laufweg Schrittnummer & Badges */
|
||||
.shop-cat-step-num {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--shop-green);
|
||||
color: #FFFFFF;
|
||||
font-family: var(--font-sans);
|
||||
font-size: 0.72rem;
|
||||
font-weight: 800;
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
padding: 0 4px;
|
||||
border-radius: 10px;
|
||||
margin-right: 4px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.shop-badge-shelf {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 3px;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
color: var(--shop-green);
|
||||
background: rgba(30, 77, 59, 0.08);
|
||||
border: 1px solid rgba(30, 77, 59, 0.2);
|
||||
border-radius: 4px;
|
||||
padding: 1px 6px;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
font-family: inherit;
|
||||
}
|
||||
.shop-badge-shelf:hover, .shop-badge-shelf:active {
|
||||
background: rgba(30, 77, 59, 0.18);
|
||||
border-color: var(--shop-green);
|
||||
}
|
||||
.shop-item-row.is-checked .shop-badge-shelf {
|
||||
opacity: 0.6;
|
||||
}
|
||||
.quick-shelf-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 9px 12px;
|
||||
background: #FFFFFF;
|
||||
border: 1.5px solid rgba(34, 34, 34, 0.15);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-size: 0.95rem;
|
||||
color: var(--shop-schwarz);
|
||||
transition: background-color 0.15s, border-color 0.15s;
|
||||
width: 100%;
|
||||
}
|
||||
.quick-shelf-btn:hover, .quick-shelf-btn:active {
|
||||
background: rgba(30, 77, 59, 0.08);
|
||||
border-color: var(--shop-green);
|
||||
}
|
||||
.quick-shelf-btn.is-current {
|
||||
background: rgba(30, 77, 59, 0.12);
|
||||
border-color: var(--shop-green);
|
||||
font-weight: 700;
|
||||
}
|
||||
.shop-preset-bar {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.shop-preset-btn {
|
||||
flex: 1 1 180px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 8px 12px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
border-radius: 6px;
|
||||
border: 1.5px solid var(--shop-green);
|
||||
background: #FFFFFF;
|
||||
color: var(--shop-green);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
font-family: inherit;
|
||||
}
|
||||
.shop-preset-btn:hover, .shop-preset-btn:active {
|
||||
background: var(--shop-green);
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
/* Badge nur für persönliche Notiz */
|
||||
.shop-item-badges {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.shop-badge-note {
|
||||
font-size: 0.74rem;
|
||||
@@ -725,6 +816,31 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- MODAL: SCHNELL-REGALWAHL (1-TAP) -->
|
||||
<!-- ========================================== -->
|
||||
<div id="modal-quick-shelf" class="cb-modal-backdrop no-print" onclick="onBackdropClick(event, 'modal-quick-shelf')">
|
||||
<div class="cb-modal-card" style="max-width: 440px;">
|
||||
<div class="cb-modal-header">
|
||||
<h3 class="cb-modal-title"><span>🛒</span> Regal / Bereich wählen</h3>
|
||||
<button type="button" class="cb-modal-close" onclick="closeModal('modal-quick-shelf')" title="Schließen">×</button>
|
||||
</div>
|
||||
<div class="cb-modal-body">
|
||||
<p id="quick-shelf-subtitle" style="font-size: 0.92rem; color: #555; margin: 0 0 12px 0;">
|
||||
Wohin gehört dieser Artikel im Supermarkt?
|
||||
</p>
|
||||
<div id="quick-shelf-options" style="display: flex; flex-direction: column; gap: 6px; max-height: 55vh; overflow-y: auto; padding-right: 2px;">
|
||||
<!-- Dynamisch befüllt -->
|
||||
</div>
|
||||
<div class="modal-btn-row" style="margin-top: 14px;">
|
||||
<button type="button" class="cb-btn cb-btn-secondary" onclick="closeModal('modal-quick-shelf')">
|
||||
Abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- MODAL: SUPERMARKT-BEREICHE & LAUFWEG -->
|
||||
<!-- ========================================== -->
|
||||
@@ -739,6 +855,21 @@
|
||||
Ordne die Bereiche mit <strong>▲</strong> und <strong>▼</strong> so an, wie du durch deinen Supermarkt gehst.
|
||||
</p>
|
||||
|
||||
<!-- Presets Bar -->
|
||||
<div style="margin-bottom: 14px; background: rgba(30, 77, 59, 0.05); border: 1.5px solid rgba(30, 77, 59, 0.15); border-radius: 8px; padding: 10px;">
|
||||
<div style="font-size: 0.85rem; font-weight: 700; color: var(--shop-green); margin-bottom: 8px;">
|
||||
Laufweg-Vorlage für deinen Markt:
|
||||
</div>
|
||||
<div class="shop-preset-bar">
|
||||
<button type="button" class="shop-preset-btn" onclick="applyCategoryPreset('standard')" title="Obst/Gemüse ➔ Backwaren ➔ Vorrat ➔ Gewürze ➔ Kühlung ➔ Fleisch ➔ Tiefkühl ➔ Getränke ➔ Drogerie">
|
||||
🛒 Supermarkt (Edeka / Rewe)
|
||||
</button>
|
||||
<button type="button" class="shop-preset-btn" onclick="applyCategoryPreset('discounter')" title="Obst/Gemüse ➔ Backwaren ➔ Kühlwand ➔ Fleisch ➔ Vorrat ➔ Tiefkühl ➔ Getränke ➔ Drogerie">
|
||||
🏬 Discounter (Aldi / Lidl)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ul id="cat-manage-list" style="list-style: none; margin: 0 0 16px 0; padding: 0; display: flex; flex-direction: column; gap: 8px;"></ul>
|
||||
|
||||
<div style="background: var(--shop-yellow); border: 1.5px solid var(--shop-schwarz); border-radius: 8px; padding: 12px;">
|
||||
@@ -746,7 +877,24 @@
|
||||
<form onsubmit="onAddCategory(event)" style="display: flex; flex-direction: column; gap: 8px;">
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<input type="text" id="new-cat-icon" style="width: 44px; text-align: center; font-size: 1.2rem; border: 1.5px solid #888; border-radius: 6px;" value="🛒" title="Symbol / Emoji">
|
||||
<input type="text" id="new-cat-name" placeholder="z. B. Drogerie & Haushalt" class="cb-input" style="flex: 1; padding: 8px 12px;" required>
|
||||
<input type="text" id="new-cat-name" placeholder="z. B. Gang 3: Nudeln & Soßen" class="cb-input" style="flex: 1; padding: 8px 12px;" required>
|
||||
</div>
|
||||
<div>
|
||||
<label for="new-cat-shelf-type" style="display: block; font-size: 0.78rem; font-weight: 700; color: #444; margin-bottom: 3px;">
|
||||
Waren-Verhalten (für automatische Zutatenerkennung):
|
||||
</label>
|
||||
<select id="new-cat-shelf-type" class="cb-select" style="padding: 6px 10px; font-size: 0.88rem; width: 100%;">
|
||||
<option value="produce">🥦 Obst, Gemüse & Kräuter</option>
|
||||
<option value="bakery">🍞 Brot & Backwaren</option>
|
||||
<option value="pantry" selected>🥫 Vorrat, Nudeln, Reis & Konserven</option>
|
||||
<option value="spices">🧂 Gewürze, Öle & Essig</option>
|
||||
<option value="dairy">🧀 Kühlregal, Milchprodukte & Käse</option>
|
||||
<option value="meat">🥩 Fleisch, Fisch & Wurst</option>
|
||||
<option value="frozen">❄️ Tiefkühl</option>
|
||||
<option value="drinks">🧃 Getränke</option>
|
||||
<option value="drugstore">🧺 Drogerie & Haushalt</option>
|
||||
<option value="other">🛒 Sonstiges</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="cb-btn cb-btn-green" style="width: 100%; justify-content: center; padding: 9px 14px; font-weight: 700;">+ Bereich hinzufügen</button>
|
||||
</form>
|
||||
@@ -754,7 +902,7 @@
|
||||
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 14px;">
|
||||
<button type="button" onclick="resetDefaultCategories()" style="background: none; border: none; font-size: 0.82rem; color: #777; cursor: pointer; text-decoration: underline;">
|
||||
↺ Standard-Bereiche wiederherstellen
|
||||
↺ Standard zurücksetzen
|
||||
</button>
|
||||
<button type="button" class="cb-btn cb-btn-secondary" onclick="closeModal('modal-categories')">
|
||||
Fertig
|
||||
@@ -781,12 +929,15 @@
|
||||
// Server-Daten & Offline-Cache einbinden
|
||||
let serverItems = [];
|
||||
let serverCats = [];
|
||||
let allShelfTypes = [];
|
||||
try {
|
||||
serverItems = {{ items_json | safe }};
|
||||
serverCats = {{ categories_json | safe }};
|
||||
allShelfTypes = {{ shelf_types_json | safe }};
|
||||
} catch (e) {
|
||||
serverItems = [];
|
||||
serverCats = [];
|
||||
allShelfTypes = [];
|
||||
}
|
||||
|
||||
const cachedQueue = JSON.parse(localStorage.getItem('wtc_shop_offline_queue') || '[]');
|
||||
@@ -1135,21 +1286,24 @@
|
||||
});
|
||||
|
||||
let html = '';
|
||||
groupNames.forEach(catName => {
|
||||
groupNames.forEach((catName, groupIdx) => {
|
||||
const group = groups[catName];
|
||||
group.open.sort((a, b) => (a.item_name || '').localeCompare(b.item_name || '', 'de', { sensitivity: 'base' }));
|
||||
group.checked.sort((a, b) => (a.item_name || '').localeCompare(b.item_name || '', 'de', { sensitivity: 'base' }));
|
||||
const allInGroup = hideChecked ? group.open : [...group.open, ...group.checked];
|
||||
if (allInGroup.length === 0) return; // Kategorie komplett ausblenden, wenn keine Einträge!
|
||||
|
||||
const catObj = allCategories.find(c => c.name === catName) || { name: catName, icon: '🛒' };
|
||||
const catObj = allCategories.find(c => c.name === catName) || { name: catName, icon: '🛒', sort_order: groupIdx + 1 };
|
||||
const stepNum = catObj.sort_order ? `${catObj.sort_order}.` : `${groupIdx + 1}.`;
|
||||
const countBadge = hideChecked ? `${group.open.length} offen` : `${group.open.length} offen / ${allInGroup.length}`;
|
||||
|
||||
html += `
|
||||
<section class="shop-cat-section">
|
||||
<div class="shop-cat-header">
|
||||
<h2 class="shop-cat-title">
|
||||
<span>${catObj.icon || '🛒'}</span> <span>${escapeHtml(catName)}</span>
|
||||
<span class="shop-cat-step-num">${stepNum}</span>
|
||||
<span>${catObj.icon || '🛒'}</span>
|
||||
<span>${escapeHtml(catName)}</span>
|
||||
</h2>
|
||||
<span class="shop-cat-count">${countBadge}</span>
|
||||
</div>
|
||||
@@ -1166,6 +1320,9 @@
|
||||
function renderItemRow(item) {
|
||||
const isChecked = item.checked ? 'is-checked' : '';
|
||||
const amountStr = formatAmount(item.amount, item.unit);
|
||||
const catObj = allCategories.find(c => c.name === item.category);
|
||||
const catIcon = catObj ? (catObj.icon || '🛒') : '🛒';
|
||||
const catName = item.category || 'Sonstiges';
|
||||
|
||||
return `
|
||||
<li class="shop-item-row ${isChecked}" id="shop-row-${item.id}">
|
||||
@@ -1180,11 +1337,14 @@
|
||||
${amountStr ? `<span class="shop-item-amount">${escapeHtml(amountStr)}</span>` : ''}
|
||||
</div>
|
||||
|
||||
${item.note ? `
|
||||
<div class="shop-item-badges">
|
||||
<div class="shop-item-badges">
|
||||
<button type="button" class="shop-badge-shelf" onclick="openQuickShelfPicker(${item.id}, event)" title="Regal / Bereich schnell wechseln (lernt mit)">
|
||||
<span>${catIcon}</span> <span>${escapeHtml(catName)}</span> <span>▾</span>
|
||||
</button>
|
||||
${item.note ? `
|
||||
<span class="shop-badge-note">📝 ${escapeHtml(item.note)}</span>
|
||||
</div>
|
||||
` : ''}
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="button" class="shop-item-delete" onclick="deleteItemDirectly(${item.id}, event)" title="Eintrag löschen">
|
||||
@@ -1626,6 +1786,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// ==========================================
|
||||
// MODAL: SUPERMARKT-BEREICHE VERWALTUNG
|
||||
// ==========================================
|
||||
@@ -1638,19 +1799,29 @@
|
||||
const ul = document.getElementById('cat-manage-list');
|
||||
if (!ul) return;
|
||||
|
||||
ul.innerHTML = allCategories.map((cat, idx) => `
|
||||
<li style="display: flex; align-items: center; justify-content: space-between; padding: 7px 12px; background: #FFFFFF; border: 1.5px solid rgba(34,34,34,0.15); border-radius: 8px;">
|
||||
<div style="display: flex; align-items: center; gap: 8px; font-weight: 600;">
|
||||
<span style="font-size: 1.25rem;">${cat.icon || '🛒'}</span>
|
||||
<span>${escapeHtml(cat.name)}</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 4px;">
|
||||
<button type="button" class="cb-btn cb-btn-secondary" style="padding: 2px 7px; font-size: 0.85rem;" onclick="moveCategory(${cat.id}, -1)" ${idx === 0 ? 'disabled style="opacity:0.3;"' : ''}>▲</button>
|
||||
<button type="button" class="cb-btn cb-btn-secondary" style="padding: 2px 7px; font-size: 0.85rem;" onclick="moveCategory(${cat.id}, 1)" ${idx === allCategories.length - 1 ? 'disabled style="opacity:0.3;"' : ''}>▼</button>
|
||||
<button type="button" class="cb-btn cb-btn-danger" style="padding: 2px 7px; font-size: 0.85rem; background:#b02a37; border-color:#b02a37; color:#fff;" onclick="deleteCategory(${cat.id})">✕</button>
|
||||
</div>
|
||||
</li>
|
||||
`).join('');
|
||||
ul.innerHTML = allCategories.map((cat, idx) => {
|
||||
const stepNum = cat.sort_order || (idx + 1);
|
||||
const shelfObj = (allShelfTypes || []).find(st => st.type === cat.shelf_type);
|
||||
const shelfLabel = shelfObj ? shelfObj.label : (cat.shelf_type || 'Sonstiges');
|
||||
|
||||
return `
|
||||
<li style="display: flex; align-items: center; justify-content: space-between; padding: 7px 12px; background: #FFFFFF; border: 1.5px solid rgba(34,34,34,0.15); border-radius: 8px; gap: 8px;">
|
||||
<div style="display: flex; align-items: center; gap: 8px; font-weight: 600; min-width: 0; flex: 1;">
|
||||
<span class="shop-cat-step-num">${stepNum}.</span>
|
||||
<span style="font-size: 1.25rem;">${cat.icon || '🛒'}</span>
|
||||
<div style="min-width: 0; display: flex; flex-direction: column;">
|
||||
<span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">${escapeHtml(cat.name)}</span>
|
||||
<span style="font-size: 0.72rem; color: #777; font-weight: 500;">Typ: ${escapeHtml(shelfLabel)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 4px; flex-shrink: 0;">
|
||||
<button type="button" class="cb-btn cb-btn-secondary" style="padding: 2px 7px; font-size: 0.85rem;" onclick="moveCategory(${cat.id}, -1)" ${idx === 0 ? 'disabled style="opacity:0.3;"' : ''}>▲</button>
|
||||
<button type="button" class="cb-btn cb-btn-secondary" style="padding: 2px 7px; font-size: 0.85rem;" onclick="moveCategory(${cat.id}, 1)" ${idx === allCategories.length - 1 ? 'disabled style="opacity:0.3;"' : ''}>▼</button>
|
||||
<button type="button" class="cb-btn cb-btn-danger" style="padding: 2px 7px; font-size: 0.85rem; background:#b02a37; border-color:#b02a37; color:#fff;" onclick="deleteCategory(${cat.id})">✕</button>
|
||||
</div>
|
||||
</li>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
async function moveCategory(catId, dir) {
|
||||
@@ -1661,6 +1832,7 @@
|
||||
|
||||
const [removed] = allCategories.splice(idx, 1);
|
||||
allCategories.splice(targetIdx, 0, removed);
|
||||
allCategories.forEach((c, i) => c.sort_order = i + 1);
|
||||
renderManageCategoriesList();
|
||||
renderCategoryFilterChips();
|
||||
renderShoppingList();
|
||||
@@ -1679,8 +1851,10 @@
|
||||
e.preventDefault();
|
||||
const iconInput = document.getElementById('new-cat-icon');
|
||||
const nameInput = document.getElementById('new-cat-name');
|
||||
const shelfSelect = document.getElementById('new-cat-shelf-type');
|
||||
const name = nameInput.value.trim();
|
||||
const icon = iconInput.value.trim() || '🛒';
|
||||
const shelfType = shelfSelect ? shelfSelect.value : 'other';
|
||||
if (!name) return;
|
||||
|
||||
nameInput.value = '';
|
||||
@@ -1689,7 +1863,12 @@
|
||||
const res = await fetch('/api/shopping-categories', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: name, icon: icon, sort_order: allCategories.length + 1 })
|
||||
body: JSON.stringify({
|
||||
name: name,
|
||||
icon: icon,
|
||||
shelf_type: shelfType,
|
||||
sort_order: allCategories.length + 1
|
||||
})
|
||||
});
|
||||
if (res.ok) {
|
||||
const d = await res.json();
|
||||
@@ -1697,6 +1876,7 @@
|
||||
localStorage.setItem('wtc_shop_cats_cache', JSON.stringify(allCategories));
|
||||
renderManageCategoriesList();
|
||||
renderCategoryFilterChips();
|
||||
renderShoppingList();
|
||||
showToast(`Bereich "${name}" angelegt`);
|
||||
}
|
||||
} catch (e) {}
|
||||
@@ -1718,20 +1898,106 @@
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
async function resetDefaultCategories() {
|
||||
if (!confirm('Möchtest du wirklich alle Supermarkt-Bereiche auf den Standard-Laufweg zurücksetzen?')) return;
|
||||
async function applyCategoryPreset(presetName) {
|
||||
const label = presetName === 'discounter' ? 'Discounter (Aldi/Lidl)' : 'Supermarkt (Edeka/Rewe)';
|
||||
if (!confirm(`Möchtest du den Laufweg auf "${label}" umstellen?\nAlle Artikel deiner Liste werden automatisch den neuen Stationen zugeordnet.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/shopping-categories/reset-defaults', { method: 'POST' });
|
||||
const res = await fetch(`/api/shopping-categories/reset-defaults?preset=${encodeURIComponent(presetName)}`, {
|
||||
method: 'POST'
|
||||
});
|
||||
if (res.ok) {
|
||||
const d = await res.json();
|
||||
allCategories = d.categories || [];
|
||||
localStorage.setItem('wtc_shop_cats_cache', JSON.stringify(allCategories));
|
||||
await refreshDataFromServer();
|
||||
renderManageCategoriesList();
|
||||
renderCategoryFilterChips();
|
||||
renderShoppingList();
|
||||
showToast('Standard-Bereiche wiederhergestellt');
|
||||
showToast(`✓ Laufweg "${label}" aktiviert`);
|
||||
}
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
console.error("Fehler beim Aktivieren der Vorlage:", e);
|
||||
showToast('Fehler beim Aktivieren der Vorlage');
|
||||
}
|
||||
}
|
||||
|
||||
async function resetDefaultCategories() {
|
||||
applyCategoryPreset('standard');
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// SCHNELL-REGALWAHL (1-TAP PRO ARTIKEL)
|
||||
// ==========================================
|
||||
let quickShelfTargetItemId = null;
|
||||
|
||||
function openQuickShelfPicker(itemId, event) {
|
||||
if (event) event.stopPropagation();
|
||||
const item = allItems.find(it => it.id === itemId);
|
||||
if (!item) return;
|
||||
|
||||
quickShelfTargetItemId = itemId;
|
||||
const subTitle = document.getElementById('quick-shelf-subtitle');
|
||||
if (subTitle) {
|
||||
subTitle.innerHTML = `Regal für <strong>${escapeHtml(item.item_name)}</strong> wählen:`;
|
||||
}
|
||||
|
||||
const container = document.getElementById('quick-shelf-options');
|
||||
if (container) {
|
||||
container.innerHTML = allCategories.map((cat, idx) => {
|
||||
const isCurrent = (item.category || 'Sonstiges') === cat.name;
|
||||
const stepNum = cat.sort_order || (idx + 1);
|
||||
return `
|
||||
<button type="button" class="quick-shelf-btn ${isCurrent ? 'is-current' : ''}" onclick="applyQuickShelfChange('${escapeAttr(cat.name)}')">
|
||||
<span class="shop-cat-step-num">${stepNum}.</span>
|
||||
<span style="font-size: 1.25rem;">${cat.icon || '🛒'}</span>
|
||||
<span style="flex: 1; text-align: left; font-weight: 600;">${escapeHtml(cat.name)}</span>
|
||||
${isCurrent ? '<span style="font-size: 0.8rem; color: var(--shop-green); font-weight: 700;">✓ Hier</span>' : ''}
|
||||
</button>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
openModal('modal-quick-shelf');
|
||||
}
|
||||
|
||||
async function applyQuickShelfChange(newCategory) {
|
||||
if (!quickShelfTargetItemId) return;
|
||||
const itemId = quickShelfTargetItemId;
|
||||
const item = allItems.find(it => it.id === itemId);
|
||||
closeModal('modal-quick-shelf');
|
||||
|
||||
if (item && item.category === newCategory) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Optimistisches Update
|
||||
if (item) {
|
||||
item.category = newCategory;
|
||||
saveLocalCache(allItems);
|
||||
renderCategoryFilterChips();
|
||||
renderShoppingList();
|
||||
}
|
||||
|
||||
showToast(`✓ Zu "${newCategory}" verschoben & gelernt!`);
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/shopping-list/${itemId}/category`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ category: newCategory })
|
||||
});
|
||||
if (!res.ok) throw new Error('Fehler beim Zuweisen der Kategorie');
|
||||
} catch (err) {
|
||||
console.warn("Offline oder Fehler bei Kategorieänderung:", err);
|
||||
pushOfflineQueue({
|
||||
method: 'POST',
|
||||
url: `/api/shopping-list/${itemId}/category`,
|
||||
body: { category: newCategory }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
@@ -1764,9 +2030,10 @@
|
||||
if (!sel) return;
|
||||
|
||||
let html = selectId === 'add-item-category' ? '<option value="">🪄 Automatisch erkennen</option>' : '';
|
||||
allCategories.forEach(cat => {
|
||||
allCategories.forEach((cat, idx) => {
|
||||
const isSel = (cat.name === selectedValue) ? 'selected' : '';
|
||||
html += `<option value="${escapeAttr(cat.name)}" ${isSel}>${cat.icon || '🛒'} ${escapeHtml(cat.name)}</option>`;
|
||||
const stepNum = cat.sort_order || (idx + 1);
|
||||
html += `<option value="${escapeAttr(cat.name)}" ${isSel}>${stepNum}. ${cat.icon || '🛒'} ${escapeHtml(cat.name)}</option>`;
|
||||
});
|
||||
|
||||
if (!allCategories.some(c => c.name === 'Sonstiges')) {
|
||||
|
||||
Reference in New Issue
Block a user