release nouri 0.4.0 templates suggestions and mobile sheet

This commit is contained in:
2026-04-12 16:00:00 +02:00
parent b68ed62887
commit d8b56e6b67
28 changed files with 2651 additions and 492 deletions
+49
View File
@@ -0,0 +1,49 @@
{% extends "base.html" %}
{% block title %}Kategorien | Nouri{% endblock %}
{% block content %}
<section class="page-intro">
<div>
<p class="eyebrow">Kategorien</p>
<h1>Kategorien global anpassen</h1>
<p class="lead">Hier pflegt ihr die Auswahl für Lebensmittel und Mahlzeiten. Bestehende Einträge bleiben auch dann erhalten, wenn eine Kategorie später pausiert wird.</p>
</div>
<a class="ghost-button" href="{{ url_for('admin.user_list') }}">Zur Nutzerverwaltung</a>
</section>
<section class="panel compact-form-panel">
<form method="post" class="inline-form">
{{ csrf_input() }}
<label class="wide">
Neue Kategorie
<input type="text" name="name" placeholder="z. B. Süßes, Vorrat, Unterwegs">
</label>
<button type="submit">Kategorie ergänzen</button>
</form>
</section>
<section class="stack-list">
{% for category in categories %}
<article class="list-row stacked-mobile">
<div>
<strong>{{ category.name }}</strong>
<p class="muted">{% if category.name in default_categories %}Teil der ruhigen Standardauswahl{% else %}Eigene Haushaltskategorie{% endif %}</p>
<div class="chip-row">
{% if category.is_active %}
<span class="chip status-home">Aktiv</span>
{% else %}
<span class="chip status-archived">Pausiert</span>
{% endif %}
</div>
</div>
<div class="row-actions">
<form method="post" action="{{ url_for('admin.category_toggle', category_id=category.id) }}">
{{ csrf_input() }}
<button class="ghost-button" type="submit">
{% if category.is_active %}Pausieren{% else %}Wieder aktivieren{% endif %}
</button>
</form>
</div>
</article>
{% endfor %}
</section>
{% endblock %}
+5 -2
View File
@@ -5,9 +5,12 @@
<div>
<p class="eyebrow">Nutzer verwalten</p>
<h1>Haushaltszugänge ruhig pflegen</h1>
<p class="lead">Admins können hier weitere Mitglieder anlegen, Rollen anpassen und Zugänge bei Bedarf pausieren.</p>
<p class="lead">Admins können hier weitere Mitglieder anlegen, Rollen anpassen, Zugänge pausieren und die gemeinsamen Kategorien pflegen.</p>
</div>
<div class="hero-actions">
<a class="button" href="{{ url_for('admin.user_create') }}">Neuen Nutzer anlegen</a>
<a class="button secondary" href="{{ url_for('admin.category_settings') }}">Kategorien</a>
</div>
<a class="button" href="{{ url_for('admin.user_create') }}">Neuen Nutzer anlegen</a>
</section>
<section class="stack-list">
+1
View File
@@ -54,6 +54,7 @@
<div class="chip-row">
<span class="chip">{{ item.visibility_label }}</span>
<span class="chip status-soft">{{ item.owner_label }}</span>
<span class="chip">{{ item.for_label }}</span>
</div>
<p class="muted">{{ item_kind_labels[item.kind] }}{% if item.category %} · {{ item.category }}{% endif %}</p>
{% if item.dayparts %}
+44 -12
View File
@@ -9,6 +9,7 @@
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
<script defer src="{{ url_for('static', filename='js/theme.js') }}"></script>
<script defer src="{{ url_for('static', filename='js/planner.js') }}"></script>
<script defer src="{{ url_for('static', filename='js/ui.js') }}"></script>
</head>
<body class="{% if g.user %}has-mobile-nav{% endif %}">
<div class="page-shell">
@@ -27,11 +28,12 @@
<nav class="site-nav desktop-nav">
<a href="{{ url_for('main.dashboard') }}" class="{{ 'active' if request.endpoint == 'main.dashboard' else '' }}"><span class="nav-link-inner"><span class="ui-icon icon-sparkles"></span><span>Heute</span></span></a>
<a href="{{ url_for('main.shopping_list') }}" class="{{ 'active' if request.endpoint == 'main.shopping_list' else '' }}"><span class="nav-link-inner"><span class="ui-icon icon-cart-shopping"></span><span>Einkauf</span></span></a>
<a href="{{ url_for('main.home_view') }}" class="{{ 'active' if request.endpoint == 'main.home_view' else '' }}"><span class="nav-link-inner"><span class="ui-icon icon-house"></span><span>Zuhause</span></span></a>
<a href="{{ url_for('main.planner_day', date=today.isoformat()) }}" class="{{ 'active' if request.endpoint == 'main.planner_day' else '' }}"><span class="nav-link-inner"><span class="ui-icon icon-calendar"></span><span>Plan</span></span></a>
<a href="{{ url_for('main.planner') }}" class="{{ 'active' if request.endpoint == 'main.planner' else '' }}"><span class="nav-link-inner"><span class="ui-icon icon-calendar-days"></span><span>Woche</span></span></a>
<a href="{{ url_for('main.home_view') }}" class="{{ 'active' if request.endpoint == 'main.home_view' else '' }}"><span class="nav-link-inner"><span class="ui-icon icon-house"></span><span>Zuhause</span></span></a>
<a href="{{ url_for('main.item_list', kind='food') }}" class="{{ 'active' if request.endpoint == 'main.item_list' and request.view_args and request.view_args.get('kind') == 'food' else '' }}"><span class="nav-link-inner"><span class="ui-icon icon-utensils"></span><span>Lebensmittel</span></span></a>
<a href="{{ url_for('main.item_list', kind='meal') }}" class="{{ 'active' if request.endpoint == 'main.item_list' and request.view_args and request.view_args.get('kind') == 'meal' else '' }}"><span class="nav-link-inner"><span class="ui-icon icon-bowl-food"></span><span>Mahlzeiten</span></span></a>
<a href="{{ url_for('main.planner') }}" class="{{ 'active' if request.endpoint == 'main.planner' else '' }}"><span class="nav-link-inner"><span class="ui-icon icon-calendar-days"></span><span>Woche</span></span></a>
<a href="{{ url_for('main.template_library') }}" class="{{ 'active' if (request.endpoint or '').startswith('main.day_template') or (request.endpoint or '').startswith('main.week_template') or (request.endpoint or '').startswith('main.item_set') or request.endpoint == 'main.template_library' else '' }}"><span class="nav-link-inner"><span class="ui-icon icon-layer-group"></span><span>Vorlagen</span></span></a>
<a href="{{ url_for('main.archive_view') }}" class="{{ 'active' if request.endpoint == 'main.archive_view' else '' }}"><span class="nav-link-inner"><span class="ui-icon icon-archive"></span><span>Archiv</span></span></a>
</nav>
@@ -42,7 +44,7 @@
<small>{{ role_labels[g.user.role] }}</small>
</a>
{% if g.user.role == 'admin' %}
<a class="ghost-button" href="{{ url_for('admin.user_list') }}">Nutzer verwalten</a>
<a class="ghost-button" href="{{ url_for('admin.user_list') }}">Nutzer</a>
{% endif %}
<form method="post" action="{{ url_for('auth.logout') }}">
{{ csrf_input() }}
@@ -50,9 +52,9 @@
</form>
</div>
<a class="mobile-profile-link" href="{{ url_for('main.more_view') }}" aria-label="Mehr">
<span class="mobile-profile-avatar">{{ (g.user.display_name or g.user.username)[0]|upper }}</span>
</a>
<button class="mobile-profile-link ghost-button" type="button" data-mobile-sheet-open aria-label="Mehr öffnen">
<span class="mobile-profile-avatar">{{ (g.user.display_name or g.user.username or 'N')[:1]|upper }}</span>
</button>
{% endif %}
</header>
@@ -72,6 +74,36 @@
</div>
{% if g.user %}
<div class="mobile-sheet-backdrop" data-mobile-sheet-backdrop hidden></div>
<aside class="mobile-more-sheet" data-mobile-sheet hidden aria-label="Mehr">
<div class="mobile-sheet-head">
<div>
<strong>{{ g.user.display_name or g.user.username }}</strong>
<small>{{ role_labels[g.user.role] }}</small>
</div>
<button class="ghost-button" type="button" data-mobile-sheet-close>Schließen</button>
</div>
<nav class="mobile-sheet-links">
<a href="{{ url_for('main.item_list', kind='food') }}">Lebensmittel</a>
<a href="{{ url_for('main.item_list', kind='meal') }}">Mahlzeiten</a>
<a href="{{ url_for('main.home_view') }}">Zuhause</a>
<a href="{{ url_for('main.archive_view') }}">Archiv</a>
<a href="{{ url_for('main.template_library') }}">Vorlagen</a>
<a href="{{ url_for('auth.profile') }}">Profil</a>
{% if g.user.role == 'admin' %}
<a href="{{ url_for('admin.user_list') }}">Nutzerverwaltung</a>
<a href="{{ url_for('admin.category_settings') }}">Kategorien</a>
{% endif %}
</nav>
<div class="mobile-sheet-actions">
<button class="ghost-button" type="button" data-theme-toggle>Modus wechseln</button>
<form method="post" action="{{ url_for('auth.logout') }}">
{{ csrf_input() }}
<button class="ghost-button" type="submit">Abmelden</button>
</form>
</div>
</aside>
<nav class="mobile-bottom-nav" aria-label="Mobile Navigation">
<a href="{{ url_for('main.dashboard') }}" class="{{ 'active' if request.endpoint == 'main.dashboard' else '' }}">
<span class="ui-icon icon-sparkles"></span>
@@ -85,14 +117,14 @@
<span class="ui-icon icon-calendar"></span>
<span>Plan</span>
</a>
<a href="{{ url_for('main.home_view') }}" class="{{ 'active' if request.endpoint == 'main.home_view' else '' }}">
<span class="ui-icon icon-house"></span>
<span>Zuhause</span>
<a href="{{ url_for('main.planner') }}" class="{{ 'active' if request.endpoint == 'main.planner' else '' }}">
<span class="ui-icon icon-calendar-days"></span>
<span>Woche</span>
</a>
<a href="{{ url_for('main.more_view') }}" class="{{ 'active' if request.endpoint == 'main.more_view' or request.endpoint == 'auth.profile' or (request.endpoint or '').startswith('admin.') else '' }}">
<span class="ui-icon icon-archive"></span>
<button type="button" class="mobile-nav-button" data-mobile-sheet-open>
<span class="ui-icon icon-ellipsis"></span>
<span>Mehr</span>
</a>
</button>
</nav>
{% endif %}
</body>
+63 -22
View File
@@ -5,11 +5,11 @@
<div>
<p class="eyebrow">Heute</p>
<h1>Ein ruhiger Blick auf euren Alltag</h1>
<p class="lead">Du siehst schnell, was zuhause da ist, was schon geplant wurde und was gemeinsam oder persönlich vorbereitet ist.</p>
<p class="lead">Du siehst schnell, was zuhause da ist, was schon geplant wurde, welche Vorlagen gut passen und wo sanfte Unterstützung hilfreich sein kann.</p>
</div>
<div class="hero-actions">
<a class="button" href="{{ url_for('main.planner_day', date=today.isoformat()) }}">Heutigen Tagesplan öffnen</a>
<a class="button secondary" href="{{ url_for('main.item_create', kind='meal') }}">Mahlzeitenidee anlegen</a>
<a class="button secondary" href="{{ url_for('main.template_library') }}">Vorlagen öffnen</a>
</div>
</section>
@@ -31,6 +31,19 @@
</article>
</section>
{% if dashboard_hints %}
<section class="panel">
<div class="panel-head">
<h2>Sanfte Hinweise</h2>
</div>
<div class="hint-list">
{% for hint in dashboard_hints %}
<p class="hint-chip">{{ hint }}</p>
{% endfor %}
</div>
</section>
{% endif %}
<section class="two-column">
<article class="panel">
<div class="panel-head">
@@ -47,6 +60,7 @@
<div class="chip-row">
<span class="chip">{{ entry.visibility_label }}</span>
<span class="chip status-soft">{{ entry.owner_label }}</span>
<span class="chip">{{ entry.for_label }}</span>
</div>
</div>
{% if entry.availability_state == 'home' %}
@@ -72,7 +86,7 @@
<div class="mini-card-body">
<strong>{{ item.name }}</strong>
<small>{{ item_kind_labels[item.kind] }} · {{ item.visibility_label }}</small>
<small>{{ item.owner_label }}</small>
<small>{{ item.for_label }}</small>
{% if item.dayparts %}
<div class="chip-row">
{% for daypart in item.dayparts %}
@@ -90,24 +104,51 @@
</article>
</section>
<section class="panel">
<div class="panel-head">
<h2>Nächste Tage</h2>
<a href="{{ url_for('main.planner') }}">Wochenansicht öffnen</a>
</div>
<div class="week-mini-grid">
{% for card in week_cards %}
<a class="week-mini-card" href="{{ url_for('main.planner_day', date=card.date.isoformat()) }}">
<strong>{{ weekday_short_name(card.date) }} {{ card.date.strftime('%d.%m.') }}</strong>
{% if card.filled_dayparts %}
<span>{{ card.planned_count }} Einträge</span>
<small>{{ card.filled_dayparts | map(attribute='name') | join(', ') }}</small>
{% else %}
<span>Noch frei</span>
<small>sanfter Einstieg für den Tag</small>
{% endif %}
</a>
{% endfor %}
</div>
<section class="two-column">
<article class="panel">
<div class="panel-head">
<h2>Vorlagen für später</h2>
<a href="{{ url_for('main.template_library') }}">Alles ansehen</a>
</div>
{% if day_templates or week_templates %}
<div class="stack-sections">
{% for template in day_templates %}
<a class="mini-card" href="{{ url_for('main.day_template_edit', template_id=template.id) }}">
<strong>{{ template.name }}</strong>
<small>Tagesvorlage · {{ template.visibility_label }}</small>
</a>
{% endfor %}
{% for template in week_templates %}
<a class="mini-card" href="{{ url_for('main.week_template_edit', template_id=template.id) }}">
<strong>{{ template.name }}</strong>
<small>Wochenvorlage · {{ template.visibility_label }}</small>
</a>
{% endfor %}
</div>
{% else %}
<p class="empty-state">Vorlagen helfen später beim Wiederverwenden. Du kannst sie direkt aus einem Tag oder einer Woche heraus anlegen.</p>
{% endif %}
</article>
<article class="panel">
<div class="panel-head">
<h2>Nächste Tage</h2>
<a href="{{ url_for('main.planner') }}">Wochenansicht öffnen</a>
</div>
<div class="week-mini-grid">
{% for card in week_cards %}
<a class="week-mini-card" href="{{ url_for('main.planner_day', date=card.date.isoformat()) }}">
<strong>{{ weekday_short_name(card.date) }} {{ card.date.strftime('%d.%m.') }}</strong>
{% if card.filled_dayparts %}
<span>{{ card.planned_count }} Einträge</span>
<small>{{ card.filled_dayparts | map(attribute='name') | join(', ') }}</small>
{% else %}
<span>Noch frei</span>
<small>sanfter Einstieg für den Tag</small>
{% endif %}
</a>
{% endfor %}
</div>
</article>
</section>
{% endblock %}
+1
View File
@@ -62,6 +62,7 @@
<div class="chip-row">
<span class="chip">{{ item.visibility_label }}</span>
<span class="chip status-soft">{{ item.owner_label }}</span>
<span class="chip">{{ item.for_label }}</span>
</div>
<p class="muted">{{ item_kind_labels[item.kind] }}{% if item.category %} · {{ item.category }}{% endif %}</p>
{% if item.components %}
+41 -21
View File
@@ -5,12 +5,13 @@
<div>
<p class="eyebrow">{{ item_kind_labels[kind] }}</p>
<h1>{% if item %}{{ item.name }} bearbeiten{% else %}Neue{% endif %} {{ item_kind_singular_labels[kind] }}</h1>
<p class="lead">Nur das Nötigste: Name, Sichtbarkeit, Bild, Tageszeiten und eine kleine Notiz, wenn sie hilft.</p>
<p class="lead">Nur das Nötigste: Name, Sichtbarkeit, für wen etwas gedacht ist, Bild, Tageszeiten und eine kleine Notiz.</p>
</div>
{% if item %}
<div class="intro-pills">
<span class="status-pill">{{ item.visibility_label }}</span>
<span class="status-pill status-soft">{{ item.owner_label }}</span>
<span class="status-pill">{{ item.for_label }}</span>
</div>
{% endif %}
</section>
@@ -23,15 +24,26 @@
<input type="text" name="name" value="{{ form_data.name }}" required>
</label>
<label>
Sichtbarkeit
<select name="visibility">
{% for value, label in visibility_options %}
<option value="{{ value }}" {% if form_data.visibility == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
<small class="helper-text">{{ visibility_descriptions[form_data.visibility] }}</small>
</label>
<div class="dual-grid">
<label>
Sichtbarkeit
<select name="visibility">
{% for value, label in visibility_options %}
<option value="{{ value }}" {% if form_data.visibility == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
<small class="helper-text">{{ visibility_descriptions[form_data.visibility] }}</small>
</label>
<label>
Für wen?
<select name="target_user_id">
{% for option in target_user_options %}
<option value="{{ option.value }}" {% if form_data.target_user_raw == option.value %}selected{% endif %}>{{ option.label }}</option>
{% endfor %}
</select>
</label>
</div>
<label>
Kategorie
@@ -40,9 +52,6 @@
{% for category in categories %}
<option value="{{ category }}" {% if form_data.category == category %}selected{% endif %}>{{ category }}</option>
{% endfor %}
{% if form_data.category and form_data.category not in categories %}
<option value="{{ form_data.category }}" selected>{{ form_data.category }}</option>
{% endif %}
</select>
</label>
@@ -77,20 +86,34 @@
{% if kind == 'meal' %}
<fieldset>
<legend>Bestandteile der Mahlzeitenidee</legend>
<p class="muted">Optional: Du kannst eine Mahlzeit frei als Idee anlegen oder sie aus sichtbaren Lebensmitteln zusammenklicken.</p>
<p class="muted">Du kannst eine Mahlzeit frei als Idee anlegen oder sie aus sichtbaren Lebensmitteln zusammenstellen.</p>
<div class="inline-form">
<label class="wide">
Lebensmittel suchen
<input
type="text"
name="food_search"
value="{{ form_data.food_search }}"
placeholder="z. B. Reis, Banane, Joghurt"
data-filter-input
data-filter-target="#meal-components-list"
>
</label>
<button class="secondary" type="submit" name="form_action" value="filter_foods">Suchen</button>
</div>
{% if food_groups %}
<div class="stack-sections">
<div class="stack-sections" id="meal-components-list">
{% for group in food_groups %}
<div class="component-group">
<div class="panel-head">
<h3>{{ group["title"] }}</h3>
<span>{{ group["items"]|length }} Einträge</span>
</div>
<div class="checkbox-grid">
<div class="checkbox-grid filterable-checkbox-group" data-filter-group>
{% for food in group["items"] %}
<label class="check-option">
<label class="check-option" data-filter-label="{{ food.name|lower }} {{ food.category|default('', true)|lower }}">
<input type="checkbox" name="component_ids" value="{{ food.id }}" {% if food.id in form_data.component_ids %}checked{% endif %}>
<span>{{ food.name }} · {{ food.visibility_label }}</span>
<span>{{ food.name }} · {{ food.visibility_label }} · {{ food.for_label }}</span>
</label>
{% endfor %}
</div>
@@ -117,9 +140,6 @@
{% for category in categories %}
<option value="{{ category }}" {% if form_data.quick_food_category == category %}selected{% endif %}>{{ category }}</option>
{% endfor %}
{% if form_data.quick_food_category and form_data.quick_food_category not in categories %}
<option value="{{ form_data.quick_food_category }}" selected>{{ form_data.quick_food_category }}</option>
{% endif %}
</select>
</label>
<label class="wide">
+2 -1
View File
@@ -5,7 +5,7 @@
<div>
<p class="eyebrow">{{ item_kind_labels[kind] }}</p>
<h1>{{ item_kind_labels[kind] }}</h1>
<p class="lead">Gemeinsame und persönliche Ideen bleiben hier ruhig sortiert und schnell wiederverwendbar.</p>
<p class="lead">Gemeinsame und persönliche Ideen bleiben hier ruhig sortiert, mit einem klaren Blick darauf, für wen etwas gedacht ist.</p>
</div>
<a class="button" href="{{ url_for('main.item_create', kind=kind) }}">Neu anlegen</a>
</section>
@@ -67,6 +67,7 @@
<div class="chip-row">
<span class="chip">{{ item.visibility_label }}</span>
<span class="chip status-soft">{{ item.owner_label }}</span>
<span class="chip">{{ item.for_label }}</span>
</div>
<p class="muted">
{% if item.category %}{{ item.category }}{% else %}ohne Kategorie{% endif %}
+93
View File
@@ -0,0 +1,93 @@
{% extends "base.html" %}
{% block title %}{% if template %}Tagesvorlage bearbeiten{% else %}Neue Tagesvorlage{% endif %} | Nouri{% endblock %}
{% block content %}
<section class="page-intro">
<div>
<p class="eyebrow">Tagesvorlage</p>
<h1>{% if template %}{{ template.name }} bearbeiten{% else %}Tagesvorlage anlegen{% endif %}</h1>
<p class="lead">Gib der Vorlage einen Namen, den du später schnell wiedererkennst. Die Einträge bleiben bewusst einfach und alltagsnah.</p>
</div>
{% if source_date %}
<span class="status-pill">Aus {{ source_date.strftime('%d.%m.%Y') }}</span>
{% endif %}
</section>
<section class="panel form-panel">
<form method="post" class="stack-form">
{{ csrf_input() }}
<label>
Name der Vorlage
<input type="text" name="name" value="{{ form_data.name }}" placeholder="{{ name_suggestions[0] }}" required>
<small class="helper-text">Zum Beispiel: Ruhiger Tag, Einfacher Bürotag oder ein ganz eigener Name.</small>
</label>
<label>
Beschreibung
<textarea name="description" rows="3" placeholder="Optional, wenn eine kleine Erinnerung hilfreich ist.">{{ form_data.description }}</textarea>
</label>
<label>
Sichtbarkeit
<select name="visibility">
{% for value, label in visibility_options %}
<option value="{{ value }}" {% if form_data.visibility == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
<small class="helper-text">{{ visibility_descriptions[form_data.visibility] }}</small>
</label>
<div class="chip-row">
{% for suggestion in name_suggestions %}
<span class="chip status-soft">{{ suggestion }}</span>
{% endfor %}
</div>
<div class="stack-sections">
{% for section in daypart_sections %}
<fieldset>
<legend>{{ section.daypart.name }}</legend>
<div class="template-search-row">
<label class="wide">
Einträge filtern
<input
type="text"
placeholder="Nach Namen suchen"
data-filter-input
data-filter-target="#day-template-list-{{ section.daypart.id }}"
>
</label>
</div>
{% if section.quick_items %}
<div class="quick-add-row">
{% for item in section.quick_items %}
<label class="quick-select-card" data-filter-label="{{ item.name|lower }} {{ item.category|default('', true)|lower }}">
<input type="checkbox" name="daypart_{{ section.daypart.id }}_item_ids" value="{{ item.id }}" {% if item.id in section.selected_ids %}checked{% endif %}>
<span>
<strong>{{ item.name }}</strong>
<small>{{ item_kind_labels[item.kind] }} · {{ item.visibility_label }} · {{ item.for_label }}</small>
</span>
</label>
{% endfor %}
</div>
{% endif %}
<div class="checkbox-grid template-checkbox-grid" id="day-template-list-{{ section.daypart.id }}">
{% for item in section.list_items %}
<label class="check-option" data-filter-label="{{ item.name|lower }} {{ item.category|default('', true)|lower }}">
<input type="checkbox" name="daypart_{{ section.daypart.id }}_item_ids" value="{{ item.id }}" {% if item.id in section.selected_ids %}checked{% endif %}>
<span>{{ item.name }} · {{ item.visibility_label }} · {{ item.for_label }}</span>
</label>
{% endfor %}
</div>
</fieldset>
{% endfor %}
</div>
<div class="form-actions">
<button type="submit">Speichern</button>
<a class="ghost-button" href="{{ url_for('main.template_library') }}">Zurück</a>
</div>
</form>
</section>
{% endblock %}
+165
View File
@@ -0,0 +1,165 @@
{% extends "base.html" %}
{% block title %}Vorlagen | Nouri{% endblock %}
{% block content %}
<section class="page-intro">
<div>
<p class="eyebrow">Vorlagen</p>
<h1>Bewährtes ruhig wiederverwenden</h1>
<p class="lead">Tagesvorlagen, Wochenvorlagen und kleine Pakete helfen dabei, vertraute Muster mit wenig Tipparbeit erneut zu nutzen.</p>
</div>
<div class="hero-actions">
<a class="button" href="{{ url_for('main.day_template_create') }}">Neue Tagesvorlage</a>
<a class="button secondary" href="{{ url_for('main.week_template_create') }}">Neue Wochenvorlage</a>
<a class="button secondary" href="{{ url_for('main.item_set_create') }}">Neues Paket</a>
</div>
</section>
<section class="panel compact-form-panel">
<form method="get" class="filter-form">
<label class="wide">
Suche
<input type="text" name="q" value="{{ query }}" placeholder="Nach Namen suchen">
</label>
<label>
Sichtbarkeit
<select name="visibility">
{% for value, label in visibility_options %}
<option value="{{ value }}" {% if selected_visibility == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</label>
<div class="filter-actions">
<button type="submit">Filtern</button>
<a class="ghost-button" href="{{ url_for('main.template_library') }}">Zurücksetzen</a>
</div>
</form>
</section>
{% if template_hints %}
<section class="panel">
<div class="panel-head">
<h2>Sanfte Hinweise</h2>
</div>
<div class="hint-list">
{% for hint in template_hints %}
<p class="hint-chip">{{ hint }}</p>
{% endfor %}
</div>
</section>
{% endif %}
<section class="template-library-grid">
<article class="panel">
<div class="panel-head">
<h2>Tagesvorlagen</h2>
<a href="{{ url_for('main.day_template_create') }}">Neu anlegen</a>
</div>
{% if day_templates %}
<div class="stack-sections">
{% for template in day_templates %}
<article class="template-list-card">
<div>
<strong>{{ template.name }}</strong>
{% if template.description %}
<p class="muted">{{ template.description }}</p>
{% endif %}
<div class="chip-row">
<span class="chip">{{ template.visibility_label }}</span>
<span class="chip status-soft">{{ template.owner_label }}</span>
{% if template.last_used_at %}
<span class="chip">Zuletzt genutzt</span>
{% endif %}
</div>
</div>
<div class="row-actions">
<form method="post" action="{{ url_for('main.day_template_apply', template_id=template.id) }}">
{{ csrf_input() }}
<input type="hidden" name="target_date" value="{{ today.isoformat() }}">
<button type="submit">Heute anwenden</button>
</form>
{% if template.can_edit %}
<a class="ghost-button" href="{{ url_for('main.day_template_edit', template_id=template.id) }}">Bearbeiten</a>
{% endif %}
</div>
</article>
{% endfor %}
</div>
{% else %}
<p class="empty-state">Noch keine passende Tagesvorlage. Du kannst eine Vorlage direkt neu anlegen oder aus einem Tagesplan speichern.</p>
{% endif %}
</article>
<article class="panel">
<div class="panel-head">
<h2>Wochenvorlagen</h2>
<a href="{{ url_for('main.week_template_create') }}">Neu anlegen</a>
</div>
{% if week_templates %}
<div class="stack-sections">
{% for template in week_templates %}
<article class="template-list-card">
<div>
<strong>{{ template.name }}</strong>
{% if template.description %}
<p class="muted">{{ template.description }}</p>
{% endif %}
<div class="chip-row">
<span class="chip">{{ template.visibility_label }}</span>
<span class="chip status-soft">{{ template.owner_label }}</span>
</div>
</div>
<div class="row-actions">
<form method="post" action="{{ url_for('main.week_template_apply', template_id=template.id) }}">
{{ csrf_input() }}
<input type="hidden" name="target_week" value="{{ today.isoformat() }}">
<button type="submit">Diese Woche anwenden</button>
</form>
{% if template.can_edit %}
<a class="ghost-button" href="{{ url_for('main.week_template_edit', template_id=template.id) }}">Bearbeiten</a>
{% endif %}
</div>
</article>
{% endfor %}
</div>
{% else %}
<p class="empty-state">Noch keine Wochenvorlage. Eine gute Woche lässt sich später hier ganz leicht wiederverwenden.</p>
{% endif %}
</article>
</section>
<section class="panel">
<div class="panel-head">
<h2>Kleine Pakete</h2>
<a href="{{ url_for('main.item_set_create') }}">Neues Paket</a>
</div>
{% if item_sets %}
<div class="stack-sections">
{% for item_set in item_sets %}
<article class="template-list-card">
<div>
<strong>{{ item_set.name }}</strong>
{% if item_set.description %}
<p class="muted">{{ item_set.description }}</p>
{% endif %}
<div class="chip-row">
<span class="chip">{{ item_set.visibility_label }}</span>
<span class="chip status-soft">{{ item_set.owner_label }}</span>
</div>
</div>
<div class="row-actions">
<form method="post" action="{{ url_for('main.item_set_apply', set_id=item_set.id) }}">
{{ csrf_input() }}
<button type="submit">Auf Einkaufsliste</button>
</form>
{% if item_set.can_edit %}
<a class="ghost-button" href="{{ url_for('main.item_set_edit', set_id=item_set.id) }}">Bearbeiten</a>
{% endif %}
</div>
</article>
{% endfor %}
</div>
{% else %}
<p class="empty-state">Pakete eignen sich gut für kleine Bündel wie schnelles Frühstück, sicherer Snack oder Einkauf für zwei Tage.</p>
{% endif %}
</section>
{% endblock %}
+73
View File
@@ -0,0 +1,73 @@
{% extends "base.html" %}
{% block title %}{% if item_set %}Paket bearbeiten{% else %}Neues Paket{% endif %} | Nouri{% endblock %}
{% block content %}
<section class="page-intro">
<div>
<p class="eyebrow">Kleines Paket</p>
<h1>{% if item_set %}{{ item_set.name }} bearbeiten{% else %}Paket anlegen{% endif %}</h1>
<p class="lead">Pakete bündeln wiederkehrende Dinge ganz leicht, zum Beispiel schnelles Frühstück, sicherer Snack oder Einkauf für zwei Tage.</p>
</div>
</section>
<section class="panel form-panel">
<form method="post" class="stack-form">
{{ csrf_input() }}
<label>
Name des Pakets
<input type="text" name="name" value="{{ form_data.name }}" placeholder="{{ name_suggestions[0] }}" required>
</label>
<label>
Beschreibung
<textarea name="description" rows="3" placeholder="Optional, wenn eine kleine Erinnerung hilfreich ist.">{{ form_data.description }}</textarea>
</label>
<label>
Sichtbarkeit
<select name="visibility">
{% for value, label in visibility_options %}
<option value="{{ value }}" {% if form_data.visibility == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
<small class="helper-text">{{ visibility_descriptions[form_data.visibility] }}</small>
</label>
<div class="chip-row">
{% for suggestion in name_suggestions %}
<span class="chip status-soft">{{ suggestion }}</span>
{% endfor %}
</div>
<fieldset>
<legend>Einträge auswählen</legend>
<label>
Einträge filtern
<input type="text" placeholder="Nach Namen suchen" data-filter-input data-filter-target="#item-set-list">
</label>
<div class="stack-sections" id="item-set-list">
{% for group in item_groups %}
<div class="component-group">
<div class="panel-head">
<h3>{{ group["title"] }}</h3>
<span>{{ group["items"]|length }} Einträge</span>
</div>
<div class="checkbox-grid">
{% for item in group["items"] %}
<label class="check-option" data-filter-label="{{ item.name|lower }} {{ item.category|default('', true)|lower }}">
<input type="checkbox" name="item_ids" value="{{ item.id }}" {% if item.id in form_data.item_ids %}checked{% endif %}>
<span>{{ item.name }} · {{ item_kind_labels[item.kind] }} · {{ item.visibility_label }} · {{ item.for_label }}</span>
</label>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</fieldset>
<div class="form-actions">
<button type="submit">Speichern</button>
<a class="ghost-button" href="{{ url_for('main.template_library') }}">Zurück</a>
</div>
</form>
</section>
{% endblock %}
+79
View File
@@ -0,0 +1,79 @@
{% extends "base.html" %}
{% block title %}{% if template %}Wochenvorlage bearbeiten{% else %}Neue Wochenvorlage{% endif %} | Nouri{% endblock %}
{% block content %}
<section class="page-intro">
<div>
<p class="eyebrow">Wochenvorlage</p>
<h1>{% if template %}{{ template.name }} bearbeiten{% else %}Wochenvorlage anlegen{% endif %}</h1>
<p class="lead">Wochenvorlagen bleiben bewusst leicht: pro Wochentag kannst du eine bestehende Tagesvorlage zuordnen oder einen aktuellen Tag als neue Vorlage übernehmen.</p>
</div>
{% if source_week %}
<span class="status-pill">Aus Woche ab {{ source_week.strftime('%d.%m.%Y') }}</span>
{% endif %}
</section>
<section class="panel form-panel">
<form method="post" class="stack-form">
{{ csrf_input() }}
<input type="hidden" name="source_week" value="{{ form_data.source_week }}">
<label>
Name der Vorlage
<input type="text" name="name" value="{{ form_data.name }}" placeholder="{{ name_suggestions[0] }}" required>
<small class="helper-text">Ein Name wie Standardwoche, leichte Woche oder etwas ganz Eigenes reicht völlig aus.</small>
</label>
<label>
Beschreibung
<textarea name="description" rows="3" placeholder="Optional, wenn eine kleine Erinnerung hilfreich ist.">{{ form_data.description }}</textarea>
</label>
<label>
Sichtbarkeit
<select name="visibility">
{% for value, label in visibility_options %}
<option value="{{ value }}" {% if form_data.visibility == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
<small class="helper-text">{{ visibility_descriptions[form_data.visibility] }}</small>
</label>
<div class="chip-row">
{% for suggestion in name_suggestions %}
<span class="chip status-soft">{{ suggestion }}</span>
{% endfor %}
</div>
<div class="stack-sections">
{% for weekday_index in range(7) %}
<div class="week-template-row">
<div>
<strong>{{ weekday_labels[weekday_index] }}</strong>
<p class="muted">Du kannst eine vorhandene Tagesvorlage auswählen oder den aktuellen Tag aus der Quellwoche übernehmen.</p>
</div>
<label>
Tagesvorlage
<select name="weekday_{{ weekday_index }}_day_template_id">
<option value="">Noch offen</option>
{% for day_template in day_templates %}
<option value="{{ day_template.id }}" {% if form_data.selected_map.get(weekday_index) == day_template.id %}selected{% endif %}>{{ day_template.name }} · {{ day_template.visibility_label }}</option>
{% endfor %}
</select>
</label>
{% if form_data.source_week %}
<label class="inline-check">
<input type="checkbox" name="weekday_{{ weekday_index }}_copy_source" value="1" {% if form_data.copy_from_source.get(weekday_index) %}checked{% endif %}>
<span>Aus Quellwoche als neue Tagesvorlage übernehmen</span>
</label>
{% endif %}
</div>
{% endfor %}
</div>
<div class="form-actions">
<button type="submit">Speichern</button>
<a class="ghost-button" href="{{ url_for('main.template_library') }}">Zurück</a>
</div>
</form>
</section>
{% endblock %}
-56
View File
@@ -1,56 +0,0 @@
{% extends "base.html" %}
{% block title %}Mehr | Nouri{% endblock %}
{% block content %}
<section class="page-intro">
<div>
<p class="eyebrow">Mehr</p>
<h1>Alles Weitere auf einen Blick</h1>
<p class="lead">Hier liegen die ruhigeren Nebenwege: Lebensmittel, Mahlzeiten, Woche, Profil und die kleinen Einstellungen.</p>
</div>
<div class="intro-pills">
<span class="status-pill">{{ g.user.display_name or g.user.username }}</span>
<span class="status-pill status-soft">{{ role_labels[g.user.role] }}</span>
</div>
</section>
<section class="panel">
<div class="more-link-grid">
<a class="more-link-card" href="{{ url_for('main.item_list', kind='food') }}">
<strong>Lebensmittel</strong>
<small>Persönliche und gemeinsame Einträge ansehen</small>
</a>
<a class="more-link-card" href="{{ url_for('main.item_list', kind='meal') }}">
<strong>Mahlzeiten</strong>
<small>Mahlzeitenideen sammeln und wiederverwenden</small>
</a>
<a class="more-link-card" href="{{ url_for('main.planner') }}">
<strong>Woche</strong>
<small>Die nächsten sieben Tage im Blick behalten</small>
</a>
<a class="more-link-card" href="{{ url_for('main.archive_view') }}">
<strong>Archiv</strong>
<small>Vertraute Dinge leicht wiederfinden</small>
</a>
<a class="more-link-card" href="{{ url_for('auth.profile') }}">
<strong>Mein Profil</strong>
<small>Zugang, Name und Passwort pflegen</small>
</a>
{% if g.user.role == 'admin' %}
<a class="more-link-card" href="{{ url_for('admin.user_list') }}">
<strong>Nutzer verwalten</strong>
<small>Weitere Haushaltsmitglieder verwalten</small>
</a>
{% endif %}
</div>
</section>
<section class="panel">
<div class="more-actions">
<button class="ghost-button" type="button" data-theme-toggle>Modus wechseln</button>
<form method="post" action="{{ url_for('auth.logout') }}">
{{ csrf_input() }}
<button class="ghost-button" type="submit">Abmelden</button>
</form>
</div>
</section>
{% endblock %}
+54 -2
View File
@@ -14,6 +14,45 @@
</div>
</section>
<section class="two-column">
<article class="panel">
<div class="panel-head">
<h2>Tagesvorlagen</h2>
<a href="{{ url_for('main.day_template_create', source_date=selected_date.isoformat()) }}">Als Vorlage speichern</a>
</div>
{% if day_templates %}
<div class="stack-sections">
{% for template in day_templates %}
<form method="post" action="{{ url_for('main.day_template_apply', template_id=template.id) }}" class="inline-form template-apply-form">
{{ csrf_input() }}
<input type="hidden" name="target_date" value="{{ selected_date.isoformat() }}">
<div class="template-card">
<strong>{{ template.name }}</strong>
<small>{{ template.visibility_label }} · {{ template.owner_label }}</small>
</div>
<button type="submit">Vorlage anwenden</button>
</form>
{% endfor %}
</div>
{% else %}
<p class="empty-state">Wenn du einen Tag öfter wiederverwenden möchtest, kannst du ihn hier als Tagesvorlage speichern.</p>
{% endif %}
</article>
{% if day_hints %}
<article class="panel">
<div class="panel-head">
<h2>Sanfte Hinweise</h2>
</div>
<div class="hint-list">
{% for hint in day_hints %}
<p class="hint-chip">{{ hint }}</p>
{% endfor %}
</div>
</article>
{% endif %}
</section>
<section class="planner-day-stack">
{% for section in sections %}
<details class="day-tile" id="daypart-{{ section.daypart.id }}" {% if section.is_open %}open{% endif %}>
@@ -33,6 +72,18 @@
</summary>
<div class="day-tile-body">
{% if section.suggestions %}
<div class="suggestion-row">
{% for suggestion in section.suggestions %}
<article class="suggestion-card">
<strong>{{ suggestion.title }}</strong>
<small>{{ suggestion.reason }}</small>
<p>{{ suggestion.note }}</p>
</article>
{% endfor %}
</div>
{% endif %}
{% if section.quick_items %}
<div class="quick-add-row">
{% for item in section.quick_items %}
@@ -44,7 +95,7 @@
<input type="hidden" name="visibility" value="{{ item.visibility }}">
<button class="quick-add-button" type="submit">
<span>{{ item.name }}</span>
<small>{{ item_kind_labels[item.kind] }} · {{ item.visibility_label }}{% if item.availability_state == 'home' %} · zuhause{% endif %}</small>
<small>{{ item_kind_labels[item.kind] }} · {{ item.visibility_label }} · {{ item.for_label }}{% if item.availability_state == 'home' %} · zuhause{% endif %}</small>
</button>
</form>
{% endfor %}
@@ -61,7 +112,7 @@
<option value="">Etwas für {{ section.daypart.name }} wählen</option>
{% for item in section.candidates %}
<option value="{{ item.id }}" {% if section.selected_item_id == item.id %}selected{% endif %}>
{{ item.name }} · {{ item_kind_labels[item.kind] }} · {{ item.visibility_label }}{% if item.availability_state == 'home' %} · zuhause{% endif %}{% if item.dayparts and section.daypart.name not in item.dayparts %} · auch flexibel{% endif %}
{{ item.name }} · {{ item_kind_labels[item.kind] }} · {{ item.visibility_label }} · {{ item.for_label }}{% if item.availability_state == 'home' %} · zuhause{% endif %}{% if item.dayparts and section.daypart.name not in item.dayparts %} · auch flexibel{% endif %}
</option>
{% endfor %}
</select>
@@ -92,6 +143,7 @@
<div class="chip-row">
<span class="chip">{{ entry.visibility_label }}</span>
<span class="chip status-soft">{{ entry.owner_label }}</span>
<span class="chip">{{ entry.for_label }}</span>
</div>
</div>
{% if entry.can_edit %}
+41 -2
View File
@@ -5,7 +5,7 @@
<div>
<p class="eyebrow">Wochenansicht</p>
<h1>Ein sanfter Blick auf die nächsten sieben Tage</h1>
<p class="lead">Du kannst bestehende Einträge zwischen Tagen und Tageszeiten verschieben. Wenn etwas noch nicht zuhause ist, landet es dabei automatisch auf der Einkaufsliste.</p>
<p class="lead">Du kannst bestehende Einträge zwischen Tagen und Tageszeiten verschieben, Vorlagen anwenden und eine Woche bei Bedarf für später sichern.</p>
</div>
<div class="week-nav">
<a class="ghost-button" href="{{ url_for('main.planner', week=prev_week.isoformat()) }}">Vorige Woche</a>
@@ -14,6 +14,45 @@
</div>
</section>
<section class="two-column">
<article class="panel">
<div class="panel-head">
<h2>Wochenvorlagen</h2>
<a href="{{ url_for('main.week_template_create', source_week=week_start.isoformat()) }}">Als Vorlage speichern</a>
</div>
{% if week_templates %}
<div class="stack-sections">
{% for template in week_templates %}
<form method="post" action="{{ url_for('main.week_template_apply', template_id=template.id) }}" class="inline-form template-apply-form">
{{ csrf_input() }}
<input type="hidden" name="target_week" value="{{ week_start.isoformat() }}">
<div class="template-card">
<strong>{{ template.name }}</strong>
<small>{{ template.visibility_label }} · {{ template.owner_label }}</small>
</div>
<button type="submit">Vorlage anwenden</button>
</form>
{% endfor %}
</div>
{% else %}
<p class="empty-state">Wenn eine Woche sich bewährt hat, kannst du sie hier später als Wochenvorlage wiederverwenden.</p>
{% endif %}
</article>
{% if week_hints %}
<article class="panel">
<div class="panel-head">
<h2>Sanfte Hinweise</h2>
</div>
<div class="hint-list">
{% for hint in week_hints %}
<p class="hint-chip">{{ hint }}</p>
{% endfor %}
</div>
</article>
{% endif %}
</section>
<section class="week-overview-grid week-board" data-csrf-token="{{ csrf_token_value }}">
{% for card in week_cards %}
<article class="week-card">
@@ -51,7 +90,7 @@
{% for entry in slot.entries %}
<article class="plan-chip draggable-plan-entry" draggable="{{ 'true' if entry.can_edit else 'false' }}" data-entry-id="{{ entry.id }}" data-move-url="{{ url_for('main.planner_move', entry_id=entry.id) }}">
<strong>{{ entry.item_name }}</strong>
<small>{{ entry.visibility_label }} · {{ entry.owner_label }}</small>
<small>{{ entry.visibility_label }} · {{ entry.for_label }}</small>
</article>
{% endfor %}
</div>
+9 -2
View File
@@ -5,7 +5,7 @@
<div>
<p class="eyebrow">Einkaufsliste</p>
<h1>Was noch mitkommen soll</h1>
<p class="lead">Abhaken legt Dinge automatisch unter Zuhause ab. Gemeinsame und persönliche Einträge bleiben dabei klar erkennbar.</p>
<p class="lead">Fehlende Lebensmittel aus einer Mahlzeit landen jetzt einzeln hier. So bleibt die Liste konkret und alltagsnah.</p>
</div>
</section>
@@ -35,6 +35,13 @@
<div class="chip-row">
<span class="chip">{{ entry.visibility_label }}</span>
<span class="chip status-soft">{{ entry.owner_label }}</span>
<span class="chip">{{ entry.for_label }}</span>
{% if entry.needed_for_label %}
<span class="chip status-home">
Für {{ entry.needed_for_label }}
{% if entry.needed_daypart_name %} · {{ entry.needed_daypart_name }}{% endif %}
</span>
{% endif %}
</div>
</div>
<div class="row-actions">
@@ -55,7 +62,7 @@
{% else %}
<section class="panel empty-panel">
<h2>Die Liste ist gerade frei</h2>
<p>Einträge aus Lebensmitteln, Mahlzeitenideen oder dem Archiv lassen sich jederzeit wieder hinzufügen.</p>
<p>Einträge aus Lebensmitteln, Vorlagen oder kleinen Paketen lassen sich jederzeit wieder hinzufügen.</p>
</section>
{% endif %}
{% endblock %}