Files
maddin 847f20c9d7
CI / checks (push) Has been cancelled
chore: sync public repository
2026-03-22 15:36:47 +00:00

821 lines
41 KiB
HTML

{% extends "base.html" %}
{% from "ui/segmented_toggle.html" import segmented_toggle %}
{% from "ui/collapsible_section.html" import collapsible_section %}
{% block title %}Einstellungen{% endblock %}
{% block body_class %}settings-theme{% endblock %}
{% block page_class %}settings-page{% endblock %}
{% block content %}
<section class="top-row">
<h1>Einstellungen</h1>
</section>
{% if is_admin %}
{{ segmented_toggle([
{'href': '/settings?tab=settings', 'label': 'Einstellungen', 'active': active_settings_tab != 'admin'},
{'href': '/settings?tab=admin', 'label': 'Admin', 'active': active_settings_tab == 'admin'}
], 'Einstellungsbereiche', 'settings-tabs') }}
{% endif %}
<section class="settings-grid">
{% if not is_admin or active_settings_tab != 'admin' %}
{% call collapsible_section('Urlaub', 'settings-vacation') %}
<p class="muted">Lege hier deine Gesamturlaubstage pro Kalenderjahr fest. Im Header siehst du danach Resturlaub/Gesamturlaub.</p>
<form method="post" action="/settings/vacation-allowance" class="stack">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<input type="hidden" name="vacation_show_in_header_present" value="1" />
<div class="inline-grid">
<label>
Gesamturlaubstage pro Jahr
<input type="number"
min="0"
max="365"
step="1"
name="vacation_days_total"
value="{{ user.vacation_days_total }}"
required />
</label>
<label>
Aktueller Stand ({{ header_vacation_year }})
<input type="text"
value="{{ header_vacation_days_remaining }} / {{ header_vacation_days_total }} (verplant/genutzt: {{ header_vacation_days_used }})"
disabled />
</label>
</div>
<label class="checkbox-row">
<input type="checkbox"
name="vacation_show_in_header"
{% if user.vacation_show_in_header %}checked{% endif %} />
<span>Resturlaub im Header anzeigen</span>
</label>
<button type="submit" class="button">Speichern</button>
</form>
<p class="muted">Definierte Urlaubstage reduzieren automatisch das Wochen-Soll für die betroffenen Wochen.</p>
<form method="post" action="/settings/vacations/add" class="stack">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<div class="inline-grid">
<label>
Startdatum
<input type="date" name="start_date" value="{{ vacation_start }}" required />
</label>
<label>
Enddatum
<input type="date" name="end_date" value="{{ vacation_end }}" required />
</label>
</div>
<label class="checkbox-row">
<input type="checkbox" name="include_weekends" />
<span>Wochenenden mit einschließen</span>
</label>
<label>
Notiz (optional)
<input type="text" name="notes" />
</label>
<button type="submit" class="button">Speichern</button>
</form>
<div class="vacation-list">
{% for vacation in vacation_ranges %}
<article class="vacation-item">
<div>
<strong>{{ vacation.start_date.strftime("%d.%m.%Y") }} - {{ vacation.end_date.strftime("%d.%m.%Y") }}</strong>
<p class="muted">Effektive Urlaubstage unter Berücksichtigung deiner Arbeitstage.</p>
</div>
<form method="post"
action="/settings/vacations/delete-range"
class="inline-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<input type="hidden"
name="start_date"
value="{{ vacation.start_date.isoformat() }}" />
<input type="hidden"
name="end_date"
value="{{ vacation.end_date.isoformat() }}" />
<button type="submit" class="button danger">Löschen</button>
</form>
</article>
{% else %}
<p class="muted">Noch keine Urlaubszeiträume angelegt.</p>
{% endfor %}
</div>
{% endcall %}
{% call collapsible_section('Wochenstunden', 'settings-weekly-target') %}
<p class="muted">Lege fest, wie viele Stunden du generell pro Woche arbeiten möchtest (Standard-Soll).</p>
<form method="post" action="/settings/weekly-target" class="stack" data-component="break-settings-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<label>
Erfassungsmodus
<select name="entry_mode" required>
<option value="manual"
{% if user.entry_mode == 'manual' %}selected{% endif %}>Manuell (jeden Tag selbst erfassen)</option>
<option value="auto_until_today"
{% if user.entry_mode == 'auto_until_today' %}selected{% endif %}>Automatisch bis heute</option>
</select>
</label>
<label>
Wochenstunden
<input type="number"
min="0.25"
step="0.25"
name="weekly_target_hours"
value="{{ '%.2f'|format(settings_weekly_target_minutes / 60) |replace('.00', '') }}"
required />
</label>
<label class="checkbox-row">
<input type="checkbox"
name="automatic_break_rules_enabled"
data-break-settings-toggle
{% if user.automatic_break_rules_enabled %}checked{% endif %} />
<span>Gesetzliche Pausen automatisch nach deutscher Arbeitszeit berechnen</span>
</label>
<label>
Tägliche Pause in Minuten
<input type="number"
min="0"
step="1"
name="default_break_minutes"
value="{{ user.default_break_minutes }}"
data-break-settings-minutes
{% if user.automatic_break_rules_enabled %}disabled{% endif %} />
</label>
<p class="muted">
Dieser Wert wird für neue reguläre Arbeitszeiteinträge und automatische Einträge verwendet, solange die gesetzliche Pausenregel nicht aktiv ist.
</p>
<button type="submit" class="button">Speichern</button>
</form>
{% endcall %}
{% call collapsible_section('Standardansicht', 'settings-preferences') %}
<form method="post" action="/settings/preferences" class="stack">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<input type="hidden" name="preferred_month_view_mode" value="flat" />
<label>
Startansicht nach Anmeldung
<select name="preferred_home_view" required>
<option value="week"
{% if user.preferred_home_view == 'week' %}selected{% endif %}>Wochenansicht</option>
<option value="month"
{% if user.preferred_home_view == 'month' %}selected{% endif %}>Monatsansicht</option>
</select>
</label>
<label>
Theme
<select name="theme_preference" required>
<option value="auto"
{% if user.theme_preference == 'auto' %}selected{% endif %}>Automatisch</option>
<option value="dark"
{% if user.theme_preference == 'dark' %}selected{% endif %}>Dark</option>
<option value="light"
{% if user.theme_preference == 'light' %}selected{% endif %}>Light</option>
</select>
</label>
<button type="submit" class="button">Speichern</button>
</form>
{% endcall %}
{% call collapsible_section('Überstunden-Regeln', 'settings-overtime') %}
<p class="muted">Optionales Startdatum und Verfall für die kumulierte Überstunden-Berechnung.</p>
<form method="post" action="/settings/overtime" class="stack">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<div class="inline-grid">
<label>
Startdatum (optional)
<input type="date"
name="overtime_start_date"
value="{{ user.overtime_start_date.isoformat() if user.overtime_start_date else '' }}" />
</label>
<label>
Verfall in Tagen (optional)
<input type="number"
min="1"
step="1"
name="overtime_expiry_days"
value="{{ user.overtime_expiry_days if user.overtime_expiry_days is not none else '' }}" />
</label>
</div>
<label class="checkbox-row">
<input type="checkbox"
name="expire_negative_overtime"
{% if user.expire_negative_overtime %}checked{% endif %} />
<span>Negative Stunden verfallen ebenfalls</span>
</label>
<button type="submit" class="button">Speichern</button>
</form>
<div class="vacation-list">
<article class="vacation-item">
<div>
<strong>Saldoaufbau gesamt</strong>
<p class="muted">{{ minutes_to_hhmm(overtime_adjustment_total_positive) }}</p>
</div>
<div>
<strong>Saldoabbau gesamt</strong>
<p class="muted">{{ minutes_to_hhmm(overtime_adjustment_total_negative) }}</p>
</div>
<div>
<strong>Ganze Tage</strong>
<p class="muted">{{ overtime_adjustment_full_day_count }}</p>
</div>
</article>
{% for adjustment in overtime_adjustments %}
<article class="vacation-item">
<div>
<strong>{{ adjustment.date.strftime("%d.%m.%Y") }}</strong>
<p class="muted">{{ adjustment.notes or "Stundenausgleich" }}</p>
</div>
<div class="settings-adjustment-meta">
<strong class="{% if adjustment.minutes < 0 %}negative{% else %}positive{% endif %}">
{{ '+' if adjustment.minutes > 0 else '' }}{{ minutes_to_hhmm(adjustment.minutes) }}
</strong>
<a href="/overtime-adjustment/edit?date={{ adjustment.date.isoformat() }}" class="button ghost">Bearbeiten</a>
</div>
</article>
{% else %}
<p class="muted">Noch keine Ausgleichsstunden eingetragen.</p>
{% endfor %}
</div>
{% endcall %}
{% call collapsible_section('Arbeitsstunden-Counter', 'settings-workhours-counter') %}
<div class="settings-workhours-intro stack">
<p class="muted">Hier kannst du einen Zeitraum festlegen und sehen, wie viele Stunden du darin insgesamt gesammelt hast. Das ist zum Beispiel hilfreich für Praxisstunden im Anerkennungsjahr.</p>
<p class="muted">Urlaub, Feiertage und Krankheit werden nur dann mitgezählt, wenn du das unter „Relevante Arbeitstage“ aktiviert hast.</p>
</div>
<form method="post" action="/settings/workhours-counter" class="settings-workhours-form stack">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<label class="checkbox-row">
<input type="checkbox"
name="workhours_counter_enabled"
{% if user.workhours_counter_enabled %}checked{% endif %} />
<span>Arbeitsstunden-Counter aktivieren</span>
</label>
<label class="checkbox-row">
<input type="checkbox"
name="workhours_counter_show_in_header"
{% if user.workhours_counter_show_in_header %}checked{% endif %} />
<span>Counter im Header anzeigen</span>
</label>
<div class="inline-grid">
<label>
Zeitraum von
<input type="date"
name="workhours_counter_start_date"
value="{{ user.workhours_counter_start_date.isoformat() if user.workhours_counter_start_date else '' }}" />
</label>
<label>
Zeitraum bis
<input type="date"
name="workhours_counter_end_date"
value="{{ user.workhours_counter_end_date.isoformat() if user.workhours_counter_end_date else '' }}" />
</label>
</div>
<div class="inline-grid settings-workhours-inline-grid">
<label class="settings-workhours-field">
Zusätzliche Stunden (optional)
<input type="number"
min="0"
step="0.25"
name="workhours_counter_manual_offset_hours"
value="{{ '%.2f'|format(user.workhours_counter_manual_offset_minutes / 60) |replace('.00', '') if user.workhours_counter_manual_offset_minutes else '' }}" />
</label>
<label class="settings-workhours-field">
Zielstunden im Zeitraum (optional)
<input type="number"
min="0.25"
step="0.25"
name="workhours_counter_target_hours"
value="{{ '%.2f'|format(user.workhours_counter_target_minutes / 60) |replace('.00', '') if user.workhours_counter_target_minutes is not none else '' }}" />
</label>
</div>
<p class="muted settings-workhours-field-hint">Zum Beispiel bereits geleistete Praxis- oder Praktikumsstunden, die nicht im Tracker erfasst wurden.</p>
<label class="checkbox-row">
<input type="checkbox"
name="workhours_counter_target_email_enabled"
{% if not mail_settings_available %}disabled{% endif %}
{% if user.workhours_counter_target_email_enabled %}checked{% endif %} />
<span>E-Mail senden, wenn das Ziel voraussichtlich nicht erreicht wird</span>
</label>
{% if not mail_settings_available %}
<p class="muted">Diese Funktion ist erst verfügbar, wenn ein E-Mail-Server eingerichtet wurde.</p>
{% endif %}
<p class="muted">Beispiel: So kannst du deine Praxisstunden im Anerkennungsjahr im Blick behalten.</p>
{% if user.workhours_counter_enabled %}
<p class="muted">
{% if workhours_counter_minutes is not none %}
Aktueller Stand im gewählten Zeitraum:
{% else %}
Bitte gültigen Zeitraum setzen, um den Counter zu berechnen.
{% endif %}
</p>
{% endif %}
{% if workhours_counter_warning %}
<div class="settings-counter-badges app-total-badges" aria-label="Ziel und Prognose">
<span class="app-total-badge app-total-badge-workhours">
<span class="app-total-badge__label">Bisher</span>
<span class="app-total-badge__value">
{% if workhours_counter_minutes is not none %}
{{ minutes_to_hhmm(workhours_counter_minutes) }}
{% else %}
--
{% endif %}
</span>
</span>
<span class="app-total-badge app-total-badge-target">
<span class="app-total-badge__label">Ziel</span>
<span class="app-total-badge__value">{{ minutes_to_hhmm(workhours_counter_warning.target_minutes) }}</span>
</span>
<span class="app-total-badge app-total-badge-projection {% if workhours_counter_warning.at_risk %}is-negative{% else %}is-positive{% endif %}">
<span class="app-total-badge__label">Prognose</span>
<span class="app-total-badge__value">{{ minutes_to_hhmm(workhours_counter_warning.projected_minutes) }}</span>
</span>
</div>
{% if workhours_counter_warning.at_risk %}
<p class="muted">
Bis zum Ziel fehlen voraussichtlich noch <strong class="negative">{{ minutes_to_hhmm(workhours_counter_warning.missing_minutes) }}</strong>
</p>
{% endif %}
{% endif %}
<button type="submit" class="button">Speichern</button>
</form>
{% endcall %}
{% call collapsible_section('Relevante Arbeitstage', 'settings-workdays') %}
<p class="muted">Diese Tage werden für Soll-/Delta-Berechnung verwendet (z. B. 4-Tage-Woche Mo-Do).</p>
<form method="post" action="/settings/workdays" class="stack">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<fieldset class="weekday-fieldset">
<legend>Arbeitstage</legend>
<div class="weekday-grid">
{% for weekday in weekday_options %}
<label class="checkbox-row">
<input type="checkbox"
name="working_days"
value="{{ weekday.value }}"
{% if weekday.value in working_days_selected %}checked{% endif %} />
<span>{{ weekday.label }}</span>
</label>
{% endfor %}
</div>
</fieldset>
<div class="stack">
<label class="checkbox-row">
<input type="checkbox"
name="count_vacation_as_worktime"
{% if user.count_vacation_as_worktime %}checked{% endif %} />
<span>Urlaubstage wie reguläre Arbeitstage rechnen</span>
</label>
<label class="checkbox-row">
<input type="checkbox"
name="count_holiday_as_worktime"
{% if user.count_holiday_as_worktime %}checked{% endif %} />
<span>Feiertage wie reguläre Arbeitstage rechnen</span>
</label>
<label class="checkbox-row">
<input type="checkbox"
name="count_sick_as_worktime"
{% if user.count_sick_as_worktime %}checked{% endif %} />
<span>Kranktage wie reguläre Arbeitstage rechnen</span>
</label>
</div>
<button type="submit" class="button">Speichern</button>
</form>
{% endcall %}
{% call collapsible_section('Sicherheit (2FA)', 'settings-mfa') %}
{% set mfa_totp_pending = mfa_setup_secret and user.mfa_method == 'none' %}
{% set mfa_selected_method = 'totp' if mfa_totp_pending else user.mfa_method %}
<p class="muted">
Status:
<strong>
{% if mfa_totp_pending %}
TOTP-Einrichtung läuft
{% else %}
{{ mfa_method_labels.get(user.mfa_method, 'Unbekannt') }}
{% endif %}
</strong>
</p>
{% if mfa_totp_pending %}
<p class="muted">2FA wird aktiviert, sobald du den aktuellen 6-stelligen Code aus deiner Authenticator-App bestätigst.</p>
{% endif %}
<form method="post" action="/settings/mfa" class="stack">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<label>
Zwei-Faktor-Methode
<select name="mfa_method" required>
<option value="none" {% if mfa_selected_method == 'none' %}selected{% endif %}>Keine 2FA</option>
<option value="totp" {% if mfa_selected_method == 'totp' %}selected{% endif %}>Authenticator-App (TOTP)</option>
<option value="email" {% if mfa_selected_method == 'email' %}selected{% endif %}>E-Mail-Code</option>
</select>
</label>
<label>
Aktuelles Passwort bestätigen
<input type="password" name="current_password" required />
</label>
<label>
Setup-Code (nur für TOTP-Aktivierung)
<input type="text"
name="setup_code"
inputmode="numeric"
pattern="[0-9]{6}"
maxlength="6" />
</label>
<label class="checkbox-row">
<input type="checkbox" name="regenerate_totp" />
<span>TOTP neu einrichten (alten Schlüssel verwerfen)</span>
</label>
{% if mfa_setup_secret %}
<div class="settings-note">
<p class="muted">
<strong>TOTP-Setup aktiv:</strong> Hinterlege den folgenden Schlüssel oder die URI in deiner Authenticator-App und bestätige danach den Code.
</p>
<label>
TOTP Secret
<input type="text" value="{{ mfa_setup_secret }}" readonly />
</label>
<label>
TOTP URI
<input type="text" value="{{ mfa_setup_uri }}" readonly />
</label>
</div>
{% endif %}
<button type="submit" class="button">Speichern</button>
</form>
{% endcall %}
<div class="settings-auth-row">
{% call collapsible_section('Account', 'settings-account', 'settings-auth-card', 'account-security') %}
<form method="post" action="/settings/profile" class="stack">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<label>
E-Mail-Adresse
<input type="email" name="email" value="{{ user.email }}" required />
</label>
<label>
Bundesland
<select name="federal_state">
<option value="">Bitte auswählen</option>
{% for state in federal_state_options %}
<option value="{{ state.code }}"
{% if user.federal_state == state.code %}selected{% endif %}>{{ state.label }}</option>
{% endfor %}
</select>
</label>
<p class="muted">
Gesetzliche Feiertage deines Bundeslands werden automatisch als Feiertag markiert, sofern an diesen Tagen keine Arbeitszeit eingetragen ist.
</p>
<label>
Aktuelles Passwort bestätigen
<input type="password" name="current_password" required />
</label>
<button type="submit" class="button">Speichern</button>
</form>
{% endcall %}
{% call collapsible_section('Passwort ändern', 'settings-password', 'settings-auth-card', 'account-security') %}
<form method="post" action="/settings/password" class="stack">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<label>
Aktuelles Passwort
<input type="password" name="current_password" required />
</label>
<label>
Neues Passwort
<input type="password" name="new_password" minlength="10" required />
</label>
<label>
Neues Passwort wiederholen
<input type="password" name="new_password_repeat" minlength="10" required />
</label>
<button type="submit" class="button">Speichern</button>
</form>
{% endcall %}
</div>
{% call collapsible_section('Datenexport', 'settings-export') %}
<p class="muted">Lade hier alle bisher eingetragenen Daten herunter. Für Excel und PDF wird dein kompletter erfasster Zeitraum exportiert. Die Backup-Datei ist für Sicherung und späteren Import gedacht.</p>
<form method="post" action="/settings/export-all" class="stack">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<div class="settings-export-actions">
<button type="submit" name="format" value="xlsx" class="button">Alles als Excel exportieren</button>
<button type="submit" name="format" value="pdf" class="button ghost">Alles als PDF exportieren</button>
<button type="submit" name="format" value="backup_json" class="button ghost">Backup-Datei herunterladen</button>
</div>
</form>
<p class="muted">Die Backup-Datei enthält deine Einstellungen, Arbeitszeiteinträge, Urlaub, Sondertage, Soll-Historie und Stundenausgleich in einem strukturierten Format. Sicherheits- und Kontodaten sind nicht enthalten.</p>
{% endcall %}
{% call collapsible_section('Backup importieren', 'settings-import') %}
<p class="muted">Du kannst eine zuvor exportierte Backup-Datei wieder einspielen. Dein Konto, dein Passwort und deine Sicherheitsdaten bleiben dabei unverändert.</p>
<form method="post" action="/settings/import/preview" enctype="multipart/form-data" class="stack">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<label>
Importmodus
<select name="import_mode">
<option value="merge" {% if import_mode_selected == 'merge' %}selected{% endif %}>Zusammenführen</option>
<option value="replace_user_data"
{% if import_mode_selected == 'replace_user_data' %}selected{% endif %}>Alle bisherigen Daten ersetzen</option>
</select>
</label>
<p class="muted">
Zusammenführen behält bestehende Tagesdaten bei und ergänzt nur konfliktfreie Inhalte. Ersetzen löscht zuerst alle importierbaren Arbeits- und Einstellungsdaten deines Kontos.
</p>
<label>
Backup-Datei
<input type="file" name="backup_file" accept=".json,application/json" required />
</label>
<button type="submit" class="button">Backup prüfen</button>
</form>
{% if import_preview %}
<div class="settings-import-preview stack">
<div class="settings-import-preview__header">
<div>
<h3>Importvorschau</h3>
<p class="muted">
Backup v{{ import_preview.backup_version }}
{% if import_preview.source_app_version %}• exportiert mit {{ import_preview.source_app_version }}{% endif %}
{% if import_preview.exported_at %}• {{ import_preview.exported_at }}{% endif %}
</p>
</div>
<span class="settings-import-preview__mode">{{ import_preview.mode_label }}</span>
</div>
<div class="settings-import-grid">
<div class="settings-import-stat">
<strong>{{ import_preview.counts.time_entries }}</strong>
<span>Arbeitszeiteinträge</span>
</div>
<div class="settings-import-stat">
<strong>{{ import_preview.counts.weekly_target_rules }}</strong>
<span>Wochenziele</span>
</div>
<div class="settings-import-stat">
<strong>{{ import_preview.counts.vacation_periods }}</strong>
<span>Urlaubszeiträume</span>
</div>
<div class="settings-import-stat">
<strong>{{ import_preview.counts.special_day_statuses }}</strong>
<span>Sondertage</span>
</div>
<div class="settings-import-stat">
<strong>{{ import_preview.counts.overtime_adjustments }}</strong>
<span>Stundenausgleich</span>
</div>
</div>
<div class="settings-import-summary">
<p class="muted">
Übernommen werden auch fachliche Einstellungen wie Wochenstunden, relevante Arbeitstage, Überstunden-Regeln, Arbeitsstunden-Counter und das Bundesland.
</p>
<ul class="settings-import-conflicts">
<li>Konflikte Arbeitszeiteinträge: {{ import_preview.conflicts.time_entries }}</li>
<li>Konflikte Wochenziele: {{ import_preview.conflicts.weekly_target_rules }}</li>
<li>Konflikte Urlaubszeiträume: {{ import_preview.conflicts.vacation_periods }}</li>
<li>Konflikte Sondertage: {{ import_preview.conflicts.special_day_statuses }}</li>
<li>Konflikte Stundenausgleich: {{ import_preview.conflicts.overtime_adjustments }}</li>
</ul>
</div>
<form method="post" action="/settings/import/execute" class="stack">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<input type="hidden" name="preview_id" value="{{ import_preview.id }}" />
{% if import_preview.mode == 'replace_user_data' %}
<label class="checkbox-row">
<input type="checkbox" name="confirm_replace" />
<span>Ich möchte meine bisherigen importierbaren Daten wirklich vollständig durch dieses Backup ersetzen.</span>
</label>
{% endif %}
<button type="submit" class="button">Import jetzt ausführen</button>
</form>
</div>
{% endif %}
{% endcall %}
{% call collapsible_section('Konto löschen', 'settings-delete-account', 'danger-card') %}
<p class="muted">Wenn du dein Konto löschst, werden alle zugehörigen Daten dauerhaft entfernt: Arbeitszeiten, Urlaub, Sondertage, Stundenausgleich, Soll-Historie und persönliche Einstellungen.</p>
<form method="post" action="/settings/account/delete" class="stack">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<label>
Zur Bestätigung deine E-Mail-Adresse eingeben
<input type="email" name="confirm_email" placeholder="{{ user.email }}" required />
</label>
<label>
Aktuelles Passwort bestätigen
<input type="password" name="current_password" required />
</label>
<label class="checkbox-row">
<input type="checkbox" name="confirm_delete" />
<span>Ich möchte mein Konto und alle zugehörigen Daten dauerhaft löschen.</span>
</label>
<button type="submit" class="button danger">Konto dauerhaft löschen</button>
</form>
{% endcall %}
{% endif %}
{% if is_admin and active_settings_tab == 'admin' %}
{% call collapsible_section('Benutzerverwaltung', 'settings-admin-users', 'admin-card') %}
<p class="muted">Aktive Admins: {{ admin_user_count }}</p>
<div class="admin-user-list">
{% for managed in managed_users %}
{% set disable_delete = managed.id == user.id or (managed.role == 'admin' and managed.is_active and admin_user_count <= 1) %}
<form method="post"
action="/settings/admin/users/{{ managed.id }}"
class="admin-user-row">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<div class="admin-user-meta">
<strong>{{ managed.email }}</strong>
<span class="muted">erstellt: {{ managed.created_at.strftime("%d.%m.%Y") }}</span>
</div>
<label>
Rolle
<select name="role" required>
<option value="user" {% if managed.role == 'user' %}selected{% endif %}>User</option>
<option value="admin" {% if managed.role == 'admin' %}selected{% endif %}>Admin</option>
</select>
</label>
<label class="checkbox-row">
<input type="checkbox"
name="is_active"
{% if managed.is_active %}checked{% endif %} />
<span>Aktiv</span>
</label>
<label class="checkbox-row">
<input type="checkbox" name="reset_mfa" />
<span>MFA zurücksetzen</span>
</label>
<div class="admin-user-actions">
<button type="submit" class="button">Speichern</button>
<button type="submit"
formaction="/settings/admin/users/{{ managed.id }}/delete"
formmethod="post"
class="button danger"
{% if disable_delete %}disabled{% endif %}
onclick="return confirm('Benutzer wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.');">
Löschen
</button>
</div>
</form>
{% endfor %}
</div>
{% endcall %}
{% call collapsible_section('E-Mail-Server', 'settings-admin-email', 'admin-card') %}
<p class="muted">Wird für Passwort-Reset, E-Mail-MFA und Registrierungsmails verwendet.</p>
<form method="post" action="/settings/admin/email-server" class="stack">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<div class="inline-grid">
<label>
SMTP Host
<input type="text"
name="smtp_host"
value="{{ email_server.smtp_host }}"
required />
</label>
<label>
SMTP Port
<input type="number"
min="1"
max="65535"
name="smtp_port"
value="{{ email_server.smtp_port }}"
required />
</label>
<label>
SMTP Username
<input type="text"
name="smtp_username"
value="{{ email_server.smtp_username }}" />
</label>
<label>
SMTP Passwort
{% if email_server.has_password %}(leer lassen = unverändert){% endif %}
<input type="password" name="smtp_password" />
</label>
<label>
Absender E-Mail
<input type="email"
name="from_email"
value="{{ email_server.from_email }}"
required />
</label>
<label>
Absender Name
<input type="text"
name="from_name"
value="{{ email_server.from_name }}"
required />
</label>
</div>
<div class="inline-grid">
<label class="checkbox-row">
<input type="checkbox"
name="use_starttls"
{% if email_server.use_starttls %}checked{% endif %} />
<span>STARTTLS verwenden</span>
</label>
<label class="checkbox-row">
<input type="checkbox"
name="use_ssl"
{% if email_server.use_ssl %}checked{% endif %} />
<span>SMTP SSL verwenden</span>
</label>
<label class="checkbox-row">
<input type="checkbox"
name="verify_tls"
{% if email_server.verify_tls %}checked{% endif %} />
<span>TLS Zertifikat prüfen</span>
</label>
<label class="checkbox-row">
<input type="checkbox"
name="registration_mails_enabled"
{% if email_server.registration_mails_enabled %}checked{% endif %} />
<span>Registrierungsmails aktiv</span>
</label>
<label class="checkbox-row">
<input type="checkbox"
name="password_reset_mails_enabled"
{% if email_server.password_reset_mails_enabled %}checked{% endif %} />
<span>Passwort-Reset-Mails aktiv</span>
</label>
<label class="checkbox-row">
<input type="checkbox"
name="registration_admin_notify_enabled"
{% if email_server.registration_admin_notify_enabled %}checked{% endif %} />
<span>Infomails bei neuer Registrierung aktiv</span>
</label>
</div>
<div class="admin-recipient-picker">
<p class="muted">Empfänger für Registrierungs-Infomails (aktive Admins)</p>
{% if admin_recipients %}
<div class="admin-recipient-grid">
{% for admin_recipient in admin_recipients %}
<label class="checkbox-row">
<input type="checkbox"
name="registration_admin_notify_admin_ids"
value="{{ admin_recipient.id }}"
{% if admin_recipient.id in email_server.registration_admin_notify_admin_ids %}checked{% endif %} />
<span>{{ admin_recipient.email }}</span>
</label>
{% endfor %}
</div>
{% else %}
<p class="muted">Keine aktiven Admins gefunden.</p>
{% endif %}
{% if email_server.registration_notify_fallback_email %}
<p class="muted">
Wenn keine Admins ausgewählt sind, wird die Fallback-Adresse
<strong>{{ email_server.registration_notify_fallback_email }}</strong> genutzt.
</p>
{% endif %}
</div>
<button type="submit" class="button">Speichern</button>
</form>
<form method="post"
action="/settings/admin/email-server/test"
class="stack">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<button type="submit" class="button ghost">Testmail an mich senden</button>
</form>
{% endcall %}
{% call collapsible_section('Rechtliches', 'settings-admin-legal', 'admin-card') %}
<p class="muted">Diese Inhalte werden öffentlich über den Footer unter Impressum und Datenschutz angezeigt. Markdown ist erlaubt und wird beim Anzeigen sicher bereinigt.</p>
<form method="post" action="/settings/admin/site-content" class="stack">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<label>
Impressum (Markdown)
<textarea name="impressum_markdown" rows="14" required>{{ site_content_markdown['impressum'] }}</textarea>
</label>
<label>
Datenschutz (Markdown)
<textarea name="privacy_markdown" rows="16" required>{{ site_content_markdown['datenschutz'] }}</textarea>
</label>
<button type="submit" class="button">Speichern</button>
</form>
{% endcall %}
{% call collapsible_section('Kontakt & Tickets', 'settings-admin-tickets', 'admin-card') %}
<p class="muted">Neue Nachrichten aus dem Kontaktformular werden hier als Tickets gesammelt. Für Benachrichtigungen werden dieselben Admin-Empfänger wie bei Registrierungs-Infomails verwendet.</p>
<div class="support-ticket-list">
{% for ticket in support_tickets %}
<article class="support-ticket-card">
<div class="support-ticket-card__header">
<div class="support-ticket-card__title-wrap">
<h3>{{ ticket.subject }}</h3>
<p class="muted">
{{ ticket_category_label(ticket.category) }} · {{ ticket_status_label(ticket.status) }} · {{ ticket.created_at.strftime("%d.%m.%Y %H:%M") }} UTC
</p>
</div>
<div class="support-ticket-card__meta">
<strong>{{ ticket.email }}</strong>
<span class="muted">{{ ticket.name or 'Ohne Namen' }}</span>
{% if ticket.user_id %}<span class="muted">Angemeldeter Nutzer</span>{% endif %}
</div>
</div>
<div class="support-ticket-card__message">
{{ ticket.message }}
</div>
<form method="post" action="/settings/admin/tickets/{{ ticket.id }}" class="stack">
<input type="hidden" name="csrf_token" value="{{ csrf_token }}" />
<div class="inline-grid support-ticket-card__controls">
<label>
Status
<select name="status" required>
<option value="open" {% if ticket.status == 'open' %}selected{% endif %}>Offen</option>
<option value="closed" {% if ticket.status == 'closed' %}selected{% endif %}>Geschlossen</option>
</select>
</label>
<label>
Interne Notiz
<textarea name="admin_notes" rows="4">{{ ticket.admin_notes or '' }}</textarea>
</label>
</div>
<button type="submit" class="button">Ticket speichern</button>
</form>
</article>
{% else %}
<p class="muted">Aktuell liegen keine Kontakt-Tickets vor.</p>
{% endfor %}
</div>
{% endcall %}
{% endif %}
</section>
{% endblock %}