Kursliste und Kurs-Seiten

This commit is contained in:
Bkolb 2026-04-03 15:06:58 +02:00
parent a0057fca32
commit 7a4a2b5fe6
11 changed files with 270 additions and 6 deletions

View File

@ -210,7 +210,45 @@ def register_visit(route_name: str) -> int:
conn.close()
return count
def get_available_courses_for_user():
mandant_level = session.get("mandant_level")
if mandant_level is None:
current_mandant_id = session.get("mandant_id")
conn = get_connection()
cur = conn.cursor()
cur.execute("""
SELECT level
FROM mandant
WHERE id = %s
""", (current_mandant_id,))
row = cur.fetchone()
cur.close()
conn.close()
mandant_level = row[0] if row else 0
session["mandant_level"] = mandant_level
conn = get_connection()
cur = conn.cursor()
cur.execute("""
SELECT id, code, title, description, video_file, min_level, sort_order
FROM course
WHERE is_active = TRUE
AND min_level <= %s
ORDER BY sort_order, code
""", (mandant_level,))
courses = fetchall_dict(cur)
cur.close()
conn.close()
return courses
@ -942,3 +980,75 @@ def useradmin_user_edit(user_id):
success_message=success_message,
**get_current_user()
)
@app.route("/courses")
@login_required
def course_list():
courses = get_available_courses_for_user()
return render_template(
"course_list.html",
page_title="Kurse",
active_page="courses",
courses=courses,
**get_current_user()
)
@app.route("/course/<int:course_id>")
@login_required
def course_start(course_id):
conn = get_connection()
cur = conn.cursor()
cur.execute("""
SELECT *
FROM course
WHERE id = %s
""", (course_id,))
course = fetchone_dict(cur)
cur.close()
conn.close()
return render_template(
"course_video.html",
course=course,
**get_current_user()
)
@app.route("/course/<int:course_id>/page/<int:page_number>")
@login_required
def course_page(course_id, page_number):
conn = get_connection()
cur = conn.cursor()
cur.execute("""
SELECT *
FROM course_page
WHERE course_id = %s
AND page_number = %s
""", (course_id, page_number))
page = fetchone_dict(cur)
if not page:
abort(404)
# Fortschritt speichern
cur.execute("""
INSERT INTO user_course_progress (user_id, course_id, last_page)
VALUES (%s, %s, %s)
ON CONFLICT (user_id, course_id)
DO UPDATE SET last_page = EXCLUDED.last_page
""", (session["user_id"], course_id, page_number))
conn.commit()
cur.close()
conn.close()
return render_template(
page["template_name"],
course_id=course_id,
page_number=page_number,
**get_current_user()
)

View File

@ -16,3 +16,24 @@ def is_video_allowed_for_level(filename: str, mandant_level: int | None) -> bool
return first_char == "A"
return False
def get_available_courses_for_user():
level = session.get("mandant_level", 0)
conn = get_connection()
cur = conn.cursor()
cur.execute("""
SELECT id, code, title, description, video_file
FROM course
WHERE is_active = TRUE
AND min_level <= %s
ORDER BY sort_order, code
""", (level,))
courses = fetchall_dict(cur)
cur.close()
conn.close()
return courses

View File

@ -23,6 +23,12 @@
<a href="/preise" class="{% if active_page == 'preise' %}active{% endif %}">Preise</a>
<a href="/allgemein" class="{% if active_page == 'allgemein' %}active{% endif %}">Allgemein</a>
{% if is_logged_in %}
<a href="/courses" class="{% if active_page == 'courses' %}active{% endif %}">
Kurse
</a>
{% endif %}
{% if is_logged_in %}
<div class="user-menu">
<button class="user-menu-toggle" type="button">{{ user_name }} ▾</button>

View File

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block content %}
<h2>AI Governance Basics</h2>
<p>
KI-Systeme müssen nachvollziehbar, sicher und kontrollierbar sein...
</p>
<div class="course-nav">
<a href="/course/{{ course_id }}" class="btn-secondary">Zurück</a>
<a href="/course/{{ course_id }}/page/2" class="btn-primary">Weiter</a>
</div>
{% endblock %}

View File

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block content %}
<h2>AI Governance Basics</h2>
<p>
KI-Systeme müssen einen hübschen Avatar haben sein...
</p>
<div class="course-nav">
<a href="/course/{{ course_id }}/page/1" class="btn-secondary">Zurück</a>
<a href="/courses" class="btn-primary">Fertig</a>
</div>
{% endblock %}

View File

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block content %}
<h2>B1 AI Privacy Basics</h2>
<p>
KI-Systeme müssen die Privatsphäre schützen sein...
</p>
<div class="course-nav">
<a href="/course/{{ course_id }}" class="btn-secondary">Zurück</a>
<a href="/course/{{ course_id }}/page/2" class="btn-primary">Weiter</a>
</div>
{% endblock %}

View File

@ -0,0 +1,16 @@
{% extends "base.html" %}
{% block content %}
<h2>B1 AI Privacy Basics</h2>
<p>
KI-Systeme müssen die Privatsphäre schützen sein...
</p>
<div class="course-nav">
<a href="/course/{{ course_id }}/page/1" class="btn-secondary">Zurück</a>
<a href="/courses" class="btn-primary">Fertig</a>
</div>
{% endblock %}

View File

@ -0,0 +1,22 @@
{% extends "base.html" %}
{% block content %}
<div class="page-header">
<h1>Lerneinheiten</h1>
</div>
<div class="course-grid">
{% for course in courses %}
<div class="course-card">
<h3>{{ course.title }}</h3>
<p>{{ course.description }}</p>
<a href="/course/{{ course.id }}" class="btn-primary">
Starten
</a>
</div>
{% endfor %}
</div>
{% endblock %}

View File

@ -0,0 +1,17 @@
{% extends "base.html" %}
{% block content %}
<h1>{{ course.title }}</h1>
<video controls width="100%">
<source src="/videos/{{ course.video_file }}" type="video/mp4">
</video>
<div class="course-actions">
<a href="/course/{{ course.id }}/page/1" class="btn-primary">
Weiter
</a>
</div>
{% endblock %}

View File

@ -695,3 +695,27 @@ button {
border-color: #8fb6e2;
box-shadow: 0 0 0 3px rgba(143, 182, 226, 0.2);
}
/* =========================
Kurse
========================= */
.course-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
.course-card {
background: #fff;
border-radius: 14px;
padding: 20px;
box-shadow: 0 10px 20px rgba(0,0,0,0.08);
}
.course-nav {
display: flex;
justify-content: space-between;
margin-top: 30px;
}