import React, { useState, useEffect, useMemo, useRef } from 'react'; import { DietData, ExerciseData, SleepData, AdviceItem, WeeklyReport, GamificationState } from './types'; import { analyzeFoodImage, generateWeeklyReportAI } from './services/geminiService'; import { calculateGamificationState } from './services/gamificationLogic'; import { StepsChart, NutritionPieChart, SleepBarChart } from './components/AnalysisCharts'; // --- Helper: Generate Mock Data --- const generateMockData = () => { const dates = Array.from({ length: 7 }, (_, i) => { const d = new Date(); d.setDate(d.getDate() - (6 - i)); return d.toISOString().split('T')[0]; }); const diets: DietData[] = dates.map(date => ({ id: Math.random().toString(), date, calorieIntake: Math.floor(Math.random() * 600 + 400), vegRatio: Math.random() * 0.4 + 0.1, // 0.1 - 0.5 proteinRatio: Math.random() * 0.3 + 0.1, starchRatio: Math.random() * 0.4 + 0.2, sugaryDrinksCount: Math.random() > 0.7 ? 1 : 0, friedFoodCount: Math.random() > 0.8 ? 1 : 0, })); const exercises: ExerciseData[] = dates.map(date => ({ id: Math.random().toString(), date, dailySteps: Math.floor(Math.random() * 5000 + 4000), // 4000 - 9000 moderateExerciseMinutes: Math.floor(Math.random() * 40), totalSittingMinutes: Math.floor(Math.random() * 100 + 300), })); const sleeps: SleepData[] = dates.map(date => ({ id: Math.random().toString(), date, bedTime: "23:45", wakeTime: "06:30", sleepDuration: 6.5 + Math.random(), usedPhoneBeforeBed: Math.random() > 0.5, })); return { diets, exercises, sleeps }; }; // --- Core Analysis Logic Functions --- const analyzeDiet = (weeklyData: DietData[]): AdviceItem[] => { const advice: AdviceItem[] = []; if (weeklyData.length === 0) return advice; // 1. Veg & Protein Ratio Check (Average of last 7 entries) const avgVeg = weeklyData.reduce((sum, d) => sum + d.vegRatio, 0) / weeklyData.length; const avgProtein = weeklyData.reduce((sum, d) => sum + d.proteinRatio, 0) / weeklyData.length; if (avgVeg < 0.5 || avgProtein < 0.25) { advice.push({ category: 'Diet', title: '營養比例失衡', description: '建議採用「健康餐盤法」:1/2 蔬菜、1/4 蛋白質、1/4 澱粉。', priority: 'High' }); } // 2. Sugary Drinks Check const totalSugary = weeklyData.reduce((sum, d) => sum + d.sugaryDrinksCount, 0); const avgSugary = totalSugary / weeklyData.length; // Threshold: > (7-3)/7 per day => > 0.57 if (avgSugary > 0.57) { advice.push({ category: 'Diet', title: '含糖飲料攝取過多', description: '提醒每週至少 3 天不喝含糖飲料,多喝水。', priority: 'Medium' }); } return advice; }; const analyzeExercise = (weeklyData: ExerciseData[]): AdviceItem[] => { const advice: AdviceItem[] = []; if (weeklyData.length === 0) return advice; // 1. Steps Check const avgSteps = weeklyData.reduce((sum, d) => sum + d.dailySteps, 0) / weeklyData.length; if (avgSteps < 8000) { advice.push({ category: 'Exercise', title: '活動量不足', description: `目前平均步數 ${Math.round(avgSteps)} 步。建議每日目標:8,000–10,000 步。`, priority: 'Medium' }); } // 2. Moderate Exercise Check (Weekly sum) const totalModMin = weeklyData.reduce((sum, d) => sum + d.moderateExerciseMinutes, 0); if (totalModMin < 180) { // 3 * 60 advice.push({ category: 'Exercise', title: '中等強度運動不足', description: '每週建議至少 3 次,共 180 分鐘的中等強度運動。', priority: 'High' }); } // 3. Sitting Check (Simplified for daily avg) const avgSitting = weeklyData.reduce((sum, d) => sum + d.totalSittingMinutes, 0) / weeklyData.length; if (avgSitting > 480) { // > 8 hours advice.push({ category: 'Exercise', title: '久坐時間過長', description: '建議每久坐 50 分鐘後,起身活動 5 分鐘。', priority: 'Low' }); } return advice; }; const analyzeSleep = (weeklyData: SleepData[]): AdviceItem[] => { const advice: AdviceItem[] = []; if (weeklyData.length === 0) return advice; // 1. Late Night Check const lateNights = weeklyData.filter(d => { const [hour] = d.bedTime.split(':').map(Number); // If hour is 0, 1, 2, 3 (past midnight) OR hour >= 23 and min > 30... simplified check return (hour >= 0 && hour < 5) || (hour === 23 && parseInt(d.bedTime.split(':')[1]) > 30); }).length; if (lateNights > 2) { advice.push({ category: 'Sleep', title: '頻繁熬夜', description: '檢測到入睡時間較晚,建議養成規律作息,目標 23:00 前就寢。', priority: 'High' }); } // 2. Duration Check const avgSleep = weeklyData.reduce((sum, d) => sum + d.sleepDuration, 0) / weeklyData.length; if (avgSleep < 7.5) { advice.push({ category: 'Sleep', title: '睡眠時數不足', description: `平均睡眠僅 ${avgSleep.toFixed(1)} 小時。高中生建議每晚睡足 7.5 - 8.5 小時。`, priority: 'High' }); } // 3. Phone Habits const phoneDays = weeklyData.filter(d => d.usedPhoneBeforeBed).length; if (phoneDays > 3) { advice.push({ category: 'Sleep', title: '睡前使用手機', description: '藍光會影響褪黑激素分泌,建議睡前 30 分鐘不要使用手機。', priority: 'Medium' }); } return advice; }; // --- Main App Component --- enum Tab { DASHBOARD = 'DASHBOARD', DIET = 'DIET', EXERCISE = 'EXERCISE', SLEEP = 'SLEEP', REPORT = 'REPORT' } export default function App() { const [activeTab, setActiveTab] = useState(Tab.DASHBOARD); // Data State const [dietData, setDietData] = useState([]); const [exerciseData, setExerciseData] = useState([]); const [sleepData, setSleepData] = useState([]); // Report State const [adviceList, setAdviceList] = useState([]); const [aiReportSummary, setAiReportSummary] = useState(''); const [isGeneratingReport, setIsGeneratingReport] = useState(false); // Gamification State const [gameState, setGameState] = useState({ totalPoints: 0, level: 1, progressToNextLevel: 0, badges: [], challenges: [] }); // Initialize with some mock data on first load for demo purposes useEffect(() => { const { diets, exercises, sleeps } = generateMockData(); setDietData(diets); setExerciseData(exercises); setSleepData(sleeps); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Recalculate advice AND Gamification whenever data changes useEffect(() => { const dietAdvice = analyzeDiet(dietData); const exerciseAdvice = analyzeExercise(exerciseData); const sleepAdvice = analyzeSleep(sleepData); setAdviceList([...dietAdvice, ...exerciseAdvice, ...sleepAdvice]); // Update Gamification const newGameState = calculateGamificationState(dietData, exerciseData, sleepData); setGameState(newGameState); }, [dietData, exerciseData, sleepData]); // --- Input Handlers --- const handleDietSubmit = (data: DietData) => { setDietData(prev => [...prev, data]); setActiveTab(Tab.DASHBOARD); }; const handleExerciseSubmit = (data: ExerciseData) => { setExerciseData(prev => [...prev, data]); setActiveTab(Tab.DASHBOARD); }; const handleSleepSubmit = (data: SleepData) => { setSleepData(prev => [...prev, data]); setActiveTab(Tab.DASHBOARD); }; const handleGenerateReport = async () => { setIsGeneratingReport(true); try { const summary = await generateWeeklyReportAI(dietData, exerciseData, sleepData, adviceList); setAiReportSummary(summary); } catch (e) { setAiReportSummary("報告生成失敗,請檢查網路連線。"); } finally { setIsGeneratingReport(false); } }; // --- Render Views --- return (
{/* Header */}

