From 24efbe7757b8033fe2ef6e17a9e032cc54e6c573 Mon Sep 17 00:00:00 2001 From: "Kalinina.e" Date: Wed, 9 Sep 2026 12:00:15 +0000 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=BF=D1=83=D0=B1=D0=BB=D0=B8=D0=BA?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=BE=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7?= =?UTF-8?q?=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- script.js | 256 +++++++++++++++++++++++++++++++++++++++++------------- server.js | 29 ++++--- 2 files changed, 216 insertions(+), 69 deletions(-) diff --git a/script.js b/script.js index d0338e0..4f5f266 100644 --- a/script.js +++ b/script.js @@ -1,11 +1,81 @@ -let allMeals = []; +const MEALS_DATA = [ + {"id":"m1","name":"Овсяная каша с ягодами и орехами","type":"завтрак","servings":4,"time_min":15,"calories":420,"protein":12,"fat":14,"carbs":62,"ingredients":[{"product_id":"p1","name":"Овсяные хлопья","amount":320,"unit":"г"},{"product_id":"p2","name":"Молоко 2.5%","amount":800,"unit":"мл"},{"product_id":"p3","name":"Ягоды замороженные","amount":200,"unit":"г"},{"product_id":"p4","name":"Орехи грецкие","amount":40,"unit":"г"},{"product_id":"p5","name":"Мёд","amount":40,"unit":"г"}]}, + {"id":"m2","name":"Омлет с овощами и сыром","type":"завтрак","servings":4,"time_min":20,"calories":380,"protein":22,"fat":28,"carbs":8,"ingredients":[{"product_id":"p6","name":"Яйца","amount":12,"unit":"шт"},{"product_id":"p2","name":"Молоко 2.5%","amount":200,"unit":"мл"},{"product_id":"p7","name":"Сыр твёрдый","amount":120,"unit":"г"},{"product_id":"p8","name":"Помидоры","amount":300,"unit":"г"},{"product_id":"p9","name":"Перец болгарский","amount":200,"unit":"г"},{"product_id":"p10","name":"Масло сливочное","amount":30,"unit":"г"}]}, + {"id":"m3","name":"Творожная запеканка с изюмом","type":"завтрак","servings":4,"time_min":45,"calories":340,"protein":18,"fat":16,"carbs":32,"ingredients":[{"product_id":"p11","name":"Творог 5%","amount":600,"unit":"г"},{"product_id":"p6","name":"Яйца","amount":4,"unit":"шт"},{"product_id":"p12","name":"Манка","amount":80,"unit":"г"},{"product_id":"p13","name":"Изюм","amount":80,"unit":"г"},{"product_id":"p14","name":"Сметана 15%","amount":100,"unit":"г"},{"product_id":"p15","name":"Сахар","amount":60,"unit":"г"}]}, + {"id":"m4","name":"Борщ с говядиной","type":"обед","servings":6,"time_min":90,"calories":320,"protein":18,"fat":14,"carbs":28,"ingredients":[{"product_id":"p16","name":"Говядина","amount":500,"unit":"г"},{"product_id":"p17","name":"Свёкла","amount":400,"unit":"г"},{"product_id":"p18","name":"Капуста белокочанная","amount":400,"unit":"г"},{"product_id":"p19","name":"Картофель","amount":400,"unit":"г"},{"product_id":"p20","name":"Морковь","amount":150,"unit":"г"},{"product_id":"p21","name":"Лук репчатый","amount":150,"unit":"г"},{"product_id":"p22","name":"Томатная паста","amount":60,"unit":"г"},{"product_id":"p14","name":"Сметана 15%","amount":120,"unit":"г"}]}, + {"id":"m5","name":"Куриный суп с лапшой","type":"обед","servings":6,"time_min":60,"calories":280,"protein":20,"fat":10,"carbs":26,"ingredients":[{"product_id":"p23","name":"Курица (филе/голени)","amount":500,"unit":"г"},{"product_id":"p19","name":"Картофель","amount":300,"unit":"г"},{"product_id":"p20","name":"Морковь","amount":100,"unit":"г"},{"product_id":"p21","name":"Лук репчатый","amount":100,"unit":"г"},{"product_id":"p24","name":"Лапша яичная","amount":150,"unit":"г"}]}, + {"id":"m6","name":"Плов с курицей","type":"обед","servings":6,"time_min":70,"calories":420,"protein":22,"fat":16,"carbs":48,"ingredients":[{"product_id":"p23","name":"Курица (филе/голени)","amount":600,"unit":"г"},{"product_id":"p25","name":"Рис длиннозерный","amount":400,"unit":"г"},{"product_id":"p20","name":"Морковь","amount":300,"unit":"г"},{"product_id":"p21","name":"Лук репчатый","amount":200,"unit":"г"},{"product_id":"p26","name":"Масло растительное","amount":60,"unit":"мл"}]}, + {"id":"m7","name":"Макароны по-флотски","type":"обед","servings":4,"time_min":35,"calories":450,"protein":24,"fat":18,"carbs":52,"ingredients":[{"product_id":"p27","name":"Фарш говяжий","amount":400,"unit":"г"},{"product_id":"p28","name":"Макароны твёрдых сортов","amount":400,"unit":"г"},{"product_id":"p21","name":"Лук репчатый","amount":150,"unit":"г"},{"product_id":"p20","name":"Морковь","amount":150,"unit":"г"},{"product_id":"p26","name":"Масло растительное","amount":30,"unit":"мл"}]}, + {"id":"m8","name":"Котлеты домашние с пюре","type":"обед","servings":4,"time_min":50,"calories":480,"protein":26,"fat":22,"carbs":42,"ingredients":[{"product_id":"p27","name":"Фарш говяжий","amount":500,"unit":"г"},{"product_id":"p19","name":"Картофель","amount":800,"unit":"г"},{"product_id":"p29","name":"Хлеб белый","amount":100,"unit":"г"},{"product_id":"p6","name":"Яйца","amount":2,"unit":"шт"},{"product_id":"p21","name":"Лук репчатый","amount":100,"unit":"г"},{"product_id":"p10","name":"Масло сливочное","amount":50,"unit":"г"}]}, + {"id":"m9","name":"Гречка с куриной печенью","type":"обед","servings":4,"time_min":40,"calories":380,"protein":24,"fat":12,"carbs":44,"ingredients":[{"product_id":"p30","name":"Печень куриная","amount":500,"unit":"г"},{"product_id":"p31","name":"Гречка","amount":320,"unit":"г"},{"product_id":"p21","name":"Лук репчатый","amount":150,"unit":"г"},{"product_id":"p20","name":"Морковь","amount":150,"unit":"г"},{"product_id":"p26","name":"Масло растительное","amount":40,"unit":"мл"}]}, + {"id":"m10","name":"Запечённая рыба с овощами","type":"ужин","servings":4,"time_min":45,"calories":320,"protein":28,"fat":14,"carbs":18,"ingredients":[{"product_id":"p32","name":"Рыба белая (минтай/треска)","amount":600,"unit":"г"},{"product_id":"p19","name":"Картофель","amount":600,"unit":"г"},{"product_id":"p20","name":"Морковь","amount":200,"unit":"г"},{"product_id":"p33","name":"Кабачок","amount":300,"unit":"г"},{"product_id":"p26","name":"Масло растительное","amount":30,"unit":"мл"}]}, + {"id":"m11","name":"Куриные котлеты с салатом","type":"ужин","servings":4,"time_min":40,"calories":340,"protein":30,"fat":12,"carbs":28,"ingredients":[{"product_id":"p34","name":"Куриное филе","amount":600,"unit":"г"},{"product_id":"p29","name":"Хлеб белый","amount":80,"unit":"г"},{"product_id":"p6","name":"Яйца","amount":2,"unit":"шт"},{"product_id":"p35","name":"Огурцы свежие","amount":300,"unit":"г"},{"product_id":"p8","name":"Помидоры","amount":300,"unit":"г"},{"product_id":"p36","name":"Зелень","amount":40,"unit":"г"}]}, + {"id":"m12","name":"Тушёная капуста с мясом","type":"ужин","servings":4,"time_min":50,"calories":290,"protein":20,"fat":14,"carbs":22,"ingredients":[{"product_id":"p27","name":"Фарш говяжий","amount":400,"unit":"г"},{"product_id":"p18","name":"Капуста белокочанная","amount":800,"unit":"г"},{"product_id":"p21","name":"Лук репчатый","amount":150,"unit":"г"},{"product_id":"p20","name":"Морковь","amount":150,"unit":"г"},{"product_id":"p26","name":"Масло растительное","amount":30,"unit":"мл"}]}, + {"id":"m13","name":"Плов с говядиной","type":"ужин","servings":6,"time_min":80,"calories":440,"protein":24,"fat":18,"carbs":46,"ingredients":[{"product_id":"p16","name":"Говядина","amount":500,"unit":"г"},{"product_id":"p25","name":"Рис длиннозерный","amount":400,"unit":"г"},{"product_id":"p20","name":"Морковь","amount":300,"unit":"г"},{"product_id":"p21","name":"Лук репчатый","amount":200,"unit":"г"},{"product_id":"p26","name":"Масло растительное","amount":60,"unit":"мл"}]}, + {"id":"m14","name":"Салат Цезарь с курицей","type":"ужин","servings":4,"time_min":25,"calories":360,"protein":26,"fat":22,"carbs":14,"ingredients":[{"product_id":"p34","name":"Куриное филе","amount":400,"unit":"г"},{"product_id":"p37","name":"Салат айсберг","amount":300,"unit":"г"},{"product_id":"p38","name":"Сухарики","amount":80,"unit":"г"},{"product_id":"p7","name":"Сыр твёрдый","amount":100,"unit":"г"},{"product_id":"p39","name":"Соус Цезарь","amount":120,"unit":"мл"}]}, + {"id":"m15","name":"Винегрет с селёдкой","type":"ужин","servings":4,"time_min":30,"calories":260,"protein":12,"fat":14,"carbs":24,"ingredients":[{"product_id":"p17","name":"Свёкла","amount":300,"unit":"г"},{"product_id":"p20","name":"Морковь","amount":200,"unit":"г"},{"product_id":"p19","name":"Картофель","amount":300,"unit":"г"},{"product_id":"p40","name":"Сельдь солёная","amount":200,"unit":"г"},{"product_id":"p41","name":"Огурцы солёные","amount":150,"unit":"г"},{"product_id":"p26","name":"Масло растительное","amount":40,"unit":"мл"}]}, + {"id":"m16","name":"Яичница с колбасой и овощами","type":"завтрак","servings":4,"time_min":15,"calories":400,"protein":20,"fat":30,"carbs":12,"ingredients":[{"product_id":"p6","name":"Яйца","amount":12,"unit":"шт"},{"product_id":"p42","name":"Колбаса варёная","amount":200,"unit":"г"},{"product_id":"p8","name":"Помидоры","amount":200,"unit":"г"},{"product_id":"p9","name":"Перец болгарский","amount":150,"unit":"г"},{"product_id":"p10","name":"Масло сливочное","amount":30,"unit":"г"}]}, + {"id":"m17","name":"Сырники со сметаной","type":"завтрак","servings":4,"time_min":30,"calories":380,"protein":16,"fat":18,"carbs":38,"ingredients":[{"product_id":"p11","name":"Творог 5%","amount":500,"unit":"г"},{"product_id":"p6","name":"Яйца","amount":3,"unit":"шт"},{"product_id":"p43","name":"Мука пшеничная","amount":100,"unit":"г"},{"product_id":"p15","name":"Сахар","amount":40,"unit":"г"},{"product_id":"p14","name":"Сметана 15%","amount":120,"unit":"г"}]}, + {"id":"m18","name":"Рассольник с курицей","type":"обед","servings":6,"time_min":70,"calories":260,"protein":16,"fat":10,"carbs":26,"ingredients":[{"product_id":"p23","name":"Курица (филе/голени)","amount":400,"unit":"г"},{"product_id":"p19","name":"Картофель","amount":400,"unit":"г"},{"product_id":"p41","name":"Огурцы солёные","amount":200,"unit":"г"},{"product_id":"p25","name":"Рис длиннозерный","amount":100,"unit":"г"},{"product_id":"p20","name":"Морковь","amount":150,"unit":"г"},{"product_id":"p21","name":"Лук репчатый","amount":150,"unit":"г"}]}, + {"id":"m19","name":"Гуляш из говядины с гречкой","type":"обед","servings":4,"time_min":80,"calories":460,"protein":28,"fat":18,"carbs":46,"ingredients":[{"product_id":"p16","name":"Говядина","amount":500,"unit":"г"},{"product_id":"p31","name":"Гречка","amount":320,"unit":"г"},{"product_id":"p21","name":"Лук репчатый","amount":150,"unit":"г"},{"product_id":"p20","name":"Морковь","amount":150,"unit":"г"},{"product_id":"p22","name":"Томатная паста","amount":60,"unit":"г"}]}, + {"id":"m20","name":"Овощное рагу с курицей","type":"ужин","servings":4,"time_min":50,"calories":310,"protein":24,"fat":12,"carbs":26,"ingredients":[{"product_id":"p34","name":"Куриное филе","amount":500,"unit":"г"},{"product_id":"p33","name":"Кабачок","amount":300,"unit":"г"},{"product_id":"p9","name":"Перец болгарский","amount":200,"unit":"г"},{"product_id":"p8","name":"Помидоры","amount":300,"unit":"г"},{"product_id":"p21","name":"Лук репчатый","amount":150,"unit":"г"},{"product_id":"p26","name":"Масло растительное","amount":30,"unit":"мл"}]} +]; + +const PRODUCTS_DATA = [ + {"id":"p1","name":"Овсяные хлопья","category":"Крупы","price_per_kg":450}, + {"id":"p2","name":"Молоко 2.5%","category":"Молочные","price_per_kg":450}, + {"id":"p3","name":"Ягоды замороженные","category":"Заморозка","price_per_kg":2500}, + {"id":"p4","name":"Орехи грецкие","category":"Бакалея","price_per_kg":4500}, + {"id":"p5","name":"Мёд","category":"Бакалея","price_per_kg":3500}, + {"id":"p6","name":"Яйца","category":"Молочные","price_per_kg":650}, + {"id":"p7","name":"Сыр твёрдый","category":"Молочные","price_per_kg":4200}, + {"id":"p8","name":"Помидоры","category":"Овощи","price_per_kg":1200}, + {"id":"p9","name":"Перец болгарский","category":"Овощи","price_per_kg":1400}, + {"id":"p10","name":"Масло сливочное","category":"Молочные","price_per_kg":3800}, + {"id":"p11","name":"Творог 5%","category":"Молочные","price_per_kg":3600}, + {"id":"p12","name":"Манка","category":"Крупы","price_per_kg":400}, + {"id":"p13","name":"Изюм","category":"Бакалея","price_per_kg":2200}, + {"id":"p14","name":"Сметана 15%","category":"Молочные","price_per_kg":1800}, + {"id":"p15","name":"Сахар","category":"Бакалея","price_per_kg":550}, + {"id":"p16","name":"Говядина","category":"Мясо","price_per_kg":3200}, + {"id":"p17","name":"Свёкла","category":"Овощи","price_per_kg":250}, + {"id":"p18","name":"Капуста белокочанная","category":"Овощи","price_per_kg":200}, + {"id":"p19","name":"Картофель","category":"Овощи","price_per_kg":300}, + {"id":"p20","name":"Морковь","category":"Овощи","price_per_kg":250}, + {"id":"p21","name":"Лук репчатый","category":"Овощи","price_per_kg":200}, + {"id":"p22","name":"Томатная паста","category":"Консервы","price_per_kg":1200}, + {"id":"p23","name":"Курица (филе/голени)","category":"Мясо","price_per_kg":2200}, + {"id":"p24","name":"Лапша яичная","category":"Макароны","price_per_kg":800}, + {"id":"p25","name":"Рис длиннозерный","category":"Крупы","price_per_kg":700}, + {"id":"p26","name":"Масло растительное","category":"Бакалея","price_per_kg":900}, + {"id":"p27","name":"Фарш говяжий","category":"Мясо","price_per_kg":2800}, + {"id":"p28","name":"Макароны твёрдых сортов","category":"Макароны","price_per_kg":900}, + {"id":"p29","name":"Хлеб белый","category":"Хлеб","price_per_kg":400}, + {"id":"p30","name":"Печень куриная","category":"Мясо","price_per_kg":1600}, + {"id":"p31","name":"Гречка","category":"Крупы","price_per_kg":650}, + {"id":"p32","name":"Рыба белая (минтай/треска)","category":"Рыба","price_per_kg":2400}, + {"id":"p33","name":"Кабачок","category":"Овощи","price_per_kg":600}, + {"id":"p34","name":"Куриное филе","category":"Мясо","price_per_kg":2600}, + {"id":"p35","name":"Огурцы свежие","category":"Овощи","price_per_kg":800}, + {"id":"p36","name":"Зелень","category":"Овощи","price_per_kg":1000}, + {"id":"p37","name":"Салат айсберг","category":"Овощи","price_per_kg":900}, + {"id":"p38","name":"Сухарики","category":"Бакалея","price_per_kg":800}, + {"id":"p39","name":"Соус Цезарь","category":"Соусы","price_per_kg":1800}, + {"id":"p40","name":"Сельдь солёная","category":"Рыба","price_per_kg":1800}, + {"id":"p41","name":"Огурцы солёные","category":"Консервы","price_per_kg":600}, + {"id":"p42","name":"Колбаса варёная","category":"Мясо","price_per_kg":2400}, + {"id":"p43","name":"Мука пшеничная","category":"Бакалея","price_per_kg":400} +]; + let currentPlan = []; document.addEventListener("DOMContentLoaded", () => { setupTabs(); - loadMeals(); + renderMeals(MEALS_DATA); + renderRecipes(MEALS_DATA); setupFilters(); setupButtons(); + console.log("✅ Данные загружены:", MEALS_DATA.length, "блюд,", PRODUCTS_DATA.length, "продуктов"); }); function setupTabs() { @@ -19,17 +89,6 @@ function setupTabs() { }); } -async function loadMeals() { - try { - const res = await fetch("/api/meals"); - allMeals = await res.json(); - renderMeals(allMeals); - renderRecipes(allMeals); - } catch (e) { - console.error("Ошибка загрузки блюд:", e); - } -} - function setupFilters() { document.querySelectorAll(".filter-btn").forEach(btn => { btn.addEventListener("click", () => { @@ -37,9 +96,9 @@ function setupFilters() { btn.classList.add("active"); const type = btn.dataset.type; if (type === "all") { - renderMeals(allMeals); + renderMeals(MEALS_DATA); } else { - renderMeals(allMeals.filter(m => m.type === type)); + renderMeals(MEALS_DATA.filter(m => m.type === type)); } }); }); @@ -87,9 +146,8 @@ function renderRecipes(meals) { `).join(""); } -async function showRecipe(id) { - const res = await fetch(`/api/meal/${id}`); - const meal = await res.json(); +function showRecipe(id) { + const meal = MEALS_DATA.find(m => m.id === id); if (!meal) return; document.getElementById("recipe-detail").innerHTML = ` @@ -119,59 +177,118 @@ async function showRecipe(id) { document.getElementById("recipe-detail").scrollIntoView({ behavior: "smooth" }); } -async function generatePlan() { +function generatePlan() { const btn = document.getElementById("btn-generate"); btn.textContent = "⏳ Генерация..."; btn.disabled = true; try { - console.log("Генерация плана..."); - const res = await fetch("/api/generate-plan", { method: "POST" }); - console.log("Статус ответа:", res.status); - if (!res.ok) { - const errText = await res.text(); - throw new Error("Сервер вернул ошибку " + res.status + ": " + errText); - } - const result = await res.json(); - console.log("План получен:", result); - currentPlan = result.plan; - - renderWeeklyTotals(result.weekly_totals); - renderWeekPlan(result.plan); - - const shoppingRes = await fetch("/api/shopping-list", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ plan: result.plan }) + console.log("🔄 Начало генерации..."); + const breakfasts = MEALS_DATA.filter(m => m.type === "завтрак"); + const lunches = MEALS_DATA.filter(m => m.type === "обед"); + const dinners = MEALS_DATA.filter(m => m.type === "ужин"); + + const days = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"]; + const plan = []; + let totalCalories = 0, totalProtein = 0, totalFat = 0, totalCarbs = 0, totalCost = 0; + + const productMap = {}; + PRODUCTS_DATA.forEach(p => { productMap[p.id] = { ...p, total_amount: 0 }; }); + + days.forEach((day, i) => { + const b = breakfasts[i % breakfasts.length]; + const l = lunches[i % lunches.length]; + const d = dinners[i % dinners.length]; + + const dayCalories = b.calories + l.calories + d.calories; + const dayProtein = b.protein + l.protein + d.protein; + const dayFat = b.fat + l.fat + d.fat; + const dayCarbs = b.carbs + l.carbs + d.carbs; + + const dayCost = calculateMealCost(b) + calculateMealCost(l) + calculateMealCost(d); + + plan.push({ + day, + date: new Date(Date.now() + i * 86400000).toISOString().slice(0, 10), + breakfast: { id: b.id, name: b.name, calories: b.calories, protein: b.protein, fat: b.fat, carbs: b.carbs, time_min: b.time_min }, + lunch: { id: l.id, name: l.name, calories: l.calories, protein: l.protein, fat: l.fat, carbs: l.carbs, time_min: l.time_min }, + dinner: { id: d.id, name: d.name, calories: d.calories, protein: d.protein, fat: d.fat, carbs: d.carbs, time_min: d.time_min }, + daily_total: { calories: dayCalories, protein: dayProtein, fat: dayFat, carbs: dayCarbs, cost: dayCost } + }); + + totalCalories += dayCalories; + totalProtein += dayProtein; + totalFat += dayFat; + totalCarbs += dayCarbs; + totalCost += dayCost; + + [b, l, d].forEach(meal => { + meal.ingredients.forEach(ing => { + if (productMap[ing.product_id]) { + productMap[ing.product_id].total_amount += ing.amount; + } + }); + }); }); - if (!shoppingRes.ok) { - throw new Error("Список продуктов: ошибка " + shoppingRes.status); - } - const shopping = await shoppingRes.json(); - console.log("Список продуктов:", shopping); - renderShoppingList(shopping); - - const timelineRes = await fetch("/api/timeline", { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ plan: result.plan }) + + currentPlan = plan; + console.log("✅ План сгенерирован:", plan.length, "дней"); + + const weekly_totals = { + calories: Math.round(totalCalories), + protein: Math.round(totalProtein), + fat: Math.round(totalFat), + carbs: Math.round(totalCarbs), + cost: Math.round(totalCost), + avg_daily_calories: Math.round(totalCalories / 7) + }; + + renderWeeklyTotals(weekly_totals); + renderWeekPlan(plan); + + const shoppingList = []; + let shoppingTotal = 0; + Object.values(productMap).forEach(p => { + if (p.total_amount > 0) { + const cost = (p.total_amount / 1000) * p.price_per_kg; + shoppingList.push({ + id: p.id, + name: p.name, + category: p.category, + amount: Math.round(p.total_amount), + unit: p.total_amount >= 1000 ? "кг" : "г", + price_per_kg: p.price_per_kg, + cost: Math.round(cost) + }); + shoppingTotal += cost; + } }); - if (!timelineRes.ok) { - throw new Error("Хронология: ошибка " + timelineRes.status); - } - const timeline = await timelineRes.json(); - console.log("Хронология:", timeline); - renderTimeline(timeline); - + shoppingList.sort((a, b) => a.category.localeCompare(b.category) || a.name.localeCompare(b.name)); + + renderShoppingList({ items: shoppingList, total_cost: Math.round(shoppingTotal), budget: 100000, remaining: 100000 - Math.round(shoppingTotal) }); + renderTimeline(plan); + btn.textContent = "🔄 Сгенерировать заново"; + console.log("✅ Генерация завершена"); } catch (e) { console.error("❌ Ошибка генерации:", e); alert("Ошибка: " + e.message); - btn.textContent = "⚠️ Ошибка: " + e.message; + btn.textContent = "⚠️ Ошибка"; } btn.disabled = false; } +function calculateMealCost(meal) { + let cost = 0; + meal.ingredients.forEach(ing => { + const product = PRODUCTS_DATA.find(p => p.id === ing.product_id); + if (product) { + cost += (ing.amount / 1000) * product.price_per_kg; + } + }); + return cost; +} + function renderWeeklyTotals(totals) { document.getElementById("weekly-totals").innerHTML = `

📊 Итого за неделю (на 4 человек)

@@ -274,15 +391,38 @@ function renderShoppingList(shopping) { `).join(""); } -function renderTimeline(timeline) { +function renderTimeline(plan) { const container = document.getElementById("timeline-list"); + const timeline = []; + + timeline.push({ + day: "Воскресенье (перед неделей)", + action: "🛒 Закупка продуктов", + details: "Купить все продукты по списку", + time_min: 90 + }); + + plan.forEach((day, dayIndex) => { + [day.breakfast, day.lunch, day.dinner].forEach((meal, mealIdx) => { + const mealData = MEALS_DATA.find(m => m.id === meal.id); + if (!mealData) return; + + const mealTypes = ["🌅 Завтрак", "☀️ Обед", "🌙 Ужин"]; + timeline.push({ + day: day.day, + action: `${mealTypes[mealIdx]}: ${mealData.name}`, + details: `Ингредиенты: ${mealData.ingredients.map(i => i.name).slice(0, 4).join(", ")}${mealData.ingredients.length > 4 ? "..." : ""}`, + time_min: mealData.time_min + }); + }); + }); + container.innerHTML = timeline.map(item => `
${item.day} ${item.action}
${item.details}
- ${item.ingredients ? `
📝 Продукты: ${item.ingredients.slice(0, 5).join(", ")}${item.ingredients.length > 5 ? "..." : ""}
` : ""}
⏱ ${item.time_min} мин
diff --git a/server.js b/server.js index 68f7663..f6dcf72 100644 --- a/server.js +++ b/server.js @@ -186,27 +186,29 @@ http.createServer(async (req, res) => { return res.end(); } - if (req.method === "GET" && req.url === "/api/meals") { + const urlPath = req.url.split("?")[0]; + + if (req.method === "GET" && urlPath === "/api/meals") { const data = readData(); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(data.meals)); } - if (req.method === "GET" && req.url === "/api/products") { + if (req.method === "GET" && urlPath === "/api/products") { const data = readData(); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(data.products)); } - if (req.method === "GET" && req.url.startsWith("/api/meal/")) { - const id = req.url.split("/")[3]; + if (req.method === "GET" && urlPath.startsWith("/api/meal/")) { + const id = urlPath.split("/")[3]; const data = readData(); const meal = data.meals.find(m => m.id === id); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(meal || {})); } - if (req.method === "POST" && req.url === "/api/generate-plan") { + if (req.method === "POST" && urlPath === "/api/generate-plan") { const result = generateWeeklyPlan(); const data = readData(); data.weekly_plan = result.plan; @@ -215,13 +217,13 @@ http.createServer(async (req, res) => { return res.end(JSON.stringify(result)); } - if (req.method === "GET" && req.url === "/api/plan") { + if (req.method === "GET" && urlPath === "/api/plan") { const data = readData(); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(data.weekly_plan || [])); } - if (req.method === "POST" && req.url === "/api/shopping-list") { + if (req.method === "POST" && urlPath === "/api/shopping-list") { const bodyData = await body(req); const shoppingList = generateShoppingList(bodyData.plan || []); const data = readData(); @@ -231,13 +233,13 @@ http.createServer(async (req, res) => { return res.end(JSON.stringify(shoppingList)); } - if (req.method === "GET" && req.url === "/api/shopping-list") { + if (req.method === "GET" && urlPath === "/api/shopping-list") { const data = readData(); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(data.shopping_list || { items: [], total_cost: 0 })); } - if (req.method === "POST" && req.url === "/api/timeline") { + if (req.method === "POST" && urlPath === "/api/timeline") { const bodyData = await body(req); const timeline = generateTimeline(bodyData.plan || []); const data = readData(); @@ -247,13 +249,18 @@ http.createServer(async (req, res) => { return res.end(JSON.stringify(timeline)); } - if (req.method === "GET" && req.url === "/api/timeline") { + if (req.method === "GET" && urlPath === "/api/timeline") { const data = readData(); res.writeHead(200, { "Content-Type": "application/json" }); return res.end(JSON.stringify(data.timeline || [])); } - let file = req.url === "/" ? "/index.html" : req.url.split("?")[0]; + if (urlPath.startsWith("/api/")) { + res.writeHead(404, { "Content-Type": "application/json" }); + return res.end(JSON.stringify({ error: "API endpoint not found: " + req.url })); + } + + let file = req.url === "/" ? "/index.html" : urlPath; const full = path.join(__dirname, file); if (full.startsWith(__dirname) && fs.existsSync(full) && fs.statSync(full).isFile()) { const ext = path.extname(full);