diff --git a/app.py b/app.py index d37044e..532cca6 100644 --- a/app.py +++ b/app.py @@ -38,6 +38,18 @@ from db import ( delete_question, activate_user, delete_user, + get_all_branchen, + get_branche_by_id, + get_thema_ids_for_branche, + create_branche, + update_branche, + delete_branche, + get_all_branchen, + get_branche_ids_for_thema, + get_all_branchen, + get_themen_for_branche, + get_next_thema_id_for_branche, + get_thema_for_branche, ) from permissions import admin_required, login_required from tools import create_assessment_chart, generate_activation_token, send_mail, verify_activation_token @@ -165,11 +177,26 @@ def logout(): return redirect(url_for("login")) -@app.route("/dashboard") +@app.route("/dashboard", methods=["GET", "POST"]) @login_required def dashboard(): - themen = get_all_themen() - return render_template("dashboard.html", themen=themen) + branchen = get_all_branchen() + + if request.method == "POST": + branche_id = request.form.get("branche_id") + + if not branche_id: + flash("Bitte wählen Sie eine Branche aus.", "warning") + return render_template("dashboard.html", branchen=branchen) + + themen = get_themen_for_branche(branche_id) + if not themen: + flash("Für diese Branche sind aktuell keine Themen hinterlegt.", "warning") + return render_template("dashboard.html", branchen=branchen) + + return redirect(url_for("topic", thema_id=themen[0]["id"], branche_id=branche_id)) + + return render_template("dashboard.html", branchen=branchen) @app.route("/profil", methods=["GET", "POST"]) @@ -193,9 +220,21 @@ def profile(): @app.route("/thema/", methods=["GET", "POST"]) @login_required def topic(thema_id): - thema = get_thema_by_id(thema_id) + branche_id = request.values.get("branche_id") + + if not branche_id: + flash("Bitte starten Sie das Assessment über die Startseite.", "warning") + return redirect(url_for("dashboard")) + + try: + branche_id = int(branche_id) + except ValueError: + flash("Ungültige Branche.", "danger") + return redirect(url_for("dashboard")) + + thema = get_thema_for_branche(thema_id, branche_id) if not thema: - flash("Thema nicht gefunden.", "danger") + flash("Thema nicht gefunden oder nicht für diese Branche freigegeben.", "danger") return redirect(url_for("dashboard")) fragen = get_thema_questions(thema_id) @@ -217,6 +256,7 @@ def topic(thema_id): fragen=fragen, ansprechpartner=ansprechpartner, assessment_id=assessment_id, + branche_id=branche_id, ) save_assessment_answer( @@ -226,11 +266,24 @@ def topic(thema_id): antwort=(value == "ja"), ) - next_thema_id = get_next_thema_id(thema_id) + next_thema_id = get_next_thema_id_for_branche(thema_id, branche_id) if next_thema_id: - return redirect(url_for("topic", thema_id=next_thema_id, assessment_id=assessment_id)) + return redirect( + url_for( + "topic", + thema_id=next_thema_id, + assessment_id=assessment_id, + branche_id=branche_id, + ) + ) - return redirect(url_for("assessment_result", assessment_id=assessment_id)) + return redirect( + url_for( + "assessment_result", + assessment_id=assessment_id, + branche_id=branche_id, + ) + ) assessment_id = request.args.get("assessment_id", "") return render_template( @@ -239,24 +292,42 @@ def topic(thema_id): fragen=fragen, ansprechpartner=ansprechpartner, assessment_id=assessment_id, + branche_id=branche_id, ) @app.route("/assessment//result") @login_required def assessment_result(assessment_id): - rows = get_assessment_result_rows(assessment_id) + branche_id = request.args.get("branche_id") + + if not branche_id: + flash("Branche fehlt für die Auswertung.", "warning") + return redirect(url_for("dashboard")) + + try: + branche_id = int(branche_id) + except ValueError: + flash("Ungültige Branche.", "danger") + return redirect(url_for("dashboard")) + + rows = get_assessment_result_rows(assessment_id, branche_id) + + if not rows: + flash("Für diese Branche konnten keine Auswertungsdaten geladen werden.", "warning") + return redirect(url_for("dashboard")) labels = [row["kurztitel"] for row in rows] values = [int(row["ja_anzahl"]) for row in rows] - filename = f"assessment_{assessment_id}.png" + filename = f"assessment_{assessment_id}_branche_{branche_id}.png" output_path = chart_dir / filename create_assessment_chart(labels, values, output_path) return render_template( "result.html", assessment_id=assessment_id, + branche_id=branche_id, chart_file=filename, rows=rows, ) @@ -292,6 +363,7 @@ def admin_themen(): @admin_required def admin_thema_new(): ansprechpartner = get_all_ansprechpartner() + branchen = get_all_branchen() if request.method == "POST": kurztitel = request.form.get("kurztitel", "").strip() @@ -299,6 +371,7 @@ def admin_thema_new(): infotext = request.form.get("infotext", "").strip() zusatztext = request.form.get("zusatztext", "").strip() ansprechpartner_ids = [int(x) for x in request.form.getlist("ansprechpartner_ids")] + branche_ids = [int(x) for x in request.form.getlist("branche_ids")] if not kurztitel or not titel: flash("Kurztitel und Titel sind Pflichtfelder.", "error") @@ -307,10 +380,19 @@ def admin_thema_new(): mode="new", thema=request.form, ansprechpartner=ansprechpartner, + branchen=branchen, selected_ansprechpartner_ids=ansprechpartner_ids, + selected_branche_ids=branche_ids, ) - create_thema(kurztitel, titel, infotext, zusatztext, ansprechpartner_ids) + create_thema( + kurztitel, + titel, + infotext, + zusatztext, + ansprechpartner_ids, + branche_ids, + ) flash("Thema wurde erstellt.", "success") return redirect(url_for("admin_themen")) @@ -319,10 +401,11 @@ def admin_thema_new(): mode="new", thema={}, ansprechpartner=ansprechpartner, + branchen=branchen, selected_ansprechpartner_ids=[], + selected_branche_ids=[], ) - @app.route("/admin/themen//edit", methods=["GET", "POST"]) @admin_required def admin_thema_edit(thema_id): @@ -332,6 +415,7 @@ def admin_thema_edit(thema_id): return redirect(url_for("admin_themen")) ansprechpartner = get_all_ansprechpartner() + branchen = get_all_branchen() if request.method == "POST": kurztitel = request.form.get("kurztitel", "").strip() @@ -339,6 +423,7 @@ def admin_thema_edit(thema_id): infotext = request.form.get("infotext", "").strip() zusatztext = request.form.get("zusatztext", "").strip() ansprechpartner_ids = [int(x) for x in request.form.getlist("ansprechpartner_ids")] + branche_ids = [int(x) for x in request.form.getlist("branche_ids")] if not kurztitel or not titel: flash("Kurztitel und Titel sind Pflichtfelder.", "error") @@ -354,24 +439,36 @@ def admin_thema_edit(thema_id): mode="edit", thema=thema_form, ansprechpartner=ansprechpartner, + branchen=branchen, selected_ansprechpartner_ids=ansprechpartner_ids, + selected_branche_ids=branche_ids, ) - update_thema(thema_id, kurztitel, titel, infotext, zusatztext, ansprechpartner_ids) + update_thema( + thema_id, + kurztitel, + titel, + infotext, + zusatztext, + ansprechpartner_ids, + branche_ids, + ) flash("Thema wurde gespeichert.", "success") return redirect(url_for("admin_themen")) selected_ansprechpartner_ids = get_ansprechpartner_ids_for_thema(thema_id) + selected_branche_ids = get_branche_ids_for_thema(thema_id) return render_template( "admin/thema_form.html", mode="edit", thema=thema, ansprechpartner=ansprechpartner, + branchen=branchen, selected_ansprechpartner_ids=selected_ansprechpartner_ids, + selected_branche_ids=selected_branche_ids, ) - @app.route("/admin/themen//delete", methods=["POST"]) @admin_required def admin_thema_delete(thema_id): @@ -569,5 +666,94 @@ def admin_user_delete(user_id): def impressum(): return render_template("impressum.html") +@app.route("/admin/branchen") +@admin_required +def admin_branchen(): + branchen = get_all_branchen() + return render_template("admin/branchen_list.html", branchen=branchen) + + +@app.route("/admin/branchen/new", methods=["GET", "POST"]) +@admin_required +def admin_branche_new(): + themen = get_all_themen() + + if request.method == "POST": + branchenname = request.form.get("branchenname", "").strip() + thema_ids = [int(x) for x in request.form.getlist("thema_ids")] + + if not branchenname: + flash("Branchenname ist ein Pflichtfeld.", "error") + return render_template( + "admin/branche_form.html", + mode="new", + branche=request.form, + themen=themen, + selected_thema_ids=thema_ids, + ) + + create_branche(branchenname, thema_ids) + flash("Branche wurde erstellt.", "success") + return redirect(url_for("admin_branchen")) + + return render_template( + "admin/branche_form.html", + mode="new", + branche={}, + themen=themen, + selected_thema_ids=[], + ) + + +@app.route("/admin/branchen//edit", methods=["GET", "POST"]) +@admin_required +def admin_branche_edit(branche_id): + branche = get_branche_by_id(branche_id) + if not branche: + flash("Branche nicht gefunden.", "error") + return redirect(url_for("admin_branchen")) + + themen = get_all_themen() + + if request.method == "POST": + branchenname = request.form.get("branchenname", "").strip() + thema_ids = [int(x) for x in request.form.getlist("thema_ids")] + + if not branchenname: + flash("Branchenname ist ein Pflichtfeld.", "error") + branche_form = { + "id": branche_id, + "branchenname": branchenname, + } + return render_template( + "admin/branche_form.html", + mode="edit", + branche=branche_form, + themen=themen, + selected_thema_ids=thema_ids, + ) + + update_branche(branche_id, branchenname, thema_ids) + flash("Branche wurde gespeichert.", "success") + return redirect(url_for("admin_branchen")) + + selected_thema_ids = get_thema_ids_for_branche(branche_id) + + return render_template( + "admin/branche_form.html", + mode="edit", + branche=branche, + themen=themen, + selected_thema_ids=selected_thema_ids, + ) + + +@app.route("/admin/branchen//delete", methods=["POST"]) +@admin_required +def admin_branche_delete(branche_id): + delete_branche(branche_id) + flash("Branche wurde gelöscht.", "success") + return redirect(url_for("admin_branchen")) + 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 55f3f87..9d16abd 100644 --- a/db.py +++ b/db.py @@ -242,7 +242,10 @@ def get_ansprechpartner_ids_for_thema(thema_id): return [row["ansprechpartner_id"] for row in rows] -def create_thema(kurztitel, titel, infotext, zusatztext, ansprechpartner_ids): +def create_thema(kurztitel, titel, infotext, zusatztext, ansprechpartner_ids, branche_ids=None): + if branche_ids is None: + branche_ids = [] + row = execute_returning( """ INSERT INTO thema (kurztitel, titel, infotext, zusatztext) @@ -262,10 +265,22 @@ def create_thema(kurztitel, titel, infotext, zusatztext, ansprechpartner_ids): (thema_id, ap_id), ) + for branche_id in branche_ids: + execute( + """ + INSERT INTO branchenthemen (branche_id, thema_id) + VALUES (%s, %s) + """, + (branche_id, thema_id), + ) + return thema_id -def update_thema(thema_id, kurztitel, titel, infotext, zusatztext, ansprechpartner_ids): +def update_thema(thema_id, kurztitel, titel, infotext, zusatztext, ansprechpartner_ids, branche_ids=None): + if branche_ids is None: + branche_ids = [] + execute( """ UPDATE thema @@ -295,6 +310,22 @@ def update_thema(thema_id, kurztitel, titel, infotext, zusatztext, ansprechpartn (thema_id, ap_id), ) + execute( + """ + DELETE FROM branchenthemen + WHERE thema_id = %s + """, + (thema_id,), + ) + + for branche_id in branche_ids: + execute( + """ + INSERT INTO branchenthemen (branche_id, thema_id) + VALUES (%s, %s) + """, + (branche_id, thema_id), + ) def delete_thema(thema_id): execute("DELETE FROM themaansprechpartner WHERE thema_id = %s", (thema_id,)) @@ -357,18 +388,24 @@ def save_assessment_answer(assessment_id, thema_id, frage_id, antwort): ) -def get_assessment_result_rows(assessment_id): +def get_assessment_result_rows(assessment_id, branche_id): return fetch_all( """ - SELECT t.kurztitel, COUNT(*) FILTER (WHERE aa.antwort = TRUE) AS ja_anzahl + SELECT + t.id, + t.kurztitel, + COUNT(*) FILTER (WHERE aa.antwort = TRUE) AS ja_anzahl FROM thema t + JOIN branchenthemen bt + ON bt.thema_id = t.id LEFT JOIN assessmentanswer aa ON aa.thema_id = t.id AND aa.assessmentid = %s + WHERE bt.branche_id = %s GROUP BY t.id, t.kurztitel ORDER BY t.id """, - (assessment_id,), + (assessment_id, branche_id), ) ############ # Ansprechpartner @@ -492,4 +529,186 @@ def delete_user(user_id, current_user_id=None): WHERE id = %s """, (user_id,), + ) + +############### +# Branchen +############### + +def get_all_branchen(): + return fetch_all( + """ + SELECT id, branchenname + FROM branche + ORDER BY branchenname ASC + """ + ) + + +def get_branche_by_id(branche_id): + return fetch_one( + """ + SELECT id, branchenname + FROM branche + WHERE id = %s + """, + (branche_id,), + ) + + +def get_thema_ids_for_branche(branche_id): + rows = fetch_all( + """ + SELECT thema_id + FROM branchenthemen + WHERE branche_id = %s + """, + (branche_id,), + ) + return [row["thema_id"] for row in rows] + + +def create_branche(branchenname, thema_ids): + row = execute_returning( + """ + INSERT INTO branche (branchenname) + VALUES (%s) + RETURNING id + """, + (branchenname,), + ) + branche_id = row["id"] + + for thema_id in thema_ids: + execute( + """ + INSERT INTO branchenthemen (branche_id, thema_id) + VALUES (%s, %s) + """, + (branche_id, thema_id), + ) + + return branche_id + + +def update_branche(branche_id, branchenname, thema_ids): + execute( + """ + UPDATE branche + SET branchenname = %s + WHERE id = %s + """, + (branchenname, branche_id), + ) + + execute( + """ + DELETE FROM branchenthemen + WHERE branche_id = %s + """, + (branche_id,), + ) + + for thema_id in thema_ids: + execute( + """ + INSERT INTO branchenthemen (branche_id, thema_id) + VALUES (%s, %s) + """, + (branche_id, thema_id), + ) + + +def delete_branche(branche_id): + execute( + """ + DELETE FROM branchenthemen + WHERE branche_id = %s + """, + (branche_id,), + ) + execute( + """ + DELETE FROM branche + WHERE id = %s + """, + (branche_id,), + ) + +def get_branche_ids_for_thema(thema_id): + rows = fetch_all( + """ + SELECT branche_id + FROM branchenthemen + WHERE thema_id = %s + """, + (thema_id,), + ) + return [row["branche_id"] for row in rows] + + +def update_thema_branchen(thema_id, branche_ids): + execute( + """ + DELETE FROM branchenthemen + WHERE thema_id = %s + """, + (thema_id,), + ) + + for branche_id in branche_ids: + execute( + """ + INSERT INTO branchenthemen (branche_id, thema_id) + VALUES (%s, %s) + """, + (branche_id, thema_id), + ) + +def get_all_branchen(): + return fetch_all( + """ + SELECT id, branchenname + FROM branche + ORDER BY branchenname ASC + """ + ) + +def get_themen_for_branche(branche_id): + return fetch_all( + """ + SELECT t.id, t.kurztitel, t.titel, t.infotext, t.zusatztext + FROM thema t + JOIN branchenthemen bt ON bt.thema_id = t.id + WHERE bt.branche_id = %s + ORDER BY t.id + """, + (branche_id,), + ) + +def get_next_thema_id_for_branche(current_thema_id, branche_id): + row = fetch_one( + """ + SELECT t.id + FROM thema t + JOIN branchenthemen bt ON bt.thema_id = t.id + WHERE bt.branche_id = %s + AND t.id > %s + ORDER BY t.id + LIMIT 1 + """, + (branche_id, current_thema_id), + ) + return row["id"] if row else None + +def get_thema_for_branche(thema_id, branche_id): + return fetch_one( + """ + SELECT t.id, t.kurztitel, t.titel, t.infotext, t.zusatztext + FROM thema t + JOIN branchenthemen bt ON bt.thema_id = t.id + WHERE t.id = %s + AND bt.branche_id = %s + """, + (thema_id, branche_id), ) \ No newline at end of file diff --git a/docker-compose.example.yaml b/docker-compose.example.yaml index 0c3ca92..8f71e95 100644 --- a/docker-compose.example.yaml +++ b/docker-compose.example.yaml @@ -13,11 +13,11 @@ services: DB_USER: UnternehmenUser DB_PASSWORD: UnternehmenPWD DB_PORT: 5432 - SMTP_SERVER: smtp.example.com - SMTP_PORT: 587 + SMTP_SERVER: mail.kolb.cc + SMTP_PORT: 25 SMTP_USERNAME: smtp-user SMTP_PASSWORD: smtp-password - MAIL_SENDER: noreply@dasunternehmen.com + MAIL_SENDER: admin@kolb.cc depends_on: - db diff --git a/static/css/style.css b/static/css/style.css index e24c52e..c15e294 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -322,3 +322,12 @@ textarea { } /* contacts verbergen bei self assessment */ +select { + width: 100%; + margin: 8px 0 18px; + padding: 14px 16px; + border-radius: 16px; + border: 1px solid var(--border); + background: #fff; + color: var(--text); +} \ No newline at end of file diff --git a/templates/admin/branche_form.html b/templates/admin/branche_form.html new file mode 100644 index 0000000..bcf6e31 --- /dev/null +++ b/templates/admin/branche_form.html @@ -0,0 +1,42 @@ +{% extends "base.html" %} + +{% block content %} +
+

