bearbeiten Button für admin angepasst, zusage funktion auch hinzugefügt

This commit is contained in:
kai
2026-08-25 12:48:45 +02:00
parent 21e2265e2d
commit 310e0db080
5 changed files with 253 additions and 27 deletions
+77
View File
@@ -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("<h1>Ungültige Auswahl</h1>", status_code=400)
if not load_concert(concert_id):
return HTMLResponse("<h1>Konzert nicht gefunden</h1>", 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,
+90 -18
View File
@@ -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 {
+60 -9
View File
@@ -173,8 +173,8 @@
{% if can_edit %}
<div class="detail-actions">
<a href="/concerts/{{ concert.id }}/edit" class="edit-button">
<div class="detail-actions" aria-label="Konzert verwalten">
<a href="/concerts/{{ concert.id }}/edit" class="concert-action concert-action-edit">
✏️ Konzert bearbeiten
</a>
@@ -185,7 +185,7 @@
action="/concerts/{{ concert.id }}/delete"
onsubmit="return confirm('Dieses Konzert wirklich löschen?');"
>
<button type="submit" class="delete-concert-button">
<button type="submit" class="concert-action concert-action-delete">
🗑️ Konzert löschen
</button>
</form>
@@ -196,16 +196,67 @@
{% endif %}
<section class="coming-soon">
<section class="attendance-section" id="attendance">
<h2>
🐧 Community
🐧 Wer ist dabei?
</h2>
<p>
Wer geht hin, wer ist interessiert?
Kommentare und Teilnehmer folgen hier.
</p>
<p>Wähle, wie es bei dir aussieht. Deine Auswahl kann jederzeit geändert werden.</p>
<div class="attendance-actions">
<form method="post" action="/concerts/{{ concert.id }}/attendance">
<input type="hidden" name="status" value="attending">
<button
type="submit"
class="attendance-option {% if current_attendance == 'attending' %}is-selected{% endif %}"
>
✓ Zugesagt
</button>
</form>
<form method="post" action="/concerts/{{ concert.id }}/attendance">
<input type="hidden" name="status" value="maybe">
<button
type="submit"
class="attendance-option {% if current_attendance == 'maybe' %}is-selected{% endif %}"
>
? Vielleicht
</button>
</form>
<form method="post" action="/concerts/{{ concert.id }}/attendance">
<input type="hidden" name="status" value="ticket_search">
<button
type="submit"
class="attendance-option {% if current_attendance == 'ticket_search' %}is-selected{% endif %}"
>
🎟️ Suche Ticket
</button>
</form>
</div>
<details class="attendance-list">
<summary>
{{ attending_count }} Zusage{% if attending_count != 1 %}n{% endif %} anzeigen
</summary>
{% if attending_users %}
<ul>
{% for attendee in attending_users %}
<li>{{ attendee.name }}</li>
{% endfor %}
</ul>
{% else %}
<p>Noch niemand hat zugesagt.</p>
{% endif %}
</details>
</section>
+13
View File
@@ -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);
+13
View File
@@ -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);