1541 lines
45 KiB
HTML
1541 lines
45 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||
<title>Einkaufsliste – What To Cook?</title>
|
||
<link rel="stylesheet" href="/static/cookbook.css?v=4.6.0">
|
||
<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 {
|
||
--shop-bg: #FDFBF7;
|
||
--shop-card: #FFFFFF;
|
||
--shop-accent: #1E4D3B;
|
||
--shop-accent-hover: #163A2C;
|
||
--shop-border: #222222;
|
||
--shop-subtle: rgba(34, 34, 34, 0.08);
|
||
--shop-yellow: #FFF9D2;
|
||
}
|
||
|
||
* {
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
body.shopping-body {
|
||
background-color: var(--creme, #FDFBF7);
|
||
background-image:
|
||
radial-gradient(rgba(30, 77, 59, 0.05) 1px, transparent 1px),
|
||
linear-gradient(to right, rgba(0,0,0,0.02) 1px, transparent 1px);
|
||
background-size: 24px 24px, 100% 100%;
|
||
min-height: 100vh;
|
||
margin: 0;
|
||
padding: 0;
|
||
font-family: var(--font-sans, system-ui, -apple-system, sans-serif);
|
||
color: var(--schwarz, #222222);
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow-x: hidden;
|
||
width: 100%;
|
||
}
|
||
|
||
/* ========================================== */
|
||
/* TOP NAVIGATION (Harmonisch & Minimalistisch)*/
|
||
/* ========================================== */
|
||
.shop-nav {
|
||
background-color: #FFFFFF;
|
||
border-bottom: 2.5px solid var(--shop-border);
|
||
padding: 10px 16px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 100;
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||
width: 100%;
|
||
}
|
||
|
||
.shop-nav-btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
background: var(--creme, #FDFBF7);
|
||
border: 2px solid var(--shop-border);
|
||
border-radius: 8px;
|
||
padding: 7px 12px;
|
||
font-size: 0.9rem;
|
||
font-weight: 700;
|
||
color: var(--shop-border);
|
||
text-decoration: none;
|
||
box-shadow: 2px 2px 0px var(--shop-border);
|
||
transition: all 0.12s ease;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
.shop-nav-btn:hover {
|
||
background: #FFFFFF;
|
||
transform: translateY(-1px);
|
||
box-shadow: 3px 3px 0px var(--shop-border);
|
||
}
|
||
.shop-nav-btn:active {
|
||
transform: translate(1px, 1px);
|
||
box-shadow: 1px 1px 0px var(--shop-border);
|
||
}
|
||
|
||
.shop-nav-center {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.shop-nav-title {
|
||
font-family: var(--font-hand, 'Caveat', cursive);
|
||
font-size: 1.85rem;
|
||
font-weight: 700;
|
||
color: var(--shop-accent);
|
||
margin: 0;
|
||
line-height: 1;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
/* ========================================== */
|
||
/* HAUPTCONTAINER (Notizblock-Charakter) */
|
||
/* ========================================== */
|
||
.shop-main {
|
||
max-width: 760px;
|
||
width: 100%;
|
||
margin: 20px auto 60px auto;
|
||
padding: 0 16px;
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.notepad-card {
|
||
background: #FFFFFF;
|
||
border: 2.5px solid var(--shop-border);
|
||
border-radius: 14px;
|
||
box-shadow: 4px 5px 0px var(--shop-border);
|
||
position: relative;
|
||
padding: 22px 20px;
|
||
width: 100%;
|
||
}
|
||
|
||
/* Rote Randlinie für subtilen Notizblock-Touch */
|
||
.notepad-card::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0; bottom: 0; left: 34px;
|
||
width: 1.5px;
|
||
background-color: rgba(210, 155, 155, 0.35);
|
||
pointer-events: none;
|
||
}
|
||
|
||
/* Header des Blocks */
|
||
.notepad-header {
|
||
margin-bottom: 18px;
|
||
padding-bottom: 14px;
|
||
border-bottom: 2px dashed rgba(30, 77, 59, 0.25);
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 10px;
|
||
}
|
||
|
||
.notepad-title-group h2 {
|
||
font-family: var(--font-hand, 'Caveat', cursive);
|
||
font-size: 2.2rem;
|
||
color: var(--shop-accent);
|
||
margin: 0;
|
||
line-height: 1.1;
|
||
}
|
||
|
||
.notepad-subtitle {
|
||
font-size: 0.88rem;
|
||
color: #666;
|
||
margin: 2px 0 0 0;
|
||
}
|
||
|
||
.notepad-counter {
|
||
font-family: var(--font-hand, 'Caveat', cursive);
|
||
font-size: 1.15rem;
|
||
font-weight: 700;
|
||
color: var(--shop-accent);
|
||
background: var(--shop-yellow);
|
||
border: 1.5px solid var(--shop-border);
|
||
padding: 4px 10px;
|
||
border-radius: 8px;
|
||
box-shadow: 2px 2px 0px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
/* ========================================== */
|
||
/* SCHNELLEINGABE */
|
||
/* ========================================== */
|
||
.shop-add-form {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
margin-bottom: 20px;
|
||
background: var(--shop-yellow);
|
||
border: 2px solid var(--shop-border);
|
||
border-radius: 12px;
|
||
padding: 10px;
|
||
box-shadow: 3px 3px 0px rgba(0,0,0,0.12);
|
||
}
|
||
|
||
.shop-add-top {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
|
||
.shop-input {
|
||
flex: 1;
|
||
min-width: 0;
|
||
border: 1.5px solid rgba(0,0,0,0.25);
|
||
border-radius: 8px;
|
||
background: #FFFFFF;
|
||
font-family: var(--font-sans);
|
||
font-size: 1rem;
|
||
padding: 9px 12px;
|
||
outline: none;
|
||
color: var(--shop-border);
|
||
transition: border-color 0.15s;
|
||
}
|
||
.shop-input:focus {
|
||
border-color: var(--shop-accent);
|
||
box-shadow: 0 0 0 2px rgba(30, 77, 59, 0.15);
|
||
}
|
||
.shop-input::placeholder {
|
||
color: #8c8260;
|
||
}
|
||
|
||
.shop-add-btn {
|
||
background: var(--shop-accent);
|
||
color: var(--creme, #FDFBF7);
|
||
border: 2px solid var(--shop-border);
|
||
border-radius: 8px;
|
||
padding: 9px 16px;
|
||
font-weight: 700;
|
||
font-size: 0.95rem;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
white-space: nowrap;
|
||
box-shadow: 2px 2px 0px var(--shop-border);
|
||
transition: all 0.12s;
|
||
}
|
||
.shop-add-btn:hover {
|
||
background: var(--shop-accent-hover);
|
||
transform: translateY(-1px);
|
||
box-shadow: 3px 3px 0px var(--shop-border);
|
||
}
|
||
.shop-add-btn:active {
|
||
transform: translate(1px, 1px);
|
||
box-shadow: 1px 1px 0px var(--shop-border);
|
||
}
|
||
|
||
.shop-add-bottom {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 0.85rem;
|
||
color: #555;
|
||
}
|
||
.shop-cat-select {
|
||
background: #FFFFFF;
|
||
border: 1.5px solid rgba(0,0,0,0.25);
|
||
border-radius: 6px;
|
||
padding: 5px 8px;
|
||
font-size: 0.85rem;
|
||
font-weight: 600;
|
||
color: var(--shop-border);
|
||
outline: none;
|
||
cursor: pointer;
|
||
}
|
||
|
||
/* ========================================== */
|
||
/* TOOLBAR AKTIONEN */
|
||
/* ========================================== */
|
||
.shop-actions-bar {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin-bottom: 18px;
|
||
padding-bottom: 12px;
|
||
border-bottom: 1.5px dashed rgba(0,0,0,0.15);
|
||
}
|
||
|
||
.shop-action-btn {
|
||
background: #FFFFFF;
|
||
border: 1.5px solid var(--shop-border);
|
||
border-radius: 6px;
|
||
padding: 6px 10px;
|
||
font-size: 0.82rem;
|
||
font-weight: 600;
|
||
color: #444;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
box-shadow: 1.5px 1.5px 0px rgba(0,0,0,0.1);
|
||
transition: all 0.12s;
|
||
text-decoration: none;
|
||
}
|
||
.shop-action-btn:hover {
|
||
background: var(--creme, #FDFBF7);
|
||
color: #111;
|
||
transform: translateY(-1px);
|
||
}
|
||
.shop-action-btn:active {
|
||
transform: translate(1px, 1px);
|
||
box-shadow: none;
|
||
}
|
||
|
||
.shop-action-btn.btn-wochenplan {
|
||
background: rgba(30, 77, 59, 0.08);
|
||
border-color: var(--shop-accent);
|
||
color: var(--shop-accent);
|
||
font-weight: 700;
|
||
}
|
||
.shop-action-btn.btn-wochenplan:hover {
|
||
background: rgba(30, 77, 59, 0.16);
|
||
}
|
||
|
||
/* ========================================== */
|
||
/* SUPERMARKT-BEREICHE & ZUTATEN-ITEMS */
|
||
/* ========================================== */
|
||
.category-sections-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 18px;
|
||
}
|
||
|
||
.category-group {
|
||
background: #FAF8F2;
|
||
border: 2px solid var(--shop-border);
|
||
border-radius: 10px;
|
||
padding: 10px 12px 6px 12px;
|
||
box-shadow: 2px 3px 0px rgba(0,0,0,0.08);
|
||
transition: all 0.2s;
|
||
}
|
||
|
||
.category-group-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding-bottom: 6px;
|
||
margin-bottom: 6px;
|
||
border-bottom: 1.5px solid rgba(0,0,0,0.08);
|
||
}
|
||
|
||
.category-title-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 7px;
|
||
}
|
||
|
||
.category-icon {
|
||
font-size: 1.25rem;
|
||
line-height: 1;
|
||
}
|
||
|
||
.category-name {
|
||
font-family: var(--font-serif, 'Lora', serif);
|
||
font-size: 1.05rem;
|
||
font-weight: 700;
|
||
color: var(--shop-accent);
|
||
margin: 0;
|
||
}
|
||
|
||
.category-count {
|
||
background: var(--shop-yellow);
|
||
border: 1px solid rgba(0,0,0,0.2);
|
||
border-radius: 12px;
|
||
padding: 1px 7px;
|
||
font-size: 0.75rem;
|
||
font-weight: 700;
|
||
color: #555;
|
||
}
|
||
|
||
/* Items Liste */
|
||
.category-items-list {
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
}
|
||
|
||
/* Einzelne Zutat-Zeile */
|
||
.shopping-item-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 10px;
|
||
padding: 8px 10px;
|
||
background: #FFFFFF;
|
||
border: 1.5px solid rgba(0,0,0,0.12);
|
||
border-radius: 8px;
|
||
transition: all 0.15s;
|
||
min-width: 0; /* Verhindert Flex-Overflow */
|
||
}
|
||
.shopping-item-row:hover {
|
||
border-color: rgba(30, 77, 59, 0.4);
|
||
box-shadow: 0 2px 6px rgba(0,0,0,0.04);
|
||
}
|
||
|
||
.shopping-item-row.is-checked {
|
||
opacity: 0.55;
|
||
background: rgba(0, 0, 0, 0.02);
|
||
border-color: rgba(0,0,0,0.08);
|
||
}
|
||
.shopping-item-row.is-checked .item-text {
|
||
text-decoration: line-through;
|
||
color: #777;
|
||
}
|
||
|
||
.item-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
flex: 1;
|
||
min-width: 0; /* Essenziell für sauberen Textumbruch */
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
|
||
.item-checkbox {
|
||
width: 22px;
|
||
height: 22px;
|
||
border: 2px solid var(--shop-border);
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
accent-color: var(--shop-accent);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.item-text-wrapper {
|
||
flex: 1;
|
||
min-width: 0; /* Zwingt Text zum Umbruch */
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
|
||
.item-text {
|
||
font-size: 1rem;
|
||
font-weight: 500;
|
||
color: #222;
|
||
line-height: 1.35;
|
||
/* Radikaler Umbruch für lange Zutatentexte */
|
||
word-break: break-word;
|
||
overflow-wrap: anywhere;
|
||
white-space: normal;
|
||
}
|
||
|
||
.item-amount-badge {
|
||
font-weight: 700;
|
||
color: var(--shop-accent);
|
||
margin-right: 4px;
|
||
}
|
||
|
||
.item-sub-info {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
margin-top: 2px;
|
||
}
|
||
|
||
/* Schlanker Quell-Badge: Nur Rezepttitel */
|
||
.item-source-badge {
|
||
font-size: 0.72rem;
|
||
background: rgba(30, 77, 59, 0.08);
|
||
color: var(--shop-accent);
|
||
border: 1px solid rgba(30, 77, 59, 0.2);
|
||
padding: 1px 6px;
|
||
border-radius: 4px;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 3px;
|
||
max-width: 240px;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
/* Klickbares Kategorie-Pill am Item */
|
||
.item-cat-pill {
|
||
font-size: 0.72rem;
|
||
background: rgba(0,0,0,0.04);
|
||
color: #666;
|
||
border: 1px dashed rgba(0,0,0,0.25);
|
||
padding: 1px 6px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 3px;
|
||
transition: all 0.12s;
|
||
}
|
||
.item-cat-pill:hover {
|
||
background: var(--shop-yellow);
|
||
border-color: var(--shop-border);
|
||
color: #222;
|
||
}
|
||
|
||
.item-delete-btn {
|
||
background: transparent;
|
||
border: none;
|
||
color: #999;
|
||
font-size: 1.25rem;
|
||
line-height: 1;
|
||
cursor: pointer;
|
||
padding: 6px 8px;
|
||
border-radius: 6px;
|
||
transition: all 0.15s;
|
||
flex-shrink: 0;
|
||
}
|
||
.item-delete-btn:hover {
|
||
color: #c00;
|
||
background: rgba(200, 0, 0, 0.08);
|
||
}
|
||
|
||
/* ========================================== */
|
||
/* SEKTION: ERLEDIGTE EINTRÄGE */
|
||
/* ========================================== */
|
||
.completed-section {
|
||
margin-top: 10px;
|
||
background: #F8F7F3;
|
||
border: 1.5px dashed rgba(0,0,0,0.25);
|
||
border-radius: 10px;
|
||
padding: 12px;
|
||
}
|
||
|
||
.completed-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
cursor: pointer;
|
||
user-select: none;
|
||
}
|
||
|
||
.completed-title {
|
||
font-family: var(--font-serif, 'Lora', serif);
|
||
font-size: 0.95rem;
|
||
font-weight: 700;
|
||
color: #777;
|
||
margin: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
|
||
/* ========================================== */
|
||
/* LEERER ZUSTAND */
|
||
/* ========================================== */
|
||
.shopping-empty-state {
|
||
text-align: center;
|
||
padding: 40px 16px;
|
||
}
|
||
|
||
.empty-icon {
|
||
font-size: 3.2rem;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.empty-title {
|
||
font-family: var(--font-hand, 'Caveat', cursive);
|
||
font-size: 2.2rem;
|
||
color: var(--shop-accent);
|
||
margin: 0 0 6px 0;
|
||
}
|
||
|
||
.empty-desc {
|
||
color: #666;
|
||
font-size: 0.95rem;
|
||
max-width: 440px;
|
||
margin: 0 auto 20px auto;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
/* ========================================== */
|
||
/* MODAL: BEREICHE VERWALTEN (SUPERMARKT) */
|
||
/* ========================================== */
|
||
.modal-backdrop {
|
||
position: fixed;
|
||
top: 0; left: 0; right: 0; bottom: 0;
|
||
background: rgba(0, 0, 0, 0.55);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
z-index: 2000;
|
||
padding: 16px;
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity 0.2s ease;
|
||
}
|
||
.modal-backdrop.active {
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.modal-card {
|
||
background: #FFFDF7;
|
||
border: 2.5px solid var(--shop-border);
|
||
border-radius: 14px;
|
||
box-shadow: 6px 6px 0px var(--shop-border);
|
||
max-width: 520px;
|
||
width: 100%;
|
||
max-height: 90vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
transform: translateY(20px);
|
||
transition: transform 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||
}
|
||
.modal-backdrop.active .modal-card {
|
||
transform: translateY(0);
|
||
}
|
||
|
||
.modal-header {
|
||
background: var(--creme, #FDFBF7);
|
||
border-bottom: 2px solid var(--shop-border);
|
||
padding: 12px 18px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.modal-title {
|
||
font-family: var(--font-serif, 'Lora', serif);
|
||
font-size: 1.25rem;
|
||
font-weight: 700;
|
||
color: var(--shop-accent);
|
||
margin: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.modal-close-btn {
|
||
background: #FFFFFF;
|
||
border: 1.5px solid var(--shop-border);
|
||
border-radius: 50%;
|
||
width: 30px;
|
||
height: 30px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 1.1rem;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
color: #555;
|
||
transition: all 0.15s;
|
||
}
|
||
.modal-close-btn:hover {
|
||
background: #fce8e8;
|
||
color: #c00;
|
||
}
|
||
|
||
.modal-body {
|
||
padding: 16px 18px;
|
||
overflow-y: auto;
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 16px;
|
||
}
|
||
|
||
.cat-manage-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
list-style: none;
|
||
margin: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
.cat-manage-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
background: #FFFFFF;
|
||
border: 1.5px solid var(--shop-border);
|
||
border-radius: 8px;
|
||
padding: 8px 10px;
|
||
box-shadow: 2px 2px 0px rgba(0,0,0,0.08);
|
||
}
|
||
|
||
.cat-manage-info {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.cat-manage-icon {
|
||
font-size: 1.25rem;
|
||
width: 28px;
|
||
text-align: center;
|
||
}
|
||
|
||
.cat-manage-name {
|
||
font-weight: 600;
|
||
font-size: 0.95rem;
|
||
color: #222;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.cat-manage-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
|
||
.cat-reorder-btn {
|
||
background: #FFFFFF;
|
||
border: 1px solid rgba(0,0,0,0.3);
|
||
border-radius: 4px;
|
||
width: 26px;
|
||
height: 26px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
font-size: 0.75rem;
|
||
font-weight: bold;
|
||
color: #444;
|
||
transition: all 0.12s;
|
||
}
|
||
.cat-reorder-btn:hover:not(:disabled) {
|
||
background: var(--shop-accent);
|
||
color: #FFF;
|
||
border-color: var(--shop-accent);
|
||
}
|
||
.cat-reorder-btn:disabled {
|
||
opacity: 0.3;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.cat-del-btn {
|
||
background: transparent;
|
||
border: none;
|
||
color: #999;
|
||
font-size: 1.1rem;
|
||
cursor: pointer;
|
||
padding: 4px 6px;
|
||
border-radius: 4px;
|
||
transition: color 0.15s;
|
||
}
|
||
.cat-del-btn:hover {
|
||
color: #c00;
|
||
}
|
||
|
||
/* Formular Neue Kategorie */
|
||
.add-cat-form {
|
||
display: flex;
|
||
gap: 6px;
|
||
background: var(--shop-yellow);
|
||
border: 1.5px solid var(--shop-border);
|
||
border-radius: 8px;
|
||
padding: 8px;
|
||
}
|
||
.add-cat-icon-input {
|
||
width: 44px;
|
||
text-align: center;
|
||
border: 1px solid rgba(0,0,0,0.3);
|
||
border-radius: 6px;
|
||
font-size: 1.1rem;
|
||
outline: none;
|
||
}
|
||
.add-cat-name-input {
|
||
flex: 1;
|
||
border: 1px solid rgba(0,0,0,0.3);
|
||
border-radius: 6px;
|
||
padding: 6px 8px;
|
||
font-size: 0.9rem;
|
||
outline: none;
|
||
}
|
||
.add-cat-btn {
|
||
background: var(--shop-accent);
|
||
color: #FFF;
|
||
border: 1.5px solid var(--shop-border);
|
||
border-radius: 6px;
|
||
padding: 6px 12px;
|
||
font-size: 0.85rem;
|
||
font-weight: 700;
|
||
cursor: pointer;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* ========================================== */
|
||
/* TOAST NOTIFICATION */
|
||
/* ========================================== */
|
||
.shop-toast {
|
||
position: fixed;
|
||
bottom: 24px;
|
||
left: 50%;
|
||
transform: translateX(-50%) translateY(100px);
|
||
background: var(--shop-accent);
|
||
color: var(--creme, #FDFBF7);
|
||
padding: 10px 20px;
|
||
border-radius: 30px;
|
||
font-weight: 600;
|
||
font-size: 0.92rem;
|
||
box-shadow: 0 6px 16px rgba(0,0,0,0.25);
|
||
border: 2px solid var(--shop-border);
|
||
z-index: 9999;
|
||
opacity: 0;
|
||
transition: all 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||
pointer-events: none;
|
||
white-space: nowrap;
|
||
}
|
||
.shop-toast.active {
|
||
transform: translateX(-50%) translateY(0);
|
||
opacity: 1;
|
||
}
|
||
|
||
/* ========================================== */
|
||
/* RESPONSIVE DESIGN (MOBILE OPTIMIERT) */
|
||
/* ========================================== */
|
||
@media (max-width: 600px) {
|
||
.shop-nav {
|
||
padding: 8px 12px;
|
||
}
|
||
.shop-nav-btn {
|
||
padding: 6px 10px;
|
||
font-size: 0.82rem;
|
||
}
|
||
.shop-nav-title {
|
||
font-size: 1.45rem;
|
||
}
|
||
.shop-main {
|
||
margin: 12px auto 40px auto;
|
||
padding: 0 10px;
|
||
}
|
||
.notepad-card {
|
||
padding: 14px 12px;
|
||
border-radius: 10px;
|
||
}
|
||
.notepad-card::before {
|
||
left: 20px;
|
||
}
|
||
.notepad-header {
|
||
margin-bottom: 12px;
|
||
padding-bottom: 10px;
|
||
}
|
||
.notepad-title-group h2 {
|
||
font-size: 1.75rem;
|
||
}
|
||
.notepad-counter {
|
||
font-size: 0.95rem;
|
||
padding: 2px 8px;
|
||
}
|
||
.shop-add-form {
|
||
padding: 8px;
|
||
gap: 6px;
|
||
margin-bottom: 14px;
|
||
}
|
||
.shop-input {
|
||
font-size: 0.92rem;
|
||
padding: 7px 10px;
|
||
}
|
||
.shop-add-btn {
|
||
padding: 7px 12px;
|
||
font-size: 0.88rem;
|
||
}
|
||
.shopping-item-row {
|
||
padding: 8px;
|
||
gap: 8px;
|
||
}
|
||
.item-checkbox {
|
||
width: 20px;
|
||
height: 20px;
|
||
}
|
||
.item-text {
|
||
font-size: 0.95rem;
|
||
}
|
||
.category-name {
|
||
font-size: 0.95rem;
|
||
}
|
||
.item-source-badge {
|
||
max-width: 170px;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body class="shopping-body">
|
||
|
||
<!-- Schlanke, stimmige Kopfzeile: Nur EIN Zurück-Button, kein doppeltes X -->
|
||
<header class="shop-nav">
|
||
<a href="/" class="shop-nav-btn" title="Zurück zum Startmenü">
|
||
<span>⬅️</span> <span>Zurück</span>
|
||
</a>
|
||
|
||
<div class="shop-nav-center">
|
||
<h1 class="shop-nav-title">🛒 Einkaufsliste</h1>
|
||
</div>
|
||
|
||
<!-- Rechts: Bereiche anpassen Button -->
|
||
<button type="button" class="shop-nav-btn" onclick="openCategoriesModal()" title="Supermarkt-Bereiche & Reihenfolge anpassen">
|
||
<span>⚙️</span> <span>Bereiche</span>
|
||
</button>
|
||
</header>
|
||
|
||
<!-- Notizblock Hauptcontainer -->
|
||
<main class="shop-main">
|
||
<div class="notepad-card">
|
||
|
||
<!-- Block-Kopf -->
|
||
<div class="notepad-header">
|
||
<div class="notepad-title-group">
|
||
<h2>Einkaufszettel</h2>
|
||
<p class="notepad-subtitle">Nach Supermarkt-Gängen sortiert • Woche {{ current_week }}</p>
|
||
</div>
|
||
<div class="notepad-counter" id="shop-counter">
|
||
<span id="checked-count">0</span> / <span id="total-count">{{ items|length }}</span> erledigt
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Schnelleingabe mit optionaler Bereich-Wahl -->
|
||
<form class="shop-add-form" id="shop-add-form" onsubmit="onAddItemSubmit(event)">
|
||
<div class="shop-add-top">
|
||
<input
|
||
type="text"
|
||
id="shop-add-input"
|
||
class="shop-input"
|
||
placeholder="✏️ Zutat oder Artikel notieren (z. B. 500g Mehl)..."
|
||
autocomplete="off"
|
||
required
|
||
>
|
||
<button type="submit" class="shop-add-btn" id="shop-add-submit-btn">
|
||
<span>➕</span> <span>Hinzufügen</span>
|
||
</button>
|
||
</div>
|
||
<div class="shop-add-bottom">
|
||
<span>Bereich:</span>
|
||
<select id="shop-add-category" class="shop-cat-select" title="Supermarkt-Bereich auswählen">
|
||
<option value="">🪄 Automatisch erkennen</option>
|
||
{% for cat in categories %}
|
||
<option value="{{ cat.name }}">{{ cat.icon }} {{ cat.name }}</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
</form>
|
||
|
||
<!-- Aktionen-Toolbar -->
|
||
<div class="shop-actions-bar">
|
||
<div>
|
||
<a href="/cookbook?open=mealplan&prompt_transfer=1" class="shop-action-btn btn-wochenplan" title="Zutaten aus dem Wochenplan abrufen">
|
||
<span>📅</span> <span>Aus Wochenplan übernehmen</span>
|
||
</a>
|
||
</div>
|
||
<div style="display: flex; gap: 6px;">
|
||
<button type="button" class="shop-action-btn" onclick="deleteCheckedItems()" title="Erledigte Einträge von der Liste entfernen">
|
||
<span>🧹</span> <span>Erledigte löschen</span>
|
||
</button>
|
||
<button type="button" class="shop-action-btn" onclick="clearAllItems()" title="Gesamte Liste leeren">
|
||
<span>🗑️</span> <span>Alles leeren</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Gruppierte Ansicht nach Supermarkt-Bereichen -->
|
||
<div class="category-sections-container" id="category-sections-container">
|
||
<!-- Wird dynamisch durch renderShoppingList() befüllt -->
|
||
</div>
|
||
|
||
<!-- Bereich für Erledigte Einträge (wird am Ende angezeigt) -->
|
||
<div class="completed-section" id="completed-section" style="display: none;">
|
||
<div class="completed-header" onclick="toggleCompletedGroup()">
|
||
<h4 class="completed-title">
|
||
<span id="completed-toggle-icon">▼</span>
|
||
<span>✓ Bereits im Einkaufswagen (<span id="completed-badge-count">0</span>)</span>
|
||
</h4>
|
||
<button type="button" class="shop-action-btn" onclick="event.stopPropagation(); deleteCheckedItems();" style="padding: 2px 6px; font-size: 0.75rem;">
|
||
🧹 Löschen
|
||
</button>
|
||
</div>
|
||
<ul class="category-items-list" id="completed-items-list" style="margin-top: 8px;">
|
||
<!-- Erledigte Items -->
|
||
</ul>
|
||
</div>
|
||
|
||
<!-- Leerer Zustand -->
|
||
<div class="shopping-empty-state" id="shopping-empty-state" style="display: none;">
|
||
<div class="empty-icon">🎉</div>
|
||
<h3 class="empty-title">Alles eingekauft!</h3>
|
||
<p class="empty-desc">Deine Einkaufsliste ist momentan leer. Trage oben neue Besorgungen ein oder übernimm die Zutaten direkt aus deinem Wochenplan.</p>
|
||
<a href="/cookbook?open=mealplan&prompt_transfer=1" class="shop-action-btn btn-wochenplan" style="padding: 10px 20px; font-size: 0.95rem;">
|
||
<span>📅</span> <span>Zutaten aus Wochenplan holen</span>
|
||
</a>
|
||
</div>
|
||
|
||
</div>
|
||
</main>
|
||
|
||
<!-- ========================================== -->
|
||
<!-- MODAL: SUPERMARKT-BEREICHE VERWALTEN -->
|
||
<!-- ========================================== -->
|
||
<div id="modal-categories" class="modal-backdrop" onclick="onModalBackdropClick(event, 'modal-categories')">
|
||
<div class="modal-card">
|
||
<div class="modal-header">
|
||
<h3 class="modal-title"><span>🛒</span> Supermarkt-Laufweg anpassen</h3>
|
||
<button type="button" class="modal-close-btn" onclick="closeCategoriesModal()">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<p style="font-size: 0.88rem; color: #666; margin: 0;">
|
||
Passe die Bereiche an deinen gewohnten Supermarkt an. Mit den Pfeilen <strong>▲</strong> und <strong>▼</strong> bestimmst du die Reihenfolge deiner Gänge beim Einkaufen.
|
||
</p>
|
||
|
||
<!-- Liste der Kategorien -->
|
||
<ul class="cat-manage-list" id="cat-manage-list">
|
||
<!-- Dynamisch -->
|
||
</ul>
|
||
|
||
<!-- Neuen Bereich hinzufügen -->
|
||
<div>
|
||
<h4 style="margin: 0 0 6px 0; font-size: 0.95rem; font-family: var(--font-serif); color: var(--shop-accent);">
|
||
Neuen Bereich hinzufügen
|
||
</h4>
|
||
<form class="add-cat-form" onsubmit="onAddCategorySubmit(event)">
|
||
<input type="text" id="new-cat-icon" class="add-cat-icon-input" value="🛒" maxlength="4" title="Emoji-Icon">
|
||
<input type="text" id="new-cat-name" class="add-cat-name-input" placeholder="Name des Bereichs (z. B. Drogerie)..." required>
|
||
<button type="submit" class="add-cat-btn">➕ Hinzufügen</button>
|
||
</form>
|
||
</div>
|
||
|
||
<div style="display: flex; justify-content: flex-end; margin-top: 6px;">
|
||
<button type="button" class="shop-action-btn" onclick="resetDefaultCategories()" style="font-size: 0.78rem; color: #888;">
|
||
↺ Standard-Bereiche wiederherstellen
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Toast Notification -->
|
||
<div id="shop-toast" class="shop-toast">✓ Gespeichert</div>
|
||
|
||
<!-- ========================================== -->
|
||
<!-- JAVASCRIPT LOGIK -->
|
||
<!-- ========================================== -->
|
||
<script>
|
||
// Initialer State
|
||
let shoppingItems = {{ items_json|safe if items_json is defined else '[]' }};
|
||
let shoppingCategories = {{ categories_json|safe if categories_json is defined else '[]' }};
|
||
let completedExpanded = true;
|
||
|
||
function showToast(msg) {
|
||
const toast = document.getElementById('shop-toast');
|
||
toast.textContent = msg;
|
||
toast.classList.add('active');
|
||
setTimeout(() => toast.classList.remove('active'), 2500);
|
||
}
|
||
|
||
function updateCounter() {
|
||
const total = shoppingItems.length;
|
||
const checked = shoppingItems.filter(it => it.checked).length;
|
||
|
||
document.getElementById('checked-count').textContent = checked;
|
||
document.getElementById('total-count').textContent = total;
|
||
document.getElementById('completed-badge-count').textContent = checked;
|
||
|
||
const emptyState = document.getElementById('shopping-empty-state');
|
||
const catContainer = document.getElementById('category-sections-container');
|
||
const completedSec = document.getElementById('completed-section');
|
||
|
||
if (total === 0) {
|
||
emptyState.style.display = 'block';
|
||
catContainer.style.display = 'none';
|
||
completedSec.style.display = 'none';
|
||
} else {
|
||
emptyState.style.display = 'none';
|
||
catContainer.style.display = 'flex';
|
||
completedSec.style.display = checked > 0 ? 'block' : 'none';
|
||
}
|
||
}
|
||
|
||
// Rendert die Einkaufsliste gruppiert nach Supermarkt-Bereichen
|
||
function renderShoppingList() {
|
||
const container = document.getElementById('category-sections-container');
|
||
const completedList = document.getElementById('completed-items-list');
|
||
container.innerHTML = '';
|
||
completedList.innerHTML = '';
|
||
|
||
// Unfertige Items nach Kategorie gruppieren
|
||
const openItems = shoppingItems.filter(it => !it.checked);
|
||
const checkedItems = shoppingItems.filter(it => it.checked);
|
||
|
||
// Map aller bekannten Kategorien
|
||
const catMap = new Map();
|
||
shoppingCategories.forEach(cat => {
|
||
catMap.set(cat.name, {
|
||
category: cat,
|
||
items: []
|
||
});
|
||
});
|
||
|
||
// Zuweisen der offenen Items zu den Kategorien
|
||
const uncategorizedItems = [];
|
||
openItems.forEach(item => {
|
||
const catName = item.category || 'Drogerie & Sonstiges';
|
||
if (catMap.has(catName)) {
|
||
catMap.get(catName).items.push(item);
|
||
} else {
|
||
// Kategorie existiert nicht mehr in der Liste
|
||
if (!catMap.has('Sonstiges')) {
|
||
catMap.set(catName, {
|
||
category: { name: catName, icon: '📦', sort_order: 999 },
|
||
items: [item]
|
||
});
|
||
} else {
|
||
catMap.get('Sonstiges').items.push(item);
|
||
}
|
||
}
|
||
});
|
||
|
||
// Für jede Kategorie mit Items eine Gruppe rendern
|
||
let renderedCategoryCount = 0;
|
||
shoppingCategories.forEach(cat => {
|
||
const group = catMap.get(cat.name);
|
||
if (group && group.items.length > 0) {
|
||
renderedCategoryCount++;
|
||
const groupEl = document.createElement('div');
|
||
groupEl.className = 'category-group';
|
||
groupEl.id = `cat-group-${encodeURIComponent(cat.name)}`;
|
||
|
||
groupEl.innerHTML = `
|
||
<div class="category-group-header">
|
||
<div class="category-title-left">
|
||
<span class="category-icon">${escapeHtml(cat.icon || '🛒')}</span>
|
||
<h3 class="category-name">${escapeHtml(cat.name)}</h3>
|
||
</div>
|
||
<span class="category-count">${group.items.length}</span>
|
||
</div>
|
||
<ul class="category-items-list" id="cat-items-${encodeURIComponent(cat.name)}"></ul>
|
||
`;
|
||
|
||
const ul = groupEl.querySelector('.category-items-list');
|
||
group.items.forEach(item => {
|
||
ul.appendChild(createItemElement(item));
|
||
});
|
||
|
||
container.appendChild(groupEl);
|
||
}
|
||
});
|
||
|
||
// Falls es Kategorien außerhalb der Hauptliste gibt
|
||
catMap.forEach((group, catName) => {
|
||
if (!shoppingCategories.some(c => c.name === catName) && group.items.length > 0) {
|
||
const groupEl = document.createElement('div');
|
||
groupEl.className = 'category-group';
|
||
groupEl.innerHTML = `
|
||
<div class="category-group-header">
|
||
<div class="category-title-left">
|
||
<span class="category-icon">${escapeHtml(group.category.icon || '📦')}</span>
|
||
<h3 class="category-name">${escapeHtml(group.category.name)}</h3>
|
||
</div>
|
||
<span class="category-count">${group.items.length}</span>
|
||
</div>
|
||
<ul class="category-items-list"></ul>
|
||
`;
|
||
const ul = groupEl.querySelector('.category-items-list');
|
||
group.items.forEach(item => {
|
||
ul.appendChild(createItemElement(item));
|
||
});
|
||
container.appendChild(groupEl);
|
||
}
|
||
});
|
||
|
||
// Erledigte Items rendern
|
||
checkedItems.forEach(item => {
|
||
completedList.appendChild(createItemElement(item));
|
||
});
|
||
|
||
updateCounter();
|
||
}
|
||
|
||
// Erstellt das DOM-Element für ein Einkaufslisten-Item
|
||
function createItemElement(item) {
|
||
const li = document.createElement('li');
|
||
li.className = `shopping-item-row ${item.checked ? 'is-checked' : ''}`;
|
||
li.id = `shop-item-${item.id}`;
|
||
li.setAttribute('data-id', item.id);
|
||
|
||
const amountStr = (item.amount || item.unit) ? `<span class="item-amount-badge">${escapeHtml(item.amount || '')} ${escapeHtml(item.unit || '')}</span>` : '';
|
||
|
||
// Saubere Quelle: Nur Rezepttitel
|
||
const displaySource = item.display_source || cleanRecipeSource(item.source);
|
||
const sourceStr = displaySource ? `<span class="item-source-badge" title="${escapeHtml(displaySource)}">📖 ${escapeHtml(displaySource)}</span>` : '';
|
||
|
||
// Kategorie-Pill
|
||
const currentCatObj = shoppingCategories.find(c => c.name === item.category);
|
||
const catIcon = currentCatObj ? currentCatObj.icon : '🛒';
|
||
const catName = item.category || 'Sonstiges';
|
||
|
||
li.innerHTML = `
|
||
<label class="item-left">
|
||
<input
|
||
type="checkbox"
|
||
class="item-checkbox"
|
||
${item.checked ? 'checked' : ''}
|
||
onchange="toggleItem(${item.id})"
|
||
>
|
||
<div class="item-text-wrapper">
|
||
<span class="item-text">
|
||
${amountStr}${escapeHtml(item.item_name)}
|
||
</span>
|
||
<div class="item-sub-info">
|
||
${sourceStr}
|
||
<span class="item-cat-pill" onclick="promptChangeItemCategory(event, ${item.id}, '${escapeHtml(catName)}')" title="Bereich ändern">
|
||
<span>${escapeHtml(catIcon)}</span>
|
||
<span>${escapeHtml(catName)}</span>
|
||
<span style="font-size: 0.65rem; opacity: 0.7;">▼</span>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</label>
|
||
<button type="button" class="item-delete-btn" onclick="deleteSingleItem(${item.id})" title="Löschen">×</button>
|
||
`;
|
||
|
||
return li;
|
||
}
|
||
|
||
function cleanRecipeSource(src) {
|
||
if (!src) return '';
|
||
const m = String(src).match(/\((.+)\)$/);
|
||
if (m && m[1]) return m[1].trim();
|
||
return String(src).replace(/^Wochenplan\s*KW\s*\d+\s*/i, '').trim();
|
||
}
|
||
|
||
// ==========================================
|
||
// AKTIONEN: ITEMS HINZUFÜGEN / TOGGLE / LÖSCHEN
|
||
// ==========================================
|
||
|
||
async function onAddItemSubmit(e) {
|
||
e.preventDefault();
|
||
const input = document.getElementById('shop-add-input');
|
||
const catSelect = document.getElementById('shop-add-category');
|
||
const text = input.value.trim();
|
||
const chosenCat = catSelect.value;
|
||
if (!text) return;
|
||
|
||
const submitBtn = document.getElementById('shop-add-submit-btn');
|
||
submitBtn.disabled = true;
|
||
|
||
try {
|
||
const payload = {
|
||
item_name: text,
|
||
source: 'Manuell'
|
||
};
|
||
if (chosenCat) {
|
||
payload.category = chosenCat;
|
||
}
|
||
|
||
const res = await fetch('/api/shopping-list', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(payload)
|
||
});
|
||
const data = await res.json();
|
||
if (data.status === 'ok' && data.item) {
|
||
shoppingItems.unshift(data.item);
|
||
input.value = '';
|
||
renderShoppingList();
|
||
showToast(`✓ "${text}" hinzugefügt`);
|
||
}
|
||
} catch (err) {
|
||
alert('Fehler beim Hinzufügen: ' + err.message);
|
||
} finally {
|
||
submitBtn.disabled = false;
|
||
}
|
||
}
|
||
|
||
async function toggleItem(id) {
|
||
const it = shoppingItems.find(x => x.id === id);
|
||
if (!it) return;
|
||
|
||
// Optimistisches UI Update
|
||
it.checked = !it.checked;
|
||
renderShoppingList();
|
||
|
||
try {
|
||
const res = await fetch(`/api/shopping-list/${id}/toggle`, { method: 'POST' });
|
||
const data = await res.json();
|
||
if (data.status === 'ok' && data.item) {
|
||
it.checked = Boolean(data.item.checked);
|
||
renderShoppingList();
|
||
}
|
||
} catch (err) {
|
||
console.error(err);
|
||
}
|
||
}
|
||
|
||
async function deleteSingleItem(id) {
|
||
shoppingItems = shoppingItems.filter(x => x.id !== id);
|
||
renderShoppingList();
|
||
|
||
try {
|
||
await fetch(`/api/shopping-list/${id}`, { method: 'DELETE' });
|
||
} catch (err) {
|
||
console.error(err);
|
||
}
|
||
}
|
||
|
||
async function deleteCheckedItems() {
|
||
const hasChecked = shoppingItems.some(x => x.checked);
|
||
if (!hasChecked) {
|
||
showToast('Keine erledigten Einträge vorhanden.');
|
||
return;
|
||
}
|
||
|
||
shoppingItems = shoppingItems.filter(x => !x.checked);
|
||
renderShoppingList();
|
||
showToast('✓ Erledigte Einträge gelöscht');
|
||
|
||
try {
|
||
await fetch('/api/shopping-list/checked', { method: 'DELETE' });
|
||
} catch (err) {
|
||
console.error(err);
|
||
}
|
||
}
|
||
|
||
async function clearAllItems() {
|
||
if (shoppingItems.length === 0) return;
|
||
if (!confirm('Möchtest du wirklich die gesamte Einkaufsliste leeren?')) return;
|
||
|
||
shoppingItems = [];
|
||
renderShoppingList();
|
||
showToast('✓ Einkaufsliste geleert');
|
||
|
||
try {
|
||
await fetch('/api/shopping-list/all', { method: 'DELETE' });
|
||
} catch (err) {
|
||
console.error(err);
|
||
}
|
||
}
|
||
|
||
// Schnelles Ändern der Kategorie an einem Item
|
||
async function promptChangeItemCategory(event, itemId, currentCat) {
|
||
event.preventDefault();
|
||
event.stopPropagation();
|
||
|
||
const catNames = shoppingCategories.map(c => `${c.icon} ${c.name}`);
|
||
const optionsStr = catNames.map((c, i) => `${i + 1}. ${c}`).join('\n');
|
||
const input = prompt(`Supermarkt-Bereich auswählen:\n\n${optionsStr}\n\nNummer oder Namen eingeben:`, currentCat);
|
||
|
||
if (!input) return;
|
||
|
||
let targetCatName = '';
|
||
const num = parseInt(input.trim(), 10);
|
||
if (!isNaN(num) && num >= 1 && num <= shoppingCategories.length) {
|
||
targetCatName = shoppingCategories[num - 1].name;
|
||
} else {
|
||
const found = shoppingCategories.find(c => c.name.toLowerCase() === input.trim().toLowerCase());
|
||
targetCatName = found ? found.name : input.trim();
|
||
}
|
||
|
||
if (!targetCatName) return;
|
||
|
||
const it = shoppingItems.find(x => x.id === itemId);
|
||
if (it) {
|
||
it.category = targetCatName;
|
||
renderShoppingList();
|
||
}
|
||
|
||
try {
|
||
await fetch(`/api/shopping-list/${itemId}/category`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ category: targetCatName })
|
||
});
|
||
showToast(`✓ Verschoben nach "${targetCatName}"`);
|
||
} catch (err) {
|
||
console.error(err);
|
||
}
|
||
}
|
||
|
||
function toggleCompletedGroup() {
|
||
const list = document.getElementById('completed-items-list');
|
||
const icon = document.getElementById('completed-toggle-icon');
|
||
completedExpanded = !completedExpanded;
|
||
list.style.display = completedExpanded ? 'flex' : 'none';
|
||
icon.textContent = completedExpanded ? '▼' : '▶';
|
||
}
|
||
|
||
// ==========================================
|
||
// MODAL: BEREICHE VERWALTEN (SUPERMARKT)
|
||
// ==========================================
|
||
|
||
function openCategoriesModal() {
|
||
renderCategoryManageList();
|
||
document.getElementById('modal-categories').classList.add('active');
|
||
}
|
||
|
||
function closeCategoriesModal() {
|
||
document.getElementById('modal-categories').classList.remove('active');
|
||
}
|
||
|
||
function onModalBackdropClick(e, modalId) {
|
||
if (e.target.id === modalId) {
|
||
closeCategoriesModal();
|
||
}
|
||
}
|
||
|
||
function renderCategoryManageList() {
|
||
const list = document.getElementById('cat-manage-list');
|
||
list.innerHTML = '';
|
||
|
||
shoppingCategories.forEach((cat, index) => {
|
||
const li = document.createElement('li');
|
||
li.className = 'cat-manage-row';
|
||
|
||
const isFirst = index === 0;
|
||
const isLast = index === shoppingCategories.length - 1;
|
||
|
||
li.innerHTML = `
|
||
<div class="cat-manage-info">
|
||
<span class="cat-manage-icon">${escapeHtml(cat.icon || '🛒')}</span>
|
||
<span class="cat-manage-name">${escapeHtml(cat.name)}</span>
|
||
</div>
|
||
<div class="cat-manage-actions">
|
||
<button type="button" class="cat-reorder-btn" onclick="moveCategory(${index}, -1)" ${isFirst ? 'disabled' : ''} title="Nach oben (früher im Laufweg)">▲</button>
|
||
<button type="button" class="cat-reorder-btn" onclick="moveCategory(${index}, 1)" ${isLast ? 'disabled' : ''} title="Nach unten (später im Laufweg)">▼</button>
|
||
<button type="button" class="cat-del-btn" onclick="deleteCategory(${cat.id}, '${escapeHtml(cat.name)}')" title="Bereich löschen">×</button>
|
||
</div>
|
||
`;
|
||
list.appendChild(li);
|
||
});
|
||
|
||
// Dropdown in Schnelleingabe aktualisieren
|
||
const select = document.getElementById('shop-add-category');
|
||
const currentVal = select.value;
|
||
select.innerHTML = '<option value="">🪄 Automatisch erkennen</option>';
|
||
shoppingCategories.forEach(cat => {
|
||
const opt = document.createElement('option');
|
||
opt.value = cat.name;
|
||
opt.textContent = `${cat.icon} ${cat.name}`;
|
||
select.appendChild(opt);
|
||
});
|
||
select.value = currentVal;
|
||
}
|
||
|
||
async function moveCategory(index, delta) {
|
||
const newIndex = index + delta;
|
||
if (newIndex < 0 || newIndex >= shoppingCategories.length) return;
|
||
|
||
const item = shoppingCategories.splice(index, 1)[0];
|
||
shoppingCategories.splice(newIndex, 0, item);
|
||
|
||
renderCategoryManageList();
|
||
renderShoppingList();
|
||
|
||
try {
|
||
const ids = shoppingCategories.map(c => c.id);
|
||
await fetch('/api/shopping-categories/reorder', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ category_ids: ids })
|
||
});
|
||
showToast('✓ Reihenfolge gespeichert');
|
||
} catch (err) {
|
||
console.error(err);
|
||
}
|
||
}
|
||
|
||
async function onAddCategorySubmit(e) {
|
||
e.preventDefault();
|
||
const iconInput = document.getElementById('new-cat-icon');
|
||
const nameInput = document.getElementById('new-cat-name');
|
||
const icon = iconInput.value.trim() || '🛒';
|
||
const name = nameInput.value.trim();
|
||
|
||
if (!name) return;
|
||
|
||
try {
|
||
const res = await fetch('/api/shopping-categories', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ name, icon })
|
||
});
|
||
const data = await res.json();
|
||
if (data.status === 'ok' && data.category) {
|
||
shoppingCategories.push(data.category);
|
||
nameInput.value = '';
|
||
renderCategoryManageList();
|
||
renderShoppingList();
|
||
showToast(`✓ Bereich "${name}" hinzugefügt`);
|
||
}
|
||
} catch (err) {
|
||
alert('Fehler: ' + err.message);
|
||
}
|
||
}
|
||
|
||
async function deleteCategory(id, name) {
|
||
if (!confirm(`Möchtest du den Bereich "${name}" wirklich löschen?\nVorhandene Zutaten werden auf "Drogerie & Sonstiges" übertragen.`)) {
|
||
return;
|
||
}
|
||
|
||
shoppingCategories = shoppingCategories.filter(c => c.id !== id);
|
||
renderCategoryManageList();
|
||
renderShoppingList();
|
||
|
||
try {
|
||
await fetch(`/api/shopping-categories/${id}`, { method: 'DELETE' });
|
||
showToast(`✓ Bereich "${name}" gelöscht`);
|
||
} catch (err) {
|
||
console.error(err);
|
||
}
|
||
}
|
||
|
||
async function resetDefaultCategories() {
|
||
if (!confirm('Möchtest du die Supermarkt-Bereiche auf die Standard-Reihenfolge zurücksetzen?')) return;
|
||
|
||
try {
|
||
const res = await fetch('/api/shopping-categories/reset-defaults', { method: 'POST' });
|
||
const data = await res.json();
|
||
if (data.status === 'ok' && data.categories) {
|
||
shoppingCategories = data.categories;
|
||
renderCategoryManageList();
|
||
renderShoppingList();
|
||
showToast('✓ Standard-Bereiche wiederhergestellt');
|
||
}
|
||
} catch (err) {
|
||
alert('Fehler: ' + err.message);
|
||
}
|
||
}
|
||
|
||
function escapeHtml(str) {
|
||
if (!str) return '';
|
||
return String(str)
|
||
.replace(/&/g, "&")
|
||
.replace(/</g, "<")
|
||
.replace(/>/g, ">")
|
||
.replace(/"/g, """)
|
||
.replace(/'/g, "'");
|
||
}
|
||
|
||
// Beim Laden der Seite initial rendern
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
renderShoppingList();
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|