feat: Compact 1-page print view, event lifecycle & conflict warnings, bulk email invites, guest pass link and post-event guest cleanup
This commit is contained in:
+272
-135
@@ -3,91 +3,87 @@
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
|
||||
<!-- Printable distribution sheet for the kitchen (hidden on screen, visible in print) -->
|
||||
<!-- ================= COMPACT PRINT VIEW (1-PAGE A4) ================= -->
|
||||
<div class="print-only">
|
||||
<div class="print-header">
|
||||
<h1>🥨 Schmankerl – Brotzeit-Verteilliste</h1>
|
||||
<p>
|
||||
<strong>Event:</strong> {{ event.title if event else 'Brotzeitrunde' }} |
|
||||
<strong>Datum:</strong> {{ event.event_date.strftime('%d.%m.%Y') if event else '' }} |
|
||||
<strong>Team:</strong> {{ tenant.name }}
|
||||
</p>
|
||||
{% if event and event.sponsor_name %}
|
||||
<div class="print-sponsor-box">
|
||||
🎉 <strong>GESPONSERTE RUNDE:</strong> Ausgegeben von <strong>{{ event.sponsor_name }}</strong> – Gesamtrechnung: <strong>{{ "%.2f"|format(total_revenue) }} €</strong>
|
||||
<!-- Single-line condensed header -->
|
||||
<div class="print-header-singleline">
|
||||
<div class="print-left">
|
||||
<span class="print-logo">🥨 Schmankerl</span>
|
||||
<strong>{{ event.title if event else 'Brotzeitrunde' }}</strong>
|
||||
<span class="print-date">({{ event.event_date.strftime('%d.%m.%Y') if event else '' }})</span>
|
||||
</div>
|
||||
<div class="print-right">
|
||||
<span>Teilnehmer: <strong>{{ orders|length }}</strong></span>
|
||||
<span> | </span>
|
||||
<span>Einkauf: <strong>{{ "%.2f"|format(total_revenue) }} €</strong></span>
|
||||
{% if event and event.sponsor_name %}
|
||||
<span> | </span>
|
||||
<span class="print-sponsor">🎉 Ausgegeben von: <strong>{{ event.sponsor_name }}</strong></span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="print-section">
|
||||
<h3>🛒 Sammelbestellung (Metzger & Bäcker)</h3>
|
||||
<table class="print-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Artikel</th>
|
||||
<th style="text-align: right;">Gesamtmenge</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for name, data in item_sums.items() %}
|
||||
<tr>
|
||||
<td>{{ name }}</td>
|
||||
<td style="text-align: right; font-weight: bold;">{{ data.quantity }} {{ data.unit }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- Compact Sammelliste (Horizontal / 2-Column) -->
|
||||
<div class="print-sammel-box">
|
||||
<span class="print-sammel-title">🛒 Einkauf (Bäcker & Metzger):</span>
|
||||
{% for name, data in item_sums.items() %}
|
||||
<span class="print-item-pill">
|
||||
<strong>{{ data.quantity }}x</strong> {{ name }}
|
||||
</span>
|
||||
{% else %}
|
||||
<span>Keine Artikel</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="print-section" style="margin-top: 1.5rem;">
|
||||
<h3>👥 Ausgabe- & Strichliste (zum Abhaken)</h3>
|
||||
<table class="print-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 40px; text-align: center;">Abgehakt</th>
|
||||
<th>Kollege / Name</th>
|
||||
<th>Bestellte Artikel</th>
|
||||
<th style="text-align: right;">Betrag</th>
|
||||
<th style="text-align: center;">Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for order in orders %}
|
||||
<tr>
|
||||
<td style="text-align: center; font-size: 1.2rem;">☐</td>
|
||||
<td>
|
||||
<strong>{{ order.user.first_name }} {{ order.user.last_name }}</strong>
|
||||
{% if order.user.role == 'guest' %}<small>(Gast)</small>{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% for item in order.items %}
|
||||
{{ item.quantity }}x {{ item.product.name }}{% if not loop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td style="text-align: right; font-weight: bold;">
|
||||
{% if event and event.sponsor_name %}
|
||||
<span style="color: #666; font-size: 9pt;">Gesponsert</span>
|
||||
{% else %}
|
||||
{{ "%.2f"|format(order.total_price) }} €
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="text-align: center;">
|
||||
{% if event and event.sponsor_name %}
|
||||
Übernommen ✓
|
||||
{% elif order.is_paid %}
|
||||
Bezahlt ✓
|
||||
{% else %}
|
||||
Offen
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Ausgabeliste zum Abhaken -->
|
||||
<table class="print-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 28px; text-align: center;">☐</th>
|
||||
<th style="width: 25%;">Kollege / Name</th>
|
||||
<th>Bestellte Artikel</th>
|
||||
<th style="width: 70px; text-align: right;">Betrag</th>
|
||||
<th style="width: 85px; text-align: center;">Kasse</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for order in orders %}
|
||||
<tr>
|
||||
<td style="text-align: center; font-size: 10pt;">☐</td>
|
||||
<td>
|
||||
<strong>{{ order.display_name }}</strong>
|
||||
{% if order.user and order.user.role == 'guest' %}<small class="guest-tag">(Gast)</small>{% endif %}
|
||||
</td>
|
||||
<td class="print-items-cell">
|
||||
{% for item in order.items %}
|
||||
<span>{{ item.quantity }}x {{ item.product.name }}</span>{% if not loop.last %}, {% endif %}
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td style="text-align: right; font-weight: bold;">
|
||||
{% if event and event.sponsor_name %}
|
||||
<span style="font-size: 8pt; color: #555;">Sponsor</span>
|
||||
{% else %}
|
||||
{{ "%.2f"|format(order.total_price) }} €
|
||||
{% endif %}
|
||||
</td>
|
||||
<td style="text-align: center; font-size: 8pt;">
|
||||
{% if event and event.sponsor_name %}
|
||||
Übernommen
|
||||
{% elif order.is_paid %}
|
||||
Bezahlt ✓
|
||||
{% else %}
|
||||
Offen
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% else %}
|
||||
<tr><td colspan="5" style="text-align: center;">Keine Bestellungen vorhanden.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Normal Screen Content -->
|
||||
<!-- ================= NORMAL SCREEN VIEW ================= -->
|
||||
<div class="no-print">
|
||||
<div class="admin-header">
|
||||
<div>
|
||||
@@ -97,11 +93,46 @@
|
||||
<div class="admin-actions-top">
|
||||
{% if event %}
|
||||
<button onclick="copyShoppingList()" class="btn-primary">📋 Einkaufsliste kopieren</button>
|
||||
<button onclick="window.print()" class="btn-secondary">🖨️ Verteilliste drucken</button>
|
||||
<button onclick="copyGuestPassLink('{{ event.id }}')" class="btn-secondary" title="Gast-Link für Dienstleister">🔗 Dienstleister-Gastlink</button>
|
||||
<button onclick="window.print()" class="btn-secondary">🖨️ Liste drucken (A4)</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if success %}
|
||||
<div class="alert-success" style="margin-bottom: 20px;">
|
||||
✓ {{ success }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if bulk_count %}
|
||||
<div class="alert-success" style="margin-bottom: 20px;">
|
||||
✓ Erfolgreich <strong>{{ bulk_count }}</strong> Einladungen generiert! Die Links sind unten aufgeführt.
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- EVENT-LEISTE (Zeigt alle Events transparent an) -->
|
||||
<div class="events-ribbon">
|
||||
<div class="events-ribbon-title">
|
||||
<span>📅 Brotzeitrunden:</span>
|
||||
</div>
|
||||
<div class="events-ribbon-list">
|
||||
{% for ev in all_events %}
|
||||
<a href="?tab={{ active_tab }}&event_id={{ ev.id }}" class="event-chip {% if event and event.id == ev.id %}active{% endif %} {% if ev.status == 'completed' %}completed{% endif %}">
|
||||
<span class="chip-date">{{ ev.event_date.strftime('%d.%m.') }}</span>
|
||||
<span class="chip-title">{{ ev.title }}</span>
|
||||
{% if ev.status == 'open' %}
|
||||
<span class="chip-badge open">🟢 Offen</span>
|
||||
{% elif ev.status == 'completed' %}
|
||||
<span class="chip-badge comp">🏁 Abgeschlossen</span>
|
||||
{% else %}
|
||||
<span class="chip-badge closed">🔴 Geschlossen</span>
|
||||
{% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab Navigation -->
|
||||
<div class="admin-nav-tabs">
|
||||
<a href="?tab=orders{% if event %}&event_id={{ event.id }}{% endif %}" class="admin-tab-btn {% if active_tab == 'orders' %}active{% endif %}">
|
||||
@@ -111,30 +142,34 @@
|
||||
📅 Event- & Frist-Planung
|
||||
</a>
|
||||
<a href="?tab=products{% if event %}&event_id={{ event.id }}{% endif %}" class="admin-tab-btn {% if active_tab == 'products' %}active{% endif %}">
|
||||
🥨 Produkte & Sortiment ({{ products|length }})
|
||||
🥨 Sortiment ({{ products|length }})
|
||||
</a>
|
||||
<a href="?tab=team{% if event %}&event_id={{ event.id }}{% endif %}" class="admin-tab-btn {% if active_tab == 'team' %}active{% endif %}">
|
||||
👥 Team & Einladungen ({{ team_members|length }})
|
||||
👥 Team & Massen-Einladung ({{ team_members|length }})
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- TAB 1: BESTELLUNGEN & KASSE -->
|
||||
{% if active_tab == 'orders' %}
|
||||
{% if all_events|length > 1 %}
|
||||
<div class="event-selector-bar">
|
||||
<label for="eventSelect">Runde auswählen:</label>
|
||||
<select id="eventSelect" onchange="window.location.href='?tab=orders&event_id=' + this.value;">
|
||||
{% for ev in all_events %}
|
||||
<option value="{{ ev.id }}" {% if event and event.id == ev.id %}selected{% endif %}>
|
||||
{{ ev.title }} ({{ ev.event_date.strftime('%d.%m.%Y') }}) – {% if ev.status == 'open' %}🟢 Offen{% else %}🔴 Geschlossen{% endif %}
|
||||
</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if event %}
|
||||
|
||||
<!-- Event Action Bar -->
|
||||
<div class="event-action-bar">
|
||||
<div class="event-info-left">
|
||||
<h2>{{ event.title }}</h2>
|
||||
<span class="text-muted">Termin: <strong>{{ event.event_date.strftime('%d.%m.%Y') }}</strong> | Bestellschluss: <strong>{{ event.deadline.strftime('%d.%m. um %H:%M Uhr') }}</strong></span>
|
||||
</div>
|
||||
<div class="event-actions-right">
|
||||
{% if event.status != 'completed' %}
|
||||
<form action="/admin/events/{{ event.id }}/complete-and-cleanup" method="POST" style="margin: 0;" onsubmit="return confirm('Möchtest du dieses Event abschließen und die temporären Gastkonten bereinigen?');">
|
||||
<button type="submit" class="btn-success-small">✅ Event abschließen & Gastkonten aufräumen</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<span class="badge-role" style="background: rgba(34, 197, 94, 0.2); color: #86efac; font-size: 0.85rem;">🏁 Abgeschlossen & Gastkonten archiviert</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if event.sponsor_name %}
|
||||
<div class="sponsor-highlight-card">
|
||||
<div class="sponsor-icon">🎁</div>
|
||||
@@ -208,7 +243,7 @@
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Kollege</th>
|
||||
<th>Kollege / Gast</th>
|
||||
<th>Bestellung</th>
|
||||
<th>Betrag</th>
|
||||
<th>Status</th>
|
||||
@@ -218,12 +253,15 @@
|
||||
{% for order in orders %}
|
||||
<tr>
|
||||
<td>
|
||||
<strong>{{ order.user.first_name }} {{ order.user.last_name }}</strong>
|
||||
{% if order.user.role == 'guest' %}
|
||||
<strong>{{ order.display_name }}</strong>
|
||||
{% if order.user and order.user.role == 'guest' %}
|
||||
<span class="badge-role guest">Gast</span>
|
||||
{% elif not order.user and order.guest_display_name %}
|
||||
<span class="badge-role" style="background: rgba(148, 163, 184, 0.15); color: #cbd5e1;">Archivierter Gast</span>
|
||||
{% endif %}
|
||||
{% if order.user %}
|
||||
<br><small class="text-muted">{{ order.user.email }}</small>
|
||||
{% endif %}
|
||||
<br>
|
||||
<small class="text-muted">{{ order.user.email }}</small>
|
||||
</td>
|
||||
<td>
|
||||
<ul class="mini-items-list">
|
||||
@@ -259,19 +297,41 @@
|
||||
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<h2>Keine offene Bestellrunde gefunden</h2>
|
||||
<p>Erstelle im Reiter <strong>Event- & Frist-Planung</strong> eine neue Runde.</p>
|
||||
<h2>Keine Runde ausgewählt</h2>
|
||||
<p>Wähle oben eine Runde aus oder erstelle eine neue.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- TAB 2: EVENT- & FRIST-PLANUNG -->
|
||||
{% elif active_tab == 'event' %}
|
||||
|
||||
{% if warn_active_event %}
|
||||
<div class="alert-warning-card">
|
||||
<h3>⚠️ Aktives Event läuft bereits!</h3>
|
||||
<p>
|
||||
Es gibt aktuell noch das geplante Event <strong>{{ warn_active_event.title }}</strong> am <strong>{{ warn_active_event.event_date.strftime('%d.%m.%Y') }}</strong>.
|
||||
Möchtest du das bisherige Event bearbeiten oder wirklich ein neues, paralleles Event starten?
|
||||
</p>
|
||||
<div style="display: flex; gap: 12px; margin-top: 12px;">
|
||||
<a href="?tab=orders&event_id={{ warn_active_event.id }}" class="btn-primary">Zum bisherigen Event</a>
|
||||
<button type="button" class="btn-secondary" onclick="document.getElementById('forceInput').value='true'; document.getElementById('newEventForm').submit();">Trotzdem neues Event anlegen</button>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="admin-columns">
|
||||
<!-- Edit current event -->
|
||||
{% if event %}
|
||||
<div class="admin-card">
|
||||
<h2>✏️ Aktives Event bearbeiten</h2>
|
||||
<p class="card-hint">Passe Titel, Frist, Gast-Erlaubnis und Sponsor an:</p>
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
|
||||
<div>
|
||||
<h2>✏️ Event bearbeiten</h2>
|
||||
<p class="card-hint">Aktuelle Runde: <strong>{{ event.title }}</strong></p>
|
||||
</div>
|
||||
<form action="/admin/events/{{ event.id }}/delete" method="POST" onsubmit="return confirm('Event und zugehörige Bestellungen wirklich löschen?');">
|
||||
<button type="submit" class="btn-revoke-small" style="font-size: 0.8rem;" title="Event löschen">🗑️ Löschen</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<form action="/admin/events/{{ event.id }}/edit" method="POST" class="admin-form">
|
||||
<div class="input-group">
|
||||
@@ -295,12 +355,11 @@
|
||||
<div class="input-group">
|
||||
<label for="edit_sponsor">🎉 Sponsor / Anlass (Optional)</label>
|
||||
<input type="text" id="edit_sponsor" name="sponsor_name" value="{{ event.sponsor_name or '' }}" placeholder="z. B. Maxi (Geburtstag) oder Firma übernimmt">
|
||||
<small class="form-hint">Falls ausgefüllt, übernimmt der Sponsor die Gesamtrechnung statt Einzelabrechnung.</small>
|
||||
</div>
|
||||
<div class="input-group-checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="allow_guests" value="true" {% if event.allow_guests %}checked{% endif %}>
|
||||
<strong>Gäste-Bestellungen erlauben</strong> (Mitbestellen ohne Registrierung)
|
||||
<strong>Gäste- & Dienstleister-Bestellungen erlauben</strong>
|
||||
</label>
|
||||
</div>
|
||||
<div class="input-group" style="margin-top: 14px;">
|
||||
@@ -308,6 +367,7 @@
|
||||
<select id="edit_status" name="status_val">
|
||||
<option value="open" {% if event.status == 'open' %}selected{% endif %}>🟢 Geöffnet (Bestellungen möglich)</option>
|
||||
<option value="closed" {% if event.status == 'closed' %}selected{% endif %}>🔴 Geschlossen (Bestellungen gesperrt)</option>
|
||||
<option value="completed" {% if event.status == 'completed' %}selected{% endif %}>🏁 Abgeschlossen (Archiviert)</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary" style="margin-top: 1rem;">Änderungen speichern</button>
|
||||
@@ -320,7 +380,8 @@
|
||||
<h2>➕ Neue Brotzeitrunde planen</h2>
|
||||
<p class="card-hint">Erstelle einen neuen Termin für dein Team:</p>
|
||||
|
||||
<form action="/admin/events/new" method="POST" class="admin-form">
|
||||
<form action="/admin/events/new" method="POST" id="newEventForm" class="admin-form">
|
||||
<input type="hidden" id="forceInput" name="force" value="false">
|
||||
<div class="input-group">
|
||||
<label for="new_title">Titel</label>
|
||||
<input type="text" id="new_title" name="title" placeholder="z. B. Weißwurstfrühstück am Freitag" required>
|
||||
@@ -346,7 +407,7 @@
|
||||
<div class="input-group-checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="allow_guests" value="true" checked>
|
||||
<strong>Gäste-Bestellungen erlauben</strong>
|
||||
<strong>Gäste- & Dienstleister-Bestellungen erlauben</strong>
|
||||
</label>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary" style="margin-top: 1rem;">Runde jetzt starten</button>
|
||||
@@ -357,7 +418,6 @@
|
||||
<!-- TAB 3: PRODUKTE & SORTIMENT -->
|
||||
{% elif active_tab == 'products' %}
|
||||
<div class="admin-columns">
|
||||
<!-- Product List -->
|
||||
<div class="admin-card" style="grid-column: 1 / -1;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
|
||||
<div>
|
||||
@@ -442,11 +502,10 @@
|
||||
<button type="submit" class="btn-primary" style="margin-top: 10px;">Artikel speichern</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Edit Product Modal (Simple JS) -->
|
||||
<!-- Edit Product Modal -->
|
||||
<div id="editProductModal" class="modal-backdrop" style="display: none;">
|
||||
<div class="modal-card">
|
||||
<h3>✏️ Produkt bearbeiten</h3>
|
||||
@@ -481,51 +540,83 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TAB 4: TEAM & EINLADUNGEN -->
|
||||
<!-- TAB 4: TEAM & MASSEN-EINLADUNG -->
|
||||
{% elif active_tab == 'team' %}
|
||||
<div class="admin-columns">
|
||||
<!-- Generate invite link -->
|
||||
<!-- Einladungs-Bereich (Einzeln & Massen-Einladung) -->
|
||||
<div class="admin-card">
|
||||
<h2>✉️ Teammitglied einladen</h2>
|
||||
<p class="card-hint">Erzeuge einen Einladungslink (einmalig oder unbegrenzt dauerhaft):</p>
|
||||
<h2>✉️ Team einladen</h2>
|
||||
|
||||
<!-- Bulk Invite Form -->
|
||||
<div class="bulk-invite-box">
|
||||
<h3>⚡ Massen-Einladung (Mehrere E-Mails)</h3>
|
||||
<p class="card-hint">Füge mehrere Adressen kommagetrennt oder zeilenweise ein (z. B. aus Outlook/Slack kopiert):</p>
|
||||
<form action="/admin/invitations/bulk" method="POST" class="admin-form">
|
||||
<div class="input-group">
|
||||
<textarea name="emails_text" rows="3" placeholder="anna@firma.de, max@firma.de, lisa@firma.de" required style="width: 100%; padding: 10px; background: rgba(11, 15, 25, 0.8); border: 1px solid var(--border-color); border-radius: var(--radius-md); color: #fff;"></textarea>
|
||||
</div>
|
||||
<div class="form-row-2">
|
||||
<div class="input-group">
|
||||
<label>Rolle</label>
|
||||
<select name="role">
|
||||
<option value="user" selected>👤 Mitarbeiter</option>
|
||||
<option value="admin">👨🍳 Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label>Gültigkeit</label>
|
||||
<select name="valid_days">
|
||||
<option value="14" selected>14 Tage</option>
|
||||
<option value="30">30 Tage</option>
|
||||
<option value="unlimited">∞ Unbegrenzt</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary" style="margin-top: 6px;">✉️ Alle einladen</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<hr class="divider">
|
||||
|
||||
<!-- Single / Open Invite Link Form -->
|
||||
<h3>🔗 Offenen Einladungslink generieren</h3>
|
||||
<p class="card-hint">Dauerhafter Link für das ganze Team (z. B. Pinned Message im Chat):</p>
|
||||
|
||||
<form action="/admin/invitations/create" method="POST" class="admin-form">
|
||||
<div class="input-group">
|
||||
<label for="inv_email">E-Mail-Adresse (Optional)</label>
|
||||
<input type="email" id="inv_email" name="email" placeholder="kollege@firma.de (optional)">
|
||||
<small class="form-hint">Freilassen für einen offenen Link, den der Kollege selbst ausfüllt.</small>
|
||||
</div>
|
||||
<div class="form-row-2">
|
||||
<div class="input-group">
|
||||
<label for="inv_role">Rolle</label>
|
||||
<select id="inv_role" name="role">
|
||||
<option value="user" selected>👤 Mitarbeiter (Besteller)</option>
|
||||
<option value="admin">👨🍳 Organisator (Admin)</option>
|
||||
<label>Rolle</label>
|
||||
<select name="role">
|
||||
<option value="user" selected>👤 Mitarbeiter</option>
|
||||
<option value="admin">👨🍳 Admin</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="inv_valid">Gültigkeit</label>
|
||||
<select id="inv_valid" name="valid_days">
|
||||
<label>Gültigkeit</label>
|
||||
<select name="valid_days">
|
||||
<option value="unlimited" selected>∞ Unbegrenzt (Dauerhafter Team-Link)</option>
|
||||
<option value="7">7 Tage</option>
|
||||
<option value="14">14 Tage</option>
|
||||
<option value="30">30 Tage</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn-primary" style="margin-top: 1rem;">🔗 Einladungslink generieren</button>
|
||||
<button type="submit" class="btn-secondary">🔗 Offenen Link generieren</button>
|
||||
</form>
|
||||
|
||||
{% if pending_invitations %}
|
||||
<h3 style="margin-top: 2rem;">Aktive Einladungslinks</h3>
|
||||
<div class="invitations-list">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-top: 2rem;">
|
||||
<h3>Aktive Einladungslinks ({{ pending_invitations|length }})</h3>
|
||||
<button type="button" class="btn-copy-small" onclick="copyAllInviteLinks()">📋 Alle Links kopieren</button>
|
||||
</div>
|
||||
|
||||
<div class="invitations-list" id="invitationsListContainer">
|
||||
{% for inv in pending_invitations %}
|
||||
<div class="invite-item">
|
||||
<div class="invite-item" data-token="{{ inv.token }}" data-email="{{ inv.email or 'Team' }}">
|
||||
<div class="invite-info">
|
||||
<strong>{% if inv.email %}{{ inv.email }}{% else %}Offener Team-Link{% endif %}</strong>
|
||||
<strong>{% if inv.email %}{{ inv.email }}{% else %}Dauerhafter Team-Link{% endif %}</strong>
|
||||
<span class="badge-role">{{ inv.role }}</span>
|
||||
{% if inv.is_reusable %}
|
||||
<span class="badge-role" style="background: rgba(34, 197, 94, 0.2); color: #86efac;">Dauerhaft wiederverwendbar</span>
|
||||
<span class="badge-role" style="background: rgba(34, 197, 94, 0.2); color: #86efac;">Dauerhaft</span>
|
||||
{% endif %}
|
||||
<br>
|
||||
<small class="text-muted">
|
||||
@@ -547,7 +638,7 @@
|
||||
<!-- Current team members list -->
|
||||
<div class="admin-card">
|
||||
<h2>👥 Registrierte Teammitglieder ({{ team_members|length }})</h2>
|
||||
<p class="card-hint">Alle aktiven Konten dieses Portals:</p>
|
||||
<p class="card-hint">Feste Teamkonten dieses Portals:</p>
|
||||
|
||||
<div class="orders-table-wrapper">
|
||||
<table class="data-table">
|
||||
@@ -568,8 +659,6 @@
|
||||
<td>
|
||||
{% if member.role == 'admin' %}
|
||||
<span class="badge-role admin">Admin</span>
|
||||
{% elif member.role == 'guest' %}
|
||||
<span class="badge-role guest">Gast</span>
|
||||
{% else %}
|
||||
<span class="badge-role">Mitarbeiter</span>
|
||||
{% endif %}
|
||||
@@ -589,6 +678,32 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if active_guests %}
|
||||
<h3 style="margin-top: 2rem;">👤 Aktive Gäste (für anstehende Events)</h3>
|
||||
<p class="card-hint">Werden beim Klick auf „Event abschließen & aufräumen“ automatisch archiviert:</p>
|
||||
<div class="orders-table-wrapper">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>E-Mail / Kennung</th>
|
||||
<th>Rolle</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for guest in active_guests %}
|
||||
<tr>
|
||||
<td><strong>{{ guest.first_name }} {{ guest.last_name }}</strong></td>
|
||||
<td><small class="text-muted">{{ guest.email }}</small></td>
|
||||
<td><span class="badge-role guest">Gast</span></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -634,7 +749,29 @@
|
||||
const origin = window.location.origin;
|
||||
const url = origin + '/join?token=' + token;
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
alert("Einladungslink kopiert!\n\n" + url + "\n\nDu kannst ihn jetzt an das Team senden.");
|
||||
alert("Einladungslink kopiert!\n\n" + url + "\n\nDu kannst ihn jetzt an den Kollegen senden.");
|
||||
});
|
||||
}
|
||||
|
||||
function copyGuestPassLink(eventId) {
|
||||
const origin = window.location.origin;
|
||||
const url = origin + '/?event_id=' + eventId + '&guest=1';
|
||||
navigator.clipboard.writeText(url).then(() => {
|
||||
alert("Dienstleister-Gastlink kopiert!\n\n" + url + "\n\nExterne können damit für diesen Termin mitbestellen, ohne ein Konto anzulegen.");
|
||||
});
|
||||
}
|
||||
|
||||
function copyAllInviteLinks() {
|
||||
const origin = window.location.origin;
|
||||
const items = document.querySelectorAll('#invitationsListContainer .invite-item');
|
||||
let text = "🥨 Einladungslinks für Schmankerl:\n\n";
|
||||
items.forEach(el => {
|
||||
const email = el.dataset.email;
|
||||
const token = el.dataset.token;
|
||||
text += email + ": " + origin + "/join?token=" + token + "\n";
|
||||
});
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
alert("Alle Einladungslinks in die Zwischenablage kopiert!");
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,20 @@
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
|
||||
<!-- Multi-Event Picker (wenn mehrere anstehende Runden offen sind) -->
|
||||
{% if open_events|length > 1 %}
|
||||
<div class="multi-event-banner">
|
||||
<span>📅 Weitere offene Termine:</span>
|
||||
<div class="multi-event-chips">
|
||||
{% for ev in open_events %}
|
||||
<a href="/?event_id={{ ev.id }}{% if is_guest_mode %}&guest=1{% endif %}" class="mini-chip {% if event and event.id == ev.id %}active{% endif %}">
|
||||
{{ ev.event_date.strftime('%d.%m.') }} – {{ ev.title }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Header Banner -->
|
||||
{% if event %}
|
||||
<div class="banner {% if is_closed %}banner-closed{% endif %}">
|
||||
@@ -76,9 +90,9 @@
|
||||
<!-- Right: User Details & Summary -->
|
||||
<div class="summary-section">
|
||||
<div class="summary-card">
|
||||
<h2>2. Bestellung & Angaben</h2>
|
||||
<h2>2. Angaben & Übersicht</h2>
|
||||
|
||||
{% if current_user %}
|
||||
{% if current_user and not is_guest_mode %}
|
||||
<div class="logged-in-badge">
|
||||
<div class="user-avatar">👤</div>
|
||||
<div>
|
||||
@@ -92,18 +106,19 @@
|
||||
<!-- Gast Angaben -->
|
||||
<div class="guest-order-box">
|
||||
<div class="privacy-note" style="margin-bottom: 12px;">
|
||||
👋 <strong>Gast-Bestellung:</strong> Du bestellst ohne dauerhaftes Benutzerkonto für diesen Termin mit.
|
||||
👋 <strong>Gast- & Dienstleister-Bestellung:</strong><br>
|
||||
Du bestellst ohne dauerhaftes Konto für diesen Termin ({{ event.event_date.strftime('%d.%m.') }}) mit.
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="g_first_name">Dein Vorname *</label>
|
||||
<input type="text" id="g_first_name" name="guest_first_name" required placeholder="z. B. Christian">
|
||||
<label for="g_first_name">Vorname / Name *</label>
|
||||
<input type="text" id="g_first_name" name="guest_first_name" required placeholder="z. B. Tobias">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="g_last_name">Nachname (optional)</label>
|
||||
<input type="text" id="g_last_name" name="guest_last_name" placeholder="z. B. Bauer">
|
||||
<label for="g_last_name">Firma / Abteilung (optional)</label>
|
||||
<input type="text" id="g_last_name" name="guest_last_name" placeholder="z. B. IT-Service Müller">
|
||||
</div>
|
||||
<div class="input-group">
|
||||
<label for="g_email">E-Mail (optional für Bestätigung)</label>
|
||||
<label for="g_email">E-Mail (optional)</label>
|
||||
<input type="email" id="g_email" name="guest_email" placeholder="name@firma.de">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user