Created
March 13, 2026 17:06
-
-
Save pksbogastro/424c5ed31119f273df7550a6d9e0f62f to your computer and use it in GitHub Desktop.
Einkaufen
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!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 - Neustart</title> | |
| <style> | |
| :root { --primary: #4CAF50; --secondary: #2196F3; --warning: #ff9800; --danger: #ff5252; --bg: #f4f4f9; } | |
| body { font-family: sans-serif; background: var(--bg); margin: 0; padding: 10px; text-align: center; -webkit-tap-highlight-color: transparent; } | |
| .header-area { background: white; padding: 12px; border-radius: 15px; margin-bottom: 15px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); position: sticky; top: 0; z-index: 2000; } | |
| select { width: 100%; padding: 12px; font-size: 18px; font-weight: bold; border: 2px solid var(--secondary); border-radius: 8px; margin-bottom: 10px; } | |
| .btn-row { display: flex; gap: 5px; margin-bottom: 5px; } | |
| button { padding: 12px; border: none; border-radius: 10px; cursor: pointer; font-weight: bold; font-size: 14px; } | |
| .btn-main { width: 95%; max-width: 420px; padding: 15px; margin: 5px auto; display: block; } | |
| .btn-add { background: var(--primary); color: white; } | |
| .btn-mode { background: var(--secondary); color: white; } | |
| .btn-multi { background: #673AB7; color: white; } | |
| #admin-panel { background: #fff; padding: 15px; border-radius: 12px; margin-bottom: 15px; border: 1px solid #ddd; display: none; } | |
| input[type="text"] { width: 90%; padding: 12px; margin-bottom: 10px; border: 1px solid #ccc; border-radius: 8px; font-size: 16px; } | |
| .card { background: white; border-radius: 12px; padding: 10px; margin-bottom: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.08); display: flex; align-items: center; position: relative; min-height: 60px; } | |
| .card img { width: 70px; height: 50px; object-fit: cover; border-radius: 6px; margin-right: 10px; } | |
| .card .info { flex-grow: 1; text-align: left; font-weight: bold; font-size: 1.1em; } | |
| .card input[type="number"] { width: 45px; padding: 8px; text-align: center; border: 1px solid #ccc; border-radius: 5px; } | |
| .multi-checkbox { width: 28px; height: 28px; margin-right: 15px; display: none; } | |
| body.multi-mode .multi-checkbox { display: block; } | |
| body.multi-mode .qty-box, body.multi-mode .del-x { display: none; } | |
| .del-x { background: var(--danger); color: white; width: 24px; height: 24px; border-radius: 50%; position: absolute; top: -5px; right: -5px; line-height: 24px; font-size: 14px; } | |
| .done { opacity: 0.3; filter: grayscale(100%); } | |
| #modal { display: none; position: fixed; z-index: 5000; left:0; top:0; width:100%; height:100%; background:rgba(0,0,0,0.8); justify-content:center; align-items:center; } | |
| .modal-box { background:white; padding:20px; border-radius:15px; width: 85%; } | |
| .opt { padding:15px; margin:8px 0; background:#f0f0f0; border-radius:10px; font-weight:bold; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="header-area"> | |
| <select id="listSelect" onchange="changeList()"></select> | |
| <div id="nav-standard" class="btn-row"> | |
| <button style="flex:1; background:#eee;" onclick="newList()">+ Neu</button> | |
| <button style="flex:1; background:#eee;" onclick="renameList()">✏️ Name</button> | |
| <button style="flex:1; background:var(--danger); color:white;" onclick="delList()">🗑️ Lösch</button> | |
| </div> | |
| <div id="nav-multi" class="btn-row" style="display:none;"> | |
| <button class="btn-multi" style="flex:3;" onclick="openCopyModal()">In Liste kopieren</button> | |
| <button style="flex:1; background:#ccc;" onclick="toggleMultiMode()">X</button> | |
| </div> | |
| </div> | |
| <div id="controls-main"> | |
| <button class="btn-main btn-add" onclick="toggleAdmin()">➕ Artikel hinzufügen</button> | |
| <div id="admin-panel"> | |
| <input type="text" id="pName" placeholder="Name..."> | |
| <input type="file" id="pImage" accept="image/*"> | |
| <button class="btn-add" onclick="saveProduct()" style="width:100%; margin-top:10px;">Speichern</button> | |
| </div> | |
| <button class="btn-main btn-multi" onclick="toggleMultiMode()">📦 Mehrere kopieren</button> | |
| <button id="modeBtn" class="btn-main btn-mode" onclick="toggleMode()">➡️ Einkaufsmodus</button> | |
| </div> | |
| <div id="list-container"></div> | |
| <div id="modal"> | |
| <div class="modal-box"> | |
| <h3>Kopieren nach...</h3> | |
| <div id="modal-opts"></div> | |
| <button onclick="closeModal()" style="width:100%; background:#ccc; padding:10px; border-radius:8px; margin-top:10px;">Abbruch</button> | |
| </div> | |
| </div> | |
| <script> | |
| // REINIGUNG & DATEN-RETTUNG | |
| function cleanupAndLoad() { | |
| let combined = { active: "Standard", lists: { "Standard": [] } }; | |
| const keys = ['shop_vUltimate', 'shop_vFinal_Final', 'shopDB_vFinal', 'shopDB_vSmart', 'shopDB_v4', 'shopDB_v5']; | |
| keys.forEach(k => { | |
| try { | |
| const data = JSON.parse(localStorage.getItem(k)); | |
| if (data && data.lists) { | |
| Object.keys(data.lists).forEach(listName => { | |
| if (!combined.lists[listName]) combined.lists[listName] = []; | |
| data.lists[listName].forEach(item => { | |
| if (!combined.lists[listName].find(i => i.name === item.name)) { | |
| combined.lists[listName].push(item); | |
| } | |
| }); | |
| }); | |
| } | |
| // Danach löschen wir den alten Schlüssel, um Platz zu schaffen | |
| if (k !== 'shop_FINAL_VERSION') localStorage.removeItem(k); | |
| } catch(e) {} | |
| }); | |
| return combined; | |
| } | |
| let db = JSON.parse(localStorage.getItem('shop_FINAL_VERSION')) || cleanupAndLoad(); | |
| let isShopping = false; | |
| let isMulti = false; | |
| function sync() { localStorage.setItem('shop_FINAL_VERSION', JSON.stringify(db)); render(); } | |
| function render() { | |
| const sel = document.getElementById('listSelect'); | |
| sel.innerHTML = ''; | |
| Object.keys(db.lists).forEach(l => { | |
| let o = document.createElement('option'); o.value = l; o.innerText = l; | |
| if(l === db.active) o.selected = true; | |
| sel.appendChild(o); | |
| }); | |
| document.body.className = isMulti ? 'multi-mode' : ''; | |
| document.getElementById('nav-standard').style.display = isMulti ? 'none' : 'flex'; | |
| document.getElementById('nav-multi').style.display = isMulti ? 'flex' : 'none'; | |
| document.getElementById('controls-main').style.display = isMulti ? 'none' : 'block'; | |
| const cont = document.getElementById('list-container'); | |
| cont.innerHTML = ''; | |
| (db.lists[db.active] || []).forEach(p => { | |
| if(isShopping && p.count <= 0) return; | |
| const card = document.createElement('div'); | |
| card.className = `card ${p.done ? 'done' : ''}`; | |
| card.innerHTML = ` | |
| <input type="checkbox" class="multi-checkbox" data-id="${p.id}"> | |
| ${!isShopping && !isMulti ? `<div class="del-x" onclick="event.stopPropagation(); delItem(${p.id})">×</div>` : ''} | |
| <img src="${p.img || ''}"> | |
| <div class="info">${p.name}</div> | |
| <div class="qty-box" style="width:50px;"> | |
| ${!isShopping ? `<input type="number" value="${p.count}" onchange="updQty(${p.id}, this.value)">` : `<b>${p.count}x</b>`} | |
| </div> | |
| `; | |
| card.onclick = (e) => { | |
| if(e.target.tagName === 'INPUT') return; | |
| if(isMulti) { const cb = card.querySelector('.multi-checkbox'); cb.checked = !cb.checked; } | |
| else if(isShopping) { p.done = !p.done; sync(); } | |
| }; | |
| cont.appendChild(card); | |
| }); | |
| } | |
| function toggleMultiMode() { isMulti = !isMulti; render(); } | |
| function openCopyModal() { | |
| const checked = Array.from(document.querySelectorAll('.multi-checkbox:checked')).map(cb => cb.dataset.id); | |
| if(checked.length === 0) return alert("Nichts gewählt!"); | |
| const opts = document.getElementById('modal-opts'); | |
| opts.innerHTML = ''; | |
| Object.keys(db.lists).forEach(l => { | |
| if(l !== db.active) { | |
| let d = document.createElement('div'); d.className = 'opt'; d.innerText = l; | |
| d.onclick = () => { | |
| checked.forEach(id => { | |
| let item = db.lists[db.active].find(x => x.id == id); | |
| db.lists[l].push({...item, id: Date.now() + Math.random(), count: 0}); | |
| }); | |
| toggleMultiMode(); closeModal(); sync(); | |
| }; | |
| opts.appendChild(d); | |
| } | |
| }); | |
| document.getElementById('modal').style.display = 'flex'; | |
| } | |
| function saveProduct() { | |
| const n = document.getElementById('pName').value; | |
| const f = document.getElementById('pImage').files[0]; | |
| if(!n || !f) return alert("Name und Bild!"); | |
| const r = new FileReader(); | |
| r.onload = e => { | |
| db.lists[db.active].push({ id: Date.now(), name: n, img: e.target.result, count: 0, done: false }); | |
| document.getElementById('admin-panel').style.display = 'none'; | |
| sync(); | |
| }; | |
| r.readAsDataURL(f); | |
| } | |
| function toggleAdmin() { let p = document.getElementById('admin-panel'); p.style.display = p.style.display === 'none' ? 'block' : 'none'; } | |
| function renameList() { let n = prompt("Neuer Name:", db.active); if(n && n !== db.active) { db.lists[n] = db.lists[db.active]; delete db.lists[db.active]; db.active = n; sync(); } } | |
| function changeList() { db.active = document.getElementById('listSelect').value; isMulti = false; sync(); } | |
| function newList() { let n = prompt("Name:"); if(n) { db.lists[n] = []; db.active = n; sync(); } } | |
| function delList() { if(confirm("Löschen?")) { delete db.lists[db.active]; db.active = Object.keys(db.lists)[0] || "Standard"; if(!db.lists[db.active]) db.lists[db.active]=[]; sync(); } } | |
| function delItem(id) { if(confirm("Löschen?")) { db.lists[db.active] = db.lists[db.active].filter(x => x.id !== id); sync(); } } | |
| function updQty(id, v) { let p = db.lists[db.active].find(x => x.id === id); if(p) p.count = parseInt(v)||0; sync(); } | |
| function toggleMode() { isShopping = !isShopping; isMulti = false; document.getElementById('modeBtn').innerText = isShopping ? "⬅️ Planen" : "➡️ Einkaufen"; render(); } | |
| function closeModal() { document.getElementById('modal').style.display = 'none'; } | |
| render(); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment