bearbeiten Button für admin angepasst, zusage funktion auch hinzugefügt
This commit is contained in:
+77
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user