summaryrefslogtreecommitdiffstats
path: root/app/templates/recipes.html
diff options
context:
space:
mode:
authorKen D'Ambrosio <ken@claude>2026-05-25 00:46:10 +0000
committerKen D'Ambrosio <ken@claude>2026-05-25 00:46:10 +0000
commit55bcec90c14db6f2956ed51cf4df1503c0767f81 (patch)
treef25bfb8c46366b5d3dc6b4f66e242c65094b4ada /app/templates/recipes.html
Initial commit — menu.jots.org Flask/SQLite meal planner
Full-featured weekly menu planner with: - Recipe library with ratings, comments, cuisine/nationality, added-by attribution - AI recipe assistant (Claude) with URL fetching and file upload - Weekly meal plan grid with shopping list generation - Sort by name, prep/cook time, or rating - Print and copy-link support - Deployed on LXC container (192.168.10.51) behind Apache reverse proxy Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'app/templates/recipes.html')
-rw-r--r--app/templates/recipes.html121
1 files changed, 121 insertions, 0 deletions
diff --git a/app/templates/recipes.html b/app/templates/recipes.html
new file mode 100644
index 0000000..92a13bb
--- /dev/null
+++ b/app/templates/recipes.html
@@ -0,0 +1,121 @@
+{% extends "base.html" %}
+{% block title %}Recipes — Menu Planner{% endblock %}
+
+{% block content %}
+<div class="d-flex align-items-center justify-content-between mb-3">
+ <h1 class="h3 fw-bold mb-0">Recipe Library</h1>
+ <span class="badge bg-secondary fs-6">{{ recipes | length }} recipes</span>
+</div>
+
+<!-- Filters -->
+<div class="card shadow-sm mb-4">
+ <div class="card-body">
+ <form method="GET" class="row g-2 align-items-end">
+ <div class="col-md-5">
+ <input type="text" name="search" class="form-control" placeholder="Search recipes…" value="{{ search }}">
+ </div>
+ <div class="col-md-3">
+ <select name="cuisine" class="form-select">
+ <option value="all" {% if cuisine=='all' %}selected{% endif %}>All Cuisines</option>
+ {% for c in cuisines %}
+ <option value="{{ c }}" {% if cuisine==c %}selected{% endif %}>{{ cuisine_emoji[c] }} {{ c }}</option>
+ {% endfor %}
+ </select>
+ </div>
+ <div class="col-md-2">
+ <select name="status" class="form-select">
+ <option value="active" {% if status=='active' %}selected{% endif %}>Active</option>
+ <option value="favorited" {% if status=='favorited' %}selected{% endif %}>Favorites</option>
+ <option value="candidate" {% if status=='candidate' %}selected{% endif %}>Candidates</option>
+ <option value="ignored" {% if status=='ignored' %}selected{% endif %}>Ignored</option>
+ <option value="all" {% if status=='all' %}selected{% endif %}>All</option>
+ </select>
+ </div>
+ <div class="col-md-2">
+ <select name="sort" class="form-select">
+ <option value="name" {% if sort=='name' %}selected{% endif %}>Sort: Name</option>
+ <option value="total" {% if sort=='total' %}selected{% endif %}>Sort: Total Time</option>
+ <option value="prep" {% if sort=='prep' %}selected{% endif %}>Sort: Prep Time</option>
+ <option value="cook" {% if sort=='cook' %}selected{% endif %}>Sort: Cook Time</option>
+ <option value="rating" {% if sort=='rating' %}selected{% endif %}>Sort: Rating</option>
+ </select>
+ </div>
+ <div class="col-md-2">
+ <button type="submit" class="btn btn-primary w-100"><i class="bi bi-funnel me-1"></i>Filter</button>
+ </div>
+ </form>
+ </div>
+</div>
+
+<!-- Cuisine tabs shortcut -->
+<div class="mb-3 d-flex gap-2 flex-wrap">
+ <a href="/recipes?cuisine=all&status={{ status }}" class="btn btn-sm {% if cuisine=='all' %}btn-dark{% else %}btn-outline-secondary{% endif %}">All</a>
+ {% for c in cuisines %}
+ <a href="/recipes?cuisine={{ c }}&status={{ status }}" class="btn btn-sm {% if cuisine==c %}btn-dark{% else %}btn-outline-secondary{% endif %}">
+ {{ cuisine_emoji[c] }} {{ c }}
+ </a>
+ {% endfor %}
+</div>
+
+{% if recipes %}
+<div class="row g-3">
+ {% for r in recipes %}
+ <div class="col-sm-6 col-lg-4 col-xl-3">
+ <div class="recipe-card card h-100 shadow-sm {% if r.status == 'ignored' %}opacity-50{% endif %}">
+ <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>
+ {% elif r.status == 'ignored' %}
+ <i class="bi bi-eye-slash text-muted"></i>
+ {% endif %}
+ </div>
+ <h5 class="card-title mb-1">{{ r.name }}</h5>
+ <p class="card-text text-muted small flex-grow-1">{{ r.description[:100] }}{% if r.description|length > 100 %}…{% 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.85rem">
+ {% 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-2 mb-3">
+ <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 class="d-flex gap-1 mt-auto">
+ <a href="/recipes/{{ r.id }}" class="btn btn-sm btn-primary flex-grow-1">View</a>
+ <button class="btn btn-sm btn-outline-danger status-btn" data-id="{{ r.id }}" data-status="favorited" title="Favorite">
+ <i class="bi bi-heart{% if r.status=='favorited' %}-fill{% endif %}"></i>
+ </button>
+ <button class="btn btn-sm btn-outline-secondary status-btn" data-id="{{ r.id }}" data-status="{% if r.status=='ignored' %}candidate{% else %}ignored{% endif %}" title="{% if r.status=='ignored' %}Restore{% else %}Ignore{% endif %}">
+ <i class="bi bi-{% if r.status=='ignored' %}eye{% else %}eye-slash{% endif %}"></i>
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ {% endfor %}
+</div>
+{% else %}
+<div class="text-center py-5">
+ <i class="bi bi-search fs-1 text-muted"></i>
+ <p class="text-muted mt-2">No recipes found. Try adjusting the filters.</p>
+</div>
+{% endif %}
+{% endblock %}
+
+{% block scripts %}
+<script>
+document.querySelectorAll('.status-btn').forEach(btn => {
+ btn.addEventListener('click', async function() {
+ const res = await api(`/recipes/${this.dataset.id}/status`, {
+ method: 'POST',
+ body: JSON.stringify({status: this.dataset.status}),
+ });
+ if (res && (await res.json()).success) location.reload();
+ });
+});
+</script>
+{% endblock %}