Add new daily log dashboard and macro summary

Introduces a new route and template for an enhanced daily log dashboard with macro nutrient summary. Refactors FoodItem model to add type annotations and a macros() method, and updates form handling to default missing values to zero. Also adds a navigation link to the new dashboard in the base template.
This commit is contained in:
2025-08-14 02:49:06 +02:00
parent c7395b07d9
commit 85297daaaf
6 changed files with 133 additions and 24 deletions

1
app.py
View File

@@ -5,7 +5,6 @@ from flask import (
)
from flask_login import (
login_required,
logout_user,
current_user,
)
from models import User

View File

View File

@@ -25,6 +25,9 @@
<li class="nav-item">
<a class="nav-link" href="{{ url_for('user.daily_log') }}">Daily Log</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('user.daily_log2') }}">Daily Log (new)</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('user.dashboard') }}">Dashboard</a>
</li>

View File

@@ -13,6 +13,8 @@ from forms import FoodItemForm
from models import FoodItem, FoodLog
from datetime import datetime, timezone, timedelta
from application.utils import login_required
from typing import cast
from numpy import array
user_bp = Blueprint(
"user",
@@ -21,6 +23,49 @@ user_bp = Blueprint(
)
def macro_arr_to_json(data: list[float]):
assert len(data) == 4
macros = [
{
"name": "Calories",
"current": data[0],
"target": 2000,
"percent": data[0] / 20,
"unit": " kcal",
"color": "bg-calories",
"overflow_color": "bg-calories-dark",
},
{
"name": "Protein",
"current": data[3],
"target": 150,
"percent": data[3] / 1.5,
"unit": "g",
"color": "bg-protein",
"overflow_color": "bg-protein-dark",
},
{
"name": "Carbs",
"current": data[2],
"target": 250,
"percent": data[2] / 2.5,
"unit": "g",
"color": "bg-carbs",
"overflow_color": "bg-carbs-dark",
},
{
"name": "Fat",
"current": data[1],
"target": 70,
"percent": data[1] / 0.7,
"unit": "g",
"color": "bg-fat",
"overflow_color": "bg-fat-dark",
},
]
return macros
user_bp.before_request(login_required)
@@ -100,6 +145,18 @@ def daily_log(offset: int = 0):
)
@user_bp.route("/daily_log2", methods=["GET"])
def daily_log2():
today = datetime.now(timezone.utc).date()
logs_today = current_user.food_logs.filter_by(date_=today).all()
macros = array((0.0, 0.0, 0.0, 0.0))
for log in logs_today:
item = cast(FoodItem, log.food_item)
macros += array(item.macros()) / 100 * log.amount
macros = macro_arr_to_json(macros.tolist())
return render_template("daily_log2.html", macros=macros, logs=logs_today)
@user_bp.route("/remove_log/<int:id>", methods=["POST"])
def remove_log(id: int):
log = db.session.get(FoodLog, id)

View File

@@ -0,0 +1,55 @@
{% extends 'base.html' %}
{% block title %}Daily Calorie Dashboard{% endblock %}
{% block content %}
<link rel="stylesheet" href="{{ url_for('static', filename='css/macros.css') }}">
<div class="container my-4">
<h2 class="mb-3">Daily Calorie Dashboard</h2>
<!-- Macro Summary -->
<div class="card p-3 mb-3">
<h5>Macros</h5>
{% for macro in macros %}
<div class="mb-2">
<span class="macro-text">{{ macro.name }}: {{ macro.current }} / {{ macro.target }}</span>
<div class="progress rounded" style="height: 24px;">
<div class="progress-bar bg-danger macro-bar" role="progressbar"
style="width: {{ macro.percent - 100 if macro.percent > 100 else 0}}%">
{{ macro.current }}{{ macro.unit }}
</div>
<div class="progress-bar bg-success macro-bar" role="progressbar"
style="width: {{ 0 if macro.percent > 200 else 200 - macro.percent if macro.percent > 100 else macro.percent }}%">
{{ macro.current }}{{ macro.unit }}
</div>
</div>
</div>
{% endfor %}
</div>
<!-- Items List -->
<div class="card p-3">
<h5>Items Eaten Today</h5>
<div class="list-group list-group-flush">
{% for log in logs %}
<div class="list-group-item item-row d-flex justify-content-between align-items-center bg-dark text-light">
{{ log.food_item.name }}
<span>{{ log.food_item.energy_100 }} kcal, {{ log.food_item.protein_100 }}g P, {{
log.food_item.carbs_100 }}g C,
{{ log.food_item.fat_100 }}g F</span>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Bottom Navigation Buttons -->
<div class="container my-4">
<div class="d-flex justify-content-between">
<a href="" class="btn btn-outline-light flex-fill me-1">Previous Day</a>
<a href="" class="btn btn-outline-light flex-fill mx-1">+ Add Item</a>
<a href="" class="btn btn-outline-light flex-fill ms-1">Next Day</a>
</div>
</div>
{% endblock %}

View File

@@ -49,17 +49,18 @@ class Unit(db.Model):
class FoodItem(db.Model):
__tablename__ = "food_item"
id = db.Column(db.Integer, primary_key=True)
barcode = db.Column(db.String)
owner_id = db.Column(db.Integer, db.ForeignKey("user.id"), nullable=False)
name = db.Column(db.String(150), nullable=False)
energy_100 = db.Column(db.Float, nullable=False)
protein_100 = db.Column(db.Float, nullable=False)
carbs_100 = db.Column(db.Float, nullable=False)
sugar_100 = db.Column(db.Float)
fat_100 = db.Column(db.Float, nullable=False)
saturated_fat_100 = db.Column(db.Float)
energy_100: float = db.Column(db.Float, nullable=False)
protein_100: float = db.Column(db.Float, nullable=False)
carbs_100: float = db.Column(db.Float, nullable=False)
sugar_100: Optional[float] = db.Column(db.Float)
fat_100: float = db.Column(db.Float, nullable=False)
saturated_fat_100: Optional[float] = db.Column(db.Float)
food_logs = db.relationship(
"FoodLog",
@@ -99,26 +100,20 @@ class FoodItem(db.Model):
def updateFromForm(self, form: FoodItemForm):
self.name = form.name.data
self.energy_100 = form.energy.data
self.protein_100 = form.protein.data
self.carbs_100 = form.carbs.data
self.energy_100 = form.energy.data or 0
self.protein_100 = form.protein.data or 0
self.carbs_100 = form.carbs.data or 0
self.sugar_100 = form.sugar.data
self.fat_100 = form.fat.data
self.fat_100 = form.fat.data or 0
self.saturated_fat_100 = form.saturated_fat.data
def to_dict(self):
return {
"id": self.id,
"barcode": self.barcode,
"name": self.name,
"owner_id": self.owner_id,
"energy_100": self.energy_100,
"protein_100": self.protein_100,
"carbs_100": self.carbs_100,
"sugar_100": self.sugar_100,
"fat_100": self.fat_100,
"saturated_fat_100": self.saturated_fat_100,
}
def macros(self) -> tuple[float, float, float, float]:
return (
self.energy_100,
self.fat_100,
self.carbs_100,
self.protein_100,
)
class FoodLog(db.Model):