460 lines
30 KiB
JavaScript
460 lines
30 KiB
JavaScript
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();
|
||
renderMeals(MEALS_DATA);
|
||
renderRecipes(MEALS_DATA);
|
||
setupFilters();
|
||
setupButtons();
|
||
console.log("✅ Данные загружены:", MEALS_DATA.length, "блюд,", PRODUCTS_DATA.length, "продуктов");
|
||
});
|
||
|
||
function setupTabs() {
|
||
document.querySelectorAll(".tab").forEach(tab => {
|
||
tab.addEventListener("click", () => {
|
||
document.querySelectorAll(".tab").forEach(t => t.classList.remove("active"));
|
||
document.querySelectorAll(".tab-content").forEach(c => c.classList.remove("active"));
|
||
tab.classList.add("active");
|
||
document.getElementById(tab.dataset.tab).classList.add("active");
|
||
});
|
||
});
|
||
}
|
||
|
||
function setupFilters() {
|
||
document.querySelectorAll(".filter-btn").forEach(btn => {
|
||
btn.addEventListener("click", () => {
|
||
document.querySelectorAll(".filter-btn").forEach(b => b.classList.remove("active"));
|
||
btn.classList.add("active");
|
||
const type = btn.dataset.type;
|
||
if (type === "all") {
|
||
renderMeals(MEALS_DATA);
|
||
} else {
|
||
renderMeals(MEALS_DATA.filter(m => m.type === type));
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
function setupButtons() {
|
||
document.getElementById("btn-generate").addEventListener("click", generatePlan);
|
||
document.getElementById("btn-export").addEventListener("click", exportToExcel);
|
||
}
|
||
|
||
function renderMeals(meals) {
|
||
const container = document.getElementById("meals-list");
|
||
if (meals.length === 0) {
|
||
container.innerHTML = '<div class="empty-state"><p>Нет блюд</p></div>';
|
||
return;
|
||
}
|
||
container.innerHTML = meals.map(m => `
|
||
<div class="meal-card" onclick="showRecipe('${m.id}')">
|
||
<span class="type-badge" data-type="${m.type}">${m.type}</span>
|
||
<h4>${m.name}</h4>
|
||
<div class="kbzu-row">
|
||
<span class="kbzu-item"><strong>${m.calories}</strong> ккал</span>
|
||
<span class="kbzu-item">Б: <strong>${m.protein}</strong>г</span>
|
||
<span class="kbzu-item">Ж: <strong>${m.fat}</strong>г</span>
|
||
<span class="kbzu-item">У: <strong>${m.carbs}</strong>г</span>
|
||
</div>
|
||
<div style="margin-top: 0.8rem; font-size: 0.85rem; color: #718096;">
|
||
⏱ ${m.time_min} мин | 🍽 ${m.servings} порц.
|
||
</div>
|
||
</div>
|
||
`).join("");
|
||
}
|
||
|
||
function renderRecipes(meals) {
|
||
const container = document.getElementById("recipes-list");
|
||
container.innerHTML = meals.map(m => `
|
||
<div class="recipe-card" onclick="showRecipe('${m.id}')">
|
||
<span class="type-badge" data-type="${m.type}">${m.type}</span>
|
||
<h4>${m.name}</h4>
|
||
<div class="kbzu-row">
|
||
<span class="kbzu-item"><strong>${m.calories}</strong> ккал</span>
|
||
<span class="kbzu-item">🍽 ${m.servings} порц.</span>
|
||
</div>
|
||
</div>
|
||
`).join("");
|
||
}
|
||
|
||
function showRecipe(id) {
|
||
const meal = MEALS_DATA.find(m => m.id === id);
|
||
if (!meal) return;
|
||
|
||
document.getElementById("recipe-detail").innerHTML = `
|
||
<h3>${meal.name}</h3>
|
||
<div class="detail-grid">
|
||
<div class="ingredients-list">
|
||
<h4>Ингредиенты (на ${meal.servings} порций)</h4>
|
||
<ul>
|
||
${meal.ingredients.map(i => `<li>${i.name} — ${i.amount} ${i.unit}</li>`).join("")}
|
||
</ul>
|
||
</div>
|
||
<div class="kbzu-table">
|
||
<h4>КБЖУ на порцию</h4>
|
||
<table>
|
||
<tr><th>Калории</th><td><strong>${meal.calories}</strong> ккал</td></tr>
|
||
<tr><th>Белки</th><td><strong>${meal.protein}</strong> г</td></tr>
|
||
<tr><th>Жиры</th><td><strong>${meal.fat}</strong> г</td></tr>
|
||
<tr><th>Углеводы</th><td><strong>${meal.carbs}</strong> г</td></tr>
|
||
</table>
|
||
<p style="margin-top: 1rem; color: #718096; font-size: 0.9rem;">
|
||
⏱ Время приготовления: <strong>${meal.time_min} мин</strong>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
`;
|
||
document.getElementById("recipe-detail").classList.add("active");
|
||
document.getElementById("recipe-detail").scrollIntoView({ behavior: "smooth" });
|
||
}
|
||
|
||
function generatePlan() {
|
||
const btn = document.getElementById("btn-generate");
|
||
btn.textContent = "⏳ Генерация...";
|
||
btn.disabled = true;
|
||
|
||
try {
|
||
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;
|
||
}
|
||
});
|
||
});
|
||
});
|
||
|
||
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;
|
||
}
|
||
});
|
||
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 = "⚠️ Ошибка";
|
||
}
|
||
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 = `
|
||
<h3>📊 Итого за неделю (на 4 человек)</h3>
|
||
<div class="totals-grid">
|
||
<div class="total-item">
|
||
<div class="value">${totals.calories.toLocaleString()}</div>
|
||
<div class="label">ккал всего</div>
|
||
</div>
|
||
<div class="total-item">
|
||
<div class="value">${totals.avg_daily_calories.toLocaleString()}</div>
|
||
<div class="label">ккал в день (среднее)</div>
|
||
</div>
|
||
<div class="total-item">
|
||
<div class="value">${totals.protein}г</div>
|
||
<div class="label">Белки</div>
|
||
</div>
|
||
<div class="total-item">
|
||
<div class="value">${totals.fat}г</div>
|
||
<div class="label">Жиры</div>
|
||
</div>
|
||
<div class="total-item">
|
||
<div class="value">${totals.carbs}г</div>
|
||
<div class="label">Углеводы</div>
|
||
</div>
|
||
<div class="total-item">
|
||
<div class="value">${totals.cost.toLocaleString()} ₸</div>
|
||
<div class="label">Примерная стоимость</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
function renderWeekPlan(plan) {
|
||
const container = document.getElementById("week-plan");
|
||
container.innerHTML = plan.map(day => `
|
||
<div class="day-card">
|
||
<h3>${day.day} <span class="date">${day.date}</span></h3>
|
||
<div class="meal-row">
|
||
<span class="meal-type">🌅 Завтрак</span>
|
||
<span class="meal-name">${day.breakfast.name}</span>
|
||
<span class="meal-kbzu">${day.breakfast.calories} ккал | Б:${day.breakfast.protein}г Ж:${day.breakfast.fat}г У:${day.breakfast.carbs}г</span>
|
||
</div>
|
||
<div class="meal-row">
|
||
<span class="meal-type">☀️ Обед</span>
|
||
<span class="meal-name">${day.lunch.name}</span>
|
||
<span class="meal-kbzu">${day.lunch.calories} ккал | Б:${day.lunch.protein}г Ж:${day.lunch.fat}г У:${day.lunch.carbs}г</span>
|
||
</div>
|
||
<div class="meal-row">
|
||
<span class="meal-type">🌙 Ужин</span>
|
||
<span class="meal-name">${day.dinner.name}</span>
|
||
<span class="meal-kbzu">${day.dinner.calories} ккал | Б:${day.dinner.protein}г Ж:${day.dinner.fat}г У:${day.dinner.carbs}г</span>
|
||
</div>
|
||
<div style="margin-top: 1rem; padding-top: 1rem; border-top: 2px solid #e2e8f0; display: flex; justify-content: space-between; align-items: center;">
|
||
<strong style="color: #667eea;">Итого за день: ${day.daily_total.calories} ккал</strong>
|
||
<span style="color: #718096; font-size: 0.9rem;">Б:${day.daily_total.protein}г | Ж:${day.daily_total.fat}г | У:${day.daily_total.carbs}г | ~${day.daily_total.cost} ₸</span>
|
||
</div>
|
||
</div>
|
||
`).join("");
|
||
}
|
||
|
||
function renderShoppingList(shopping) {
|
||
const container = document.getElementById("shopping-list");
|
||
const summary = document.getElementById("shopping-summary");
|
||
|
||
summary.innerHTML = `
|
||
<h3>🛒 Список продуктов на неделю</h3>
|
||
<div class="totals-grid">
|
||
<div class="total-item">
|
||
<div class="value">${shopping.total_cost.toLocaleString()} ₸</div>
|
||
<div class="label">Общая стоимость</div>
|
||
</div>
|
||
<div class="total-item">
|
||
<div class="value">${shopping.budget.toLocaleString()} ₸</div>
|
||
<div class="label">Бюджет</div>
|
||
</div>
|
||
<div class="total-item">
|
||
<div class="value" style="color: ${shopping.remaining >= 0 ? '#48bb78' : '#f56565'}">${shopping.remaining.toLocaleString()} ₸</div>
|
||
<div class="label">Остаток</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
const byCategory = {};
|
||
shopping.items.forEach(item => {
|
||
if (!byCategory[item.category]) byCategory[item.category] = [];
|
||
byCategory[item.category].push(item);
|
||
});
|
||
|
||
container.innerHTML = Object.entries(byCategory).map(([cat, items]) => `
|
||
<div class="category-group">
|
||
<h3>${cat}</h3>
|
||
${items.map(p => `
|
||
<div class="product-item">
|
||
<span class="name">${p.name}</span>
|
||
<span class="amount">${p.amount >= 1000 ? (p.amount/1000).toFixed(2) + ' кг' : p.amount + ' г'}</span>
|
||
<span class="cost">${p.cost.toLocaleString()} ₸</span>
|
||
</div>
|
||
`).join("")}
|
||
</div>
|
||
`).join("");
|
||
}
|
||
|
||
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 => `
|
||
<div class="timeline-item">
|
||
<span class="day">${item.day}</span>
|
||
<span class="action">
|
||
<strong>${item.action}</strong>
|
||
<div class="details">${item.details}</div>
|
||
</span>
|
||
<span class="time">⏱ ${item.time_min} мин</span>
|
||
</div>
|
||
`).join("");
|
||
}
|
||
|
||
function exportToExcel() {
|
||
const shopping = document.getElementById("shopping-list");
|
||
if (!shopping || shopping.children.length === 0) {
|
||
alert("Сначала сгенерируйте план питания!");
|
||
return;
|
||
}
|
||
|
||
let csv = "\uFEFF";
|
||
csv += "Категория;Продукт;Количество;Стоимость (₸)\n";
|
||
|
||
document.querySelectorAll(".category-group").forEach(group => {
|
||
const category = group.querySelector("h3").textContent;
|
||
group.querySelectorAll(".product-item").forEach(item => {
|
||
const name = item.querySelector(".name").textContent;
|
||
const amount = item.querySelector(".amount").textContent;
|
||
const cost = item.querySelector(".cost").textContent.replace(" ₸", "");
|
||
csv += `${category};${name};${amount};${cost}\n`;
|
||
});
|
||
});
|
||
|
||
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
|
||
const link = document.createElement("a");
|
||
link.href = URL.createObjectURL(blob);
|
||
link.download = "spisok_produktov.csv";
|
||
link.click();
|
||
}
|
||
|
||
window.showRecipe = showRecipe;
|