Reports der Assessments

This commit is contained in:
Bkolb 2026-04-10 18:20:37 +02:00
parent 226d3bf69c
commit 4e278ef7df
4 changed files with 238 additions and 0 deletions

View File

@ -2127,3 +2127,102 @@ def pwdchange():
success_message=success_message, success_message=success_message,
**get_current_user() **get_current_user()
) )
@app.route("/reporting/assessments")
@user_admin_required
def reporting_assessments():
current_mandant_id = session.get("mandant_id")
mandant_level = session.get("mandant_level")
conn = get_connection()
cur = conn.cursor()
# Mandant
cur.execute("SELECT name, level FROM mandant WHERE id = %s", (current_mandant_id,))
mandant_row = cur.fetchone()
mandant_name = mandant_row[0]
mandant_level_value = mandant_row[1]
# User
cur.execute("""
SELECT id, name, email, status
FROM app_user
WHERE mandant_id = %s
""", (current_mandant_id,))
users = fetchall_dict(cur)
# Kurse
cur.execute("""
SELECT id, code, title, min_level
FROM course
WHERE is_active = TRUE
""")
all_courses = fetchall_dict(cur)
available_courses = [
c for c in all_courses
if is_course_allowed_for_level(c["code"], mandant_level)
]
# Assessments (nur bestanden)
cur.execute("""
SELECT user_id, course_id, score, created_at
FROM user_assessment
WHERE passed = TRUE
""")
assessments = fetchall_dict(cur)
cur.close()
conn.close()
passed_map = {(a["user_id"], a["course_id"]): a for a in assessments}
detail_rows = []
completed_count = 0
open_count = 0
for u in users:
for c in available_courses:
a = passed_map.get((u["id"], c["id"]))
done = a is not None
if done:
completed_count += 1
else:
open_count += 1
detail_rows.append({
"user_name": u["name"],
"user_email": u["email"],
"course_code": c["code"],
"course_title": c["title"],
"done": done,
"done_label": "erledigt" if done else "offen",
"completed_at": a["created_at"] if done else None,
"score": a["score"] if done else None,
})
total_expected = len(users) * len(available_courses)
progress_percent = int((completed_count / total_expected) * 100) if total_expected else 0
users = sorted(users, key=lambda u: (u["name"] or "", u["email"] or ""))
available_courses = sorted(available_courses, key=lambda c: c["code"])
user_list = sorted(
[{"id": u["id"], "name": u["name"]} for u in users],
key=lambda x: x["name"] or ""
)
return render_template(
"reporting_assessments.html",
mandant_name=mandant_name,
mandant_level_label=format_level(mandant_level_value),
available_courses=available_courses,
user_list=user_list,
detail_rows=detail_rows,
dashboard_completed=completed_count,
dashboard_open=open_count,
dashboard_total_expected=total_expected,
dashboard_progress_percent=progress_percent,
**get_current_user()
)

View File

@ -42,6 +42,7 @@
{% endif %} {% endif %}
{% if is_user_admin %} {% if is_user_admin %}
<a href="/useradmin/mandant">Useradministration</a> <a href="/useradmin/mandant">Useradministration</a>
<a href="/reporting/assessments">Reporting</a>
{% endif %} {% endif %}
{% if is_contentmanager %} {% if is_contentmanager %}
<a href="/dokumente">Dokumente</a> <a href="/dokumente">Dokumente</a>

View File

@ -0,0 +1,112 @@
{% extends "base.html" %}
{% block content %}
<h1>Reporting</h1>
<p>Mandant: <strong>{{ mandant_name }}</strong> ({{ mandant_level_label }})</p>
<!-- Dashboard -->
<div class="dashboard-grid">
<div class="dashboard-card">
<div class="dashboard-label">Erledigt</div>
<div class="dashboard-value">{{ dashboard_completed }}</div>
</div>
<div class="dashboard-card">
<div class="dashboard-label">Offen</div>
<div class="dashboard-value">{{ dashboard_open }}</div>
</div>
</div>
<!-- Progress -->
<div class="progress-bar" style="margin:20px 0;">
<div class="progress-fill" style="width: {{ dashboard_progress_percent }}%;"></div>
</div>
<!-- Filter -->
<div class="report-filters">
<!-- Kurs -->
<select id="filter-course">
<option value="">Alle Kurse</option>
{% for c in available_courses %}
<option value="{{ c.code }}">{{ c.code }} {{ c.title }}</option>
{% endfor %}
</select>
<!-- Status -->
<select id="filter-status">
<option value="">Alle</option>
<option value="erledigt">erledigt</option>
<option value="offen">offen</option>
</select>
<!-- NEU: User -->
<select id="filter-user">
<option value="">Alle User</option>
{% for u in user_list %}
<option value="{{ u.name }}">{{ u.name }}</option>
{% endfor %}
</select>
</div>
<!-- Tabelle -->
<table id="report-table" class="mandanten-table">
<thead>
<tr>
<th>User</th>
<th>Kurs</th>
<th>Status</th>
<th>Datum</th>
</tr>
</thead>
<tbody>
{% for r in detail_rows %}
<tr
data-course="{{ r.course_code }}"
data-status="{{ r.done_label }}"
data-user="{{ r.user_name }}"
>
<td>{{ r.user_name }}</td>
<td>{{ r.course_code }} - {{ r.course_title }}</td>
<td>{{ r.done_label }}</td>
<td>
{% if r.completed_at %}
{{ r.completed_at.strftime("%d.%m.%Y") }}
{% else %}
-
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<script>
const rows = document.querySelectorAll("#report-table tbody tr");
const courseFilter = document.getElementById("filter-course");
const statusFilter = document.getElementById("filter-status");
const userFilter = document.getElementById("filter-user");
courseFilter.onchange = applyFilters;
statusFilter.onchange = applyFilters;
userFilter.onchange = applyFilters;
function applyFilters() {
const c = courseFilter.value;
const s = statusFilter.value;
const u = userFilter.value;
rows.forEach(r => {
const matchC = !c || r.dataset.course === c;
const matchS = !s || r.dataset.status === s;
const matchU = !u || r.dataset.user === u;
r.style.display = (matchC && matchS && matchU) ? "" : "none";
});
}
</script>
{% endblock %}

View File

@ -1270,3 +1270,29 @@ button {
.profile-value { .profile-value {
font-weight: 500; font-weight: 500;
} }
.dashboard-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
.dashboard-card {
padding: 16px;
background: #f8fbff;
border: 1px solid #dce3ea;
border-radius: 12px;
}
.dashboard-value {
font-size: 28px;
font-weight: bold;
}
.report-filters {
margin: 20px 0;
display: flex;
gap: 12px;
flex-wrap: wrap;
}