Fix duplicate item display immediately after adding to shopping list

This commit is contained in:
2026-10-23 15:08:42 +02:00
parent e3bf88f018
commit a9f8e83202
2 changed files with 40 additions and 12 deletions
+3 -2
View File
@@ -1181,8 +1181,9 @@ async def api_add_shopping_item(payload: ShoppingItemCreate):
category=payload.category, category=payload.category,
note=payload.note or "" note=payload.note or ""
) )
await shopping_ws_manager.broadcast({"action": "items_updated", "items": get_shopping_list()}) current_items = get_shopping_list()
return {"status": "ok", "item": item} await shopping_ws_manager.broadcast({"action": "items_updated", "items": current_items})
return {"status": "ok", "item": item, "items": current_items}
@app.put("/api/shopping-list/{item_id}") @app.put("/api/shopping-list/{item_id}")
+37 -10
View File
@@ -955,10 +955,25 @@
localStorage.setItem('wtc_shop_cats_cache', JSON.stringify(allCategories)); localStorage.setItem('wtc_shop_cats_cache', JSON.stringify(allCategories));
} }
function deduplicateItems(items) {
if (!Array.isArray(items)) return [];
const seenIds = new Set();
const unique = [];
for (const item of items) {
if (!item) continue;
const key = item.id ? `id_${item.id}` : `name_${(item.item_name || '').toLowerCase().trim()}_${item.checked ? '1' : '0'}`;
if (!seenIds.has(key)) {
seenIds.add(key);
unique.push(item);
}
}
return unique;
}
function saveLocalCache(items) { function saveLocalCache(items) {
allItems = items; allItems = deduplicateItems(items);
try { try {
localStorage.setItem('wtc_shop_items_cache', JSON.stringify(items)); localStorage.setItem('wtc_shop_items_cache', JSON.stringify(allItems));
} catch (e) { } catch (e) {
console.warn('LocalStorage voll oder nicht verfügbar:', e); console.warn('LocalStorage voll oder nicht verfügbar:', e);
} }
@@ -1227,8 +1242,8 @@
const emptyState = document.getElementById('empty-state'); const emptyState = document.getElementById('empty-state');
if (!container) return; if (!container) return;
// Filter anwenden // Filter anwenden (dedupliziert gegen parallele WebSocket- & Fetch-Ereignisse)
let filtered = allItems; let filtered = deduplicateItems(allItems);
if (currentFilterCategory !== null) { if (currentFilterCategory !== null) {
filtered = filtered.filter(it => (it.category || 'Sonstiges') === currentFilterCategory); filtered = filtered.filter(it => (it.category || 'Sonstiges') === currentFilterCategory);
} }
@@ -1459,12 +1474,19 @@
}); });
if (res.ok) { if (res.ok) {
const data = await res.json(); const data = await res.json();
if (data.item) { if (data.items && Array.isArray(data.items)) {
allItems.push(data.item); allItems = data.items;
saveLocalCache(allItems); } else if (data.item) {
renderShoppingList(); const existingIdx = allItems.findIndex(it => it.id === data.item.id);
renderCategoryFilterChips(); if (existingIdx >= 0) {
allItems[existingIdx] = data.item;
} else {
allItems.push(data.item);
}
} }
saveLocalCache(allItems);
renderShoppingList();
renderCategoryFilterChips();
} else { } else {
throw new Error('Server-Fehler'); throw new Error('Server-Fehler');
} }
@@ -1480,7 +1502,12 @@
checked: false, checked: false,
source: '' source: ''
}; };
allItems.push(tempItem); const existingIdx = allItems.findIndex(it => (it.item_name || '').toLowerCase().trim() === name.toLowerCase().trim() && !it.checked);
if (existingIdx >= 0) {
allItems[existingIdx] = tempItem;
} else {
allItems.push(tempItem);
}
pushOfflineQueue({ method: 'POST', url: '/api/shopping-list', body: payload }); pushOfflineQueue({ method: 'POST', url: '/api/shopping-list', body: payload });
saveLocalCache(allItems); saveLocalCache(allItems);
renderShoppingList(); renderShoppingList();