Redirect login to home, reorder cookbook menu, fix sticky header+filter in shopping list, update modal unit row and clean item view

This commit is contained in:
2026-10-15 21:40:40 +02:00
parent d59629cc6e
commit dd875d40ef
4 changed files with 121 additions and 268 deletions
+4 -4
View File
@@ -248,10 +248,10 @@ def get_service_worker():
# ==========================================
@app.get("/login", response_class=HTMLResponse)
async def login_get(request: Request, next: str = "/cookbook"):
async def login_get(request: Request, next: str = "/"):
token = request.cookies.get(COOKIE_NAME)
if verify_auth_token(token):
return RedirectResponse(url=next or "/cookbook", status_code=303)
return RedirectResponse(url=next or "/", status_code=303)
return templates.TemplateResponse(
request=request,
name="login.html",
@@ -264,7 +264,7 @@ async def login_post(
username: str = Form("admin"),
password: str = Form(...),
remember: Optional[str] = Form(None),
next: str = Form("/cookbook")
next: str = Form("/")
):
valid_user = (username.strip().lower() == APP_USERNAME.lower()) or (username.strip() == "admin")
valid_pass = (password == APP_PASSWORD)
@@ -272,7 +272,7 @@ async def login_post(
if valid_user and valid_pass:
days = 30 if remember else 1
token = create_auth_token(username.strip(), days=days)
target = next if next and not next.startswith("/login") else "/cookbook"
target = next if next and not next.startswith("/login") else "/"
resp = RedirectResponse(url=target, status_code=303)
max_age = days * 86400
resp.set_cookie(
+13 -12
View File
@@ -494,12 +494,6 @@
<!-- Desktop Buttons (auf Bildschirmen > 768px) -->
<div class="cb-nav-actions desktop-nav-actions">
<a href="/" class="cb-nav-btn" title="Menü-Übersicht öffnen">
<span>🏠</span> <span>Menü</span>
</a>
<a href="/shopping-list" class="cb-nav-btn" title="Einkaufsliste öffnen">
<span>🛒</span> <span>Einkaufsliste</span>
</a>
<button type="button" onclick="openMealPlanModal(event)" class="cb-nav-btn" title="Wochenmenü-Plan öffnen">
<span>📅</span> <span>Wochenplan</span>
</button>
@@ -512,6 +506,12 @@
<button type="button" onclick="openPrintModal(event)" class="cb-nav-btn" title="Druckansicht & PDF">
<span>🖨️</span> <span>Drucken</span>
</button>
<a href="/" class="cb-nav-btn" title="Zur Startseite">
<span>🏠</span> <span>Startseite</span>
</a>
<a href="/shopping-list" class="cb-nav-btn" title="Einkaufsliste öffnen">
<span>🛒</span> <span>Einkaufsliste</span>
</a>
<a href="/logout" class="cb-nav-btn cb-nav-btn-logout" title="Abmelden">
<span>🚪</span> <span>Abmelden</span>
</a>
@@ -523,12 +523,6 @@
<span>⋮</span>
</button>
<div id="cb-mobile-menu" class="cb-mobile-dropdown">
<a href="/" class="cb-mobile-item">
<span>🏠</span> <span>Menü-Übersicht</span>
</a>
<a href="/shopping-list" class="cb-mobile-item">
<span>🛒</span> <span>Einkaufsliste</span>
</a>
<button type="button" onclick="handleMobileMenuAction(openMealPlanModal)" class="cb-mobile-item">
<span>📅</span> <span>Wochenmenü-Plan</span>
</button>
@@ -542,6 +536,13 @@
<span>🖨️</span> <span>Drucken & PDF</span>
</button>
<hr class="cb-mobile-divider">
<a href="/" class="cb-mobile-item">
<span>🏠</span> <span>Startseite</span>
</a>
<a href="/shopping-list" class="cb-mobile-item">
<span>🛒</span> <span>Einkaufsliste</span>
</a>
<hr class="cb-mobile-divider">
<a href="/logout" class="cb-mobile-item cb-mobile-logout">
<span>🚪</span> <span>Abmelden</span>
</a>
+1 -1
View File
@@ -159,7 +159,7 @@
{% endif %}
<form method="POST" action="/login" class="login-form">
<input type="hidden" name="next" value="{{ next_url or '/cookbook' }}">
<input type="hidden" name="next" value="{{ next_url or '/' }}">
<div class="form-group">
<label class="form-label" for="username">Benutzername</label>
+103 -251
View File
@@ -10,7 +10,7 @@
<meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="What To Cook?">
<link rel="apple-touch-icon" href="/static/icon-192.png">
<link rel="stylesheet" href="/static/cookbook.css?v=4.6.3">
<link rel="stylesheet" href="/static/cookbook.css?v=4.6.4">
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🛒</text></svg>">
<style>
:root {
@@ -36,12 +36,18 @@
background-size: 20px 20px;
font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
color: var(--shop-schwarz);
display: flex;
flex-direction: column;
overflow-x: hidden;
width: 100%;
}
/* Fester Container für Header + Filterleiste (kleben gemeinsam oben) */
.cb-sticky-header-container {
position: sticky;
top: 0;
z-index: 1000;
width: 100%;
background: var(--shop-green);
}
/* Live Online Badge in Navbar */
.cb-online-pill {
font-size: 0.72rem;
@@ -65,7 +71,6 @@
/* Hauptinhalt Container */
.shop-main-container {
flex: 1 0 auto;
max-width: 820px;
width: 100%;
margin: 0 auto;
@@ -212,7 +217,7 @@
background: rgba(30, 77, 59, 0.05);
}
/* Badges: Notiz & Rezept-Quelle */
/* Badge nur noch für Notiz (Quelle wurde entfernt) */
.shop-item-badges {
display: flex;
align-items: center;
@@ -230,17 +235,6 @@
align-items: center;
gap: 3px;
}
.shop-badge-source {
font-size: 0.75rem;
color: #555;
background: rgba(0, 0, 0, 0.05);
border: 1px solid rgba(0, 0, 0, 0.1);
padding: 1px 6px;
border-radius: 4px;
display: inline-flex;
align-items: center;
gap: 3px;
}
/* Löschen-Icon am rechten Rand */
.shop-item-delete {
@@ -328,11 +322,6 @@
flex-direction: column;
gap: 14px;
}
.modal-form-row-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.modal-btn-row {
display: flex;
justify-content: flex-end;
@@ -374,95 +363,83 @@
</head>
<body class="shopping-app-body">
<!-- ========================================== -->
<!-- 100% IDENTISCHE NAVIGATION ZUM KOCHBUCH -->
<!-- ========================================== -->
<header class="cb-nav">
<a href="/shopping-list" class="cb-nav-brand" title="Einkaufsliste neu laden">
<span class="cb-brand-icon">🛒</span>
<h1 class="cb-nav-title">Einkaufsliste</h1>
</a>
<span id="shop-online-pill" class="cb-online-pill" title="Synchronisations-Status">● Live</span>
<!-- Suchfeld (automatisch angepasst, überläuft mobil nicht) -->
<div class="cb-search-box no-print">
<span class="cb-search-icon">🔍</span>
<input type="search" id="shop-search-input" class="cb-search-input" placeholder="Artikel filtern..." oninput="onSearchInput(this.value)" autocomplete="off">
<button type="button" id="shop-search-clear" class="cb-search-clear" onclick="clearSearch()" style="display: none;" title="Suche leeren">✕</button>
</div>
<!-- Desktop Buttons (auf Bildschirmen > 768px) -->
<div class="cb-nav-actions desktop-nav-actions">
<a href="/" class="cb-nav-btn" title="Startmenü">
<span>🏠</span> <span>Menü</span>
<!-- ============================================================== -->
<!-- FIXIERTER KOPFBEREICH: HEADER & KATEGORIE-FILTERLEISTE GEMEINSAM -->
<!-- ============================================================== -->
<div class="cb-sticky-header-container">
<header class="cb-nav">
<a href="/shopping-list" class="cb-nav-brand" title="Einkaufsliste neu laden">
<span class="cb-brand-icon">🛒</span>
<h1 class="cb-nav-title">Einkaufsliste</h1>
</a>
<a href="/cookbook" class="cb-nav-btn" title="Kochbuch & Rezepte">
<span>📖</span> <span>Kochbuch</span>
</a>
<button type="button" onclick="openMealPlanImportModal()" class="cb-nav-btn" title="Zutaten aus aktuellem Wochenplan holen">
<span>📅</span> <span>Wochenplan</span>
</button>
<button type="button" onclick="toggleSortMode()" class="cb-nav-btn" id="desktop-sort-btn" title="Sortierung umschalten">
<span id="desktop-sort-icon">⇅</span> <span id="desktop-sort-label">Kategorien</span>
</button>
<button type="button" onclick="openCategoriesModal()" class="cb-nav-btn" title="Supermarkt-Bereiche & Laufweg anpassen">
<span>🏬</span> <span>Bereiche</span>
</button>
<button type="button" onclick="openDeleteStep1Modal()" class="cb-nav-btn cb-btn-danger" style="background:#b02a37; border-color:#b02a37; color:#fff;" title="Liste leeren">
<span>🗑️</span> <span>Leeren</span>
</button>
<a href="/logout" class="cb-nav-btn cb-nav-btn-logout" title="Abmelden">
<span>🚪</span> <span>Abmelden</span>
</a>
</div>
<span id="shop-online-pill" class="cb-online-pill" title="Synchronisations-Status">● Live</span>
<!-- Mobile 3-Punkte (Kebab) Menü (auf Bildschirmen <= 768px) -->
<div class="mobile-nav-wrapper">
<button type="button" class="cb-kebab-btn" onclick="toggleMobileMenu(event)" aria-label="Menü öffnen" title="Menü">
<span>⋮</span>
</button>
<div id="cb-mobile-menu" class="cb-mobile-dropdown">
<a href="/" class="cb-mobile-item">
<span>🏠</span> <span>Startmenü</span>
</a>
<a href="/cookbook" class="cb-mobile-item">
<span>📖</span> <span>Zum Kochbuch</span>
</a>
<button type="button" onclick="handleMobileMenuAction(openMealPlanImportModal)" class="cb-mobile-item">
<span>📅</span> <span>Aus Wochenplan holen</span>
</button>
<button type="button" onclick="handleMobileMenuAction(toggleSortMode)" class="cb-mobile-item">
<span id="mobile-sort-icon">⇅</span> <span id="mobile-sort-label">Sortierung: Laufweg</span>
</button>
<button type="button" onclick="handleMobileMenuAction(toggleCategoryFilterBar)" class="cb-mobile-item">
<span>🏷️</span> <span id="mobile-filter-label">Kategorien filtern</span>
</button>
<button type="button" onclick="handleMobileMenuAction(openCategoriesModal)" class="cb-mobile-item">
<!-- Desktop Buttons (auf Bildschirmen > 768px) -->
<div class="cb-nav-actions desktop-nav-actions">
<button type="button" onclick="openCategoriesModal()" class="cb-nav-btn" title="Supermarkt-Bereiche & Laufweg anpassen">
<span>🏬</span> <span>Bereiche anpassen</span>
</button>
<button type="button" onclick="handleMobileMenuAction(deleteCheckedItems)" class="cb-mobile-item">
<button type="button" onclick="openMealPlanImportModal()" class="cb-nav-btn" title="Zutaten aus Wochenplan holen">
<span>📅</span> <span>Aus Wochenplan holen</span>
</button>
<button type="button" onclick="deleteCheckedItems()" class="cb-nav-btn" title="Erledigte Artikel löschen">
<span>🧹</span> <span>Erledigte löschen</span>
</button>
<hr class="cb-mobile-divider">
<button type="button" onclick="handleMobileMenuAction(openDeleteStep1Modal)" class="cb-mobile-item" style="color: #b02a37; font-weight: 700;">
<span>🗑️</span> <span>Einkaufsliste leeren</span>
<button type="button" onclick="openDeleteStep1Modal()" class="cb-nav-btn cb-btn-danger" style="background:#b02a37; border-color:#b02a37; color:#fff;" title="Liste leeren">
<span>🗑️</span> <span>Liste leeren</span>
</button>
<hr class="cb-mobile-divider">
<a href="/logout" class="cb-mobile-item cb-mobile-logout">
<a href="/" class="cb-nav-btn" title="Zur Startseite">
<span>🏠</span> <span>Startseite</span>
</a>
<a href="/cookbook" class="cb-nav-btn" title="Kochbuch & Rezepte">
<span>📖</span> <span>Kochbuch</span>
</a>
<a href="/logout" class="cb-nav-btn cb-nav-btn-logout" title="Abmelden">
<span>🚪</span> <span>Abmelden</span>
</a>
</div>
</div>
</header>
<!-- ========================================== -->
<!-- KATEGORIE-FILTERLEISTE (WIE IM KOCHBUCH) -->
<!-- ========================================== -->
<nav class="cb-filter-bar no-print" id="shop-category-bar">
<div class="cb-filter-scroll-container" id="shop-category-chips">
<!-- Dynamisch befüllt mit Kategorien -->
</div>
</nav>
<!-- Mobile 3-Punkte (Kebab) Menü (auf Bildschirmen <= 768px) -->
<div class="mobile-nav-wrapper">
<button type="button" class="cb-kebab-btn" onclick="toggleMobileMenu(event)" aria-label="Menü öffnen" title="Menü">
<span>⋮</span>
</button>
<div id="cb-mobile-menu" class="cb-mobile-dropdown">
<button type="button" onclick="handleMobileMenuAction(openCategoriesModal)" class="cb-mobile-item">
<span>🏬</span> <span>Bereiche anpassen</span>
</button>
<button type="button" onclick="handleMobileMenuAction(openMealPlanImportModal)" class="cb-mobile-item">
<span>📅</span> <span>Aus Wochenplan holen</span>
</button>
<button type="button" onclick="handleMobileMenuAction(deleteCheckedItems)" class="cb-mobile-item">
<span>🧹</span> <span>Erledigte löschen</span>
</button>
<hr class="cb-mobile-divider">
<button type="button" onclick="handleMobileMenuAction(openDeleteStep1Modal)" class="cb-mobile-item" style="color: #b02a37; font-weight: 700;">
<span>🗑️</span> <span>Liste leeren</span>
</button>
<hr class="cb-mobile-divider">
<a href="/" class="cb-mobile-item">
<span>🏠</span> <span>Startseite</span>
</a>
<a href="/cookbook" class="cb-mobile-item">
<span>📖</span> <span>Kochbuch</span>
</a>
<hr class="cb-mobile-divider">
<a href="/logout" class="cb-mobile-item cb-mobile-logout">
<span>🚪</span> <span>Abmelden</span>
</a>
</div>
</div>
</header>
<!-- DAUERHAFT FIXIERTE KATEGORIEN-LEISTE -->
<nav class="cb-filter-bar no-print" id="shop-category-bar">
<div class="cb-filter-scroll-container" id="shop-category-chips">
<!-- Dynamisch befüllt mit Kategorien -->
</div>
</nav>
</div>
<!-- ========================================== -->
<!-- HAUPTINHALT: EINKAUFSLISTEN-EINTRÄGE -->
@@ -515,15 +492,14 @@
<select id="edit-item-category" class="cb-select"></select>
</div>
<div class="modal-form-row-2">
<div>
<label class="cb-label" for="edit-item-amount">Menge</label>
<input type="text" id="edit-item-amount" class="cb-input" placeholder="z. B. 2 oder 500">
</div>
<div>
<label class="cb-label" for="edit-item-unit">Einheit</label>
<input type="text" id="edit-item-unit" class="cb-input" placeholder="z. B. g, l, Bund, Dose">
</div>
<div>
<label class="cb-label" for="edit-item-amount">Menge</label>
<input type="text" id="edit-item-amount" class="cb-input" placeholder="z. B. 2 oder 500">
</div>
<div>
<label class="cb-label" for="edit-item-unit">Einheit</label>
<input type="text" id="edit-item-unit" class="cb-input" placeholder="z. B. g, l, Bund, Dose">
</div>
<div>
@@ -572,15 +548,14 @@
</select>
</div>
<div class="modal-form-row-2">
<div>
<label class="cb-label" for="add-item-amount">Menge</label>
<input type="text" id="add-item-amount" class="cb-input" placeholder="z. B. 2 oder 500">
</div>
<div>
<label class="cb-label" for="add-item-unit">Einheit</label>
<input type="text" id="add-item-unit" class="cb-input" placeholder="z. B. g, l, Bund, Dose">
</div>
<div>
<label class="cb-label" for="add-item-amount">Menge</label>
<input type="text" id="add-item-amount" class="cb-input" placeholder="z. B. 2 oder 500">
</div>
<div>
<label class="cb-label" for="add-item-unit">Einheit</label>
<input type="text" id="add-item-unit" class="cb-input" placeholder="z. B. g, l, Bund, Dose">
</div>
<div>
@@ -723,9 +698,6 @@
let allItems = [];
let allCategories = [];
let currentFilterCategory = null;
let currentSortMode = 'category'; // 'category' (Laufweg) oder 'alpha'
let currentSearchTerm = '';
let isFilterBarVisible = true;
let ws = null;
let reconnectTimeout = null;
let pingInterval = null;
@@ -742,10 +714,6 @@
allCategories = JSON.parse(localStorage.getItem('wtc_shop_cats_cache') || '[]');
}
// Gespeicherte Sortiereinstellung laden
const savedSort = localStorage.getItem('wtc_shop_sort_mode');
if (savedSort) currentSortMode = savedSort;
// ==========================================
// MOBILE NAVIGATION (3-PUNKTE-MENÜ)
// ==========================================
@@ -784,7 +752,6 @@
ws.onopen = () => {
updateOnlineStatus(true);
// Ping Heartbeat starten
if (pingInterval) clearInterval(pingInterval);
pingInterval = setInterval(() => {
if (ws && ws.readyState === WebSocket.OPEN) {
@@ -937,7 +904,7 @@
}
});
// Auch 'Sonstiges' anzeigen falls vorhanden
// 'Sonstiges' anzeigen falls vorhanden
const uncategorizedCount = allItems.filter(it => !it.category || it.category === 'Sonstiges').length;
if (uncategorizedCount > 0 && !allCategories.some(c => c.name === 'Sonstiges')) {
const isAct = currentFilterCategory === 'Sonstiges';
@@ -957,67 +924,8 @@
renderShoppingList();
}
function toggleCategoryFilterBar() {
const bar = document.getElementById('shop-category-bar');
if (!bar) return;
isFilterBarVisible = !isFilterBarVisible;
bar.style.display = isFilterBarVisible ? 'flex' : 'none';
const label = document.getElementById('mobile-filter-label');
if (label) {
label.textContent = isFilterBarVisible ? 'Kategorien ausblenden' : 'Kategorien filtern';
}
}
// ==========================================
// LIVE-SUCHE
// ==========================================
function onSearchInput(val) {
currentSearchTerm = val.trim().toLowerCase();
const clearBtn = document.getElementById('shop-search-clear');
if (clearBtn) clearBtn.style.display = currentSearchTerm ? 'inline-block' : 'none';
renderShoppingList();
}
function clearSearch() {
const input = document.getElementById('shop-search-input');
if (input) input.value = '';
currentSearchTerm = '';
const clearBtn = document.getElementById('shop-search-clear');
if (clearBtn) clearBtn.style.display = 'none';
renderShoppingList();
}
// ==========================================
// SORTIERUNG TOGGLE (LAUFWEG VS ALPHABETISCH)
// ==========================================
function toggleSortMode() {
currentSortMode = (currentSortMode === 'category') ? 'alpha' : 'category';
localStorage.setItem('wtc_shop_sort_mode', currentSortMode);
updateSortButtonUI();
renderShoppingList();
showToast(currentSortMode === 'category' ? 'Sortierung: Supermarkt-Laufweg' : 'Sortierung: Alphabetisch (A–Z)');
}
function updateSortButtonUI() {
const isCat = (currentSortMode === 'category');
const dIcon = document.getElementById('desktop-sort-icon');
const dLabel = document.getElementById('desktop-sort-label');
if (dIcon && dLabel) {
dIcon.textContent = isCat ? '⇅' : '🔤';
dLabel.textContent = isCat ? 'Kategorien' : 'A–Z';
}
const mIcon = document.getElementById('mobile-sort-icon');
const mLabel = document.getElementById('mobile-sort-label');
if (mIcon && mLabel) {
mIcon.textContent = isCat ? '⇅' : '🔤';
mLabel.textContent = isCat ? 'Sortierung: Laufweg' : 'Sortierung: Alphabetisch';
}
}
// ==========================================
// RENDERING: EINKAUFSLISTEN-ITEMS
// RENDERING: EINKAUFSLISTEN-ITEMS (LAUFWEG)
// ==========================================
function renderShoppingList() {
const container = document.getElementById('items-container');
@@ -1029,13 +937,6 @@
if (currentFilterCategory !== null) {
filtered = filtered.filter(it => (it.category || 'Sonstiges') === currentFilterCategory);
}
if (currentSearchTerm) {
filtered = filtered.filter(it =>
(it.item_name || '').toLowerCase().includes(currentSearchTerm) ||
(it.note || '').toLowerCase().includes(currentSearchTerm) ||
(it.source || '').toLowerCase().includes(currentSearchTerm)
);
}
if (filtered.length === 0) {
container.innerHTML = '';
@@ -1045,11 +946,7 @@
if (emptyState) emptyState.style.display = 'none';
}
if (currentSortMode === 'alpha') {
renderAlphabeticalList(container, filtered);
} else {
renderCategorizedList(container, filtered);
}
renderCategorizedList(container, filtered);
}
function renderCategorizedList(container, items) {
@@ -1059,7 +956,7 @@
catOrderMap[c.name] = (c.sort_order !== null && c.sort_order !== undefined) ? c.sort_order : idx;
});
// 2. Items in Gruppen aufteilen
// 2. Items in Gruppen aufteilen (offen vs. abgehakt)
const groups = {};
items.forEach(it => {
const cat = it.category || 'Sonstiges';
@@ -1104,54 +1001,10 @@
container.innerHTML = html;
}
function renderAlphabeticalList(container, items) {
// Offene alphabetisch, erledigte alphabetisch am Ende
const openItems = items.filter(it => !it.checked).sort((a, b) => (a.item_name || '').localeCompare(b.item_name || '', 'de'));
const checkedItems = items.filter(it => it.checked).sort((a, b) => (a.item_name || '').localeCompare(b.item_name || '', 'de'));
let html = `
<section class="shop-cat-section">
<div class="shop-cat-header">
<h2 class="shop-cat-title">
<span>🔤</span> <span>Alphabetische Liste</span>
</h2>
<span class="shop-cat-count">${openItems.length} offen</span>
</div>
<ul class="shop-items-list">
${openItems.map(item => renderItemRow(item)).join('')}
</ul>
</section>
`;
if (checkedItems.length > 0) {
html += `
<section class="shop-cat-section" style="margin-top: 24px;">
<div class="shop-cat-header" style="border-left-color: #888; background: rgba(0,0,0,0.04);">
<h2 class="shop-cat-title" style="color: #666;">
<span>✓</span> <span>Bereits erledigt</span>
</h2>
<span class="shop-cat-count">${checkedItems.length}</span>
</div>
<ul class="shop-items-list">
${checkedItems.map(item => renderItemRow(item)).join('')}
</ul>
</section>
`;
}
container.innerHTML = html;
}
function renderItemRow(item) {
const isChecked = item.checked ? 'is-checked' : '';
const amountStr = formatAmount(item.amount, item.unit);
// Clean Source: Falls Name "Wochenmenüplan - Rezeptname" enthält, nur Rezeptname anzeigen
let cleanSource = '';
if (item.source) {
cleanSource = item.source.replace(/^Wochenmenüplan\s*[-–:]\s*/i, '').trim();
}
return `
<li class="shop-item-row ${isChecked}" id="shop-row-${item.id}">
<label class="shop-checkbox-wrap" onclick="event.stopPropagation();">
@@ -1165,10 +1018,11 @@
${amountStr ? `<span class="shop-item-amount">${escapeHtml(amountStr)}</span>` : ''}
</div>
<div class="shop-item-badges">
${item.note ? `<span class="shop-badge-note">📝 ${escapeHtml(item.note)}</span>` : ''}
${cleanSource ? `<span class="shop-badge-source">🍳 ${escapeHtml(cleanSource)}</span>` : ''}
</div>
${item.note ? `
<div class="shop-item-badges">
<span class="shop-badge-note">📝 ${escapeHtml(item.note)}</span>
</div>
` : ''}
</div>
<button type="button" class="shop-item-delete" onclick="deleteItemDirectly(${item.id}, event)" title="Eintrag löschen">
@@ -1463,7 +1317,6 @@
data-name="${escapeAttr(ing.item_name)}"
data-amount="${escapeAttr(ing.amount || '')}"
data-unit="${escapeAttr(ing.unit || '')}"
data-source="${escapeAttr(g.recipe_title)}"
onchange="updateMealPlanConfirmBtn()">
<span><strong>${escapeHtml(formatAmount(ing.amount, ing.unit))}</strong> ${escapeHtml(ing.item_name)}</span>
</label>
@@ -1504,7 +1357,7 @@
item_name: cb.dataset.name,
amount: cb.dataset.amount || '',
unit: cb.dataset.unit || '',
source: cb.dataset.source || 'Wochenmenüplan',
source: '',
category: null,
note: ''
}));
@@ -1714,7 +1567,6 @@
}
document.addEventListener('DOMContentLoaded', () => {
updateSortButtonUI();
renderCategoryFilterChips();
renderShoppingList();
initWebSocket();