AI 智慧健康管理

{/* Main Content Area */}
{activeTab === Tab.DASHBOARD && ( )} {activeTab === Tab.DIET && setActiveTab(Tab.DASHBOARD)} />} {activeTab === Tab.EXERCISE && setActiveTab(Tab.DASHBOARD)} />} {activeTab === Tab.SLEEP && setActiveTab(Tab.DASHBOARD)} />} {activeTab === Tab.REPORT && ( )}
{/* Bottom Navigation */}
); } // --- Sub-Components --- const NavButton = ({ icon, label, active, onClick }: { icon: string, label: string, active: boolean, onClick: () => void }) => ( ); const DashboardView: React.FC = ({ dietData, exerciseData, sleepData, adviceCount, gameState, onNavigate }) => { return (
{/* --- Gamification Section --- */}

等級 {gameState.level}

總積分: {gameState.totalPoints}
{gameState.progressToNextLevel}%
{/* Progress Bar */}
{/* Badges Preview */}
{gameState.badges.map((badge: any) => (
{badge.name}
))}
{/* --- Active Challenges --- */}

本週挑戰

{gameState.challenges.map((challenge: any) => (

{challenge.title}

{challenge.current} / {challenge.target} {challenge.unit}
))}
{/* Status Cards */}
onNavigate(Tab.REPORT)}>
待處理建議
{adviceCount}
點擊查看建議
今日紀錄 {(new Date()).toISOString().split('T')[0].slice(5)} 保持健康好習慣!
{/* Charts */}
); }; // --- Input Views --- const DietInputView: React.FC<{ onSave: (d: DietData) => void, onCancel: () => void }> = ({ onSave, onCancel }) => { const [analyzing, setAnalyzing] = useState(false); const [imagePreview, setImagePreview] = useState(null); const [formData, setFormData] = useState>({ date: new Date().toISOString().split('T')[0], sugaryDrinksCount: 0, friedFoodCount: 0 }); const handleFileChange = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; // Preview const reader = new FileReader(); reader.onloadend = async () => { const base64 = reader.result as string; setImagePreview(base64); // Analyze with Gemini setAnalyzing(true); try { const base64Data = base64.split(',')[1]; // Remove data:image/jpeg;base64, const result = await analyzeFoodImage(base64Data); setFormData(prev => ({ ...prev, ...result })); } catch (error) { alert("分析失敗,請手動輸入數據"); } finally { setAnalyzing(false); } }; reader.readAsDataURL(file); }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (formData.calorieIntake !== undefined) { onSave({ id: Math.random().toString(), date: formData.date!, calorieIntake: formData.calorieIntake!, vegRatio: formData.vegRatio || 0, proteinRatio: formData.proteinRatio || 0, starchRatio: formData.starchRatio || 0, sugaryDrinksCount: formData.sugaryDrinksCount || 0, friedFoodCount: formData.friedFoodCount || 0 }); } else { alert("請輸入完整的飲食資訊或上傳照片"); } }; return (

