4035176370
- Die Suche ist international und kombiniert lokale Ergebnisse mit Nominatim. - Externe IDs werden korrekt als nominatim:<Typ>:<ID> gespeichert und dedupliziert. - Straße, PLZ, Stadt und Land können beim Anlegen manuell ergänzt werden. - Im Adminbereich können Orte korrigiert, bestätigt, mit Aliasen versehen und zusammengeführt werden.
119 lines
7.3 KiB
HTML
119 lines
7.3 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Verwaltung · Pingu Concerts</title>
|
|
<link rel="stylesheet" href="/static/css/style.css">
|
|
<style>
|
|
.admin-section { margin: 0 0 28px; padding: 24px; background: var(--surface); border: 1px solid var(--border); border-radius: 16px; }
|
|
.invite-link { display: block; margin-top: 14px; padding: 12px; overflow-wrap: anywhere; background: #0f1420; border: 1px solid var(--border); border-radius: 9px; color: #c4b5fd; }
|
|
.user-list { display: grid; gap: 12px; }
|
|
.user-row { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 12px; align-items: center; padding: 16px; background: #0f1420; border: 1px solid var(--border); border-radius: 10px; }
|
|
.user-meta { color: var(--muted); font-size: .9rem; margin-top: 4px; }
|
|
.role-form, .delete-form { margin: 0; display: flex; align-items: center; gap: 8px; }
|
|
.role-form select { padding: 9px; background: #0b0f19; color: var(--text); border: 1px solid var(--border); border-radius: 8px; }
|
|
.delete-button { padding: 9px 12px; color: #fecaca; border: 1px solid #7f1d1d; background: #450a0a; border-radius: 8px; cursor: pointer; }
|
|
.venue-list { display: grid; gap: 14px; }
|
|
.venue-card { padding: 16px; background: #0f1420; border: 1px solid var(--border); border-radius: 10px; }
|
|
.venue-fields { display: grid; grid-template-columns: 2fr 2fr .8fr 1.4fr 1.2fr; gap: 8px; }
|
|
.venue-fields input, .merge-form select { min-width: 0; width: 100%; }
|
|
.venue-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 10px; }
|
|
.venue-actions label { display: flex; align-items: center; gap: 6px; }
|
|
.venue-aliases { margin-top: 8px; }
|
|
.merge-form { display: grid; grid-template-columns: minmax(180px, 1fr) auto; gap: 8px; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); }
|
|
@media (max-width: 700px) { .user-row { grid-template-columns: 1fr; } .role-form, .delete-form { width: 100%; } }
|
|
@media (max-width: 900px) { .venue-fields { grid-template-columns: 1fr 1fr; } }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header>
|
|
<div class="header-inner">
|
|
<a href="/" class="logo">🎸 Pingu <span>Concerts</span></a>
|
|
<span>{{ user.display_name }}</span>
|
|
</div>
|
|
</header>
|
|
<main>
|
|
<div class="page-title">
|
|
<h1>⚙️ Verwaltung</h1>
|
|
<p>Einladungen und Benutzerkonten verwalten.</p>
|
|
</div>
|
|
<section class="admin-section">
|
|
<h2>Einladung erstellen</h2>
|
|
<p>Der Link kann einmalig verwendet werden, um einen neuen Account anzulegen.</p>
|
|
<form method="post" action="/admin/invites">
|
|
<button class="button" type="submit">+ Einladungslink erzeugen</button>
|
|
</form>
|
|
{% if invite_url %}
|
|
<label class="invite-link">{{ invite_url }}</label>
|
|
{% endif %}
|
|
</section>
|
|
<section class="admin-section">
|
|
<h2>Benutzer</h2>
|
|
<div class="user-list">
|
|
{% for managed_user in users %}
|
|
<div class="user-row">
|
|
<div>
|
|
<strong>{{ managed_user.display_name }}</strong>
|
|
<div class="user-meta">@{{ managed_user.username }} · {{ managed_user.email }} · seit {{ managed_user.created_at }}</div>
|
|
</div>
|
|
<form class="role-form" method="post" action="/admin/users/{{ managed_user.id }}">
|
|
<select name="role" aria-label="Rolle für {{ managed_user.username }}">
|
|
<option value="user" {% if not managed_user.is_admin %}selected{% endif %}>Benutzer</option>
|
|
<option value="admin" {% if managed_user.is_admin %}selected{% endif %}>Admin</option>
|
|
</select>
|
|
<button class="button" type="submit">Speichern</button>
|
|
</form>
|
|
{% if managed_user.id != user.id %}
|
|
<form class="delete-form" method="post" action="/admin/users/{{ managed_user.id }}/delete" onsubmit="return confirm('Diesen Benutzer wirklich löschen?');">
|
|
<button class="delete-button" type="submit">Löschen</button>
|
|
</form>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</section>
|
|
<section class="admin-section" id="venues">
|
|
<h2>Veranstaltungsorte</h2>
|
|
<p>Namen und Adressen korrigieren, Such-Aliase ergänzen oder Dubletten zusammenführen.</p>
|
|
<div class="venue-list">
|
|
{% for venue in venues %}
|
|
<div class="venue-card">
|
|
<form method="post" action="/admin/venues/{{ venue.id }}">
|
|
<div class="venue-fields">
|
|
<input name="name" value="{{ venue.name }}" aria-label="Name" required>
|
|
<input name="street" value="{{ venue.street }}" placeholder="Straße" aria-label="Straße">
|
|
<input name="postal_code" value="{{ venue.postal_code }}" placeholder="PLZ" aria-label="PLZ">
|
|
<input name="city" value="{{ venue.city }}" placeholder="Ort" aria-label="Ort">
|
|
<input name="country" value="{{ venue.country }}" placeholder="Land" aria-label="Land">
|
|
</div>
|
|
<input class="venue-aliases" name="aliases" value="{{ venue.aliases }}" placeholder="Aliase, durch Komma getrennt" aria-label="Aliase">
|
|
<div class="venue-actions">
|
|
<label><input type="checkbox" name="is_verified" {% if venue.is_verified %}checked{% endif %}> bestätigt</label>
|
|
<span class="user-meta">Quelle: {{ venue.source }} · {{ venue.concert_count }} Konzert(e) · ID {{ venue.id }}</span>
|
|
<button class="button" type="submit">Ort speichern</button>
|
|
</div>
|
|
</form>
|
|
{% if venues|length > 1 %}
|
|
<form class="merge-form" method="post" action="/admin/venues/{{ venue.id }}/merge" onsubmit="return confirm('Dieser Ort wird gelöscht und alle Konzerte werden dem Zielort zugeordnet. Fortfahren?');">
|
|
<select name="target_venue_id" required aria-label="Zielort">
|
|
<option value="">Mit anderem Ort zusammenführen …</option>
|
|
{% for target in venues %}
|
|
{% if target.id != venue.id %}
|
|
<option value="{{ target.id }}">{{ target.name }}{% if target.city %}, {{ target.city }}{% endif %}</option>
|
|
{% endif %}
|
|
{% endfor %}
|
|
</select>
|
|
<button class="delete-button" type="submit">Zusammenführen</button>
|
|
</form>
|
|
{% endif %}
|
|
</div>
|
|
{% else %}
|
|
<p>Noch keine Veranstaltungsorte vorhanden.</p>
|
|
{% endfor %}
|
|
</div>
|
|
</section>
|
|
</main>
|
|
</body>
|
|
</html>
|