Files
schmankerl/app/templates/admin.html
T

985 lines
47 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% extends "base.html" %}
{% block content %}
<div class="container">
<!-- ================= COMPACT PRINT VIEW (1-PAGE A4) ================= -->
<div class="print-only">
<!-- 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>
</div>
<!-- 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>
<!-- 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 VIEW ================= -->
<div class="no-print">
<div class="admin-header">
<div>
<h1>⚙️ Verwaltung & Orga-Cockpit</h1>
<p class="admin-sub">Portal: <strong>{{ tenant.name }}</strong></p>
</div>
<div class="admin-actions-top">
<a href="/einkauf{% if event %}?event_id={{ event.id }}{% endif %}" class="btn-primary" style="text-decoration: none; display: inline-flex; align-items: center; gap: 6px;">
🛒 Zur Einkaufs- & Kassenansicht
</a>
{% if event %}
<button onclick="copyGuestPassLink('{{ event.id }}', '{{ event.guest_token or '' }}')" class="btn-secondary" title="Gast-Link für Dienstleister">🔗 Dienstleister-Gastlink</button>
<button onclick="openOutlookModal()" class="btn-secondary" title="Outlook Einladungsvorlage anzeigen">✉️ Outlook-Vorlage</button>
<a href="/events/{{ event.id }}/calendar.ics" class="btn-secondary" title="Termin als Outlook/Kalenderdatei (.ics) herunterladen" style="text-decoration: none; display: inline-flex; align-items: center;">📅 Outlook (.ics)</a>
<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 error %}
<div class="alert-warning-card" style="margin-bottom: 20px; border-color: #ef4444; background: rgba(239, 68, 68, 0.15); color: #fca5a5;">
⚠️ {{ error }}
</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 %}">
🛒 Bestellungen & Kasse
</a>
<a href="?tab=event{% if event %}&event_id={{ event.id }}{% endif %}" class="admin-tab-btn {% if active_tab == 'event' %}active{% endif %}">
📅 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 %}">
🥨 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 & Massen-Einladung ({{ team_members|length }})
</a>
</div>
<!-- TAB 1: BESTELLUNGEN & KASSE -->
{% if active_tab == 'orders' %}
{% 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>
<div>
<h3>Diese Runde wird gesponsert!</h3>
<p>Sponsor / Anlass: <strong>{{ event.sponsor_name }}</strong> übernimmt die Gesamtrechnung von <strong>{{ "%.2f"|format(total_revenue) }} €</strong> für das gesamte Team.</p>
</div>
</div>
{% endif %}
<!-- Stats Cards -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-label">Gesamtsumme Einkauf</div>
<div class="stat-value">{{ "%.2f"|format(total_revenue) }} €</div>
</div>
<div class="stat-card">
<div class="stat-label">Bereits bezahlt</div>
<div class="stat-value success">
{% if event.sponsor_name %}Gesponsert{% else %}{{ "%.2f"|format(total_paid) }} €{% endif %}
</div>
</div>
<div class="stat-card">
<div class="stat-label">Noch offen</div>
<div class="stat-value warning">
{% if event.sponsor_name %}0,00 €{% else %}{{ "%.2f"|format(total_revenue - total_paid) }} €{% endif %}
</div>
</div>
<div class="stat-card">
<div class="stat-label">Teilnehmer</div>
<div class="stat-value">{{ orders|length }} Personen</div>
</div>
{% if event and event.status == 'open' %}
<div style="display: flex; justify-content: flex-end; margin-bottom: 16px;">
<form action="/setup/seed-test-data" method="POST" style="margin: 0;" onsubmit="return confirm('Möchtest du 5 realistische Demo-Bestellungen für Kollegen und Gäste für diese Runde einfügen?');">
<input type="hidden" name="event_id" value="{{ event.id }}">
<button type="submit" class="btn-secondary" style="font-size: 0.85rem;" title="Erstellt Beispielbestellungen für Anna Huber, Stefan Meier, Michael Schmidt etc.">
🧪 Realistische Demo-Bestellungen einfügen (5 Kollegen)
</button>
</form>
</div>
{% endif %}
<div class="admin-columns">
<!-- Aggregated Shopping List -->
<div class="admin-card">
<h2>🛒 Sammelliste für Metzger & Bäcker</h2>
<p class="card-hint">Aufsummierte Gesamtmengen für den Einkauf:</p>
<div class="shopping-table-wrapper">
<table class="data-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><strong>{{ name }}</strong></td>
<td style="text-align: right; font-size: 1.15rem; font-weight: 700; color: var(--accent);">
{{ data.quantity }} {{ data.unit }}
</td>
</tr>
{% else %}
<tr><td colspan="2" class="empty-cell">Noch keine Bestellungen eingegangen.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Individual Orders & Payment Checklist -->
<div class="admin-card">
<h2>👥 Wer hat bestellt? (Strichliste)</h2>
<p class="card-hint">Einzelübersicht mit Artikeln und Kassenstatus:</p>
<div class="orders-table-wrapper">
<table class="data-table">
<thead>
<tr>
<th>Kollege / Gast</th>
<th>Bestellung</th>
<th>Betrag</th>
<th>Status</th>
</tr>
</thead>
<tbody>
{% for order in orders %}
<tr>
<td>
<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 %}
</td>
<td>
<ul class="mini-items-list">
{% for item in order.items %}
<li>{{ item.quantity }}x {{ item.product.name }}</li>
{% endfor %}
</ul>
</td>
<td>
<strong>{{ "%.2f"|format(order.total_price) }} €</strong>
{% if event.sponsor_name %}<br><small class="text-muted">von Sponsor</small>{% endif %}
</td>
<td>
<div style="display: flex; align-items: center; justify-content: space-between; gap: 6px;">
{% if event.sponsor_name %}
<span class="badge-btn paid" style="cursor: default;">✓ Gesponsert</span>
{% else %}
<form action="/admin/toggle-paid/{{ order.id }}" method="POST" style="margin: 0;">
<button type="submit" class="badge-btn {% if order.is_paid %}paid{% else %}unpaid{% endif %}" title="Klick um Bezahlstatus umzuschalten (Guthaben wird automatisch erstattet)">
{% if order.is_paid %}
{% if order.payment_method == 'balance' %}✓ Guthaben{% else %}✓ Bezahlt{% endif %}
{% else %}
⏳ Offen
{% endif %}
</button>
</form>
{% endif %}
<form action="/admin/orders/{{ order.id }}/delete" method="POST" style="margin: 0;" onsubmit="return confirm('Bestellung für {{ order.display_name|escape }} wirklich stornieren? Bereits bezahltes Guthaben wird sofort erstattet.');">
<button type="submit" class="btn-revoke-small" title="Bestellung stornieren / löschen" style="padding: 2px 6px; font-size: 0.8rem;">🗑️</button>
</form>
</div>
</td>
</tr>
{% else %}
<tr><td colspan="4" class="empty-cell">Noch keine Bestellungen vorhanden.</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% else %}
<div class="empty-state">
<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">
<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">
<label for="edit_title">Titel der Runde</label>
<input type="text" id="edit_title" name="title" value="{{ event.title }}" required>
</div>
<div class="input-group">
<label for="edit_event_date">Brotzeit-Tag (Datum)</label>
<input type="date" id="edit_event_date" name="event_date" value="{{ event.event_date.strftime('%Y-%m-%d') }}" required>
</div>
<div class="form-row-2">
<div class="input-group">
<label for="edit_deadline_date">Bestellschluss (Tag)</label>
<input type="date" id="edit_deadline_date" name="deadline_date" value="{{ event.deadline.strftime('%Y-%m-%d') }}" required>
</div>
<div class="input-group">
<label for="edit_deadline_time">Bestellschluss (Uhrzeit)</label>
<input type="time" id="edit_deadline_time" name="deadline_time" value="{{ event.deadline.strftime('%H:%M') }}" required>
</div>
</div>
<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">
</div>
<div class="input-group-checkbox">
<label>
<input type="checkbox" name="allow_guests" value="true" {% if event.allow_guests %}checked{% endif %}>
<strong>Gäste- & Dienstleister-Bestellungen erlauben</strong>
</label>
</div>
<div class="input-group" style="margin-top: 14px;">
<label for="edit_buyer">🛒 Zugewiesener Einkäufer für diese Runde</label>
<select id="edit_buyer" name="buyer_id">
<option value="">-- Nicht zugewiesen / Admin übernimmt --</option>
{% for member in team_members %}
<option value="{{ member.id }}" {% if event.buyer_id == member.id %}selected{% endif %}>
{{ member.first_name }} {{ member.last_name }} ({{ member.email }})
</option>
{% endfor %}
</select>
<small class="field-hint">Der ausgewählte Kollege sieht diese Runde direkt in der Einkaufs-Zentrale.</small>
</div>
<div class="input-group" style="margin-top: 14px;">
<label for="edit_status">Status der Runde</label>
<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>
</form>
</div>
{% endif %}
<!-- Create new event -->
<div class="admin-card">
<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" 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>
</div>
<div class="input-group">
<label for="new_event_date">Brotzeit-Tag (Datum)</label>
<input type="date" id="new_event_date" name="event_date" required>
</div>
<div class="form-row-2">
<div class="input-group">
<label for="new_deadline_date">Bestellschluss (Tag)</label>
<input type="date" id="new_deadline_date" name="deadline_date" required>
</div>
<div class="input-group">
<label for="new_deadline_time">Bestellschluss (Uhrzeit)</label>
<input type="time" id="new_deadline_time" name="deadline_time" value="08:30" required>
</div>
</div>
<div class="input-group">
<label for="new_sponsor">🎉 Sponsor / Anlass (Optional)</label>
<input type="text" id="new_sponsor" name="sponsor_name" placeholder="z. B. Geburtstag von Anna">
</div>
<div class="input-group-checkbox">
<label>
<input type="checkbox" name="allow_guests" value="true" checked>
<strong>Gäste- & Dienstleister-Bestellungen erlauben</strong>
</label>
</div>
<div class="input-group" style="margin-top: 14px;">
<label for="new_buyer">🛒 Zugewiesener Einkäufer für diese Runde (Optional)</label>
<select id="new_buyer" name="buyer_id">
<option value="">-- Nicht zugewiesen / Admin übernimmt --</option>
{% for member in team_members %}
<option value="{{ member.id }}">
{{ member.first_name }} {{ member.last_name }} ({{ member.email }})
</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn-primary" style="margin-top: 1rem;">Runde jetzt starten</button>
</form>
</div>
</div>
<!-- TAB 3: PRODUKTE & SORTIMENT -->
{% elif active_tab == 'products' %}
<div class="admin-columns">
<div class="admin-card" style="grid-column: 1 / -1;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<div>
<h2>🥨 Sortiment verwalten ({{ products|length }} Artikel)</h2>
<p class="card-hint">Artikel für die Bestellkarte anpassen, aktivieren oder deaktivieren:</p>
</div>
<a href="/setup" class="btn-secondary" style="font-size: 0.85rem;">⚡ Standard-Bayerisch vorladen</a>
</div>
<div class="orders-table-wrapper">
<table class="data-table">
<thead>
<tr>
<th>Artikel</th>
<th>Beschreibung</th>
<th>Preis</th>
<th>Einheit</th>
<th>Status</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
{% for prod in products %}
<tr class="{% if not prod.is_active %}row-inactive{% endif %}">
<td>
<strong>{{ prod.name }}</strong>
{% if prod.id in locked_product_ids %}
<span class="badge-role" style="background: rgba(245, 158, 11, 0.15); color: #f59e0b; margin-left: 6px;" title="Für die offene Runde bestellt. Preis und Deaktivierung gesichert.">🔒 Gesichert</span>
{% endif %}
</td>
<td><small class="text-muted">{{ prod.description }}</small></td>
<td><strong>{{ "%.2f"|format(prod.price) }} €</strong></td>
<td>{{ prod.unit }}</td>
<td>
{% if prod.is_active %}
<span class="badge-role" style="background: rgba(34, 197, 94, 0.15); color: #86efac;">Aktiv</span>
{% else %}
<span class="badge-role" style="background: rgba(239, 68, 68, 0.15); color: #fca5a5;">Inaktiv</span>
{% endif %}
</td>
<td>
<div style="display: flex; gap: 6px;">
<button type="button" class="btn-copy-small" onclick="openEditProduct('{{ prod.id }}', '{{ prod.name|escape }}', '{{ prod.description|escape }}', '{{ '%.2f'|format(prod.price) }}', '{{ prod.unit }}', '{{ prod.sort_order }}', {{ 'true' if prod.id in locked_product_ids else 'false' }})">✏️ Bearbeiten</button>
{% if prod.id in locked_product_ids and prod.is_active %}
<button type="button" class="btn-revoke-small" style="opacity: 0.5; cursor: not-allowed; color: #fca5a5;" title="In der laufenden Runde bereits bestellt – Deaktivierung gesperrt" onclick="alert('Dieser Artikel wurde in der laufenden Runde bereits bestellt und kann daher erst nach Abschluss des Events deaktiviert werden.')">Deaktivieren</button>
{% else %}
<form action="/admin/products/{{ prod.id }}/toggle" method="POST" style="margin: 0; display: inline;">
<button type="submit" class="btn-revoke-small" style="color: {% if prod.is_active %}#fca5a5{% else %}#86efac{% endif %};">
{% if prod.is_active %}Deaktivieren{% else %}Aktivieren{% endif %}
</button>
</form>
{% endif %}
</div>
</td>
</tr>
{% else %}
<tr><td colspan="6" class="empty-cell">Noch keine Produkte angelegt. Nutze das Formular unten oder lade das Standard-Sortiment!</td></tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- Add new product -->
<div class="admin-card" style="margin-top: 2rem; background: rgba(11, 15, 25, 0.6);">
<h3>➕ Neues Produkt hinzufügen</h3>
<form action="/admin/products/new" method="POST" class="admin-form" style="margin-top: 14px;">
<div class="form-row-2">
<div class="input-group">
<label for="prod_name">Artikelname</label>
<input type="text" id="prod_name" name="name" placeholder="z. B. Wiener Würstchen" required>
</div>
<div class="input-group">
<label for="prod_desc">Beschreibung (optional)</label>
<input type="text" id="prod_desc" name="description" placeholder="z. B. Knackig, vom lokalen Metzger">
</div>
</div>
<div class="form-row-3">
<div class="input-group">
<label for="prod_price">Preis (€)</label>
<input type="text" id="prod_price" name="price" placeholder="2.50" required>
</div>
<div class="input-group">
<label for="prod_unit">Einheit</label>
<input type="text" id="prod_unit" name="unit" value="Stück" placeholder="Stück, Paar, Portion">
</div>
<div class="input-group">
<label for="prod_sort">Sortierung</label>
<input type="number" id="prod_sort" name="sort_order" value="10">
</div>
</div>
<button type="submit" class="btn-primary" style="margin-top: 10px;">Artikel speichern</button>
</form>
</div>
</div>
</div>
<!-- Edit Product Modal -->
<div id="editProductModal" class="modal-backdrop" style="display: none;">
<div class="modal-card">
<h3>✏️ Produkt bearbeiten</h3>
<div id="productLockedNotice" class="alert-warning-card" style="display: none; padding: 10px 14px; margin-top: 10px; margin-bottom: 10px; font-size: 0.85rem; border-color: rgba(245, 158, 11, 0.4);">
🔒 <strong>Artikel gesichert:</strong> Dieses Produkt wurde für eine offene Runde bereits bestellt. Der Preis ist eingefroren und kann erst nach Abschluss des Events geändert werden.
</div>
<form id="editProductForm" method="POST" class="admin-form" style="margin-top: 14px;">
<div class="input-group">
<label for="m_prod_name">Artikelname</label>
<input type="text" id="m_prod_name" name="name" required>
</div>
<div class="input-group">
<label for="m_prod_desc">Beschreibung</label>
<input type="text" id="m_prod_desc" name="description">
</div>
<div class="form-row-3">
<div class="input-group">
<label for="m_prod_price">Preis (€)</label>
<input type="text" id="m_prod_price" name="price" required>
</div>
<div class="input-group">
<label for="m_prod_unit">Einheit</label>
<input type="text" id="m_prod_unit" name="unit" required>
</div>
<div class="input-group">
<label for="m_prod_sort">Sortierung</label>
<input type="number" id="m_prod_sort" name="sort_order">
</div>
</div>
<div style="display: flex; gap: 10px; margin-top: 16px;">
<button type="submit" class="btn-primary">Änderungen speichern</button>
<button type="button" class="btn-secondary" onclick="closeEditProduct()">Abbrechen</button>
</div>
</form>
</div>
</div>
<!-- TAB 4: TEAM & MASSEN-EINLADUNG -->
{% elif active_tab == 'team' %}
<div class="admin-columns">
<!-- Einladungs-Bereich (Einzeln & Massen-Einladung) -->
<div class="admin-card">
<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="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="unlimited" selected>∞ Unbegrenzt (Dauerhafter Team-Link)</option>
<option value="14">14 Tage</option>
<option value="30">30 Tage</option>
</select>
</div>
</div>
<button type="submit" class="btn-secondary">🔗 Offenen Link generieren</button>
</form>
{% if pending_invitations %}
<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" data-token="{{ inv.token }}" data-email="{{ inv.email or 'Team' }}">
<div class="invite-info">
<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</span>
{% endif %}
<br>
<small class="text-muted">
{% if inv.expires_at %}Gültig bis {{ inv.expires_at.strftime('%d.%m.%Y') }}{% else %}Unbegrenzt gültig{% endif %}
</small>
</div>
<div class="invite-actions">
<button type="button" class="btn-copy-small" onclick="copyInviteUrl('{{ inv.token }}')">📋 Kopieren</button>
<form action="/admin/invitations/{{ inv.id }}/revoke" method="POST" style="margin: 0; display: inline;">
<button type="submit" class="btn-revoke-small" title="Link widerrufen">✕</button>
</form>
</div>
</div>
{% endfor %}
</div>
{% endif %}
</div>
<!-- Current team members list -->
<div class="admin-card">
<h2>👥 Registrierte Teammitglieder ({{ team_members|length }})</h2>
<p class="card-hint">Feste Teamkonten dieses Portals:</p>
<div class="orders-table-wrapper">
<table class="data-table">
<thead>
<tr>
<th>Name & E-Mail</th>
<th>Rolle</th>
<th style="text-align: right;">Guthaben</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
{% for member in team_members %}
<tr>
<td>
<strong>{{ member.first_name }} {{ member.last_name }}</strong><br>
<small class="text-muted">{{ member.email }}</small>
</td>
<td>
{% if current_user.role == 'admin' and member.id != current_user.id %}
<form action="/admin/users/{{ member.id }}/role" method="POST" style="margin: 0;">
<select name="role" onchange="this.form.submit()" style="padding: 4px 8px; font-size: 0.8rem; border-radius: 6px; background: var(--bg-input); color: var(--text-main); border: 1px solid var(--border-color);">
<option value="user" {% if member.role == 'user' %}selected{% endif %}>👤 Mitarbeiter</option>
<option value="organizer" {% if member.role == 'organizer' %}selected{% endif %}>📋 Organisator</option>
<option value="admin" {% if member.role == 'admin' %}selected{% endif %}>👑 Admin</option>
</select>
</form>
{% else %}
{% if member.role == 'admin' %}
<span class="badge-role admin">👑 Admin</span>
{% elif member.role == 'organizer' %}
<span class="badge-role organizer">📋 Organisator</span>
{% else %}
<span class="badge-role">👤 Mitarbeiter</span>
{% endif %}
{% endif %}
</td>
<td style="text-align: right; font-weight: 700;">
<a href="/kasse" style="text-decoration: none; color: {% if member.balance > 0 %}var(--success){% elif member.balance < 0 %}var(--danger){% else %}var(--text-muted){% endif %};">
{{ "%.2f"|format(member.balance) }} €
</a>
</td>
<td>
<div style="display: flex; gap: 6px;">
{% if current_user.role == 'admin' %}
<button type="button" class="btn-copy-small" onclick="openResetPassword('{{ member.id }}', '{{ member.first_name|escape }} {{ member.last_name|escape }}')">🔑 Passwort</button>
{% if member.id != current_user.id %}
<form action="/admin/users/{{ member.id }}/delete" method="POST" style="margin: 0;" onsubmit="return confirm('Möchtest du {{ member.first_name }} {{ member.last_name }} wirklich unwiderruflich löschen?');">
<button type="submit" class="btn-revoke-small" title="Benutzer löschen">🗑️</button>
</form>
{% endif %}
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</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>
<!-- Password Reset Modal -->
<div id="resetPasswordModal" class="modal-backdrop" style="display: none;">
<div class="modal-card">
<h3>🔑 Passwort zurücksetzen</h3>
<p class="card-hint" id="resetModalUserHint">Neues Passwort für den Benutzer vergeben:</p>
<form id="resetPasswordForm" method="POST" class="admin-form">
<div class="input-group">
<label for="new_pw_input">Neues Passwort</label>
<div style="display: flex; gap: 8px;">
<input type="text" id="new_pw_input" name="new_password" required minlength="4" placeholder="Mindestens 4 Zeichen">
<button type="button" class="btn-secondary" onclick="generateRandomPw()">🎲 Zufall</button>
</div>
</div>
<div style="display: flex; gap: 10px; margin-top: 16px;">
<button type="submit" class="btn-primary">Passwort jetzt speichern</button>
<button type="button" class="btn-secondary" onclick="closeResetPassword()">Abbrechen</button>
</div>
</form>
</div>
</div>
{% endif %}
<!-- Outlook Template Modal (Global für alle Tabs verfügbar) -->
<div id="outlookModal" class="modal-backdrop" style="display: none;">
<div class="modal-card" style="max-width: 640px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">
<h3 style="margin: 0;">✉️ Outlook-Einladungsvorlage</h3>
<button type="button" class="btn-revoke-small" onclick="closeOutlookModal()" style="font-size: 1rem; padding: 2px 8px;">✕</button>
</div>
<p class="card-hint">Kopiere diese Vorlage direkt in Outlook oder öffne dein Mailprogramm per Klick:</p>
<div class="input-group" style="margin-bottom: 12px;">
<label for="outlookSubject">Betreff</label>
<input type="text" id="outlookSubject" readonly value="🥨 Einladung: {{ event.title if event else 'Brotzeitrunde' }} ({{ event.event_date.strftime('%d.%m.%Y') if event else '' }})" style="width: 100%;">
</div>
<div class="input-group" style="margin-bottom: 16px;">
<label for="outlookBody">E-Mail-Text (inkl. Bestelllinks)</label>
<textarea id="outlookBody" rows="11" readonly style="width: 100%; padding: 12px; font-family: inherit; font-size: 0.85rem; line-height: 1.5; background: rgba(11, 15, 25, 0.8); border: 1px solid var(--border-color); border-radius: var(--radius-md); color: #fff; resize: vertical;">{% if event %}Hallo zusammen,
wir machen wieder eine gemeinsame Brotzeitrunde am {{ event.event_date.strftime('%d.%m.%Y') }}!
🥨 Bitte tragt eure Bestellung bis spätestens {{ event.deadline.strftime('%d.%m.%Y um %H:%M Uhr') }} im Brotzeit-Portal ein:
👉 https://demo.dev.waiswuaschd.de/?event_id={{ event.id }}
{% if event.allow_guests and event.guest_token %}
Für externe Dienstleister & Gäste ohne Firmenkonto:
🔗 Gast-Bestelllink: https://demo.dev.waiswuaschd.de/?event_id={{ event.id }}&token={{ event.guest_token }}
{% endif %}
{% if event.sponsor_name %}
🎉 Gute Nachrichten: Die Runde wird heute von {{ event.sponsor_name }} spendiert!
{% endif %}
Viele Grüße,
{{ current_user.first_name }} {{ current_user.last_name }}{% endif %}</textarea>
</div>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<button type="button" class="btn-primary" onclick="copyOutlookTemplate()">📋 Text kopieren</button>
<button type="button" class="btn-secondary" onclick="openMailtoClient()">✉️ In Outlook öffnen</button>
{% if event %}
<a href="/events/{{ event.id }}/calendar.ics" class="btn-secondary" style="text-decoration: none;">📅 .ics Kalenderdatei</a>
{% endif %}
<button type="button" class="btn-secondary" onclick="closeOutlookModal()" style="margin-left: auto;">Schließen</button>
</div>
</div>
</div>
</div>
</div>
<script>
function copyShoppingList() {
let text = "🥨 Einkaufsliste für {{ event.title if event else 'Brotzeit' }}:\n\n";
{% for name, data in item_sums.items() %}
text += "• {{ name }}: {{ data.quantity }} {{ data.unit }}\n";
{% endfor %}
text += "\nGesamtsumme: {{ '%.2f'|format(total_revenue) }} €";
navigator.clipboard.writeText(text).then(() => {
alert("Einkaufsliste in die Zwischenablage kopiert! Kann direkt in WhatsApp oder Slack eingefügt werden.");
});
}
function copyInviteUrl(token) {
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 den Kollegen senden.");
});
}
function copyGuestPassLink(eventId, guestToken) {
const origin = window.location.origin;
const url = origin + '/?event_id=' + eventId + (guestToken ? '&token=' + guestToken : '');
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 openOutlookModal() {
document.getElementById('outlookModal').style.display = 'flex';
}
function closeOutlookModal() {
document.getElementById('outlookModal').style.display = 'none';
}
function copyOutlookTemplate() {
const body = document.getElementById('outlookBody').value;
navigator.clipboard.writeText(body).then(() => {
alert("Outlook-Einladungstext in die Zwischenablage kopiert! Kann direkt in Outlook eingefügt werden.");
});
}
function openMailtoClient() {
const subject = encodeURIComponent(document.getElementById('outlookSubject').value);
const body = encodeURIComponent(document.getElementById('outlookBody').value);
window.location.href = `mailto:?subject=${subject}&body=${body}`;
}
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!");
});
}
function openResetPassword(userId, userName) {
const modal = document.getElementById('resetPasswordModal');
const form = document.getElementById('resetPasswordForm');
const hint = document.getElementById('resetModalUserHint');
form.action = '/admin/users/' + userId + '/reset-password';
hint.innerText = 'Neues Passwort für ' + userName + ' vergeben:';
document.getElementById('new_pw_input').value = '';
modal.style.display = 'flex';
}
function closeResetPassword() {
document.getElementById('resetPasswordModal').style.display = 'none';
}
function generateRandomPw() {
const chars = 'abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ23456789!#';
let pw = '';
for (let i = 0; i < 8; i++) {
pw += chars.charAt(Math.floor(Math.random() * chars.length));
}
document.getElementById('new_pw_input').value = pw;
}
function openEditProduct(id, name, desc, price, unit, sort, isLocked) {
const modal = document.getElementById('editProductModal');
const form = document.getElementById('editProductForm');
form.action = '/admin/products/' + id + '/edit';
document.getElementById('m_prod_name').value = name;
document.getElementById('m_prod_desc').value = desc;
document.getElementById('m_prod_price').value = price;
document.getElementById('m_prod_unit').value = unit;
document.getElementById('m_prod_sort').value = sort;
const notice = document.getElementById('productLockedNotice');
const priceInput = document.getElementById('m_prod_price');
if (isLocked) {
if (notice) notice.style.display = 'block';
priceInput.readOnly = true;
priceInput.title = 'Preis ist gesichert (bereits bestellt)';
priceInput.style.opacity = '0.7';
} else {
if (notice) notice.style.display = 'none';
priceInput.readOnly = false;
priceInput.title = '';
priceInput.style.opacity = '1';
}
modal.style.display = 'flex';
}
function closeEditProduct() {
document.getElementById('editProductModal').style.display = 'none';
}
</script>
{% endblock %}