新增飲食紀錄

{analyzing && (
AI 正在分析營養成分...
)}
setFormData({...formData, date: e.target.value})} className="w-full border rounded p-2" />
setFormData({...formData, calorieIntake: Number(e.target.value)})} className="w-full border rounded p-2" placeholder="0" />
setFormData({...formData, sugaryDrinksCount: Number(e.target.value)})} className="w-full border rounded p-2" />
setFormData({...formData, vegRatio: Number(e.target.value)})} className="w-full border rounded p-2 bg-green-50" /> setFormData({...formData, proteinRatio: Number(e.target.value)})} className="w-full border rounded p-2 bg-red-50" /> setFormData({...formData, starchRatio: Number(e.target.value)})} className="w-full border rounded p-2 bg-yellow-50" />
); }; const ExerciseInputView: React.FC<{ onSave: (d: ExerciseData) => void, onCancel: () => void }> = ({ onSave, onCancel }) => { const [data, setData] = useState>({ date: new Date().toISOString().split('T')[0], dailySteps: 0, moderateExerciseMinutes: 0, totalSittingMinutes: 0 }); return (

新增運動紀錄

setData({...data, date: e.target.value})} className="w-full border rounded p-2" />
setData({...data, dailySteps: Number(e.target.value)})} className="w-full border rounded p-2" placeholder="ex: 8000" />
setData({...data, moderateExerciseMinutes: Number(e.target.value)})} className="w-full border rounded p-2" placeholder="ex: 30" />
setData({...data, totalSittingMinutes: Number(e.target.value)})} className="w-full border rounded p-2" placeholder="ex: 480" />
) } const SleepInputView: React.FC<{ onSave: (d: SleepData) => void, onCancel: () => void }> = ({ onSave, onCancel }) => { const [data, setData] = useState>({ date: new Date().toISOString().split('T')[0], bedTime: '23:00', wakeTime: '07:00', usedPhoneBeforeBed: false }); const calculateDuration = () => { const start = new Date(`2000-01-01T${data.bedTime}`); const end = new Date(`2000-01-01T${data.wakeTime}`); if (end < start) end.setDate(end.getDate() + 1); const diff = (end.getTime() - start.getTime()) / 1000 / 60 / 60; return diff; } return (

新增睡眠紀錄

setData({...data, date: e.target.value})} className="w-full border rounded p-2" />
setData({...data, bedTime: e.target.value})} className="w-full border rounded p-2" />
setData({...data, wakeTime: e.target.value})} className="w-full border rounded p-2" />
setData({...data, usedPhoneBeforeBed: e.target.checked})} className="w-5 h-5 text-primary rounded" />
) } const ReportView: React.FC<{ adviceList: AdviceItem[], summary: string, isGenerating: boolean, onGenerate: () => void }> = ({ adviceList, summary, isGenerating, onGenerate }) => { return (
{/* AI Report Card */}

AI 健康週報

{!summary && !isGenerating && ( )}
{isGenerating ? (

AI 正在分析您的健康數據...

) : summary ? (
{summary.split('\n').map((line, i) =>

{line}

)}
) : (

點擊生成按鈕,讓 AI 為您分析本週的健康狀況並提供建議。

)}
{/* Advice Table */}

系統建議摘要

{adviceList.length === 0 ? (

太棒了!目前沒有發現顯著的健康問題。

) : (
{adviceList.map((advice, idx) => (
{advice.category}

{advice.title}

{advice.description}

))}
)}
); }; import React from 'react'; import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, PieChart, Pie, Cell, BarChart, Bar } from 'recharts'; import { DietData, ExerciseData, SleepData } from '../types'; interface ChartProps { dietData: DietData[]; exerciseData: ExerciseData[]; sleepData: SleepData[]; } const COLORS = ['#10B981', '#3B82F6', '#F59E0B', '#EF4444']; export const StepsChart: React.FC<{ data: ExerciseData[] }> = ({ data }) => { // Take last 7 entries const chartData = data.slice(-7).map(d => ({ name: d.date.substring(5), // MM-DD steps: d.dailySteps })); return (

每日步數趨勢

); }; export const NutritionPieChart: React.FC<{ data: DietData[] }> = ({ data }) => { // Calculate average ratios const total = data.length || 1; const avgVeg = data.reduce((acc, curr) => acc + curr.vegRatio, 0) / total; const avgProtein = data.reduce((acc, curr) => acc + curr.proteinRatio, 0) / total; const avgStarch = data.reduce((acc, curr) => acc + curr.starchRatio, 0) / total; // Normalize slightly if they don't add to 1 for the chart const sum = avgVeg + avgProtein + avgStarch || 1; const chartData = [ { name: '蔬菜', value: avgVeg / sum }, { name: '蛋白質', value: avgProtein / sum }, { name: '澱粉', value: avgStarch / sum }, ]; return (

平均營養比例

{chartData.map((entry, index) => ( ))}
); }; export const SleepBarChart: React.FC<{ data: SleepData[] }> = ({ data }) => { const chartData = data.slice(-7).map(d => ({ name: d.date.substring(5), hours: d.sleepDuration })); return (

睡眠時數

) } AI 智慧健康管理系統
import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; const rootElement = document.getElementById('root'); if (!rootElement) { throw new Error("Could not find root element to mount to"); } const root = ReactDOM.createRoot(rootElement); root.render( ); { "name": "AI 智慧健康管理系統", "description": "高中生全方位健康追蹤應用程式。整合飲食、運動與睡眠數據,利用 Gemini Vision 辨識飲食內容,並提供個人化健康建議與週報分析。", "requestFramePermissions": [ "camera" ] } import { DietData, ExerciseData, SleepData, Badge, Challenge, GamificationState } from '../types'; export const calculateGamificationState = ( diets: DietData[], exercises: ExerciseData[], sleeps: SleepData[] ): GamificationState => { let points = 0; // --- 1. Point Calculation System --- // Diet Points diets.forEach(d => { // Balanced Meal: Veg >= 30%, Protein >= 20% if (d.vegRatio >= 0.3 && d.proteinRatio >= 0.2) points += 20; // Healthy Choice: No sugary drinks if (d.sugaryDrinksCount === 0) points += 10; // Healthy Choice: No fried food if (d.friedFoodCount === 0) points += 10; }); // Exercise Points exercises.forEach(e => { // Step Goal if (e.dailySteps >= 8000) points += 15; // Extra Push if (e.dailySteps >= 12000) points += 10; // Moderate Exercise if (e.moderateExerciseMinutes >= 30) points += 20; }); // Sleep Points sleeps.forEach(s => { // Good Sleep Duration if (s.sleepDuration >= 7.0 && s.sleepDuration <= 9.0) points += 20; // Good Habits if (!s.usedPhoneBeforeBed) points += 15; }); // Level System: Every 200 points is a level const pointsPerLevel = 200; const level = Math.floor(points / pointsPerLevel) + 1; const progressToNextLevel = Math.floor(((points % pointsPerLevel) / pointsPerLevel) * 100); // --- 2. Badges Logic --- const badges: Badge[] = [ { id: 'first_step', name: '初出茅廬', description: '紀錄第一筆運動數據', icon: 'fa-person-walking', color: 'text-blue-400', isUnlocked: exercises.length > 0 }, { id: 'step_master', name: '萬步天王', description: '單日步數超過 10,000 步', icon: 'fa-fire', color: 'text-red-500', isUnlocked: exercises.some(e => e.dailySteps >= 10000) }, { id: 'sleep_streak', name: '好眠大師', description: '連續 3 天睡眠達標 (7+ 小時)', icon: 'fa-moon', color: 'text-purple-400', isUnlocked: false // Calculated below }, { id: 'clean_eater', name: '飲食清流', description: '累積 3 天不喝含糖飲料', icon: 'fa-leaf', color: 'text-green-500', isUnlocked: diets.filter(d => d.sugaryDrinksCount === 0).length >= 3 } ]; // Logic for Sleep Streak let sleepStreak = 0; let maxSleepStreak = 0; const sortedSleeps = [...sleeps].sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()); sortedSleeps.forEach(s => { if (s.sleepDuration >= 7) { sleepStreak++; maxSleepStreak = Math.max(maxSleepStreak, sleepStreak); } else { sleepStreak = 0; } }); if (maxSleepStreak >= 3) { const badge = badges.find(b => b.id === 'sleep_streak'); if (badge) badge.isUnlocked = true; } // --- 3. Weekly Challenges Logic --- // We assume the data passed in is roughly for the "current week" for simplicity const totalSteps = exercises.reduce((acc, curr) => acc + curr.dailySteps, 0); const totalModMins = exercises.reduce((acc, curr) => acc + curr.moderateExerciseMinutes, 0); const balancedMeals = diets.filter(d => d.vegRatio >= 0.3 && d.proteinRatio >= 0.2).length; const challenges: Challenge[] = [ { id: 'weekly_steps', title: '本週步數挑戰', description: '累積 50,000 步', target: 50000, current: totalSteps, unit: '步', isCompleted: totalSteps >= 50000, type: 'Exercise' }, { id: 'active_week', title: '熱血燃燒', description: '累積 150 分鐘中等強度運動', target: 150, current: totalModMins, unit: '分鐘', isCompleted: totalModMins >= 150, type: 'Exercise' }, { id: 'balanced_diet', title: '營養滿分', description: '攝取 5 餐均衡飲食', target: 5, current: balancedMeals, unit: '餐', isCompleted: balancedMeals >= 5, type: 'Diet' } ]; return { totalPoints: points, level, progressToNextLevel, badges, challenges }; }; import { GoogleGenAI, Type, Schema } from "@google/genai"; import { DietData, ExerciseData, SleepData, WeeklyReport, AdviceItem } from '../types'; const getClient = () => { const apiKey = process.env.API_KEY; if (!apiKey) { throw new Error("API Key not found in environment variables"); } return new GoogleGenAI({ apiKey }); }; // --- Image Analysis for Diet --- interface FoodAnalysisResult { calorieIntake: number; vegRatio: number; proteinRatio: number; starchRatio: number; sugaryDrinksCount: number; friedFoodCount: number; } export const analyzeFoodImage = async (base64Image: string): Promise => { const ai = getClient(); const modelId = "gemini-2.5-flash"; // Fast and capable for vision const schema: Schema = { type: Type.OBJECT, properties: { calorieIntake: { type: Type.NUMBER, description: "Estimated total calories in kcal" }, vegRatio: { type: Type.NUMBER, description: "Ratio of vegetables (0.0 to 1.0)" }, proteinRatio: { type: Type.NUMBER, description: "Ratio of protein sources (0.0 to 1.0)" }, starchRatio: { type: Type.NUMBER, description: "Ratio of starch/carbs (0.0 to 1.0)" }, sugaryDrinksCount: { type: Type.INTEGER, description: "Count of sugary drinks visible (usually 0 or 1)" }, friedFoodCount: { type: Type.INTEGER, description: "Count of fried food items visible (usually 0 or 1)" }, }, required: ["calorieIntake", "vegRatio", "proteinRatio", "starchRatio", "sugaryDrinksCount", "friedFoodCount"], }; const prompt = `Analyze this food image for a high school student's health log. Estimate the nutritional content based on the visible food. Provide ratios for vegetables, protein, and starch (they should roughly sum to 1.0, but it's okay if not exact). Identify if there are sugary drinks or fried foods.`; try { const response = await ai.models.generateContent({ model: modelId, contents: { parts: [ { inlineData: { mimeType: "image/jpeg", data: base64Image } }, { text: prompt } ] }, config: { responseMimeType: "application/json", responseSchema: schema, }, }); const text = response.text; if (!text) throw new Error("No response from Gemini"); return JSON.parse(text) as FoodAnalysisResult; } catch (error) { console.error("Gemini Image Analysis Error:", error); // Fallback or re-throw throw error; } }; // --- Weekly Report Generation --- export const generateWeeklyReportAI = async ( dietData: DietData[], exerciseData: ExerciseData[], sleepData: SleepData[], manualAdvice: AdviceItem[] ): Promise => { const ai = getClient(); const modelId = "gemini-2.5-flash"; // Format data for the prompt const dataSummary = { diet: dietData.map(d => ({ date: d.date, cal: d.calorieIntake, veg: d.vegRatio, sugary: d.sugaryDrinksCount })), exercise: exerciseData.map(e => ({ date: e.date, steps: e.dailySteps, modMin: e.moderateExerciseMinutes, sitting: e.totalSittingMinutes })), sleep: sleepData.map(s => ({ date: s.date, duration: s.sleepDuration, bedTime: s.bedTime, phone: s.usedPhoneBeforeBed })), systemDetectedIssues: manualAdvice }; const prompt = ` Role: School Health Nurse AI. Target Audience: High School Student. Task: Write a friendly, encouraging, but serious weekly health summary based on the provided data JSON. Data Provided: ${JSON.stringify(dataSummary)} Requirements: 1. Summarize their performance in Diet, Exercise, and Sleep. 2. Specifically reference the "systemDetectedIssues" if any exist. 3. Give 1 concrete, actionable goal for next week. 4. Keep it under 200 words. 5. Use Traditional Chinese (zh-TW). `; try { const response = await ai.models.generateContent({ model: modelId, contents: prompt, config: { maxOutputTokens: 500, temperature: 0.7, }, }); return response.text || "無法生成報告,請稍後再試。"; } catch (error) { console.error("Gemini Report Generation Error:", error); return "生成報告時發生錯誤。"; } }; export interface DietData { id: string; date: string; // YYYY-MM-DD calorieIntake: number; vegRatio: number; // 0.0 - 1.0 proteinRatio: number; // 0.0 - 1.0 starchRatio: number; // 0.0 - 1.0 sugaryDrinksCount: number; friedFoodCount: number; imageUrl?: string; } export interface ExerciseData { id: string; date: string; // YYYY-MM-DD dailySteps: number; moderateExerciseMinutes: number; totalSittingMinutes: number; } export interface SleepData { id: string; date: string; // YYYY-MM-DD bedTime: string; // HH:mm wakeTime: string; // HH:mm sleepDuration: number; // Hours (float) usedPhoneBeforeBed: boolean; } export interface AdviceItem { category: 'Diet' | 'Exercise' | 'Sleep'; title: string; description: string; priority: 'High' | 'Medium' | 'Low'; } export interface WeeklyReport { generatedAt: string; summary: string; adviceList: AdviceItem[]; } // --- Gamification Types --- export interface Badge { id: string; name: string; description: string; icon: string; // Font Awesome class suffix (e.g., 'trophy') color: string; // Tailwind color class (e.g., 'text-yellow-400') isUnlocked: boolean; unlockedDate?: string; } export interface Challenge { id: string; title: string; description: string; target: number; current: number; unit: string; isCompleted: boolean; type: 'Diet' | 'Exercise' | 'Sleep'; } export interface GamificationState { totalPoints: number; level: number; progressToNextLevel: number; // 0 - 100 badges: Badge[]; challenges: Challenge[]; }