985 lines
47 KiB
HTML
985 lines
47 KiB
HTML
{% 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 %}
|