From 310e0db08009f59e8797b65c5a0da67732d97953 Mon Sep 17 00:00:00 2001 From: kai Date: Tue, 25 Aug 2026 12:48:45 +0200 Subject: [PATCH] =?UTF-8?q?bearbeiten=20Button=20f=C3=BCr=20admin=20angepa?= =?UTF-8?q?sst,=20zusage=20funktion=20auch=20hinzugef=C3=BCgt?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/main.py | 77 +++++++++++++++++++++ app/static/style.css | 108 +++++++++++++++++++++++++----- app/templates/concert_detail.html | 69 ++++++++++++++++--- db/init/01_initial.sql | 13 ++++ db/migrations/03_community.sql | 13 ++++ 5 files changed, 253 insertions(+), 27 deletions(-) diff --git a/app/main.py b/app/main.py index e27463a..0c27d48 100644 --- a/app/main.py +++ b/app/main.py @@ -108,6 +108,17 @@ def ensure_schema(): created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ) """, + """ + CREATE TABLE IF NOT EXISTS concert_attendance ( + concert_id INTEGER NOT NULL REFERENCES concerts(id) ON DELETE CASCADE, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + status VARCHAR(20) NOT NULL CHECK ( + status IN ('attending', 'maybe', 'ticket_search') + ), + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (concert_id, user_id) + ) + """, ] with get_db_connection() as connection: @@ -1090,6 +1101,22 @@ def concert_detail(request: Request, concert_id: int): ) photo_rows = cursor.fetchall() + cursor.execute( + """ + SELECT + concert_attendance.user_id, + concert_attendance.status, + users.display_name, + users.username + FROM concert_attendance + JOIN users ON users.id = concert_attendance.user_id + WHERE concert_attendance.concert_id = %s + ORDER BY users.display_name NULLS LAST, users.username + """, + (concert_id,), + ) + attendance_rows = cursor.fetchall() + comments = [ { "id": row[0], @@ -1110,6 +1137,20 @@ def concert_detail(request: Request, concert_id: int): for row in photo_rows ] + attendance = [ + { + "user_id": row[0], + "status": row[1], + "name": row[2] or row[3], + } + for row in attendance_rows + ] + attending_users = [item for item in attendance if item["status"] == "attending"] + current_attendance = next( + (item["status"] for item in attendance if item["user_id"] == user["id"]), + None, + ) + template = templates.get_template("concert_detail.html") return template.render( user=user, @@ -1118,6 +1159,9 @@ def concert_detail(request: Request, concert_id: int): photos=photos, can_edit=can_edit_concert(user, concert), can_delete=can_delete_concert(user, concert), + current_attendance=current_attendance, + attending_users=attending_users, + attending_count=len(attending_users), ) @@ -1404,6 +1448,39 @@ def delete_concert(request: Request, concert_id: int): return RedirectResponse("/", status_code=303) +@app.post("/concerts/{concert_id}/attendance") +def set_attendance( + request: Request, + concert_id: int, + status: str = Form(...), +): + user = get_current_user(request) + + if not user: + return login_redirect(f"/concerts/{concert_id}") + if status not in {"attending", "maybe", "ticket_search"}: + return HTMLResponse("

Ungültige Auswahl

", status_code=400) + if not load_concert(concert_id): + return HTMLResponse("

Konzert nicht gefunden

", status_code=404) + + with get_db_connection() as connection: + with connection.cursor() as cursor: + cursor.execute( + """ + INSERT INTO concert_attendance (concert_id, user_id, status) + VALUES (%s, %s, %s) + ON CONFLICT (concert_id, user_id) + DO UPDATE SET + status = EXCLUDED.status, + updated_at = CURRENT_TIMESTAMP + """, + (concert_id, user["id"], status), + ) + connection.commit() + + return RedirectResponse(f"/concerts/{concert_id}#attendance", status_code=303) + + @app.post("/concerts/{concert_id}/comments") def add_comment( request: Request, diff --git a/app/static/style.css b/app/static/style.css index 2a3ba45..803fbb9 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -30,16 +30,6 @@ a { color: #ffffff; } -.edit-button { - display: inline-block; - padding: 10px 14px; - background: #238636; - color: #ffffff; - border-radius: 9px; - text-decoration: none; - font-weight: bold; -} - .detail-actions { margin-top: 30px; display: flex; @@ -51,26 +41,44 @@ a { margin: 0; } -.delete-concert-button { - display: inline-block; - padding: 10px 14px; - background: #b42318; +.concert-action { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 42px; + padding: 10px 16px; color: #ffffff; border: 0; - border-radius: 9px; + border-radius: 10px; font: inherit; font-weight: bold; + line-height: 1; + text-decoration: none; cursor: pointer; + transition: transform 0.15s ease, background 0.15s ease, box-shadow 0.15s ease; } -.delete-concert-button:hover { - background: #dc2626; +.concert-action:hover { + transform: translateY(-1px); + box-shadow: 0 5px 16px rgba(0, 0, 0, 0.25); } -.edit-button:hover { +.concert-action-edit { + background: #238636; +} + +.concert-action-edit:hover { background: #2ea043; } +.concert-action-delete { + background: #b42318; +} + +.concert-action-delete:hover { + background: #dc2626; +} + .concert-detail { background: #161b22; border: 1px solid #30363d; @@ -180,6 +188,70 @@ a { color: #8b949e; } +.attendance-section { + margin-top: 35px; + padding: 25px; + background: #0d1117; + border: 1px solid #30363d; + border-radius: 12px; +} + +.attendance-section h2 { + margin-top: 0; +} + +.attendance-section > p { + color: #8b949e; +} + +.attendance-actions { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 18px; +} + +.attendance-actions form { + margin: 0; +} + +.attendance-option { + padding: 10px 14px; + background: #161b22; + border: 1px solid #3d4856; + border-radius: 9px; + color: #e6edf3; + font: inherit; + font-weight: bold; + cursor: pointer; +} + +.attendance-option:hover, +.attendance-option.is-selected { + border-color: #58a6ff; + background: #1b3554; +} + +.attendance-option.is-selected { + color: #ffffff; +} + +.attendance-list { + margin-top: 20px; + color: #b8c1cc; +} + +.attendance-list summary { + color: #58a6ff; + font-weight: bold; + cursor: pointer; +} + +.attendance-list ul { + margin: 12px 0 0; + padding-left: 22px; +} + @media (max-width: 600px) { .container { diff --git a/app/templates/concert_detail.html b/app/templates/concert_detail.html index b1fa240..e027279 100644 --- a/app/templates/concert_detail.html +++ b/app/templates/concert_detail.html @@ -173,8 +173,8 @@ {% if can_edit %} -
- +
+ ✏️ Konzert bearbeiten @@ -185,7 +185,7 @@ action="/concerts/{{ concert.id }}/delete" onsubmit="return confirm('Dieses Konzert wirklich löschen?');" > - @@ -196,16 +196,67 @@ {% endif %} -
+

- 🐧 Community + 🐧 Wer ist dabei?

-

- Wer geht hin, wer ist interessiert? - Kommentare und Teilnehmer folgen hier. -

+

Wähle, wie es bei dir aussieht. Deine Auswahl kann jederzeit geändert werden.

+ +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + {{ attending_count }} Zusage{% if attending_count != 1 %}n{% endif %} anzeigen + + + {% if attending_users %} + +
    + {% for attendee in attending_users %} +
  • {{ attendee.name }}
  • + {% endfor %} +
+ + {% else %} + +

Noch niemand hat zugesagt.

+ + {% endif %} +
diff --git a/db/init/01_initial.sql b/db/init/01_initial.sql index 2b8703e..9d47f82 100644 --- a/db/init/01_initial.sql +++ b/db/init/01_initial.sql @@ -69,6 +69,16 @@ CREATE TABLE concert_photos ( created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); +CREATE TABLE concert_attendance ( + concert_id INTEGER NOT NULL REFERENCES concerts(id) ON DELETE CASCADE, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + status VARCHAR(20) NOT NULL CHECK ( + status IN ('attending', 'maybe', 'ticket_search') + ), + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (concert_id, user_id) +); + CREATE INDEX idx_concerts_start_datetime ON concerts(start_datetime); @@ -83,3 +93,6 @@ CREATE INDEX idx_concert_comments_concert CREATE INDEX idx_concert_photos_concert ON concert_photos(concert_id, created_at); + +CREATE INDEX idx_concert_attendance_concert + ON concert_attendance(concert_id, status); diff --git a/db/migrations/03_community.sql b/db/migrations/03_community.sql index 3b4c83d..1f13d3a 100644 --- a/db/migrations/03_community.sql +++ b/db/migrations/03_community.sql @@ -48,3 +48,16 @@ CREATE TABLE IF NOT EXISTS concert_photos ( path TEXT NOT NULL, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); + +CREATE TABLE IF NOT EXISTS concert_attendance ( + concert_id INTEGER NOT NULL REFERENCES concerts(id) ON DELETE CASCADE, + user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + status VARCHAR(20) NOT NULL CHECK ( + status IN ('attending', 'maybe', 'ticket_search') + ), + updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (concert_id, user_id) +); + +CREATE INDEX IF NOT EXISTS idx_concert_attendance_concert + ON concert_attendance(concert_id, status);