initial commit

This commit is contained in:
Jp
2026-01-30 23:32:43 +08:00
commit 3df16ee995
20 changed files with 6405 additions and 0 deletions

294
views/dashboard.ejs Normal file
View File

@@ -0,0 +1,294 @@
<div class="max-w-7xl mx-auto">
<!-- Header -->
<div class="flex justify-between items-center mb-6">
<div>
<h1 class="text-3xl font-bold text-gray-800">Today's Summary</h1>
<p class="text-gray-600"><%= new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }) %></p>
</div>
<div class="space-x-2">
<a href="/add-meal" class="bg-primary text-white px-6 py-3 rounded-lg hover:bg-red-700 transition inline-block">
Add Meal
</a>
</div>
</div>
<!-- Top Cards Row -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
<!-- Calories Card -->
<div class="glass rounded-xl p-6 shadow-lg">
<div class="flex justify-between items-start mb-4">
<div>
<p class="text-gray-600 text-sm">Calories</p>
<h2 class="text-3xl font-bold text-primary"><%= Math.round(nutrition.calories) %></h2>
<p class="text-sm text-gray-500">/ <%= current_user.target_daily_calories %></p>
</div>
<span class="text-4xl">🔥</span>
</div>
<!-- Progress Bar -->
<div class="w-full bg-gray-200 rounded-full h-3">
<% let cal_percent = current_user.target_daily_calories > 0 ? Math.round(nutrition.calories / current_user.target_daily_calories * 100) : 0; %>
<div class="bg-primary h-3 rounded-full transition-all" style="width: <%= Math.min(cal_percent, 100) %>%"></div>
</div>
<p class="text-sm mt-2 <%= remaining.calories >= 0 ? 'text-success' : 'text-red-600' %>">
<%= Math.round(remaining.calories) %> remaining
</p>
</div>
<!-- Protein Card -->
<div class="glass rounded-xl p-6 shadow-lg">
<div class="flex justify-between items-start mb-4">
<div>
<p class="text-gray-600 text-sm">Protein</p>
<h2 class="text-3xl font-bold text-secondary"><%= Math.round(nutrition.protein) %>g</h2>
<p class="text-sm text-gray-500">/ <%= goals.target_protein_g %>g</p>
</div>
<span class="text-4xl">💪</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-3">
<% let prot_percent = goals.target_protein_g > 0 ? Math.round(nutrition.protein / goals.target_protein_g * 100) : 0; %>
<div class="bg-secondary h-3 rounded-full" style="width: <%= Math.min(prot_percent, 100) %>%"></div>
</div>
<p class="text-sm mt-2 <%= remaining.protein >= 0 ? 'text-success' : 'text-red-600' %>">
<%= Math.round(remaining.protein) %>g remaining
</p>
</div>
<!-- Carbs & Fat Card -->
<div class="glass rounded-xl p-6 shadow-lg">
<div class="mb-3">
<div class="flex justify-between items-center">
<span class="text-gray-600 text-sm">Carbs</span>
<span class="font-bold text-warning"><%= Math.round(nutrition.carbs) %>g / <%= goals.target_carbs_g %>g</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 mt-1">
<% let carb_percent = goals.target_carbs_g > 0 ? Math.round(nutrition.carbs / goals.target_carbs_g * 100) : 0; %>
<div class="bg-warning h-2 rounded-full" style="width: <%= Math.min(carb_percent, 100) %>%"></div>
</div>
</div>
<div>
<div class="flex justify-between items-center">
<span class="text-gray-600 text-sm">Fat</span>
<span class="font-bold text-orange-600"><%= Math.round(nutrition.fat) %>g / <%= goals.target_fat_g %>g</span>
</div>
<div class="w-full bg-gray-200 rounded-full h-2 mt-1">
<% let fat_percent = goals.target_fat_g > 0 ? Math.round(nutrition.fat / goals.target_fat_g * 100) : 0; %>
<div class="bg-orange-600 h-2 rounded-full" style="width: <%= Math.min(fat_percent, 100) %>%"></div>
</div>
</div>
</div>
<!-- Water & Weight Card -->
<div class="glass rounded-xl p-6 shadow-lg">
<div class="mb-3">
<div class="flex justify-between items-center">
<span class="text-gray-600 text-sm">Water</span>
<span class="font-bold text-blue-600"><%= water.total_ml %>ml / <%= goals.target_water_ml %>ml</span>
</div>
<div class="flex space-x-1 mt-2">
<% let glasses_filled = Math.floor(water.total_ml / 250); %>
<% for (let i = 0; i < 8; i++) { %>
<span class="<%= i < glasses_filled ? 'text-blue-500' : 'text-gray-300' %>">💧</span>
<% } %>
</div>
<!-- Quick add water buttons -->
<form method="POST" action="/add-water" class="flex space-x-1 mt-2">
<button type="submit" name="amount_ml" value="250" class="bg-blue-100 text-blue-700 px-2 py-1 rounded text-xs hover:bg-blue-200">+250ml</button>
<button type="submit" name="amount_ml" value="500" class="bg-blue-100 text-blue-700 px-2 py-1 rounded text-xs hover:bg-blue-200">+500ml</button>
</form>
</div>
<div class="pt-3 border-t">
<div class="flex justify-between items-center">
<span class="text-gray-600 text-sm">Weight</span>
<% if (typeof weight_today !== 'undefined' && weight_today) { %>
<div class="text-right">
<span class="font-bold"><%= weight_today.weight_kg %>kg</span>
<% if (typeof weight_change !== 'undefined' && weight_change !== null) { %>
<span class="text-xs <%= weight_change < 0 ? 'text-success' : 'text-red-600' %>">
<%= weight_change.toFixed(1) %>kg
</span>
<% } %>
</div>
<% } else { %>
<form method="POST" action="/add-weight" class="flex items-center space-x-2">
<input type="number" step="0.1" name="weight_kg" placeholder="kg" class="w-20 px-2 py-1 border rounded text-sm" required>
<button type="submit" class="bg-green-500 text-white px-2 py-1 rounded text-xs hover:bg-green-600">Log</button>
</form>
<% } %>
</div>
</div>
</div>
</div>
<!-- Macro Distribution Pie Chart -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6">
<div class="glass rounded-xl p-6 shadow-lg">
<h3 class="text-lg font-bold mb-4">Macro Distribution</h3>
<canvas id="macroChart" height="200"></canvas>
</div>
<!-- Suggestions -->
<div class="glass rounded-xl p-6 shadow-lg lg:col-span-2">
<h3 class="text-lg font-bold mb-4">💡 Smart Suggestions</h3>
<% if (suggestions && suggestions.length > 0) { %>
<div class="space-y-3">
<% suggestions.forEach(function(suggestion) { %>
<div class="bg-blue-50 border-l-4 border-blue-500 p-3 rounded">
<p class="font-semibold text-blue-900"><%= suggestion.category %></p>
<p class="text-sm text-blue-700">Try: <%= suggestion.examples.join(', ') %></p>
</div>
<% }); %>
</div>
<% } else { %>
<p class="text-gray-600">You're on track! Keep up the good work! 🎉</p>
<% } %>
</div>
</div>
<!-- Today's Meals -->
<div class="glass rounded-xl p-6 shadow-lg mb-6">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold">Today's Meals</h3>
<a href="/add-meal" class="text-primary hover:underline">+ Add Meal</a>
</div>
<% if (nutrition.meals && nutrition.meals.length > 0) { %>
<div class="space-y-4">
<% nutrition.meals.forEach(function(meal) { %>
<div class="border-l-4 <%= meal.type == 'breakfast' ? 'border-yellow-500' : (meal.type == 'lunch' ? 'border-green-500' : (meal.type == 'dinner' ? 'border-blue-500' : 'border-purple-500')) %> pl-4 py-2">
<div class="flex justify-between items-start">
<div class="flex-1">
<div class="flex items-center space-x-2">
<span class="text-2xl">
<% if (meal.type == 'breakfast') { %>🌅<% } else if (meal.type == 'lunch') { %>🌞<% } else if (meal.type == 'dinner') { %>🌙<% } else { %>🍪<% } %>
</span>
<h4 class="font-bold text-lg capitalize"><%= meal.type %></h4>
<% if (meal.time) { %>
<span class="text-sm text-gray-500"><%= meal.time %></span>
<% } %>
</div>
<div class="mt-2 space-y-1">
<% meal.foods.forEach(function(food) { %>
<p class="text-sm text-gray-700">• <%= food.name %> (<%= food.quantity %>x) - <%= Math.round(food.calories) %> cal</p>
<% }); %>
</div>
</div>
<div class="text-right">
<p class="text-2xl font-bold text-primary"><%= Math.round(meal.totals.calories) %></p>
<p class="text-xs text-gray-500">calories</p>
<p class="text-xs text-gray-600 mt-1">
P: <%= Math.round(meal.totals.protein) %>g |
C: <%= Math.round(meal.totals.carbs) %>g |
F: <%= Math.round(meal.totals.fat) %>g
</p>
</div>
</div>
</div>
<% }); %>
</div>
<% } else { %>
<div class="text-center py-12 text-gray-500">
<p class="text-4xl mb-4">🍽️</p>
<p>No meals logged yet today.</p>
<a href="/add-meal" class="text-primary hover:underline mt-2 inline-block">Add your first meal</a>
</div>
<% } %>
</div>
<!-- Weekly Trends -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
<!-- Calorie Trend -->
<div class="glass rounded-xl p-6 shadow-lg">
<h3 class="text-lg font-bold mb-4">📈 Calorie Trend (7 Days)</h3>
<canvas id="calorieChart" height="200"></canvas>
</div>
<!-- Weight Trend -->
<div class="glass rounded-xl p-6 shadow-lg">
<h3 class="text-lg font-bold mb-4">⚖️ Weight Trend (7 Days)</h3>
<canvas id="weightChart" height="200"></canvas>
</div>
</div>
</div>
<script>
// Macro Distribution Chart
const macroCtx = document.getElementById('macroChart').getContext('2d');
new Chart(macroCtx, {
type: 'doughnut',
data: {
labels: ['Protein', 'Carbs', 'Fat'],
datasets: [{
data: [<%= macro_percentages.protein %>, <%= macro_percentages.carbs %>, <%= macro_percentages.fat %>],
backgroundColor: ['#003F87', '#FFB703', '#FF6B35']
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
// Calorie Trend Chart
const calorieCtx = document.getElementById('calorieChart').getContext('2d');
new Chart(calorieCtx, {
type: 'line',
data: {
labels: <%- JSON.stringify(calorie_trend.map(d => d.date)) %>,
datasets: [{
label: 'Calories',
data: <%- JSON.stringify(calorie_trend.map(d => d.calories)) %>,
borderColor: '#D62828',
backgroundColor: 'rgba(214, 40, 40, 0.1)',
tension: 0.4
}, {
label: 'Target',
data: Array(7).fill(<%= current_user.target_daily_calories %>),
borderColor: '#06D6A0',
borderDash: [5, 5],
pointRadius: 0
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: {
display: true
}
}
}
});
// Weight Trend Chart
<% if (weight_trend && weight_trend.length > 0) { %>
const weightCtx = document.getElementById('weightChart').getContext('2d');
new Chart(weightCtx, {
type: 'line',
data: {
labels: <%- JSON.stringify(weight_trend.map(d => d.date)) %>,
datasets: [{
label: 'Weight (kg)',
data: <%- JSON.stringify(weight_trend.map(d => d.weight_kg)) %>,
borderColor: '#003F87',
backgroundColor: 'rgba(0, 63, 135, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: {
display: false
}
}
}
});
<% } %>
</script>