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:
+4
-4
@@ -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
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
+103
-251
@@ -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,95 +363,83 @@
|
|||||||
</head>
|
</head>
|
||||||
<body class="shopping-app-body">
|
<body class="shopping-app-body">
|
||||||
|
|
||||||
<!-- ========================================== -->
|
<!-- ============================================================== -->
|
||||||
<!-- 100% IDENTISCHE NAVIGATION ZUM KOCHBUCH -->
|
<!-- FIXIERTER KOPFBEREICH: HEADER & KATEGORIE-FILTERLEISTE GEMEINSAM -->
|
||||||
<!-- ========================================== -->
|
<!-- ============================================================== -->
|
||||||
<header class="cb-nav">
|
<div class="cb-sticky-header-container">
|
||||||
<a href="/shopping-list" class="cb-nav-brand" title="Einkaufsliste neu laden">
|
<header class="cb-nav">
|
||||||
<span class="cb-brand-icon">🛒</span>
|
<a href="/shopping-list" class="cb-nav-brand" title="Einkaufsliste neu laden">
|
||||||
<h1 class="cb-nav-title">Einkaufsliste</h1>
|
<span class="cb-brand-icon">🛒</span>
|
||||||
</a>
|
<h1 class="cb-nav-title">Einkaufsliste</h1>
|
||||||
<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>
|
|
||||||
</a>
|
</a>
|
||||||
<a href="/cookbook" class="cb-nav-btn" title="Kochbuch & Rezepte">
|
<span id="shop-online-pill" class="cb-online-pill" title="Synchronisations-Status">● Live</span>
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- Mobile 3-Punkte (Kebab) Menü (auf Bildschirmen <= 768px) -->
|
<!-- Desktop Buttons (auf Bildschirmen > 768px) -->
|
||||||
<div class="mobile-nav-wrapper">
|
<div class="cb-nav-actions desktop-nav-actions">
|
||||||
<button type="button" class="cb-kebab-btn" onclick="toggleMobileMenu(event)" aria-label="Menü öffnen" title="Menü">
|
<button type="button" onclick="openCategoriesModal()" class="cb-nav-btn" title="Supermarkt-Bereiche & Laufweg anpassen">
|
||||||
<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">
|
|
||||||
<span>🏬</span> <span>Bereiche anpassen</span>
|
<span>🏬</span> <span>Bereiche anpassen</span>
|
||||||
</button>
|
</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>
|
<span>🧹</span> <span>Erledigte löschen</span>
|
||||||
</button>
|
</button>
|
||||||
<hr class="cb-mobile-divider">
|
<button type="button" onclick="openDeleteStep1Modal()" class="cb-nav-btn cb-btn-danger" style="background:#b02a37; border-color:#b02a37; color:#fff;" title="Liste leeren">
|
||||||
<button type="button" onclick="handleMobileMenuAction(openDeleteStep1Modal)" class="cb-mobile-item" style="color: #b02a37; font-weight: 700;">
|
<span>🗑️</span> <span>Liste leeren</span>
|
||||||
<span>🗑️</span> <span>Einkaufsliste leeren</span>
|
|
||||||
</button>
|
</button>
|
||||||
<hr class="cb-mobile-divider">
|
<a href="/" class="cb-nav-btn" title="Zur Startseite">
|
||||||
<a href="/logout" class="cb-mobile-item cb-mobile-logout">
|
<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>
|
<span>🚪</span> <span>Abmelden</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- ========================================== -->
|
<!-- Mobile 3-Punkte (Kebab) Menü (auf Bildschirmen <= 768px) -->
|
||||||
<!-- KATEGORIE-FILTERLEISTE (WIE IM KOCHBUCH) -->
|
<div class="mobile-nav-wrapper">
|
||||||
<!-- ========================================== -->
|
<button type="button" class="cb-kebab-btn" onclick="toggleMobileMenu(event)" aria-label="Menü öffnen" title="Menü">
|
||||||
<nav class="cb-filter-bar no-print" id="shop-category-bar">
|
<span>⋮</span>
|
||||||
<div class="cb-filter-scroll-container" id="shop-category-chips">
|
</button>
|
||||||
<!-- Dynamisch befüllt mit Kategorien -->
|
<div id="cb-mobile-menu" class="cb-mobile-dropdown">
|
||||||
</div>
|
<button type="button" onclick="handleMobileMenuAction(openCategoriesModal)" class="cb-mobile-item">
|
||||||
</nav>
|
<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 -->
|
<!-- HAUPTINHALT: EINKAUFSLISTEN-EINTRÄGE -->
|
||||||
@@ -515,15 +492,14 @@
|
|||||||
<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>
|
||||||
@@ -572,15 +548,14 @@
|
|||||||
</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>
|
||||||
@@ -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,11 +946,7 @@
|
|||||||
if (emptyState) emptyState.style.display = 'none';
|
if (emptyState) emptyState.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentSortMode === 'alpha') {
|
renderCategorizedList(container, filtered);
|
||||||
renderAlphabeticalList(container, filtered);
|
|
||||||
} else {
|
|
||||||
renderCategorizedList(container, filtered);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderCategorizedList(container, items) {
|
function renderCategorizedList(container, items) {
|
||||||
@@ -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>
|
||||||
|
|
||||||
<div class="shop-item-badges">
|
${item.note ? `
|
||||||
${item.note ? `<span class="shop-badge-note">📝 ${escapeHtml(item.note)}</span>` : ''}
|
<div class="shop-item-badges">
|
||||||
${cleanSource ? `<span class="shop-badge-source">🍳 ${escapeHtml(cleanSource)}</span>` : ''}
|
<span class="shop-badge-note">📝 ${escapeHtml(item.note)}</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();
|
||||||
|
|||||||
Reference in New Issue
Block a user