+ {% if mode == "edit" %} + Branche bearbeiten + {% else %} + Neue Branche erstellen + {% endif %} +

+ +
+
+ + +
+ +
+ +
+ {% for thema in themen %} + + {% endfor %} +
+
+ +
+ + Abbrechen +
+
+
+{% endblock %} \ No newline at end of file diff --git a/templates/admin/branchen_list.html b/templates/admin/branchen_list.html new file mode 100644 index 0000000..e141717 --- /dev/null +++ b/templates/admin/branchen_list.html @@ -0,0 +1,44 @@ +{% extends "base.html" %} + +{% block content %} +
+ + + {% if branchen %} + + + + + + + + + + {% for branche in branchen %} + + + + + + {% endfor %} + +
IDBranchennameAktionen
{{ branche.id }}{{ branche.branchenname }} + + Bearbeiten + + +
+ +
+
+ {% else %} +

Keine Branchen vorhanden.

+ {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/templates/admin/index.html b/templates/admin/index.html index 1f4f08d..8844241 100644 --- a/templates/admin/index.html +++ b/templates/admin/index.html @@ -26,7 +26,11 @@

Ansprechpartner

Ansprechpartner anzeigen und verwalten.

- + + +

Branchen

+

Branchen anlegen, bearbeiten und Themen zuordnen.

+
{% endblock %} \ No newline at end of file diff --git a/templates/admin/thema_form.html b/templates/admin/thema_form.html index a430385..aadbbb4 100644 --- a/templates/admin/thema_form.html +++ b/templates/admin/thema_form.html @@ -48,6 +48,23 @@ +
+ +
+ {% for branche in branchen %} + + {% endfor %} +
+
+
Abbrechen diff --git a/templates/dashboard.html b/templates/dashboard.html index db2e7d4..5dc30fb 100644 --- a/templates/dashboard.html +++ b/templates/dashboard.html @@ -8,14 +8,20 @@ Wir helfen Ihnen mit einer kurzen Selbsteinschätzung sich einen Überblick über ihre Unternehmenssituation zu verschaffen.

- {% if themen and themen|length > 0 %} -
- - Self Assessment starten - +
+
+ +
- {% else %} -

Aktuell sind keine Themen verfügbar.

- {% endif %} + +
+ +
+
{% endblock %} \ No newline at end of file diff --git a/templates/topic.html b/templates/topic.html index ff2a2b3..c8b5840 100644 --- a/templates/topic.html +++ b/templates/topic.html @@ -10,6 +10,7 @@
+ {% for frage in fragen %}