From 8a9823e9de94a6749578741407f7963b118a40c8 Mon Sep 17 00:00:00 2001 From: Bernhard Kolb Date: Mon, 13 Apr 2026 13:32:31 +0200 Subject: [PATCH] Wartung Fragen --- app.py | 88 ++++++++++++- db.py | 52 +++++++- static/css/style.css | 119 +++++++++++++++++ templates/admin/question_form_thema.html | 57 ++++++++ templates/admin/questions.html | 161 +++++++++++++++++++---- templates/admin/themen_list.html | 13 +- templates/partials/menu.html | 4 +- 7 files changed, 458 insertions(+), 36 deletions(-) create mode 100644 templates/admin/question_form_thema.html diff --git a/app.py b/app.py index 532cca6..44f975d 100644 --- a/app.py +++ b/app.py @@ -50,6 +50,8 @@ from db import ( get_themen_for_branche, get_next_thema_id_for_branche, get_thema_for_branche, + get_question_count_for_thema, + get_all_themen_with_question_count, ) from permissions import admin_required, login_required from tools import create_assessment_chart, generate_activation_token, send_mail, verify_activation_token @@ -355,7 +357,7 @@ def admin_users(): @app.route("/admin/themen") @admin_required def admin_themen(): - themen = get_all_themen() + themen = get_all_themen_with_question_count() return render_template("admin/themen_list.html", themen=themen) @@ -562,7 +564,8 @@ def admin_contact_delete(ansprechpartner_id): def admin_questions(): fragen = get_all_questions_with_thema() themen = get_all_themen() - return render_template("admin/questions.html", fragen=fragen, themen=themen) + branchen = get_all_branchen() + return render_template("admin/questions.html", fragen=fragen, themen=themen, branchen=branchen) @app.route("/admin/fragen/new", methods=["GET", "POST"]) @admin_required @@ -755,5 +758,86 @@ def admin_branche_delete(branche_id): flash("Branche wurde gelöscht.", "success") return redirect(url_for("admin_branchen")) +@app.route("/admin/themen//fragen/new", methods=["GET", "POST"]) +@admin_required +def admin_question_new_for_thema(thema_id): + thema = get_thema_by_id(thema_id) + if not thema: + flash("Thema nicht gefunden.", "error") + return redirect(url_for("admin_themen")) + + question_count = get_question_count_for_thema(thema_id) + + if request.method == "POST": + text = request.form.get("text", "").strip() + + if not text: + flash("Der Fragetext ist ein Pflichtfeld.", "error") + return render_template( + "admin/question_form_thema.html", + mode="new", + thema=thema, + frage=request.form, + question_count=question_count, + min_questions=8, + ) + + create_question(thema_id, text) + flash("Frage wurde erstellt.", "success") + return redirect(url_for("admin_question_new_for_thema", thema_id=thema_id)) + + return render_template( + "admin/question_form_thema.html", + mode="new", + thema=thema, + frage={}, + question_count=question_count, + min_questions=8, + ) + +@app.route("/admin/themen//fragen//edit", methods=["GET", "POST"]) +@admin_required +def admin_question_edit_for_thema(thema_id, frage_id): + thema = get_thema_by_id(thema_id) + frage = get_question_by_id(frage_id) + + if not thema or not frage or int(frage["thema_id"]) != int(thema_id): + flash("Frage oder Thema nicht gefunden.", "error") + return redirect(url_for("admin_themen")) + + question_count = get_question_count_for_thema(thema_id) + + if request.method == "POST": + text = request.form.get("text", "").strip() + + if not text: + frage_form = { + "id": frage_id, + "thema_id": thema_id, + "text": text, + } + flash("Der Fragetext ist ein Pflichtfeld.", "error") + return render_template( + "admin/question_form_thema.html", + mode="edit", + thema=thema, + frage=frage_form, + question_count=question_count, + min_questions=8, + ) + + update_question(frage_id, thema_id, text) + flash("Frage wurde gespeichert.", "success") + return redirect(url_for("admin_question_new_for_thema", thema_id=thema_id)) + + return render_template( + "admin/question_form_thema.html", + mode="edit", + thema=thema, + frage=frage, + question_count=question_count, + min_questions=8, + ) + if __name__ == "__main__": app.run(host="0.0.0.0", port=5000, debug=True) \ No newline at end of file diff --git a/db.py b/db.py index 9d16abd..3c1dffd 100644 --- a/db.py +++ b/db.py @@ -352,9 +352,28 @@ def get_thema_questions(thema_id): def get_all_questions_with_thema(): return fetch_all( """ - SELECT f.id, f.text, f.thema_id, t.kurztitel, t.titel + SELECT + f.id, + f.text, + f.thema_id, + t.id AS thema_sort_id, + t.kurztitel, + t.titel, + COALESCE(STRING_AGG(b.branchenname, ' | '), '') AS branchen FROM fragen f - JOIN thema t ON t.id = f.thema_id + JOIN thema t + ON t.id = f.thema_id + LEFT JOIN branchenthemen bt + ON bt.thema_id = t.id + LEFT JOIN branche b + ON b.id = bt.branche_id + GROUP BY + f.id, + f.text, + f.thema_id, + t.id, + t.kurztitel, + t.titel ORDER BY t.id, f.id """ ) @@ -711,4 +730,33 @@ def get_thema_for_branche(thema_id, branche_id): AND bt.branche_id = %s """, (thema_id, branche_id), + ) + +def get_question_count_for_thema(thema_id): + row = fetch_one( + """ + SELECT COUNT(*) AS anzahl + FROM fragen + WHERE thema_id = %s + """, + (thema_id,), + ) + return int(row["anzahl"]) if row else 0 + + +def get_all_themen_with_question_count(): + return fetch_all( + """ + SELECT + t.id, + t.kurztitel, + t.titel, + t.infotext, + t.zusatztext, + COUNT(f.id) AS fragen_anzahl + FROM thema t + LEFT JOIN fragen f ON f.thema_id = t.id + GROUP BY t.id, t.kurztitel, t.titel, t.infotext, t.zusatztext + ORDER BY t.id + """ ) \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css index c15e294..57d7b62 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -330,4 +330,123 @@ select { border: 1px solid var(--border); background: #fff; color: var(--text); +} + +/* Progress bar */ +.progress-bar-wrap { + width: 100%; + height: 18px; + background: #f1e8dc; + border: 1px solid var(--border); + border-radius: 999px; + overflow: hidden; +} + +.progress-bar-fill { + height: 100%; + background: var(--accent); + border-radius: 999px; + transition: width 0.25s ease; +} + +.filter-bar { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 16px; + margin-bottom: 24px; +} + +.filter-item { + margin-bottom: 0; +} + +.question-group { + background: #fcf8f2; + border: 1px solid var(--border); + border-radius: 24px; + padding: 20px; + margin-bottom: 22px; +} + +.question-group:nth-child(odd) { + background: #fffaf4; +} + +.question-group:nth-child(even) { + background: #f8f1e7; +} + +.question-group-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 16px; + margin-bottom: 18px; + padding-bottom: 12px; + border-bottom: 1px solid var(--border); +} + +.question-group-header h2 { + margin: 0 0 6px 0; + font-size: 1.15rem; +} + +.question-list { + display: grid; + gap: 12px; +} + +.question-item { + display: flex; + justify-content: space-between; + gap: 16px; + align-items: flex-start; + background: #fff; + border: 1px solid #eadfce; + border-radius: 18px; + padding: 16px; +} + +.question-item-main { + flex: 1; + min-width: 0; +} + +.question-meta { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-bottom: 8px; + font-size: 0.92rem; +} + +.question-id { + font-weight: 700; + color: var(--accent-dark); +} + +.question-branch { + color: #6f5d4f; +} + +.question-text { + line-height: 1.45; +} + +.question-actions { + display: flex; + gap: 8px; + flex-wrap: wrap; + justify-content: flex-end; +} + +@media (max-width: 700px) { + .question-group-header, + .question-item { + flex-direction: column; + } + + .question-actions { + justify-content: flex-start; + } } \ No newline at end of file diff --git a/templates/admin/question_form_thema.html b/templates/admin/question_form_thema.html new file mode 100644 index 0000000..4c129f9 --- /dev/null +++ b/templates/admin/question_form_thema.html @@ -0,0 +1,57 @@ +{% extends "base.html" %} + +{% block content %} +
+ + +
+
+ Fortschritt Fragen + {{ question_count }}/{{ min_questions }} +
+ + {% set progress = (question_count * 100 / min_questions) %} + {% if progress > 100 %} + {% set progress = 100 %} + {% endif %} + +
+
+
+ + {% if question_count < min_questions %} +

+ Für dieses Thema sollten mindestens {{ min_questions }} Fragen gepflegt werden. +

+ {% else %} +

+ Mindestanzahl erreicht. +

+ {% endif %} +
+ +
+
+ + +
+ +
+ + Neu laden +
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/admin/questions.html b/templates/admin/questions.html index c14b960..476e735 100644 --- a/templates/admin/questions.html +++ b/templates/admin/questions.html @@ -7,35 +7,138 @@ Neue Frage - - - - - - - - - - - {% for f in fragen %} - - - - - - - {% endfor %} - -
IDThemaFrageAktionen
{{ f.id }}{{ f.kurztitel }}{{ f.text }} - Bearbeiten +
+
+ + +
-
- -
-
+
+ + +
+ +
+ + +
+ + +
+ {% for thema in themen %} + {% set group_questions = fragen | selectattr("thema_id", "equalto", thema.id) | list %} + {% if group_questions %} +
+ +
+
+

{{ thema.kurztitel }} - {{ thema.titel }}

+

Fragen: {{ group_questions|length }}

+
+ + Frage zu diesem Thema + +
+ +
+ {% for f in group_questions %} +
+
+
+ #{{ f.id }} + {% if f.branchen %} + {{ f.branchen }} + {% else %} + Keine Branche zugeordnet + {% endif %} +
+ +
+ {{ f.text }} +
+
+ +
+ + Bearbeiten + + +
+ +
+
+
+ {% endfor %} +
+
+ {% endif %} + {% endfor %} +
+ + {% endblock %} \ No newline at end of file diff --git a/templates/admin/themen_list.html b/templates/admin/themen_list.html index 6d180ff..b3dcc61 100644 --- a/templates/admin/themen_list.html +++ b/templates/admin/themen_list.html @@ -14,6 +14,7 @@ ID Kurztitel Titel + Fragen Aktionen @@ -23,10 +24,20 @@ {{ thema.id }} {{ thema.kurztitel }} {{ thema.titel }} + + {{ thema.fragen_anzahl }} + {% if thema.fragen_anzahl < 8 %} + / 8 + {% endif %} + Bearbeiten + Fragen pflegen -
+
diff --git a/templates/partials/menu.html b/templates/partials/menu.html index 552568d..20c52ac 100644 --- a/templates/partials/menu.html +++ b/templates/partials/menu.html @@ -12,9 +12,9 @@ Profil {% if is_admin %} Admin - Themen + {% endif %} Logout