diff --git a/app/main.py b/app/main.py index d741adf..f6097e0 100644 --- a/app/main.py +++ b/app/main.py @@ -1461,6 +1461,34 @@ def admin_patches_page(request: Request): return template.render(user=user, patches=patches) +@app.get("/admin/statistics", response_class=HTMLResponse) +def admin_statistics_page(request: Request): + user = require_admin(request) + if not user: + return HTMLResponse("

Nicht erlaubt

", status_code=403) + with get_db_connection() as connection: + with connection.cursor() as cursor: + queries = { + "users": "SELECT COUNT(*) FROM users", + "events": "SELECT COUNT(*) FROM concerts", + "upcoming": "SELECT COUNT(*) FROM concerts WHERE start_datetime >= NOW()", + "comments": "SELECT COUNT(*) FROM concert_comments", + "attendance": "SELECT COUNT(*) FROM concert_attendance", + "friendships": "SELECT COUNT(*) FROM friendships WHERE status = 'accepted'", + "messages": "SELECT COUNT(*) FROM direct_messages", + } + stats = {} + for key, query in queries.items(): + try: + cursor.execute(query) + stats[key] = cursor.fetchone()[0] + except Exception: + connection.rollback() + stats[key] = 0 + template = templates.get_template("admin_statistics.html") + return template.render(user=user, stats=stats) + + @app.post("/admin/invites", response_class=HTMLResponse) def create_invite(request: Request): user = require_admin(request) diff --git a/app/static/css/style.css b/app/static/css/style.css index 78e9c4d..5e4bee3 100644 --- a/app/static/css/style.css +++ b/app/static/css/style.css @@ -463,6 +463,41 @@ h1, .page-title h1 { } .empty-state { background: rgba(16,16,16,.86); border-style: solid; } +.stats-grid { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:14px; } +.stat-card { display:flex; flex-direction:column; gap:5px; padding:20px; background:linear-gradient(145deg,#171212,#0d0d0d); border:1px solid var(--border); border-top:3px solid var(--accent); border-radius:12px; } +.stat-card strong { color:#fca5a5; font-size:2.2rem; } +.stat-card span { color:var(--muted); } + +/* Konzertdetailansicht: konsistente Spalten statt Browser-Standardlayout */ +.detail-topbar { display:flex; align-items:center; justify-content:space-between; gap:16px; margin-bottom:24px; } +.back-link { color:#fca5a5; font-weight:700; } +.concert-detail { max-width:900px; margin:0 auto; padding:26px; background:rgba(12,12,12,.92); border:1px solid var(--border); border-radius:16px; box-shadow:0 14px 32px rgba(0,0,0,.4); } +.flyer-container { display:flex; flex-direction:column; align-items:center; margin-bottom:26px; } +.concert-flyer { display:block; width:auto; max-width:100%; max-height:560px; object-fit:contain; border-radius:10px; } +.concert-content h1 { margin:10px 0 20px; font-size:clamp(1.8rem,4vw,2.8rem); text-align:center; } +.concert-info { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:12px; margin:20px 0; } +.info-item { display:flex; gap:10px; align-items:flex-start; padding:14px; background:rgba(5,5,5,.7); border:1px solid var(--border); border-radius:10px; } +.info-icon { font-size:1.4rem; } +.description, .attendance-section, .comments-section { margin-top:26px; padding-top:22px; border-top:1px solid var(--border); } +.attendance-actions { display:flex; flex-wrap:wrap; gap:9px; } +.attendance-actions form { margin:0; } +.attendance-option { padding:10px 14px; border:1px solid #7f1d1d; border-radius:9px; background:#241010; color:var(--text); cursor:pointer; } +.attendance-option.is-selected { background:#991b1b; border-color:#ef4444; } +.attendance-list { margin-top:16px; padding:12px 14px; background:#0b0b0b; border:1px solid var(--border); border-radius:10px; } +.attendance-list summary { cursor:pointer; color:#fca5a5; font-weight:700; } +.attendance-groups { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:18px; margin-top:16px; } +.attendance-groups ul { margin:0; padding-left:20px; } +.comment-form { max-width:none; } +.comment-form textarea { width:100%; min-height:100px; resize:vertical; } +.comment-list { display:grid; gap:12px; margin-top:20px; } +.comment { padding:14px; background:rgba(5,5,5,.7); border:1px solid var(--border); border-radius:10px; } +.comment-meta { display:flex; justify-content:space-between; gap:12px; color:var(--muted); font-size:.9rem; } +.comment-meta strong { color:var(--text); } +.comment p { margin:10px 0 0; white-space:pre-wrap; overflow-wrap:anywhere; } +.ticket-button-container, .detail-actions { display:flex; flex-wrap:wrap; justify-content:center; gap:10px; margin-top:22px; } +.ticket-button, .concert-action { display:inline-flex; padding:11px 16px; border:1px solid #b91c1c; border-radius:9px; background:#7f1d1d; color:#fff; font-weight:700; } +.concert-action-delete { background:#450a0a; cursor:pointer; } +@media (max-width:600px) { .detail-topbar { align-items:flex-start; } .concert-detail { padding:16px; } .comment-meta { flex-direction:column; gap:4px; } } @media (max-width: 600px) { .logo { max-width: 150px; } diff --git a/app/templates/_admin_nav.html b/app/templates/_admin_nav.html index 7104a43..778aae8 100644 --- a/app/templates/_admin_nav.html +++ b/app/templates/_admin_nav.html @@ -2,4 +2,5 @@ ๐Ÿ‘ฅ Benutzer ๐Ÿ“ Veranstaltungsorte ๐Ÿงต Patch-Bilder + ๐Ÿ“Š Nutzungsstatistik diff --git a/app/templates/admin_statistics.html b/app/templates/admin_statistics.html new file mode 100644 index 0000000..5f9a2a8 --- /dev/null +++ b/app/templates/admin_statistics.html @@ -0,0 +1,6 @@ +Nutzungsstatistik ยท MetalCircle +
{% include '_user_menu.html' %}
+

๐Ÿ“Š Nutzungsstatistik

Aktuelle Gesamtzahlen der Plattform.

{% set admin_section = 'statistics' %}{% include '_admin_nav.html' %} +
+{% for key, label in [('users','Benutzer'),('events','Veranstaltungen'),('upcoming','Bevorstehende Veranstaltungen'),('comments','Kommentare'),('attendance','Teilnahmen'),('friendships','Freundschaften'),('messages','Nachrichten')] %}
{{ stats[key] }}{{ label }}
{% endfor %} +