diff --git a/app/main.py b/app/main.py index 09c60e6..31aea58 100755 --- a/app/main.py +++ b/app/main.py @@ -1181,8 +1181,9 @@ async def api_add_shopping_item(payload: ShoppingItemCreate): category=payload.category, note=payload.note or "" ) - await shopping_ws_manager.broadcast({"action": "items_updated", "items": get_shopping_list()}) - return {"status": "ok", "item": item} + current_items = get_shopping_list() + 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}") diff --git a/app/templates/shopping_list.html b/app/templates/shopping_list.html index c7b7090..1b7c009 100644 --- a/app/templates/shopping_list.html +++ b/app/templates/shopping_list.html @@ -955,10 +955,25 @@ 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) { - allItems = items; + allItems = deduplicateItems(items); try { - localStorage.setItem('wtc_shop_items_cache', JSON.stringify(items)); + localStorage.setItem('wtc_shop_items_cache', JSON.stringify(allItems)); } catch (e) { console.warn('LocalStorage voll oder nicht verfügbar:', e); } @@ -1227,8 +1242,8 @@ const emptyState = document.getElementById('empty-state'); if (!container) return; - // Filter anwenden - let filtered = allItems; + // Filter anwenden (dedupliziert gegen parallele WebSocket- & Fetch-Ereignisse) + let filtered = deduplicateItems(allItems); if (currentFilterCategory !== null) { filtered = filtered.filter(it => (it.category || 'Sonstiges') === currentFilterCategory); } @@ -1459,12 +1474,19 @@ }); if (res.ok) { const data = await res.json(); - if (data.item) { - allItems.push(data.item); - saveLocalCache(allItems); - renderShoppingList(); - renderCategoryFilterChips(); + if (data.items && Array.isArray(data.items)) { + allItems = data.items; + } else if (data.item) { + const existingIdx = allItems.findIndex(it => it.id === data.item.id); + if (existingIdx >= 0) { + allItems[existingIdx] = data.item; + } else { + allItems.push(data.item); + } } + saveLocalCache(allItems); + renderShoppingList(); + renderCategoryFilterChips(); } else { throw new Error('Server-Fehler'); } @@ -1480,7 +1502,12 @@ checked: false, 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 }); saveLocalCache(allItems); renderShoppingList();