feat(meal-planner): add auto-generate meal plan functionality
Add new utility function to generate daily meal plans based on user's calorie targets and food categories. Include auto-generate button in meal planner UI with confirmation prompt. Add new Filipino food items to seed data for better meal variety. Fix database column references to use capitalized field names consistently.
This commit is contained in:
6
.devdbrc
Normal file
6
.devdbrc
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"type": "sqlite",
|
||||||
|
"path": "d:\\projects\\calorie_tracker\\calorie_tracker_app\\data\\calorie_tracker.db"
|
||||||
|
}
|
||||||
|
]
|
||||||
Binary file not shown.
BIN
data/sessions.db
BIN
data/sessions.db
Binary file not shown.
116
scripts/seed.js
116
scripts/seed.js
@@ -319,6 +319,122 @@ const filipinoFoods = [
|
|||||||
fat_g: 12,
|
fat_g: 12,
|
||||||
serving_description: '1 piece',
|
serving_description: '1 piece',
|
||||||
serving_size_g: 120
|
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
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
52
server.js
52
server.js
@@ -153,10 +153,10 @@ app.get('/dashboard', ensureAuthenticated, async (req, res) => {
|
|||||||
const water = await utils.calculateWaterTotal(req.user.id, dateStr);
|
const water = await utils.calculateWaterTotal(req.user.id, dateStr);
|
||||||
|
|
||||||
// Get user goals
|
// 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) {
|
if (!goals) {
|
||||||
goals = await db.UserGoal.create({
|
goals = await db.UserGoal.create({
|
||||||
user_id: req.user.id,
|
UserId: req.user.id,
|
||||||
target_protein_g: 150,
|
target_protein_g: 150,
|
||||||
target_carbs_g: 200,
|
target_carbs_g: 200,
|
||||||
target_fat_g: 60,
|
target_fat_g: 60,
|
||||||
@@ -166,7 +166,7 @@ app.get('/dashboard', ensureAuthenticated, async (req, res) => {
|
|||||||
|
|
||||||
// Get weight info
|
// Get weight info
|
||||||
const weightLogToday = await db.WeightLog.findOne({
|
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);
|
const yesterday = new Date(today);
|
||||||
@@ -174,7 +174,7 @@ app.get('/dashboard', ensureAuthenticated, async (req, res) => {
|
|||||||
const yesterdayStr = yesterday.toISOString().split('T')[0];
|
const yesterdayStr = yesterday.toISOString().split('T')[0];
|
||||||
|
|
||||||
const weightLogYesterday = await db.WeightLog.findOne({
|
const weightLogYesterday = await db.WeightLog.findOne({
|
||||||
where: { user_id: req.user.id, date: yesterdayStr }
|
where: { UserId: req.user.id, date: yesterdayStr }
|
||||||
});
|
});
|
||||||
|
|
||||||
let weightChange = null;
|
let weightChange = null;
|
||||||
@@ -259,7 +259,7 @@ app.post('/add-meal', ensureAuthenticated, async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const meal = await db.Meal.create({
|
const meal = await db.Meal.create({
|
||||||
user_id: req.user.id,
|
UserId: req.user.id,
|
||||||
date: date || new Date(),
|
date: date || new Date(),
|
||||||
meal_type,
|
meal_type,
|
||||||
time: time || null
|
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];
|
const date = req.body.date || new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
await db.WaterLog.create({
|
await db.WaterLog.create({
|
||||||
user_id: req.user.id,
|
UserId: req.user.id,
|
||||||
date: date,
|
date: date,
|
||||||
amount_ml: amountMl,
|
amount_ml: amountMl,
|
||||||
time: new Date()
|
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];
|
const date = req.body.date || new Date().toISOString().split('T')[0];
|
||||||
|
|
||||||
let weightLog = await db.WeightLog.findOne({
|
let weightLog = await db.WeightLog.findOne({
|
||||||
where: { user_id: req.user.id, date: date }
|
where: { UserId: req.user.id, date: date }
|
||||||
});
|
});
|
||||||
|
|
||||||
if (weightLog) {
|
if (weightLog) {
|
||||||
@@ -369,7 +369,7 @@ app.post('/add-weight', ensureAuthenticated, async (req, res) => {
|
|||||||
await weightLog.save();
|
await weightLog.save();
|
||||||
} else {
|
} else {
|
||||||
await db.WeightLog.create({
|
await db.WeightLog.create({
|
||||||
user_id: req.user.id,
|
UserId: req.user.id,
|
||||||
date: date,
|
date: date,
|
||||||
weight_kg: weightKg,
|
weight_kg: weightKg,
|
||||||
time: new Date()
|
time: new Date()
|
||||||
@@ -388,7 +388,7 @@ app.post('/add-weight', ensureAuthenticated, async (req, res) => {
|
|||||||
|
|
||||||
app.get('/goals', ensureAuthenticated, async (req, res) => {
|
app.get('/goals', ensureAuthenticated, async (req, res) => {
|
||||||
try {
|
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 bmr = null;
|
||||||
let tdee = null;
|
let tdee = null;
|
||||||
@@ -444,9 +444,9 @@ app.post('/goals', ensureAuthenticated, async (req, res) => {
|
|||||||
await user.save();
|
await user.save();
|
||||||
|
|
||||||
// Update or create goals
|
// 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) {
|
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;
|
userGoals.goal_type = goalType;
|
||||||
@@ -578,7 +578,7 @@ app.get('/meal-planner', ensureAuthenticated, async (req, res) => {
|
|||||||
const dateStr = d.toISOString().split('T')[0];
|
const dateStr = d.toISOString().split('T')[0];
|
||||||
|
|
||||||
const plans = await db.MealPlan.findAll({
|
const plans = await db.MealPlan.findAll({
|
||||||
where: { user_id: req.user.id, date: dateStr },
|
where: { UserId: req.user.id, date: dateStr },
|
||||||
include: [{
|
include: [{
|
||||||
model: db.PlannedFood,
|
model: db.PlannedFood,
|
||||||
include: [db.FoodItem]
|
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) => {
|
app.post('/meal-planner/add', ensureAuthenticated, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
let { date, meal_type, 'food_id[]': foodIds, 'quantity[]': quantities } = req.body;
|
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
|
// Find or create meal plan
|
||||||
let mealPlan = await db.MealPlan.findOne({
|
let mealPlan = await db.MealPlan.findOne({
|
||||||
where: {
|
where: {
|
||||||
user_id: req.user.id,
|
UserId: req.user.id,
|
||||||
date: date,
|
date: date,
|
||||||
meal_type: meal_type
|
meal_type: meal_type
|
||||||
}
|
}
|
||||||
@@ -656,7 +676,7 @@ app.post('/meal-planner/add', ensureAuthenticated, async (req, res) => {
|
|||||||
|
|
||||||
if (!mealPlan) {
|
if (!mealPlan) {
|
||||||
mealPlan = await db.MealPlan.create({
|
mealPlan = await db.MealPlan.create({
|
||||||
user_id: req.user.id,
|
UserId: req.user.id,
|
||||||
date: date,
|
date: date,
|
||||||
meal_type: meal_type
|
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) => {
|
app.post('/meal-planner/complete/:id', ensureAuthenticated, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const plan = await db.MealPlan.findOne({
|
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) {
|
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) => {
|
app.post('/meal-planner/delete/:id', ensureAuthenticated, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
await db.MealPlan.destroy({
|
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');
|
req.flash('success_msg', 'Meal plan deleted');
|
||||||
res.redirect('/meal-planner');
|
res.redirect('/meal-planner');
|
||||||
|
|||||||
134
utils/index.js
134
utils/index.js
@@ -65,7 +65,7 @@ async function calculateDailyTotals(userId, targetDate = null) {
|
|||||||
// Get all meals for the date
|
// Get all meals for the date
|
||||||
const meals = await db.Meal.findAll({
|
const meals = await db.Meal.findAll({
|
||||||
where: {
|
where: {
|
||||||
user_id: userId,
|
UserId: userId,
|
||||||
date: dateStr
|
date: dateStr
|
||||||
},
|
},
|
||||||
include: [{
|
include: [{
|
||||||
@@ -142,7 +142,7 @@ async function calculateWaterTotal(userId, targetDate = null) {
|
|||||||
|
|
||||||
const waterLogs = await db.WaterLog.findAll({
|
const waterLogs = await db.WaterLog.findAll({
|
||||||
where: {
|
where: {
|
||||||
user_id: userId,
|
UserId: userId,
|
||||||
date: dateStr
|
date: dateStr
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -170,7 +170,7 @@ async function getWeightTrend(userId, days = 7) {
|
|||||||
|
|
||||||
const weightLogs = await db.WeightLog.findAll({
|
const weightLogs = await db.WeightLog.findAll({
|
||||||
where: {
|
where: {
|
||||||
user_id: userId,
|
UserId: userId,
|
||||||
date: {
|
date: {
|
||||||
[Op.between]: [startDateStr, endDateStr]
|
[Op.between]: [startDateStr, endDateStr]
|
||||||
}
|
}
|
||||||
@@ -226,7 +226,7 @@ async function updateDailySummary(userId, targetDate = null) {
|
|||||||
// Get weight for the day
|
// Get weight for the day
|
||||||
const weightLog = await db.WeightLog.findOne({
|
const weightLog = await db.WeightLog.findOne({
|
||||||
where: {
|
where: {
|
||||||
user_id: userId,
|
UserId: userId,
|
||||||
date: dateStr
|
date: dateStr
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -239,14 +239,14 @@ async function updateDailySummary(userId, targetDate = null) {
|
|||||||
// Find or create summary
|
// Find or create summary
|
||||||
let summary = await db.DailySummary.findOne({
|
let summary = await db.DailySummary.findOne({
|
||||||
where: {
|
where: {
|
||||||
user_id: userId,
|
UserId: userId,
|
||||||
date: dateStr
|
date: dateStr
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!summary) {
|
if (!summary) {
|
||||||
summary = await db.DailySummary.create({
|
summary = await db.DailySummary.create({
|
||||||
user_id: userId,
|
UserId: userId,
|
||||||
date: dateStr
|
date: dateStr
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -326,6 +326,125 @@ function suggestFoodsForMacros(remainingProtein, remainingCarbs, remainingFat) {
|
|||||||
return suggestions;
|
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 = {
|
module.exports = {
|
||||||
calculateBMR,
|
calculateBMR,
|
||||||
calculateTDEE,
|
calculateTDEE,
|
||||||
@@ -336,5 +455,6 @@ module.exports = {
|
|||||||
getCalorieTrend,
|
getCalorieTrend,
|
||||||
updateDailySummary,
|
updateDailySummary,
|
||||||
getMacroPercentages,
|
getMacroPercentages,
|
||||||
suggestFoodsForMacros
|
suggestFoodsForMacros,
|
||||||
|
generateDailyMealPlan
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -64,10 +64,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
|
<!-- Buttons -->
|
||||||
|
<div class="mt-auto space-y-2">
|
||||||
<!-- Add Button -->
|
<!-- Add Button -->
|
||||||
<button onclick="openPlanModal('<%= dateStr %>')" class="w-full py-2 border-2 border-dashed border-gray-300 rounded-lg text-gray-400 hover:border-primary hover:text-primary transition text-sm mt-auto">
|
<button onclick="openPlanModal('<%= dateStr %>')" class="w-full py-2 border-2 border-dashed border-gray-300 rounded-lg text-gray-400 hover:border-primary hover:text-primary transition text-sm">
|
||||||
+ Plan Meal
|
+ Plan Meal
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- Auto Generate Button -->
|
||||||
|
<form action="/meal-planner/auto-generate" method="POST" onsubmit="return confirm('This will replace any existing meal plan for this date. Continue?');">
|
||||||
|
<input type="hidden" name="date" value="<%= dateStr %>">
|
||||||
|
<button type="submit" class="w-full py-2 bg-green-50 text-green-600 rounded-lg hover:bg-green-100 transition text-sm font-medium">
|
||||||
|
⚡ Auto Generate
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% }); %>
|
<% }); %>
|
||||||
|
|||||||
Reference in New Issue
Block a user