Kursliste und Kurs-Seiten
This commit is contained in:
parent
a0057fca32
commit
7a4a2b5fe6
@ -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
|
||||
|
||||
|
||||
|
||||
@ -941,4 +979,76 @@ def useradmin_user_edit(user_id):
|
||||
form_error=form_error,
|
||||
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()
|
||||
)
|
||||
@ -12,8 +12,8 @@ class Config:
|
||||
|
||||
LOG_DIR = os.getenv("LOG_DIR", "./logs")
|
||||
|
||||
COUNTRY_VAT_LABELS = {
|
||||
"DE": "inkl. 19% USt",
|
||||
"AT": "inkl. 20% USt",
|
||||
"CH": "exkl. MwSt",
|
||||
}
|
||||
COUNTRY_VAT_LABELS = {
|
||||
"DE": "inkl. 19% USt",
|
||||
"AT": "inkl. 20% USt",
|
||||
"CH": "exkl. MwSt",
|
||||
}
|
||||
@ -15,4 +15,25 @@ def is_video_allowed_for_level(filename: str, mandant_level: int | None) -> bool
|
||||
if mandant_level == 3:
|
||||
return first_char == "A"
|
||||
|
||||
return False
|
||||
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
|
||||
@ -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>
|
||||
|
||||
16
app/flask-postgres/app/templates/course/A1_page1.html
Normal file
16
app/flask-postgres/app/templates/course/A1_page1.html
Normal 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 %}
|
||||
16
app/flask-postgres/app/templates/course/A1_page2.html
Normal file
16
app/flask-postgres/app/templates/course/A1_page2.html
Normal 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 %}
|
||||
16
app/flask-postgres/app/templates/course/B1_page1.html
Normal file
16
app/flask-postgres/app/templates/course/B1_page1.html
Normal 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 %}
|
||||
16
app/flask-postgres/app/templates/course/B1_page2.html
Normal file
16
app/flask-postgres/app/templates/course/B1_page2.html
Normal 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 %}
|
||||
22
app/flask-postgres/app/templates/course_list.html
Normal file
22
app/flask-postgres/app/templates/course_list.html
Normal 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 %}
|
||||
17
app/flask-postgres/app/templates/course_video.html
Normal file
17
app/flask-postgres/app/templates/course_video.html
Normal 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 %}
|
||||
@ -694,4 +694,28 @@ button {
|
||||
outline: none;
|
||||
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;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user