diff --git a/components/WeekEditor.tsx b/components/WeekEditor.tsx index b724eb8..4e7f9c3 100644 --- a/components/WeekEditor.tsx +++ b/components/WeekEditor.tsx @@ -35,6 +35,19 @@ export default function WeekEditor({ monday }: { monday: string }) { null ); const [selectedDish, setSelectedDish] = useState(""); + const [dishSearch, setDishSearch] = useState(""); + + function startAdding(iso: string, section: string) { + setAdding({ iso, section }); + setSelectedDish(""); + setDishSearch(""); + } + + function cancelAdding() { + setAdding(null); + setSelectedDish(""); + setDishSearch(""); + } const load = useCallback(async () => { setError(null); @@ -77,8 +90,7 @@ export default function WeekEditor({ monday }: { monday: string }) { : formatMessage(dict.weekEditor.saveError, { message: data.error ?? String(res.status) }) ); } - setAdding(null); - setSelectedDish(""); + cancelAdding(); await load(); } @@ -90,6 +102,11 @@ export default function WeekEditor({ monday }: { monday: string }) { } const [hintBefore, hintAfter] = dict.weekEditor.newDishesHint.split("{link}"); + const selectedDishObj = dishes.find((d) => d.id === selectedDish) ?? null; + const query = dishSearch.trim().toLowerCase(); + const filteredDishes = query + ? dishes.filter((d) => localized(d, locale).toLowerCase().includes(query)) + : dishes; return (
@@ -168,18 +185,57 @@ export default function WeekEditor({ monday }: { monday: string }) { })} {isAdding ? (
- + {selectedDishObj ? ( +
+ + {localized(selectedDishObj, locale)} ( + {formatPrice(selectedDishObj.price)}) + + +
+ ) : ( + <> + setDishSearch(e.target.value)} + /> + + + )}
@@ -205,10 +258,7 @@ export default function WeekEditor({ monday }: { monday: string }) { diff --git a/db/seed-demo.mjs b/db/seed-demo.mjs new file mode 100644 index 0000000..3c1a072 --- /dev/null +++ b/db/seed-demo.mjs @@ -0,0 +1,30 @@ +#!/usr/bin/env node +// Spielt db/seed-demo.sql ein: ersetzt Essenszeiten/Gerichte/Allergene/ +// Zusatzstoffe/Wochenplan durch den Beispieldatensatz. Nur für lokale +// Entwicklung/Demos gedacht, läuft nicht automatisch beim Start. + +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; +import pg from "pg"; + +const sql = readFileSync( + join(dirname(fileURLToPath(import.meta.url)), "seed-demo.sql"), + "utf8" +); + +async function main() { + const client = new pg.Client({ connectionString: process.env.DATABASE_URL }); + await client.connect(); + try { + await client.query(sql); + console.log("db:seed-demo – Beispieldaten eingespielt."); + } finally { + await client.end(); + } +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/db/seed-demo.sql b/db/seed-demo.sql new file mode 100644 index 0000000..bddcf6d --- /dev/null +++ b/db/seed-demo.sql @@ -0,0 +1,130 @@ +-- ============================================================= +-- Beispieldaten für die lokale Entwicklung/Demo (NICHT Teil der +-- automatischen Migrationen – frische Deployments starten bewusst +-- leer, s. db/migrations/0001_init.sql). Manuell anwendbar über +-- "npm run db:seed-demo". +-- +-- Ersetzt vorhandene Essenszeiten/Gerichte/Allergene/Zusatzstoffe/ +-- Wochenplan-Einträge durch den ursprünglichen Beispieldatensatz. +-- ============================================================= + +delete from menu_entries; +delete from dish_suggestions; +delete from dishes; +delete from sections; +delete from allergens; +delete from additives; + +-- ---------- Essenszeiten ---------- +insert into sections (key, label, label_en, label_pl, time_range, sort) values + ('brotzeit', 'Brotzeit', 'Breakfast', 'Śniadanie', '09:00 - 10:00', 1), + ('mittagessen', 'Mittagessen', 'Lunch', 'Obiad', '11:30 - 13:30', 2), + ('mittagessen_veg', 'Mittagessen vegetarisch', 'Lunch (vegetarian)', 'Obiad (wegetariański)', '11:30 - 13:30', 3), + ('suppe', 'Suppe', 'Soup', 'Zupa', '11:30 - 13:30', 4); + +-- ---------- Allergene (EU-Kennzeichnung, Buchstaben) ---------- +insert into allergens (code, name, name_en, name_pl, sort) values + ('A', 'Glutenhaltiges Getreide', 'Cereals containing gluten', 'Zboża zawierające gluten', 1), + ('B', 'Krebstiere', 'Crustaceans', 'Skorupiaki', 2), + ('C', 'Eier', 'Eggs', 'Jaja', 3), + ('D', 'Fisch', 'Fish', 'Ryby', 4), + ('E', 'Erdnüsse', 'Peanuts', 'Orzeszki ziemne', 5), + ('F', 'Sojabohnen', 'Soybeans', 'Soja', 6), + ('G', 'Milch (einschließlich Laktose)', 'Milk (including lactose)', 'Mleko (w tym laktoza)', 7), + ('H', 'Schalenfrüchte (Nüsse)', 'Tree nuts', 'Orzechy', 8), + ('I', 'Sellerie', 'Celery', 'Seler', 9), + ('J', 'Senf', 'Mustard', 'Gorczyca', 10), + ('K', 'Sesamsamen', 'Sesame seeds', 'Nasiona sezamu', 11), + ('L', 'Schwefeldioxid und Sulfite', 'Sulphur dioxide and sulphites', 'Dwutlenek siarki i siarczyny', 12), + ('M', 'Lupinen', 'Lupin', 'Łubin', 13), + ('N', 'Weichtiere', 'Molluscs', 'Mięczaki', 14); + +-- ---------- Zusatzstoffe (Ziffern) ---------- +insert into additives (code, name, name_en, name_pl, sort) values + ('1', 'mit Farbstoff', 'with colouring', 'z barwnikiem', 1), + ('2', 'mit Konservierungsstoff', 'with preservative', 'z konserwantem', 2), + ('3', 'mit Antioxidationsmittel', 'with antioxidant', 'z przeciwutleniaczem', 3), + ('4', 'mit Geschmacksverstärker', 'with flavour enhancer', 'ze wzmacniaczem smaku', 4), + ('5', 'geschwefelt', 'sulphured', 'siarkowany', 5), + ('6', 'geschwärzt', 'blackened', 'barwiony na czarno', 6), + ('7', 'gewachst', 'waxed', 'woskowany', 7), + ('8', 'mit Phosphat', 'with phosphate', 'z fosforanem', 8), + ('9', 'mit Süßungsmittel(n)', 'with sweetener(s)', 'ze słodzikiem/słodzikami', 9), + ('10', 'enthält eine Phenylalaninquelle', 'contains a source of phenylalanine', 'zawiera źródło fenyloalaniny', 10), + ('11', 'koffeinhaltig', 'caffeinated', 'zawiera kofeinę', 11); + +-- ---------- Testgerichte ---------- +insert into dishes (name, price) values + -- Brotzeit + ('1 Stk. Rindskäsewurst mit Brötchen', 2.30), + ('Hackfleischbaguette', 3.20), + ('Knusperhähnchen Wrap', 3.70), + ('Leberkäse im Brötchen', 2.30), + ('Chili con Carne mit Brötchen', 3.90), + -- Mittagessen + ('Schnitzel Baguette', 3.50), + ('Penne mit Grünen Spargel und Garnelenspieße', 5.90), + ('Bratkartoffel Gemüse Pfanne mit Hähnchenbrust und Dip', 5.90), + ('Schweinefilet im Speckmantel dazu Rahmsoße und Butterspätzle', 5.90), + -- Mittagessen vegetarisch + ('Penne mit Grünen Spargel, get. Tomaten und Cherrytomaten', 5.30), + ('Spaghetti mit fruchtiger Tomatensauce und Parmesan', 5.30), + ('Gebackener Feta mit griechischen Bauernsalat und Baguette', 5.30), + ('Gemüse-Curry mit Basmatireis', 4.90), + -- Suppen + ('Kartoffelcremesuppe', 1.50), + ('Gulaschsuppe', 2.50), + ('Tomatensuppe mit Croutons', 1.80), + ('Kürbiscremesuppe', 1.80), + ('Linseneintopf mit Würstchen', 2.20); + +-- ---------- Beispielwoche 13.–17. Juli 2026 ---------- +with d as (select id, name from dishes) +insert into menu_entries (menu_date, section_key, dish_id) +select x.menu_date::date, x.section_key, d.id +from (values + ('2026-07-13', 'brotzeit', '1 Stk. Rindskäsewurst mit Brötchen'), + ('2026-07-14', 'brotzeit', 'Hackfleischbaguette'), + ('2026-07-15', 'brotzeit', 'Knusperhähnchen Wrap'), + ('2026-07-16', 'brotzeit', 'Leberkäse im Brötchen'), + ('2026-07-17', 'brotzeit', 'Chili con Carne mit Brötchen'), + ('2026-07-13', 'mittagessen', 'Schnitzel Baguette'), + ('2026-07-14', 'mittagessen', 'Penne mit Grünen Spargel und Garnelenspieße'), + ('2026-07-15', 'mittagessen', 'Bratkartoffel Gemüse Pfanne mit Hähnchenbrust und Dip'), + ('2026-07-16', 'mittagessen', 'Schweinefilet im Speckmantel dazu Rahmsoße und Butterspätzle'), + ('2026-07-14', 'mittagessen_veg', 'Penne mit Grünen Spargel, get. Tomaten und Cherrytomaten'), + ('2026-07-15', 'mittagessen_veg', 'Spaghetti mit fruchtiger Tomatensauce und Parmesan'), + ('2026-07-16', 'mittagessen_veg', 'Gebackener Feta mit griechischen Bauernsalat und Baguette'), + ('2026-07-13', 'suppe', 'Kartoffelcremesuppe'), + ('2026-07-14', 'suppe', 'Gulaschsuppe'), + ('2026-07-15', 'suppe', 'Tomatensuppe mit Croutons'), + ('2026-07-16', 'suppe', 'Kürbiscremesuppe'), + ('2026-07-17', 'suppe', 'Linseneintopf mit Würstchen') +) as x (menu_date, section_key, name) +join d on d.name = x.name; + +-- ---------- Beispielwoche 20.–24. Juli 2026 ("Nächste Woche") ---------- +with d as (select id, name from dishes) +insert into menu_entries (menu_date, section_key, dish_id) +select x.menu_date::date, x.section_key, d.id +from (values + ('2026-07-20', 'brotzeit', 'Leberkäse im Brötchen'), + ('2026-07-21', 'brotzeit', 'Knusperhähnchen Wrap'), + ('2026-07-22', 'brotzeit', '1 Stk. Rindskäsewurst mit Brötchen'), + ('2026-07-23', 'brotzeit', 'Hackfleischbaguette'), + ('2026-07-24', 'brotzeit', 'Chili con Carne mit Brötchen'), + ('2026-07-20', 'mittagessen', 'Bratkartoffel Gemüse Pfanne mit Hähnchenbrust und Dip'), + ('2026-07-21', 'mittagessen', 'Schweinefilet im Speckmantel dazu Rahmsoße und Butterspätzle'), + ('2026-07-22', 'mittagessen', 'Schnitzel Baguette'), + ('2026-07-23', 'mittagessen', 'Penne mit Grünen Spargel und Garnelenspieße'), + ('2026-07-20', 'mittagessen_veg', 'Gemüse-Curry mit Basmatireis'), + ('2026-07-21', 'mittagessen_veg', 'Spaghetti mit fruchtiger Tomatensauce und Parmesan'), + ('2026-07-22', 'mittagessen_veg', 'Gebackener Feta mit griechischen Bauernsalat und Baguette'), + ('2026-07-23', 'mittagessen_veg', 'Penne mit Grünen Spargel, get. Tomaten und Cherrytomaten'), + ('2026-07-20', 'suppe', 'Gulaschsuppe'), + ('2026-07-21', 'suppe', 'Kartoffelcremesuppe'), + ('2026-07-22', 'suppe', 'Linseneintopf mit Würstchen'), + ('2026-07-23', 'suppe', 'Tomatensuppe mit Croutons'), + ('2026-07-24', 'suppe', 'Kürbiscremesuppe') +) as x (menu_date, section_key, name) +join d on d.name = x.name; diff --git a/lib/i18n/dictionaries.ts b/lib/i18n/dictionaries.ts index d5fd1ec..d8c81a9 100644 --- a/lib/i18n/dictionaries.ts +++ b/lib/i18n/dictionaries.ts @@ -68,7 +68,7 @@ const de = { weekEditor: { title: "Wochenplan", loading: "Lade Wochenplan…", - selectDish: "– Gericht wählen –", + changeDish: "Anderes Gericht wählen", addMeal: "Mahlzeit hinzufügen", newDishesHint: "Neue Gerichte legst du unter {link} an.", newDishesLinkLabel: "Gerichte", @@ -322,7 +322,7 @@ const en: Dict = { weekEditor: { title: "Weekly plan", loading: "Loading weekly plan…", - selectDish: "– Select dish –", + changeDish: "Choose a different dish", addMeal: "Add meal", newDishesHint: "Add new dishes under {link}.", newDishesLinkLabel: "Dishes", @@ -574,7 +574,7 @@ const pl: Dict = { weekEditor: { title: "Plan tygodnia", loading: "Wczytywanie planu tygodnia…", - selectDish: "– Wybierz danie –", + changeDish: "Wybierz inne danie", addMeal: "Dodaj posiłek", newDishesHint: "Nowe dania możesz dodać w zakładce {link}.", newDishesLinkLabel: "Dania", diff --git a/package.json b/package.json index 4fce37d..99e1cee 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "prestart": "node --env-file-if-exists=.env.local db/migrate.mjs && node --env-file-if-exists=.env.local scripts/bootstrap-admin.mjs", "start": "next start -H 0.0.0.0", "lint": "eslint", - "db:migrate": "node --env-file-if-exists=.env.local db/migrate.mjs" + "db:migrate": "node --env-file-if-exists=.env.local db/migrate.mjs", + "db:seed-demo": "node --env-file-if-exists=.env.local db/seed-demo.mjs" }, "dependencies": { "@simplewebauthn/browser": "^13.3.0",