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) @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) token = request.cookies.get(COOKIE_NAME)
if verify_auth_token(token): 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( return templates.TemplateResponse(
request=request, request=request,
name="login.html", name="login.html",
@@ -264,7 +264,7 @@ async def login_post(
username: str = Form("admin"), username: str = Form("admin"),
password: str = Form(...), password: str = Form(...),
remember: Optional[str] = Form(None), 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_user = (username.strip().lower() == APP_USERNAME.lower()) or (username.strip() == "admin")
valid_pass = (password == APP_PASSWORD) valid_pass = (password == APP_PASSWORD)
@@ -272,7 +272,7 @@ async def login_post(
if valid_user and valid_pass: if valid_user and valid_pass:
days = 30 if remember else 1 days = 30 if remember else 1
token = create_auth_token(username.strip(), days=days) 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) resp = RedirectResponse(url=target, status_code=303)
max_age = days * 86400 max_age = days * 86400
resp.set_cookie( resp.set_cookie(
+13 -12
View File
@@ -494,12 +494,6 @@
<!-- Desktop Buttons (auf Bildschirmen > 768px) --> <!-- Desktop Buttons (auf Bildschirmen > 768px) -->
<div class="cb-nav-actions desktop-nav-actions"> <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"> <button type="button" onclick="openMealPlanModal(event)" class="cb-nav-btn" title="Wochenmenü-Plan öffnen">
<span>📅</span> <span>Wochenplan</span> <span>📅</span> <span>Wochenplan</span>
</button> </button>
@@ -512,6 +506,12 @@
<button type="button" onclick="openPrintModal(event)" class="cb-nav-btn" title="Druckansicht & PDF"> <button type="button" onclick="openPrintModal(event)" class="cb-nav-btn" title="Druckansicht & PDF">
<span>🖨️</span> <span>Drucken</span> <span>🖨️</span> <span>Drucken</span>
</button> </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"> <a href="/logout" class="cb-nav-btn cb-nav-btn-logout" title="Abmelden">
<span>🚪</span> <span>Abmelden</span> <span>🚪</span> <span>Abmelden</span>
</a> </a>
@@ -523,12 +523,6 @@
<span>⋮</span> <span>⋮</span>
</button> </button>
<div id="cb-mobile-menu" class="cb-mobile-dropdown"> <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"> <button type="button" onclick="handleMobileMenuAction(openMealPlanModal)" class="cb-mobile-item">
<span>📅</span> <span>Wochenmenü-Plan</span> <span>📅</span> <span>Wochenmenü-Plan</span>
</button> </button>
@@ -542,6 +536,13 @@
<span>🖨️</span> <span>Drucken & PDF</span> <span>🖨️</span> <span>Drucken & PDF</span>
</button> </button>
<hr class="cb-mobile-divider"> <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"> <a href="/logout" class="cb-mobile-item cb-mobile-logout">
<span>🚪</span> <span>Abmelden</span> <span>🚪</span> <span>Abmelden</span>
</a> </a>
+1 -1
View File
@@ -159,7 +159,7 @@
{% endif %} {% endif %}
<form method="POST" action="/login" class="login-form"> <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"> <div class="form-group">
<label class="form-label" for="username">Benutzername</label> <label class="form-label" for="username">Benutzername</label>
+51 -199
View File
@@ -10,7 +10,7 @@
<meta name="apple-mobile-web-app-status-bar-style" content="default"> <meta name="apple-mobile-web-app-status-bar-style" content="default">
<meta name="apple-mobile-web-app-title" content="What To Cook?"> <meta name="apple-mobile-web-app-title" content="What To Cook?">
<link rel="apple-touch-icon" href="/static/icon-192.png"> <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>"> <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> <style>
:root { :root {
@@ -36,12 +36,18 @@
background-size: 20px 20px; background-size: 20px 20px;
font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif); font-family: var(--font-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
color: var(--shop-schwarz); color: var(--shop-schwarz);
display: flex;
flex-direction: column;
overflow-x: hidden;
width: 100%; 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 */ /* Live Online Badge in Navbar */
.cb-online-pill { .cb-online-pill {
font-size: 0.72rem; font-size: 0.72rem;
@@ -65,7 +71,6 @@
/* Hauptinhalt Container */ /* Hauptinhalt Container */
.shop-main-container { .shop-main-container {
flex: 1 0 auto;
max-width: 820px; max-width: 820px;
width: 100%; width: 100%;
margin: 0 auto; margin: 0 auto;
@@ -212,7 +217,7 @@
background: rgba(30, 77, 59, 0.05); background: rgba(30, 77, 59, 0.05);
} }
/* Badges: Notiz & Rezept-Quelle */ /* Badge nur noch für Notiz (Quelle wurde entfernt) */
.shop-item-badges { .shop-item-badges {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -230,17 +235,6 @@
align-items: center; align-items: center;
gap: 3px; 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 */ /* Löschen-Icon am rechten Rand */
.shop-item-delete { .shop-item-delete {
@@ -328,11 +322,6 @@
flex-direction: column; flex-direction: column;
gap: 14px; gap: 14px;
} }
.modal-form-row-2 {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.modal-btn-row { .modal-btn-row {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
@@ -374,9 +363,10 @@
</head> </head>
<body class="shopping-app-body"> <body class="shopping-app-body">
<!-- ========================================== --> <!-- ============================================================== -->
<!-- 100% IDENTISCHE NAVIGATION ZUM KOCHBUCH --> <!-- FIXIERTER KOPFBEREICH: HEADER & KATEGORIE-FILTERLEISTE GEMEINSAM -->
<!-- ========================================== --> <!-- ============================================================== -->
<div class="cb-sticky-header-container">
<header class="cb-nav"> <header class="cb-nav">
<a href="/shopping-list" class="cb-nav-brand" title="Einkaufsliste neu laden"> <a href="/shopping-list" class="cb-nav-brand" title="Einkaufsliste neu laden">
<span class="cb-brand-icon">🛒</span> <span class="cb-brand-icon">🛒</span>
@@ -384,33 +374,26 @@
</a> </a>
<span id="shop-online-pill" class="cb-online-pill" title="Synchronisations-Status">● Live</span> <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) --> <!-- Desktop Buttons (auf Bildschirmen > 768px) -->
<div class="cb-nav-actions desktop-nav-actions"> <div class="cb-nav-actions desktop-nav-actions">
<a href="/" class="cb-nav-btn" title="Startmenü"> <button type="button" onclick="openCategoriesModal()" class="cb-nav-btn" title="Supermarkt-Bereiche & Laufweg anpassen">
<span>🏠</span> <span>Menü</span> <span>🏬</span> <span>Bereiche anpassen</span>
</button>
<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>
<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>
<a href="/" class="cb-nav-btn" title="Zur Startseite">
<span>🏠</span> <span>Startseite</span>
</a> </a>
<a href="/cookbook" class="cb-nav-btn" title="Kochbuch & Rezepte"> <a href="/cookbook" class="cb-nav-btn" title="Kochbuch & Rezepte">
<span>📖</span> <span>Kochbuch</span> <span>📖</span> <span>Kochbuch</span>
</a> </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"> <a href="/logout" class="cb-nav-btn cb-nav-btn-logout" title="Abmelden">
<span>🚪</span> <span>Abmelden</span> <span>🚪</span> <span>Abmelden</span>
</a> </a>
@@ -422,32 +405,27 @@
<span>⋮</span> <span>⋮</span>
</button> </button>
<div id="cb-mobile-menu" class="cb-mobile-dropdown"> <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"> <button type="button" onclick="handleMobileMenuAction(openCategoriesModal)" class="cb-mobile-item">
<span>🏬</span> <span>Bereiche anpassen</span> <span>🏬</span> <span>Bereiche anpassen</span>
</button> </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"> <button type="button" onclick="handleMobileMenuAction(deleteCheckedItems)" class="cb-mobile-item">
<span>🧹</span> <span>Erledigte löschen</span> <span>🧹</span> <span>Erledigte löschen</span>
</button> </button>
<hr class="cb-mobile-divider"> <hr class="cb-mobile-divider">
<button type="button" onclick="handleMobileMenuAction(openDeleteStep1Modal)" class="cb-mobile-item" style="color: #b02a37; font-weight: 700;"> <button type="button" onclick="handleMobileMenuAction(openDeleteStep1Modal)" class="cb-mobile-item" style="color: #b02a37; font-weight: 700;">
<span>🗑️</span> <span>Einkaufsliste leeren</span> <span>🗑️</span> <span>Liste leeren</span>
</button> </button>
<hr class="cb-mobile-divider"> <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"> <a href="/logout" class="cb-mobile-item cb-mobile-logout">
<span>🚪</span> <span>Abmelden</span> <span>🚪</span> <span>Abmelden</span>
</a> </a>
@@ -455,14 +433,13 @@
</div> </div>
</header> </header>
<!-- ========================================== --> <!-- DAUERHAFT FIXIERTE KATEGORIEN-LEISTE -->
<!-- KATEGORIE-FILTERLEISTE (WIE IM KOCHBUCH) -->
<!-- ========================================== -->
<nav class="cb-filter-bar no-print" id="shop-category-bar"> <nav class="cb-filter-bar no-print" id="shop-category-bar">
<div class="cb-filter-scroll-container" id="shop-category-chips"> <div class="cb-filter-scroll-container" id="shop-category-chips">
<!-- Dynamisch befüllt mit Kategorien --> <!-- Dynamisch befüllt mit Kategorien -->
</div> </div>
</nav> </nav>
</div>
<!-- ========================================== --> <!-- ========================================== -->
<!-- HAUPTINHALT: EINKAUFSLISTEN-EINTRÄGE --> <!-- HAUPTINHALT: EINKAUFSLISTEN-EINTRÄGE -->
@@ -515,16 +492,15 @@
<select id="edit-item-category" class="cb-select"></select> <select id="edit-item-category" class="cb-select"></select>
</div> </div>
<div class="modal-form-row-2">
<div> <div>
<label class="cb-label" for="edit-item-amount">Menge</label> <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"> <input type="text" id="edit-item-amount" class="cb-input" placeholder="z. B. 2 oder 500">
</div> </div>
<div> <div>
<label class="cb-label" for="edit-item-unit">Einheit</label> <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"> <input type="text" id="edit-item-unit" class="cb-input" placeholder="z. B. g, l, Bund, Dose">
</div> </div>
</div>
<div> <div>
<label class="cb-label" for="edit-item-note">Notiz / Extra</label> <label class="cb-label" for="edit-item-note">Notiz / Extra</label>
@@ -572,16 +548,15 @@
</select> </select>
</div> </div>
<div class="modal-form-row-2">
<div> <div>
<label class="cb-label" for="add-item-amount">Menge</label> <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"> <input type="text" id="add-item-amount" class="cb-input" placeholder="z. B. 2 oder 500">
</div> </div>
<div> <div>
<label class="cb-label" for="add-item-unit">Einheit</label> <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"> <input type="text" id="add-item-unit" class="cb-input" placeholder="z. B. g, l, Bund, Dose">
</div> </div>
</div>
<div> <div>
<label class="cb-label" for="add-item-note">Notiz / Extra</label> <label class="cb-label" for="add-item-note">Notiz / Extra</label>
@@ -723,9 +698,6 @@
let allItems = []; let allItems = [];
let allCategories = []; let allCategories = [];
let currentFilterCategory = null; let currentFilterCategory = null;
let currentSortMode = 'category'; // 'category' (Laufweg) oder 'alpha'
let currentSearchTerm = '';
let isFilterBarVisible = true;
let ws = null; let ws = null;
let reconnectTimeout = null; let reconnectTimeout = null;
let pingInterval = null; let pingInterval = null;
@@ -742,10 +714,6 @@
allCategories = JSON.parse(localStorage.getItem('wtc_shop_cats_cache') || '[]'); 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Ü) // MOBILE NAVIGATION (3-PUNKTE-MENÜ)
// ========================================== // ==========================================
@@ -784,7 +752,6 @@
ws.onopen = () => { ws.onopen = () => {
updateOnlineStatus(true); updateOnlineStatus(true);
// Ping Heartbeat starten
if (pingInterval) clearInterval(pingInterval); if (pingInterval) clearInterval(pingInterval);
pingInterval = setInterval(() => { pingInterval = setInterval(() => {
if (ws && ws.readyState === WebSocket.OPEN) { 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; const uncategorizedCount = allItems.filter(it => !it.category || it.category === 'Sonstiges').length;
if (uncategorizedCount > 0 && !allCategories.some(c => c.name === 'Sonstiges')) { if (uncategorizedCount > 0 && !allCategories.some(c => c.name === 'Sonstiges')) {
const isAct = currentFilterCategory === 'Sonstiges'; const isAct = currentFilterCategory === 'Sonstiges';
@@ -957,67 +924,8 @@
renderShoppingList(); 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 // RENDERING: EINKAUFSLISTEN-ITEMS (LAUFWEG)
// ==========================================
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
// ========================================== // ==========================================
function renderShoppingList() { function renderShoppingList() {
const container = document.getElementById('items-container'); const container = document.getElementById('items-container');
@@ -1029,13 +937,6 @@
if (currentFilterCategory !== null) { if (currentFilterCategory !== null) {
filtered = filtered.filter(it => (it.category || 'Sonstiges') === currentFilterCategory); 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) { if (filtered.length === 0) {
container.innerHTML = ''; container.innerHTML = '';
@@ -1045,12 +946,8 @@
if (emptyState) emptyState.style.display = 'none'; if (emptyState) emptyState.style.display = 'none';
} }
if (currentSortMode === 'alpha') {
renderAlphabeticalList(container, filtered);
} else {
renderCategorizedList(container, filtered); renderCategorizedList(container, filtered);
} }
}
function renderCategorizedList(container, items) { function renderCategorizedList(container, items) {
// 1. Kategorien nach sort_order sortieren // 1. Kategorien nach sort_order sortieren
@@ -1059,7 +956,7 @@
catOrderMap[c.name] = (c.sort_order !== null && c.sort_order !== undefined) ? c.sort_order : idx; 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 = {}; const groups = {};
items.forEach(it => { items.forEach(it => {
const cat = it.category || 'Sonstiges'; const cat = it.category || 'Sonstiges';
@@ -1104,54 +1001,10 @@
container.innerHTML = html; 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) { function renderItemRow(item) {
const isChecked = item.checked ? 'is-checked' : ''; const isChecked = item.checked ? 'is-checked' : '';
const amountStr = formatAmount(item.amount, item.unit); 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 ` return `
<li class="shop-item-row ${isChecked}" id="shop-row-${item.id}"> <li class="shop-item-row ${isChecked}" id="shop-row-${item.id}">
<label class="shop-checkbox-wrap" onclick="event.stopPropagation();"> <label class="shop-checkbox-wrap" onclick="event.stopPropagation();">
@@ -1165,10 +1018,11 @@
${amountStr ? `<span class="shop-item-amount">${escapeHtml(amountStr)}</span>` : ''} ${amountStr ? `<span class="shop-item-amount">${escapeHtml(amountStr)}</span>` : ''}
</div> </div>
${item.note ? `
<div class="shop-item-badges"> <div class="shop-item-badges">
${item.note ? `<span class="shop-badge-note">📝 ${escapeHtml(item.note)}</span>` : ''} <span class="shop-badge-note">📝 ${escapeHtml(item.note)}</span>
${cleanSource ? `<span class="shop-badge-source">🍳 ${escapeHtml(cleanSource)}</span>` : ''}
</div> </div>
` : ''}
</div> </div>
<button type="button" class="shop-item-delete" onclick="deleteItemDirectly(${item.id}, event)" title="Eintrag löschen"> <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-name="${escapeAttr(ing.item_name)}"
data-amount="${escapeAttr(ing.amount || '')}" data-amount="${escapeAttr(ing.amount || '')}"
data-unit="${escapeAttr(ing.unit || '')}" data-unit="${escapeAttr(ing.unit || '')}"
data-source="${escapeAttr(g.recipe_title)}"
onchange="updateMealPlanConfirmBtn()"> onchange="updateMealPlanConfirmBtn()">
<span><strong>${escapeHtml(formatAmount(ing.amount, ing.unit))}</strong> ${escapeHtml(ing.item_name)}</span> <span><strong>${escapeHtml(formatAmount(ing.amount, ing.unit))}</strong> ${escapeHtml(ing.item_name)}</span>
</label> </label>
@@ -1504,7 +1357,7 @@
item_name: cb.dataset.name, item_name: cb.dataset.name,
amount: cb.dataset.amount || '', amount: cb.dataset.amount || '',
unit: cb.dataset.unit || '', unit: cb.dataset.unit || '',
source: cb.dataset.source || 'Wochenmenüplan', source: '',
category: null, category: null,
note: '' note: ''
})); }));
@@ -1714,7 +1567,6 @@
} }
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
updateSortButtonUI();
renderCategoryFilterChips(); renderCategoryFilterChips();
renderShoppingList(); renderShoppingList();
initWebSocket(); initWebSocket();