feat: Paket 2 - Product catalog management, guest orders, sponsor rounds, password reset, user deletion, unlimited invite links and setup wizard

This commit is contained in:
2026-09-21 11:32:41 +02:00
parent 83393e363e
commit e5ca9c3cbf
7 changed files with 910 additions and 119 deletions
+302 -23
View File
@@ -3,7 +3,7 @@
{% block content %}
<div class="container">
<!-- Printable distribution sheet for the kitchen (hidden in screen, visible in print) -->
<!-- Printable distribution sheet for the kitchen (hidden on screen, visible in print) -->
<div class="print-only">
<div class="print-header">
<h1>🥨 Schmankerl – Brotzeit-Verteilliste</h1>
@@ -12,6 +12,11 @@
<strong>Datum:</strong> {{ event.event_date.strftime('%d.%m.%Y') if event else '' }} &nbsp;|&nbsp;
<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>
</div>
{% endif %}
</div>
<div class="print-section">
@@ -50,14 +55,31 @@
{% 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></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;">{{ "%.2f"|format(order.total_price) }} €</td>
<td style="text-align: center;">{% if order.is_paid %}Bezahlt ✓{% else %}Offen{% endif %}</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>
@@ -88,6 +110,9 @@
<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 %}">
🥨 Produkte & 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 }})
</a>
@@ -109,6 +134,17 @@
{% endif %}
{% if event %}
{% 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">
@@ -117,15 +153,19 @@
</div>
<div class="stat-card">
<div class="stat-label">Bereits bezahlt</div>
<div class="stat-value success">{{ "%.2f"|format(total_paid) }} €</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">{{ "%.2f"|format(total_revenue - total_paid) }} €</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 }} Kollegen</div>
<div class="stat-value">{{ orders|length }} Personen</div>
</div>
</div>
@@ -133,7 +173,7 @@
<!-- Aggregated Shopping List -->
<div class="admin-card">
<h2>🛒 Sammelliste für Metzger & Bäcker</h2>
<p class="card-hint">Aufsummierte Mengen für den Einkauf:</p>
<p class="card-hint">Aufsummierte Gesamtmengen für den Einkauf:</p>
<div class="shopping-table-wrapper">
<table class="data-table">
@@ -162,7 +202,7 @@
<!-- Individual Orders & Payment Checklist -->
<div class="admin-card">
<h2>👥 Wer hat bestellt? (Strichliste)</h2>
<p class="card-hint">Klicke auf den Status, um bezahlt/offen umzuschalten:</p>
<p class="card-hint">Einzelübersicht mit Artikeln und Kassenstatus:</p>
<div class="orders-table-wrapper">
<table class="data-table">
@@ -178,7 +218,11 @@
{% for order in orders %}
<tr>
<td>
<strong>{{ order.user.first_name }} {{ order.user.last_name }}</strong><br>
<strong>{{ order.user.first_name }} {{ order.user.last_name }}</strong>
{% if order.user.role == 'guest' %}
<span class="badge-role guest">Gast</span>
{% endif %}
<br>
<small class="text-muted">{{ order.user.email }}</small>
</td>
<td>
@@ -188,13 +232,20 @@
{% endfor %}
</ul>
</td>
<td><strong>{{ "%.2f"|format(order.total_price) }} €</strong></td>
<td>
<strong>{{ "%.2f"|format(order.total_price) }} €</strong>
{% if event.sponsor_name %}<br><small class="text-muted">von Sponsor</small>{% endif %}
</td>
<td>
{% 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 %}">
{% if order.is_paid %}✓ Bezahlt{% else %}⏳ Offen{% endif %}
</button>
</form>
{% endif %}
</td>
</tr>
{% else %}
@@ -220,7 +271,7 @@
{% if event %}
<div class="admin-card">
<h2>✏️ Aktives Event bearbeiten</h2>
<p class="card-hint">Passe Titel, Termin und die Bestellfrist sekundengenau an:</p>
<p class="card-hint">Passe Titel, Frist, Gast-Erlaubnis und Sponsor an:</p>
<form action="/admin/events/{{ event.id }}/edit" method="POST" class="admin-form">
<div class="input-group">
@@ -242,6 +293,17 @@
</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">
<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)
</label>
</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>
@@ -277,18 +339,155 @@
<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-Bestellungen erlauben</strong>
</label>
</div>
<button type="submit" class="btn-primary" style="margin-top: 1rem;">Runde jetzt starten</button>
</form>
</div>
</div>
<!-- TAB 3: TEAM & EINLADUNGEN -->
<!-- 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>
<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></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 }}')">✏️ Bearbeiten</button>
<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>
</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 (Simple JS) -->
<div id="editProductModal" class="modal-backdrop" style="display: none;">
<div class="modal-card">
<h3>✏️ Produkt bearbeiten</h3>
<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 & EINLADUNGEN -->
{% elif active_tab == 'team' %}
<div class="admin-columns">
<!-- Generate invite link -->
<div class="admin-card">
<h2>✉️ Teammitglied einladen</h2>
<p class="card-hint">Erzeuge einen sicheren Einladungslink zum Teilen per Chat (Slack, Teams, WhatsApp):</p>
<p class="card-hint">Erzeuge einen Einladungslink (einmalig oder unbegrenzt dauerhaft):</p>
<form action="/admin/invitations/create" method="POST" class="admin-form">
<div class="input-group">
@@ -307,8 +506,9 @@
<div class="input-group">
<label for="inv_valid">Gültigkeit</label>
<select id="inv_valid" name="valid_days">
<option value="unlimited" selected>∞ Unbegrenzt (Dauerhafter Team-Link)</option>
<option value="7">7 Tage</option>
<option value="14" selected>14 Tage</option>
<option value="14">14 Tage</option>
<option value="30">30 Tage</option>
</select>
</div>
@@ -317,15 +517,20 @@
</form>
{% if pending_invitations %}
<h3 style="margin-top: 2rem;">Offene Einladungslinks</h3>
<h3 style="margin-top: 2rem;">Aktive Einladungslinks</h3>
<div class="invitations-list">
{% for inv in pending_invitations %}
<div class="invite-item">
<div class="invite-info">
<strong>{% if inv.email %}{{ inv.email }}{% else %}Offener Link{% endif %}</strong>
<strong>{% if inv.email %}{{ inv.email }}{% else %}Offener 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>
{% endif %}
<br>
<small class="text-muted">Gültig bis {{ inv.expires_at.strftime('%d.%m.%Y') }}</small>
<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>
@@ -348,23 +553,37 @@
<table class="data-table">
<thead>
<tr>
<th>Name</th>
<th>E-Mail</th>
<th>Name & E-Mail</th>
<th>Rolle</th>
<th>Aktionen</th>
</tr>
</thead>
<tbody>
{% for member in team_members %}
<tr>
<td><strong>{{ member.first_name }} {{ member.last_name }}</strong></td>
<td><small class="text-muted">{{ member.email }}</small></td>
<td>
<strong>{{ member.first_name }} {{ member.last_name }}</strong><br>
<small class="text-muted">{{ member.email }}</small>
</td>
<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 %}
</td>
<td>
<div style="display: flex; gap: 6px;">
<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 %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
@@ -372,6 +591,27 @@
</div>
</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 %}
</div>
@@ -394,8 +634,47 @@
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.");
alert("Einladungslink kopiert!\n\n" + url + "\n\nDu kannst ihn jetzt an das Team senden.");
});
}
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) {
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;
modal.style.display = 'flex';
}
function closeEditProduct() {
document.getElementById('editProductModal').style.display = 'none';
}
</script>
{% endblock %}
+46 -5
View File
@@ -22,6 +22,12 @@
Bestellschluss: <strong>{{ event.deadline.strftime('%d.%m.%Y um %H:%M Uhr') }}</strong>
</p>
{% if event.sponsor_name %}
<div class="sponsor-badge-banner">
🎉 <strong>Runde gesponsert:</strong> Die Brotzeit geht heute auf <strong>{{ event.sponsor_name }}</strong>!
</div>
{% endif %}
<div class="banner-stats">
<span>👥 Bereits <strong>{{ order_count }}</strong> Bestellungen von Kollegen eingegangen</span>
</div>
@@ -48,7 +54,14 @@
<div class="product-info">
<h3>{{ product.name }}</h3>
<p class="product-desc">{{ product.description }}</p>
<div class="product-price">{{ "%.2f"|format(product.price) }} € <small>/ {{ product.unit }}</small></div>
<div class="product-price">
{% if event.sponsor_name %}
<span style="text-decoration: line-through; opacity: 0.6; font-size: 0.9rem;">{{ "%.2f"|format(product.price) }} €</span>
<span style="color: var(--accent-gold); font-weight: 700;"> 0,00 € (Gesponsert)</span>
{% else %}
{{ "%.2f"|format(product.price) }} € <small>/ {{ product.unit }}</small>
{% endif %}
</div>
</div>
<div class="quantity-control">
<button type="button" class="qty-btn" onclick="changeQty({{ product.id }}, -1)" {% if is_closed %}disabled{% endif %}>-</button>
@@ -63,15 +76,38 @@
<!-- Right: User Details & Summary -->
<div class="summary-section">
<div class="summary-card">
<h2>2. Zusammenfassung</h2>
<h2>2. Bestellung & Angaben</h2>
{% if current_user %}
<div class="logged-in-badge">
<div class="user-avatar">👤</div>
<div>
<strong>{{ current_user.first_name }} {{ current_user.last_name }}</strong><br>
<strong>{{ current_user.first_name }} {{ current_user.last_name }}</strong>
{% if current_user.role == 'guest' %}<small>(Gast)</small>{% endif %}
<br>
<small class="text-muted">{{ current_user.email }}</small>
</div>
</div>
{% else %}
<!-- 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.
</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">
</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">
</div>
<div class="input-group">
<label for="g_email">E-Mail (optional für Bestätigung)</label>
<input type="email" id="g_email" name="guest_email" placeholder="name@firma.de">
</div>
</div>
{% endif %}
{% if existing_order %}
<div class="notice-box">
@@ -87,6 +123,12 @@
<div class="total-amount" id="totalAmount">0,00 €</div>
</div>
{% if event.sponsor_name %}
<div style="font-size: 0.85rem; color: var(--accent-gold); margin-bottom: 14px; text-align: center;">
🎁 Die Rechnung wird von <strong>{{ event.sponsor_name }}</strong> übernommen!
</div>
{% endif %}
<button type="submit" class="btn-submit" id="submitBtn" {% if is_closed %}disabled{% endif %}>
{% if is_closed %}
🔒 Bestellschluss vorüber
@@ -112,7 +154,7 @@
<div class="empty-icon">🏖️</div>
<h2>Aktuell keine offene Bestellrunde</h2>
<p>Für dieses Team ist im Moment keine Brotzeit geplant.</p>
{% if current_user.role == 'admin' %}
{% if current_user and current_user.role == 'admin' %}
<a href="/admin?tab=event" class="btn-primary" style="margin-top: 1rem;">Neue Bestellrunde im Cockpit anlegen</a>
{% endif %}
</div>
@@ -163,7 +205,6 @@
if (containerEl) {
containerEl.classList.add('expired');
}
// Formular sperren
if (submitBtn) {
submitBtn.disabled = true;
submitBtn.innerText = "🔒 Bestellschluss vorüber";
+9
View File
@@ -62,6 +62,15 @@
<button type="submit" class="btn-submit">Anmelden</button>
</form>
{% if allow_guests %}
<div style="text-align: center; margin-top: 1.5rem; padding-top: 1.5rem; border-top: 1px solid var(--border-color);">
<p class="card-hint" style="margin-bottom: 8px;">Nur zu Besuch oder für eine Runde eingeladen?</p>
<a href="/" class="btn-secondary" style="width: 100%; text-align: center; display: block;">
🥨 Als Gast mitbestellen (ohne Registrierung)
</a>
</div>
{% endif %}
<div class="invite-notice-card">
<div class="invite-icon">✉️</div>
<div>
+68
View File
@@ -0,0 +1,68 @@
{% extends "base.html" %}
{% block content %}
<div class="container narrow">
<div class="login-box" style="max-width: 650px; margin: 0 auto;">
<div class="login-header">
<div class="login-icon">🥨</div>
<h1>Mandanten-Setup & Schnellstart</h1>
<p class="login-sub">Portal: <strong>{{ tenant.name }}</strong> ({{ tenant.slug }})</p>
<div class="privacy-note">
🚀 Willkommen bei Schmankerl! Hier richtest du dein Portal mit 3 einfachen Schritten ein.
</div>
</div>
<!-- Step 1: Sortiment -->
<div class="setup-step-card">
<div class="step-num">1</div>
<div class="step-content">
<h3>Bayerisches Standard-Sortiment laden</h3>
<p class="card-hint" style="margin-bottom: 12px;">
Aktuell im Sortiment: <strong>{{ prod_count }} Artikel</strong>.<br>
Lädt mit einem Klick die Klassiker vor: Original Münchner Weißwürste, frische Laugenbrezen, Händlmaier Hausmachersenf, warme Leberkassemmeln, Wiener Würstchen und alkoholfreies Weißbier.
</p>
<form action="/setup/seed-products" method="POST">
<button type="submit" class="btn-primary" style="font-size: 0.9rem;">
🥨 Standard-Sortiment jetzt laden
</button>
</form>
</div>
</div>
<!-- Step 2: Event -->
<div class="setup-step-card">
<div class="step-num">2</div>
<div class="step-content">
<h3>Erste Brotzeitrunde planen</h3>
<p class="card-hint" style="margin-bottom: 12px;">
Lege Datum, Uhrzeit und Bestellfrist fest, damit Kollegen und Gäste bestellen können.
</p>
<a href="/admin?tab=event" class="btn-secondary" style="font-size: 0.9rem;">
📅 Event-Planung im Cockpit öffnen
</a>
</div>
</div>
<!-- Step 3: Invite -->
<div class="setup-step-card">
<div class="step-num">3</div>
<div class="step-content">
<h3>Team einladen (Dauerhafter Team-Link)</h3>
<p class="card-hint" style="margin-bottom: 12px;">
Erzeuge einen unbegrenzten Einladungslink und pinne ihn in deinen Slack- oder Teams-Kanal.
</p>
<a href="/admin?tab=team" class="btn-secondary" style="font-size: 0.9rem;">
👥 Team-Link im Cockpit generieren
</a>
</div>
</div>
<div style="text-align: center; margin-top: 2rem;">
<a href="/admin" class="btn-primary">Zum Admin-Cockpit</a>
</div>
</div>
</div>
{% endblock %}