Skip to main content
Glama
Dinesh-Satram

Health & Fitness Coach MCP

meal-tracker.tsx11.5 kB
"use client" import { useState, useEffect, useCallback } from "react" import { Utensils, Plus, Clock, Flame } from "lucide-react" import { Button } from "@/components/ui/button" import { Progress } from "@/components/ui/progress" import { cn } from "@/lib/utils" import { useFitnessData } from "@/hooks/useFitnessData" interface Meal { id: string type: string name: string calories: number time: string completed: boolean protein: number carbs: number fat: number } interface MealTrackerProps { loggedNutrition?: Array<{ id: string foodItem: string mealType: string calories: number protein: number carbs: number fat: number timestamp: string }> } export function MealTracker({ loggedNutrition = [] }: MealTrackerProps) { const { logNewEntry, refreshContext } = useFitnessData() // Helper function to check if a meal is completed based on logged nutrition const isMealCompleted = useCallback((mealName: string) => { return loggedNutrition.some(nutrition => nutrition.foodItem.toLowerCase().includes(mealName.toLowerCase()) || mealName.toLowerCase().includes(nutrition.foodItem.toLowerCase()) ) }, [loggedNutrition]) // Helper function to get actual calories from logged nutrition const getActualCalories = useCallback((mealName: string) => { const loggedMeal = loggedNutrition.find(nutrition => nutrition.foodItem.toLowerCase().includes(mealName.toLowerCase()) || mealName.toLowerCase().includes(nutrition.foodItem.toLowerCase()) ) return loggedMeal?.calories || 0 }, [loggedNutrition]) const [meals, setMeals] = useState<Meal[]>([ { id: "1", type: "Breakfast", name: "Oatmeal with berries & almonds", calories: 320, time: "8:00 AM", completed: isMealCompleted("Oatmeal with berries & almonds"), protein: 12, carbs: 45, fat: 8, }, { id: "2", type: "Lunch", name: "Grilled chicken Caesar salad", calories: 450, time: "12:30 PM", completed: isMealCompleted("Grilled chicken Caesar salad"), protein: 35, carbs: 15, fat: 22, }, { id: "3", type: "Snack", name: "Greek yogurt with honey", calories: 150, time: "3:00 PM", completed: isMealCompleted("Greek yogurt with honey"), protein: 15, carbs: 18, fat: 3, }, { id: "4", type: "Dinner", name: "Baked salmon with quinoa & broccoli", calories: 520, time: "7:00 PM", completed: isMealCompleted("Baked salmon with quinoa & broccoli"), protein: 40, carbs: 35, fat: 18, }, ]) // Update meal completion status when loggedNutrition changes useEffect(() => { setMeals(prevMeals => prevMeals.map(meal => ({ ...meal, completed: isMealCompleted(meal.name) })) ) }, [isMealCompleted]) const toggleMeal = async (id: string) => { const meal = meals.find(m => m.id === id) if (!meal) return const newCompletedState = !meal.completed // Check if this meal is already logged via + button const isAlreadyLogged = isMealCompleted(meal.name) if (isAlreadyLogged && !newCompletedState) { // If trying to uncheck a logged meal, just return (keep it completed) return } // Update local state immediately for UI responsiveness setMeals( meals.map((m) => (m.id === id ? { ...m, completed: newCompletedState } : m)), ) // If completing the meal and it's not already logged, log it to the API if (newCompletedState && !isAlreadyLogged) { try { const result = await logNewEntry('nutrition', { nutrition: { mealType: meal.type.toLowerCase() as 'breakfast' | 'lunch' | 'dinner' | 'snack', foodItem: meal.name, calories: meal.calories, protein: meal.protein, carbs: meal.carbs, fat: meal.fat, } }) if (result.success) { console.log(`✅ Logged completed meal: ${meal.name} (${meal.calories} cal)`) } else { console.error('❌ Failed to log meal:', result.error) } } catch (error) { console.error('Failed to log meal completion:', error) // Revert the state if API call failed setMeals( meals.map((m) => (m.id === id ? { ...m, completed: !newCompletedState } : m)), ) } } } const completedMeals = meals.filter((meal) => meal.completed) // Calculate totals using actual logged calories if available, otherwise plan calories let totalCalories = 0 let totalProtein = 0 completedMeals.forEach(meal => { const loggedCalories = getActualCalories(meal.name) if (loggedCalories > 0) { totalCalories += loggedCalories // Estimate protein from logged calories (25% of calories / 4 cal per gram) totalProtein += Math.round(loggedCalories * 0.25 / 4) } else { // Use plan values for manually completed meals totalCalories += meal.calories totalProtein += meal.protein } }) const progress = (completedMeals.length / meals.length) * 100 const targetCalories = 1440 const targetProtein = 120 return ( <div className="bg-white/80 backdrop-blur-sm rounded-3xl shadow-lg border border-white/20 p-6"> {/* Header */} <div className="flex items-center justify-between mb-6"> <div className="flex items-center gap-3"> <div className="w-12 h-12 bg-gradient-to-br from-green-400 to-emerald-500 rounded-2xl flex items-center justify-center"> <Utensils className="w-6 h-6 text-white" /> </div> <div> <h3 className="text-lg font-bold text-slate-800">Today's Meals</h3> <div className="flex items-center gap-2 mt-1"> <Progress value={progress} className="w-20 h-2" /> <span className="text-sm text-slate-600">{Math.round(progress)}%</span> </div> </div> </div> <Button size="sm" className="rounded-full bg-gradient-to-r from-green-400 to-emerald-500"> <Plus className="w-4 h-4" /> </Button> </div> {/* Meals List */} <div className="space-y-3 mb-6"> {meals.map((meal) => ( <button key={meal.id} onClick={() => toggleMeal(meal.id)} className={cn( "w-full p-4 rounded-2xl border-2 transition-all duration-200 text-left group", meal.completed ? "bg-gradient-to-r from-green-50 to-emerald-50 border-green-200 shadow-sm" : "bg-gradient-to-r from-slate-50 to-gray-50 border-slate-200 hover:border-green-300 hover:shadow-md hover:scale-[1.01]", )} > <div className="flex items-start justify-between"> <div className="flex items-start gap-3 flex-1"> <div className={cn( "w-4 h-4 rounded-full mt-0.5 transition-all duration-200 flex items-center justify-center", meal.completed ? "bg-green-500 shadow-lg shadow-green-200" : "bg-slate-300 group-hover:bg-green-400", )} > {meal.completed && ( <svg className="w-2.5 h-2.5 text-white" fill="currentColor" viewBox="0 0 20 20"> <path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" /> </svg> )} </div> <div className="flex-1 min-w-0"> <div className="flex items-center gap-2 mb-1"> <span className={cn( "text-xs font-semibold px-2 py-1 rounded-full", meal.completed ? "bg-green-100 text-green-700" : "bg-slate-100 text-slate-600 group-hover:bg-green-100 group-hover:text-green-700", )} > {meal.type.toUpperCase()} </span> <div className="flex items-center gap-1 text-xs text-slate-500"> <Clock className="w-3 h-3" /> {meal.time} </div> </div> <p className={cn( "font-medium text-sm leading-relaxed mb-2", meal.completed ? "text-green-800" : "text-slate-700", )} > {meal.name} </p> <div className="flex items-center gap-4 text-xs"> <div className="flex items-center gap-1"> <Flame className="w-3 h-3 text-orange-500" /> <span className={meal.completed ? "text-green-600" : "text-slate-500"}>{meal.calories} cal</span> </div> <span className={meal.completed ? "text-green-600" : "text-slate-500"}>P: {meal.protein}g</span> <span className={meal.completed ? "text-green-600" : "text-slate-500"}>C: {meal.carbs}g</span> <span className={meal.completed ? "text-green-600" : "text-slate-500"}>F: {meal.fat}g</span> </div> </div> </div> </div> </button> ))} </div> {/* Nutrition Summary */} <div className="bg-gradient-to-r from-green-50 to-emerald-50 rounded-2xl p-4 border border-green-200/50"> <h4 className="font-semibold text-green-800 mb-3 text-sm flex items-center gap-2"> <Flame className="w-4 h-4" /> Daily Progress </h4> <div className="grid grid-cols-2 gap-4"> <div> <div className="flex items-center justify-between mb-1"> <span className="text-xs text-green-600">Calories</span> <span className="text-xs text-green-600"> {totalCalories}/{targetCalories} </span> </div> <div className="w-full bg-green-200 rounded-full h-2"> <div className="bg-gradient-to-r from-green-400 to-emerald-500 h-2 rounded-full transition-all duration-300" style={{ width: `${Math.min((totalCalories / targetCalories) * 100, 100)}%` }} ></div> </div> </div> <div> <div className="flex items-center justify-between mb-1"> <span className="text-xs text-green-600">Protein</span> <span className="text-xs text-green-600"> {totalProtein}g/{targetProtein}g </span> </div> <div className="w-full bg-green-200 rounded-full h-2"> <div className="bg-gradient-to-r from-green-400 to-emerald-500 h-2 rounded-full transition-all duration-300" style={{ width: `${Math.min((totalProtein / targetProtein) * 100, 100)}%` }} ></div> </div> </div> </div> </div> </div> ) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Dinesh-Satram/fitness_coach_MCP'

If you have feedback or need assistance with the MCP directory API, please join our Discord server