diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/app.py | 20 | ||||
| -rw-r--r-- | app/static/css/style.css | 9 | ||||
| -rw-r--r-- | app/templates/index.html | 10 | ||||
| -rw-r--r-- | app/templates/recipe_overview.html | 81 |
4 files changed, 116 insertions, 4 deletions
@@ -253,6 +253,26 @@ def index(): # ── Recipes ─────────────────────────────────────────────────────────────────── +@app.route('/recipes/overview') +def recipes_overview(): + db = database.get_db() + favorites = db.execute( + "SELECT * FROM recipes WHERE status = 'favorited' ORDER BY name" + ).fetchall() + recent = db.execute( + """SELECT * FROM recipes WHERE status != 'ignored' + ORDER BY id DESC LIMIT 12""" + ).fetchall() + db.close() + fav_ids = {r['id'] for r in favorites} + recent_new = [r for r in recent if r['id'] not in fav_ids] + return render_template( + 'recipe_overview.html', + favorites=favorites, recent=recent_new, + cuisine_emoji=CUISINE_EMOJI_MAP, + ) + + SORT_OPTIONS = { 'name': 'name', 'prep': 'prep_time, name', diff --git a/app/static/css/style.css b/app/static/css/style.css index 7167e05..37d36f6 100644 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -67,6 +67,15 @@ body { line-height: 1; } +.stat-card-link { + transition: box-shadow 0.15s, transform 0.15s; + cursor: pointer; +} +.stat-card-link:hover { + box-shadow: 0 4px 12px rgba(0,0,0,0.12) !important; + transform: translateY(-2px); +} + .stat-label { font-size: 0.8rem; color: var(--muted); diff --git a/app/templates/index.html b/app/templates/index.html index c33e348..55b5ef0 100644 --- a/app/templates/index.html +++ b/app/templates/index.html @@ -28,10 +28,12 @@ </div> </div> <div class="col-6 col-md-3"> - <div class="stat-card"> - <div class="stat-number">{{ stat_map.get('candidate', 0) + stat_map.get('favorited', 0) }}</div> - <div class="stat-label">Active Recipes</div> - </div> + <a href="/recipes/overview" class="text-decoration-none"> + <div class="stat-card stat-card-link"> + <div class="stat-number">{{ stat_map.get('candidate', 0) + stat_map.get('favorited', 0) }}</div> + <div class="stat-label">Active Recipes</div> + </div> + </a> </div> <div class="col-6 col-md-3"> <div class="stat-card"> diff --git a/app/templates/recipe_overview.html b/app/templates/recipe_overview.html new file mode 100644 index 0000000..252aed2 --- /dev/null +++ b/app/templates/recipe_overview.html @@ -0,0 +1,81 @@ +{% extends "base.html" %} +{% block title %}Recipe Overview — Menu Planner{% endblock %} + +{% block content %} +<div class="d-flex align-items-center justify-content-between mb-4"> + <h1 class="h3 fw-bold mb-0">Recipe Overview</h1> + <a href="/recipes" class="btn btn-sm btn-outline-secondary"><i class="bi bi-grid me-1"></i>Full Library</a> +</div> + +{% if favorites %} +<h2 class="h5 fw-semibold mb-3"><i class="bi bi-heart-fill text-danger me-2"></i>Favorites</h2> +<div class="row g-3 mb-5"> + {% for r in favorites %} + <div class="col-sm-6 col-lg-4 col-xl-3"> + <a href="/recipes/{{ r.id }}" class="text-decoration-none"> + <div class="recipe-card card h-100 shadow-sm border-danger border-opacity-25"> + <div class="card-body d-flex flex-column"> + <div class="d-flex align-items-start justify-content-between mb-2"> + <span class="cuisine-pill">{{ cuisine_emoji.get(r.cuisine) }} {{ r.cuisine }}</span> + <i class="bi bi-heart-fill text-danger"></i> + </div> + <h5 class="card-title mb-1 text-dark">{{ r.name }}</h5> + <p class="card-text text-muted small flex-grow-1">{{ r.description[:90] }}{% if r.description|length > 90 %}…{% endif %}</p> + {% if r.rating %} + <div class="mb-1" style="color:#f59e0b;font-size:0.8rem"> + {% for i in range(1,6) %}<i class="bi bi-star{% if r.rating >= i %}-fill{% endif %}"></i>{% endfor %} + </div> + {% endif %} + <div class="nutrition-row mt-auto"> + <span class="nutri-badge"><i class="bi bi-lightning-charge"></i> {{ r.calories_per_serving|int }} cal</span> + <span class="nutri-badge"><i class="bi bi-circle-half"></i> {{ r.carbs_per_serving|int }}g carbs</span> + <span class="nutri-badge"><i class="bi bi-clock"></i> {{ r.prep_time + r.cook_time }} min</span> + </div> + </div> + </div> + </a> + </div> + {% endfor %} +</div> +{% endif %} + +{% if recent %} +<h2 class="h5 fw-semibold mb-3"><i class="bi bi-clock-history text-primary me-2"></i>Recently Added</h2> +<div class="row g-3"> + {% for r in recent %} + <div class="col-sm-6 col-lg-4 col-xl-3"> + <a href="/recipes/{{ r.id }}" class="text-decoration-none"> + <div class="recipe-card card h-100 shadow-sm"> + <div class="card-body d-flex flex-column"> + <div class="d-flex align-items-start justify-content-between mb-2"> + <span class="cuisine-pill">{{ cuisine_emoji.get(r.cuisine) }} {{ r.cuisine }}</span> + {% if r.status == 'favorited' %}<i class="bi bi-heart-fill text-danger"></i>{% endif %} + </div> + <h5 class="card-title mb-1 text-dark">{{ r.name }}</h5> + <p class="card-text text-muted small flex-grow-1">{{ r.description[:90] }}{% if r.description|length > 90 %}…{% endif %}</p> + {% if r.added_by %}<p class="text-muted small mb-1"><i class="bi bi-person me-1"></i>{{ r.added_by }}</p>{% endif %} + {% if r.rating %} + <div class="mb-1" style="color:#f59e0b;font-size:0.8rem"> + {% for i in range(1,6) %}<i class="bi bi-star{% if r.rating >= i %}-fill{% endif %}"></i>{% endfor %} + </div> + {% endif %} + <div class="nutrition-row mt-auto"> + <span class="nutri-badge"><i class="bi bi-lightning-charge"></i> {{ r.calories_per_serving|int }} cal</span> + <span class="nutri-badge"><i class="bi bi-circle-half"></i> {{ r.carbs_per_serving|int }}g carbs</span> + <span class="nutri-badge"><i class="bi bi-clock"></i> {{ r.prep_time + r.cook_time }} min</span> + </div> + </div> + </div> + </a> + </div> + {% endfor %} +</div> +{% endif %} + +{% if not favorites and not recent %} +<div class="text-center py-5 text-muted"> + <i class="bi bi-book fs-1"></i> + <p class="mt-3">No recipes yet. <a href="/recipes/add">Add one</a> or use the <a href="/ai">AI assistant</a>.</p> +</div> +{% endif %} +{% endblock %} |
