diff --git a/.devdbrc b/.devdbrc new file mode 100644 index 0000000..dc44e22 --- /dev/null +++ b/.devdbrc @@ -0,0 +1,6 @@ +[ + { + "type": "sqlite", + "path": "d:\\projects\\calorie_tracker\\calorie_tracker_app\\data\\calorie_tracker.db" + } +] \ No newline at end of file diff --git a/data/calorie_tracker.db b/data/calorie_tracker.db index e2825ac..29fc8cf 100644 Binary files a/data/calorie_tracker.db and b/data/calorie_tracker.db differ diff --git a/data/sessions.db b/data/sessions.db index 9e11b4e..fa797e9 100644 Binary files a/data/sessions.db and b/data/sessions.db differ diff --git a/scripts/seed.js b/scripts/seed.js index fa1430b..07a677e 100644 --- a/scripts/seed.js +++ b/scripts/seed.js @@ -319,6 +319,122 @@ const filipinoFoods = [ fat_g: 12, serving_description: '1 piece', serving_size_g: 120 + }, + + // Vegetables (Gulay) + { + name: 'Pinakbet', + name_tagalog: 'Pinakbet', + category: 'gulay', + calories: 150, + protein_g: 6, + carbs_g: 15, + fat_g: 8, + serving_description: '1 cup', + serving_size_g: 200 + }, + { + name: 'Ginisang Monggo', + name_tagalog: 'Ginisang Monggo', + category: 'gulay', + calories: 220, + protein_g: 15, + carbs_g: 30, + fat_g: 6, + serving_description: '1 cup', + serving_size_g: 240 + }, + { + name: 'Chopsuey', + name_tagalog: 'Chopsuey', + category: 'gulay', + calories: 180, + protein_g: 12, + carbs_g: 10, + fat_g: 10, + serving_description: '1 cup', + serving_size_g: 200 + }, + { + name: 'Laing', + name_tagalog: 'Laing', + category: 'gulay', + calories: 250, + protein_g: 5, + carbs_g: 12, + fat_g: 22, + serving_description: '1 serving', + serving_size_g: 150 + }, + + // Breakfast + { + name: 'Tapsilog', + name_tagalog: 'Tapsilog', + category: 'almusal', + calories: 550, + protein_g: 35, + carbs_g: 45, + fat_g: 25, + serving_description: '1 order (rice, egg, beef)', + serving_size_g: 350 + }, + { + name: 'Longsilog', + name_tagalog: 'Longsilog', + category: 'almusal', + calories: 600, + protein_g: 20, + carbs_g: 45, + fat_g: 35, + serving_description: '1 order (rice, egg, sausage)', + serving_size_g: 350 + }, + { + name: 'Pandesal', + name_tagalog: 'Pandesal', + category: 'almusal', + calories: 35, + protein_g: 1.5, + carbs_g: 6, + fat_g: 0.5, + serving_description: '1 piece', + serving_size_g: 30 + }, + + // Desserts/Snacks + { + name: 'Halo-Halo', + name_tagalog: 'Halo-Halo', + category: 'meryenda', + calories: 450, + protein_g: 12, + carbs_g: 85, + fat_g: 8, + serving_description: '1 glass', + serving_size_g: 400 + }, + { + name: 'Leche Flan', + name_tagalog: 'Leche Flan', + category: 'meryenda', + calories: 220, + protein_g: 5, + carbs_g: 25, + fat_g: 10, + serving_description: '1 slice', + serving_size_g: 80 + }, + { + name: 'Turon', + name_tagalog: 'Turon', + category: 'meryenda', + calories: 200, + protein_g: 2, + carbs_g: 35, + fat_g: 7, + serving_description: '1 piece', + serving_size_g: 100 } ]; diff --git a/server.js b/server.js index 5ee31e0..e4b478e 100644 --- a/server.js +++ b/server.js @@ -153,10 +153,10 @@ app.get('/dashboard', ensureAuthenticated, async (req, res) => { const water = await utils.calculateWaterTotal(req.user.id, dateStr); // Get user goals - let goals = await db.UserGoal.findOne({ where: { user_id: req.user.id } }); + let goals = await db.UserGoal.findOne({ where: { UserId: req.user.id } }); if (!goals) { goals = await db.UserGoal.create({ - user_id: req.user.id, + UserId: req.user.id, target_protein_g: 150, target_carbs_g: 200, target_fat_g: 60, @@ -166,7 +166,7 @@ app.get('/dashboard', ensureAuthenticated, async (req, res) => { // Get weight info const weightLogToday = await db.WeightLog.findOne({ - where: { user_id: req.user.id, date: dateStr } + where: { UserId: req.user.id, date: dateStr } }); const yesterday = new Date(today); @@ -174,7 +174,7 @@ app.get('/dashboard', ensureAuthenticated, async (req, res) => { const yesterdayStr = yesterday.toISOString().split('T')[0]; const weightLogYesterday = await db.WeightLog.findOne({ - where: { user_id: req.user.id, date: yesterdayStr } + where: { UserId: req.user.id, date: yesterdayStr } }); let weightChange = null; @@ -259,7 +259,7 @@ app.post('/add-meal', ensureAuthenticated, async (req, res) => { } const meal = await db.Meal.create({ - user_id: req.user.id, + UserId: req.user.id, date: date || new Date(), meal_type, time: time || null @@ -337,7 +337,7 @@ app.post('/add-water', ensureAuthenticated, async (req, res) => { const date = req.body.date || new Date().toISOString().split('T')[0]; await db.WaterLog.create({ - user_id: req.user.id, + UserId: req.user.id, date: date, amount_ml: amountMl, time: new Date() @@ -360,7 +360,7 @@ app.post('/add-weight', ensureAuthenticated, async (req, res) => { const date = req.body.date || new Date().toISOString().split('T')[0]; let weightLog = await db.WeightLog.findOne({ - where: { user_id: req.user.id, date: date } + where: { UserId: req.user.id, date: date } }); if (weightLog) { @@ -369,7 +369,7 @@ app.post('/add-weight', ensureAuthenticated, async (req, res) => { await weightLog.save(); } else { await db.WeightLog.create({ - user_id: req.user.id, + UserId: req.user.id, date: date, weight_kg: weightKg, time: new Date() @@ -388,7 +388,7 @@ app.post('/add-weight', ensureAuthenticated, async (req, res) => { app.get('/goals', ensureAuthenticated, async (req, res) => { try { - const userGoals = await db.UserGoal.findOne({ where: { user_id: req.user.id } }); + const userGoals = await db.UserGoal.findOne({ where: { UserId: req.user.id } }); let bmr = null; let tdee = null; @@ -444,9 +444,9 @@ app.post('/goals', ensureAuthenticated, async (req, res) => { await user.save(); // Update or create goals - let userGoals = await db.UserGoal.findOne({ where: { user_id: req.user.id } }); + let userGoals = await db.UserGoal.findOne({ where: { UserId: req.user.id } }); if (!userGoals) { - userGoals = await db.UserGoal.create({ user_id: req.user.id }); + userGoals = await db.UserGoal.create({ UserId: req.user.id }); } userGoals.goal_type = goalType; @@ -578,7 +578,7 @@ app.get('/meal-planner', ensureAuthenticated, async (req, res) => { const dateStr = d.toISOString().split('T')[0]; const plans = await db.MealPlan.findAll({ - where: { user_id: req.user.id, date: dateStr }, + where: { UserId: req.user.id, date: dateStr }, include: [{ model: db.PlannedFood, include: [db.FoodItem] @@ -630,6 +630,26 @@ app.get('/meal-planner', ensureAuthenticated, async (req, res) => { } }); +app.post('/meal-planner/auto-generate', ensureAuthenticated, async (req, res) => { + try { + const { date } = req.body; + + if (!date) { + req.flash('error_msg', 'Please select a date'); + return res.redirect('/meal-planner'); + } + + await utils.generateDailyMealPlan(req.user.id, date); + + req.flash('success_msg', 'Meal plan generated successfully!'); + res.redirect('/meal-planner'); + } catch (err) { + console.error(err); + req.flash('error_msg', 'Error generating meal plan'); + res.redirect('/meal-planner'); + } +}); + app.post('/meal-planner/add', ensureAuthenticated, async (req, res) => { try { let { date, meal_type, 'food_id[]': foodIds, 'quantity[]': quantities } = req.body; @@ -648,7 +668,7 @@ app.post('/meal-planner/add', ensureAuthenticated, async (req, res) => { // Find or create meal plan let mealPlan = await db.MealPlan.findOne({ where: { - user_id: req.user.id, + UserId: req.user.id, date: date, meal_type: meal_type } @@ -656,7 +676,7 @@ app.post('/meal-planner/add', ensureAuthenticated, async (req, res) => { if (!mealPlan) { mealPlan = await db.MealPlan.create({ - user_id: req.user.id, + UserId: req.user.id, date: date, meal_type: meal_type }); @@ -688,7 +708,7 @@ app.post('/meal-planner/add', ensureAuthenticated, async (req, res) => { app.post('/meal-planner/complete/:id', ensureAuthenticated, async (req, res) => { try { const plan = await db.MealPlan.findOne({ - where: { id: req.params.id, user_id: req.user.id } + where: { id: req.params.id, UserId: req.user.id } }); if (plan) { @@ -710,7 +730,7 @@ app.post('/meal-planner/complete/:id', ensureAuthenticated, async (req, res) => app.post('/meal-planner/delete/:id', ensureAuthenticated, async (req, res) => { try { await db.MealPlan.destroy({ - where: { id: req.params.id, user_id: req.user.id } + where: { id: req.params.id, UserId: req.user.id } }); req.flash('success_msg', 'Meal plan deleted'); res.redirect('/meal-planner'); diff --git a/utils/index.js b/utils/index.js index c6551d5..e8c1ce6 100644 --- a/utils/index.js +++ b/utils/index.js @@ -65,7 +65,7 @@ async function calculateDailyTotals(userId, targetDate = null) { // Get all meals for the date const meals = await db.Meal.findAll({ where: { - user_id: userId, + UserId: userId, date: dateStr }, include: [{ @@ -142,7 +142,7 @@ async function calculateWaterTotal(userId, targetDate = null) { const waterLogs = await db.WaterLog.findAll({ where: { - user_id: userId, + UserId: userId, date: dateStr } }); @@ -170,7 +170,7 @@ async function getWeightTrend(userId, days = 7) { const weightLogs = await db.WeightLog.findAll({ where: { - user_id: userId, + UserId: userId, date: { [Op.between]: [startDateStr, endDateStr] } @@ -226,7 +226,7 @@ async function updateDailySummary(userId, targetDate = null) { // Get weight for the day const weightLog = await db.WeightLog.findOne({ where: { - user_id: userId, + UserId: userId, date: dateStr } }); @@ -239,14 +239,14 @@ async function updateDailySummary(userId, targetDate = null) { // Find or create summary let summary = await db.DailySummary.findOne({ where: { - user_id: userId, + UserId: userId, date: dateStr } }); if (!summary) { summary = await db.DailySummary.create({ - user_id: userId, + UserId: userId, date: dateStr }); } @@ -326,6 +326,125 @@ function suggestFoodsForMacros(remainingProtein, remainingCarbs, remainingFat) { return suggestions; } +async function generateDailyMealPlan(userId, dateStr) { + try { + // Get user's calorie target + const user = await db.User.findByPk(userId); + const targetCalories = user ? user.target_daily_calories : 2000; + + // Distribution: Breakfast 25%, Lunch 35%, Dinner 30%, Snack 10% + const targets = { + breakfast: Math.round(targetCalories * 0.25), + lunch: Math.round(targetCalories * 0.35), + dinner: Math.round(targetCalories * 0.30), + snack: Math.round(targetCalories * 0.10) + }; + + // Fetch all foods + const foods = await db.FoodItem.findAll(); + + // Categorize foods + const foodByCat = { + almusal: foods.filter(f => f.category === 'almusal'), + ulam: foods.filter(f => ['ulam', 'sabaw'].includes(f.category)), + kanin: foods.filter(f => f.category === 'kanin'), + gulay: foods.filter(f => f.category === 'gulay'), + meryenda: foods.filter(f => f.category === 'meryenda') + }; + + // Fallback if categories are empty (use all foods) + const allFoods = foods; + + // Helper to pick random item + const pickRandom = (arr) => arr.length > 0 ? arr[Math.floor(Math.random() * arr.length)] : null; + + // Helper to generate a meal + const generateMeal = async (mealType, targetCal) => { + let currentCal = 0; + const selectedFoods = []; + + // Strategy per meal type + if (mealType === 'breakfast') { + // Try almusal first + const main = pickRandom(foodByCat.almusal) || pickRandom(allFoods); + if (main) { + selectedFoods.push({ food: main, qty: 1 }); + currentCal += main.calories; + } + } else if (['lunch', 'dinner'].includes(mealType)) { + // Rice + Ulam + (maybe) Gulay + const rice = pickRandom(foodByCat.kanin); + const ulam = pickRandom(foodByCat.ulam) || pickRandom(allFoods); + const gulay = pickRandom(foodByCat.gulay); + + if (rice) { + selectedFoods.push({ food: rice, qty: 1 }); + currentCal += rice.calories; + } + if (ulam) { + selectedFoods.push({ food: ulam, qty: 1 }); + currentCal += ulam.calories; + } + // Add vegetable if we have room + if (gulay && currentCal < targetCal) { + selectedFoods.push({ food: gulay, qty: 1 }); + currentCal += gulay.calories; + } + } else if (mealType === 'snack') { + const snack = pickRandom(foodByCat.meryenda) || pickRandom(allFoods); + if (snack) { + selectedFoods.push({ food: snack, qty: 1 }); + currentCal += snack.calories; + } + } + + // Adjust quantities to closer match target (simple scaling) + if (currentCal > 0 && selectedFoods.length > 0) { + const ratio = targetCal / currentCal; + // Limit scaling to reasonable bounds (0.5x to 2.0x) to avoid weird portions + // But for simplicity, let's just create the plan as is, maybe adding another item if way under + + // Create MealPlan + const mealPlan = await db.MealPlan.create({ + UserId: userId, + date: dateStr, + meal_type: mealType, + is_completed: false + }); + + // Add foods + for (const item of selectedFoods) { + await db.PlannedFood.create({ + MealPlanId: mealPlan.id, + FoodItemId: item.food.id, + quantity: item.qty + }); + } + } + }; + + // Delete existing plans for this date? Or just append? + // Let's clear existing plans for this date to avoid duplicates if re-generating + await db.MealPlan.destroy({ + where: { + UserId: userId, + date: dateStr + } + }); + + // Generate for each meal type + await generateMeal('breakfast', targets.breakfast); + await generateMeal('lunch', targets.lunch); + await generateMeal('dinner', targets.dinner); + await generateMeal('snack', targets.snack); + + return true; + } catch (error) { + console.error('Error generating meal plan:', error); + throw error; + } +} + module.exports = { calculateBMR, calculateTDEE, @@ -336,5 +455,6 @@ module.exports = { getCalorieTrend, updateDailySummary, getMacroPercentages, - suggestFoodsForMacros + suggestFoodsForMacros, + generateDailyMealPlan }; diff --git a/views/meal_planner.ejs b/views/meal_planner.ejs index 1242961..126bd47 100644 --- a/views/meal_planner.ejs +++ b/views/meal_planner.ejs @@ -64,10 +64,21 @@ <% } %> - - + +
+ + + + +
+ + +
+
<% }); %>