From c344d59d5a24b5ce5f9d680db6ae129d70321fcb Mon Sep 17 00:00:00 2001 From: ulsklyc <108589275+ulsklyc@users.noreply.github.com> Date: Tue, 24 Mar 2026 20:28:19 +0100 Subject: [PATCH] =?UTF-8?q?feat:=20Phase=202=20Schritt=2011+12=20=E2=80=94?= =?UTF-8?q?=20Essensplan-Modul=20+=20Einkaufslisten-Integration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - server/routes/meals.js: vollständige REST-API (GET Woche, POST/PUT/DELETE Mahlzeit, POST/PATCH/DELETE Zutaten, GET Autocomplete-Suggestions, POST to-shopping-list, POST week-to-shopping-list) - public/pages/meals.js: Wochengitter (Mo–So × 4 Mahlzeit-Typen), Navigations-Buttons, CRUD-Modal mit Autocomplete, Zutaten-Verwaltung, Einkaufslisten-Transfer-Button - public/styles/meals.css: Wochengitter, Slot-Karten, Modal-Overlay, Zutaten-Zeilen, Transfer-Panel, Typ-Farben - test-meals.js: 22 Tests (CRUD, Wochensortierung, Constraint, CASCADE, Integration, Autocomplete, Wochenberechnung) - package.json: test:meals + Gesamt-Test-Suite erweitert - public/index.html: meals.css eingebunden Gesamt: 93 Tests bestanden (29 DB + 8 Dashboard + 17 Tasks + 17 Shopping + 22 Meals) Co-Authored-By: Claude Sonnet 4.6 --- package.json | 3 +- public/index.html | 1 + public/pages/meals.js | 593 +++++++++++++++++++++++++++++++++++++++- public/styles/meals.css | 509 ++++++++++++++++++++++++++++++++++ server/routes/meals.js | 467 ++++++++++++++++++++++++++++++- test-meals.js | 317 +++++++++++++++++++++ 6 files changed, 1871 insertions(+), 19 deletions(-) create mode 100644 public/styles/meals.css create mode 100644 test-meals.js diff --git a/package.json b/package.json index 80895d1..a8e0f9d 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "test:dashboard": "node --experimental-sqlite test-dashboard.js", "test:tasks": "node --experimental-sqlite test-tasks.js", "test:shopping": "node --experimental-sqlite test-shopping.js", - "test": "node --experimental-sqlite test-db.js && node --experimental-sqlite test-dashboard.js && node --experimental-sqlite test-tasks.js && node --experimental-sqlite test-shopping.js" + "test:meals": "node --experimental-sqlite test-meals.js", + "test": "node --experimental-sqlite test-db.js && node --experimental-sqlite test-dashboard.js && node --experimental-sqlite test-tasks.js && node --experimental-sqlite test-shopping.js && node --experimental-sqlite test-meals.js" }, "dependencies": { "bcrypt": "^5.1.1", diff --git a/public/index.html b/public/index.html index ab09bcb..9f4b52f 100644 --- a/public/index.html +++ b/public/index.html @@ -19,6 +19,7 @@ + diff --git a/public/pages/meals.js b/public/pages/meals.js index db1103a..84ff9ee 100644 --- a/public/pages/meals.js +++ b/public/pages/meals.js @@ -1,25 +1,592 @@ /** - * Modul: Meals - * Zweck: Seite für das Meals-Modul - * Abhängigkeiten: /api.js + * Modul: Essensplan (Meals) + * Zweck: Wochenansicht mit Mahlzeit-CRUD, Zutaten-Verwaltung und Einkaufslisten-Integration + * Abhängigkeiten: /api.js, /router.js (window.oikos) */ import { api } from '/api.js'; -/** - * @param {HTMLElement} container - * @param {{ user: object }} context - */ +// -------------------------------------------------------- +// Konstanten +// -------------------------------------------------------- + +const MEAL_TYPES = [ + { key: 'breakfast', label: 'Frühstück', icon: 'sunrise' }, + { key: 'lunch', label: 'Mittagessen', icon: 'sun' }, + { key: 'dinner', label: 'Abendessen', icon: 'moon' }, + { key: 'snack', label: 'Snack', icon: 'cookie' }, +]; + +const DAY_NAMES = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So']; + +// -------------------------------------------------------- +// State +// -------------------------------------------------------- + +let state = { + currentWeek: null, // YYYY-MM-DD (Montag) + meals: [], + lists: [], // Einkaufslisten für Transfer-Dropdown + modal: null, +}; + +// -------------------------------------------------------- +// Datumshelfer +// -------------------------------------------------------- + +function getMondayOf(dateStr) { + const d = new Date(dateStr + 'T00:00:00Z'); + const day = d.getUTCDay(); + const diff = (day === 0 ? -6 : 1 - day); + d.setUTCDate(d.getUTCDate() + diff); + return d.toISOString().slice(0, 10); +} + +function addDays(dateStr, n) { + const d = new Date(dateStr + 'T00:00:00Z'); + d.setUTCDate(d.getUTCDate() + n); + return d.toISOString().slice(0, 10); +} + +function formatWeekLabel(monday) { + const sunday = addDays(monday, 6); + const fmt = (s) => { + const d = new Date(s + 'T00:00:00Z'); + return `${d.getUTCDate().toString().padStart(2, '0')}.${(d.getUTCMonth() + 1).toString().padStart(2, '0')}.${d.getUTCFullYear()}`; + }; + return `${fmt(monday)} – ${fmt(sunday)}`; +} + +function isToday(dateStr) { + return dateStr === new Date().toISOString().slice(0, 10); +} + +function formatDayDate(dateStr) { + const d = new Date(dateStr + 'T00:00:00Z'); + return `${d.getUTCDate()}.${d.getUTCMonth() + 1}.`; +} + +// -------------------------------------------------------- +// API-Wrapper +// -------------------------------------------------------- + +async function loadWeek(week) { + const res = await api.get(`/meals?week=${week}`); + state.meals = res.data; + state.currentWeek = getMondayOf(week); +} + +async function loadLists() { + try { + const res = await api.get('/shopping'); + state.lists = res.data; + } catch { + state.lists = []; + } +} + +// -------------------------------------------------------- +// Render +// -------------------------------------------------------- + export async function render(container, { user }) { container.innerHTML = ` -
-