961 lines
48 KiB
HTML
961 lines
48 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Einstellungen - MYP Platform{% endblock %}
|
|
|
|
{% block head %}
|
|
{{ super() }}
|
|
<!-- CSRF Token für AJAX-Anfragen -->
|
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
<!-- Header -->
|
|
<div class="mb-8">
|
|
<h1 class="text-3xl font-bold text-slate-900 dark:text-white transition-colors duration-300">Einstellungen</h1>
|
|
<p class="mt-2 text-slate-600 dark:text-slate-400 transition-colors duration-300">Passen Sie die Anwendung an Ihre Bedürfnisse an</p>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
|
|
<!-- Settings Sections (Left Side) -->
|
|
<div class="lg:col-span-2 space-y-8">
|
|
<!-- Appearance Settings -->
|
|
<div class="glass-card">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h2 class="text-xl font-bold text-slate-900 dark:text-white transition-colors duration-300">Erscheinungsbild</h2>
|
|
</div>
|
|
|
|
<div class="space-y-6">
|
|
<!-- Theme Settings -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
|
Farbschema
|
|
</label>
|
|
<div class="flex items-center space-x-4">
|
|
<button id="light-theme-btn" class="theme-btn active px-4 py-2 rounded-lg border border-gray-200 dark:border-slate-700 bg-white text-slate-900 dark:bg-slate-800 dark:text-white hover:bg-gray-50 dark:hover:bg-slate-700 transition-colors duration-200">
|
|
<svg class="w-5 h-5 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
|
|
</svg>
|
|
Hell
|
|
</button>
|
|
<button id="dark-theme-btn" class="theme-btn px-4 py-2 rounded-lg border border-gray-200 dark:border-slate-700 bg-white text-slate-900 dark:bg-slate-800 dark:text-white hover:bg-gray-50 dark:hover:bg-slate-700 transition-colors duration-200">
|
|
<svg class="w-5 h-5 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
|
</svg>
|
|
Dunkel
|
|
</button>
|
|
<button id="system-theme-btn" class="theme-btn px-4 py-2 rounded-lg border border-gray-200 dark:border-slate-700 bg-white text-slate-900 dark:bg-slate-800 dark:text-white hover:bg-gray-50 dark:hover:bg-slate-700 transition-colors duration-200">
|
|
<svg class="w-5 h-5 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
|
</svg>
|
|
System
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Reduced Motion Settings -->
|
|
<div>
|
|
<div class="flex items-center justify-between">
|
|
<label for="reduced-motion" class="text-sm font-medium text-slate-700 dark:text-slate-300">
|
|
Reduzierte Bewegung
|
|
</label>
|
|
<div class="relative inline-block w-10 mr-2 align-middle select-none">
|
|
<input type="checkbox" id="reduced-motion" name="reduced_motion" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
|
|
<label for="reduced-motion" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 dark:bg-slate-700 cursor-pointer"></label>
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400 mt-1">
|
|
Reduziert Animationen für bessere Barrierefreiheit
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Contrast Settings -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
|
Kontrast
|
|
</label>
|
|
<div class="flex items-center space-x-4">
|
|
<button id="normal-contrast-btn" class="contrast-btn active px-4 py-2 rounded-lg border border-gray-200 dark:border-slate-700 bg-white text-slate-900 dark:bg-slate-800 dark:text-white hover:bg-gray-50 dark:hover:bg-slate-700 transition-colors duration-200">
|
|
Normal
|
|
</button>
|
|
<button id="high-contrast-btn" class="contrast-btn px-4 py-2 rounded-lg border border-gray-200 dark:border-slate-700 bg-white text-slate-900 dark:bg-slate-800 dark:text-white hover:bg-gray-50 dark:hover:bg-slate-700 transition-colors duration-200">
|
|
Hoher Kontrast
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Notification Settings -->
|
|
<div class="glass-card">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h2 class="text-xl font-bold text-slate-900 dark:text-white transition-colors duration-300">Benachrichtigungen</h2>
|
|
</div>
|
|
|
|
<div class="space-y-4">
|
|
<div class="flex items-center justify-between py-2">
|
|
<div>
|
|
<h3 class="text-sm font-medium text-slate-900 dark:text-white">Neue Aufträge</h3>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">Benachrichtigung, wenn neue Aufträge erstellt werden</p>
|
|
</div>
|
|
<div class="relative inline-block w-10 mr-2 align-middle select-none">
|
|
<input type="checkbox" id="notify-new-jobs" name="notify_new_jobs" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer" checked/>
|
|
<label for="notify-new-jobs" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 dark:bg-slate-700 cursor-pointer"></label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between py-2">
|
|
<div>
|
|
<h3 class="text-sm font-medium text-slate-900 dark:text-white">Auftragsaktualisierungen</h3>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">Benachrichtigung bei Statusänderungen Ihrer Aufträge</p>
|
|
</div>
|
|
<div class="relative inline-block w-10 mr-2 align-middle select-none">
|
|
<input type="checkbox" id="notify-job-updates" name="notify_job_updates" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer" checked/>
|
|
<label for="notify-job-updates" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 dark:bg-slate-700 cursor-pointer"></label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between py-2">
|
|
<div>
|
|
<h3 class="text-sm font-medium text-slate-900 dark:text-white">Systembenachrichtigungen</h3>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">Wichtige Systemhinweise und Wartungsmeldungen</p>
|
|
</div>
|
|
<div class="relative inline-block w-10 mr-2 align-middle select-none">
|
|
<input type="checkbox" id="notify-system" name="notify_system" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer" checked/>
|
|
<label for="notify-system" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 dark:bg-slate-700 cursor-pointer"></label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between py-2">
|
|
<div>
|
|
<h3 class="text-sm font-medium text-slate-900 dark:text-white">E-Mail-Benachrichtigungen</h3>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">Zusammenfassung per E-Mail erhalten</p>
|
|
</div>
|
|
<div class="relative inline-block w-10 mr-2 align-middle select-none">
|
|
<input type="checkbox" id="notify-email" name="notify_email" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
|
|
<label for="notify-email" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 dark:bg-slate-700 cursor-pointer"></label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Privacy & Security -->
|
|
<div class="glass-card">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<h2 class="text-xl font-bold text-slate-900 dark:text-white transition-colors duration-300">Datenschutz & Sicherheit</h2>
|
|
</div>
|
|
|
|
<div class="space-y-4">
|
|
<div class="flex items-center justify-between py-2">
|
|
<div>
|
|
<h3 class="text-sm font-medium text-slate-900 dark:text-white">Aktivitätsprotokolle</h3>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">Protokollieren Sie Ihre Aktivitäten für erhöhte Sicherheit</p>
|
|
</div>
|
|
<div class="relative inline-block w-10 mr-2 align-middle select-none">
|
|
<input type="checkbox" id="activity-logs" name="activity_logs" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer" checked/>
|
|
<label for="activity-logs" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 dark:bg-slate-700 cursor-pointer"></label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between py-2">
|
|
<div>
|
|
<h3 class="text-sm font-medium text-slate-900 dark:text-white">Zwei-Faktor-Authentifizierung</h3>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">Zusätzliche Sicherheitsebene für Ihr Konto</p>
|
|
</div>
|
|
<div class="relative inline-block w-10 mr-2 align-middle select-none">
|
|
<input type="checkbox" id="two-factor" name="two_factor" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer"/>
|
|
<label for="two-factor" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 dark:bg-slate-700 cursor-pointer"></label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center justify-between py-2">
|
|
<div>
|
|
<h3 class="text-sm font-medium text-slate-900 dark:text-white">Automatische Abmeldung</h3>
|
|
<p class="text-xs text-slate-500 dark:text-slate-400">Nach einer bestimmten Zeit der Inaktivität abmelden</p>
|
|
</div>
|
|
<select id="auto-logout" name="auto_logout" class="form-select rounded-lg bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 text-slate-900 dark:text-white py-1 px-3 text-sm">
|
|
<option value="never">Nie</option>
|
|
<option value="30">Nach 30 Minuten</option>
|
|
<option value="60" selected>Nach 1 Stunde</option>
|
|
<option value="120">Nach 2 Stunden</option>
|
|
<option value="480">Nach 8 Stunden</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-6 pt-4 border-t border-gray-200 dark:border-slate-700">
|
|
<button id="save-security-settings" class="bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white px-4 py-2 rounded-lg text-sm font-medium shadow-sm hover:shadow-md transition-all duration-300">
|
|
Sicherheitseinstellungen speichern
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidebar -->
|
|
<div class="space-y-6">
|
|
<!-- Settings Navigation -->
|
|
<div class="glass-card">
|
|
<h3 class="text-lg font-bold text-slate-900 dark:text-white transition-colors duration-300 mb-4">Einstellungen</h3>
|
|
|
|
<nav class="space-y-1">
|
|
<a href="#appearance" class="nav-item flex items-center px-3 py-2 text-sm font-medium rounded-lg bg-blue-50 dark:bg-blue-900/20 text-blue-700 dark:text-blue-300">
|
|
<svg class="mr-3 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" />
|
|
</svg>
|
|
Erscheinungsbild
|
|
</a>
|
|
|
|
<a href="#notifications" class="nav-item flex items-center px-3 py-2 text-sm font-medium rounded-lg text-slate-900 dark:text-white hover:bg-gray-100 dark:hover:bg-slate-800 transition-colors duration-200">
|
|
<svg class="mr-3 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
|
</svg>
|
|
Benachrichtigungen
|
|
</a>
|
|
|
|
<a href="#privacy" class="nav-item flex items-center px-3 py-2 text-sm font-medium rounded-lg text-slate-900 dark:text-white hover:bg-gray-100 dark:hover:bg-slate-800 transition-colors duration-200">
|
|
<svg class="mr-3 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
|
</svg>
|
|
Datenschutz & Sicherheit
|
|
</a>
|
|
|
|
<a href="/user/profile" class="nav-item flex items-center px-3 py-2 text-sm font-medium rounded-lg text-slate-900 dark:text-white hover:bg-gray-100 dark:hover:bg-slate-800 transition-colors duration-200">
|
|
<svg class="mr-3 h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
|
</svg>
|
|
Profil
|
|
</a>
|
|
</nav>
|
|
</div>
|
|
|
|
<!-- About System -->
|
|
<div class="glass-card">
|
|
<h3 class="text-lg font-bold text-slate-900 dark:text-white transition-colors duration-300 mb-4">Über das System</h3>
|
|
|
|
<div class="space-y-3 text-sm">
|
|
<div>
|
|
<span class="text-slate-600 dark:text-slate-400">Version:</span>
|
|
<div class="font-medium text-slate-900 dark:text-white">3.0.0</div>
|
|
</div>
|
|
|
|
<div>
|
|
<span class="text-slate-600 dark:text-slate-400">Letzte Aktualisierung:</span>
|
|
<div class="font-medium text-slate-900 dark:text-white">15.06.2024</div>
|
|
</div>
|
|
|
|
<div>
|
|
<span class="text-slate-600 dark:text-slate-400">Support:</span>
|
|
<div class="font-medium text-blue-600 dark:text-blue-400">
|
|
<a href="mailto:till.tomczak@mercedes-benz.com" class="hover:underline">till.tomczak@mercedes-benz.com</a>
|
|
<br>
|
|
<a href="mailto:torben.haack@mercedes-benz.com" class="hover:underline">torben.haack@mercedes-benz.com</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mt-4 pt-4 border-t border-gray-200 dark:border-slate-700">
|
|
<a href="/terms" class="text-blue-600 dark:text-blue-400 hover:underline text-sm">
|
|
Nutzungsbedingungen
|
|
</a>
|
|
<span class="text-slate-400 mx-2">|</span>
|
|
<a href="/privacy" class="text-blue-600 dark:text-blue-400 hover:underline text-sm">
|
|
Datenschutzerklärung
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Theme Switcher
|
|
const lightThemeBtn = document.getElementById('light-theme-btn');
|
|
const darkThemeBtn = document.getElementById('dark-theme-btn');
|
|
const systemThemeBtn = document.getElementById('system-theme-btn');
|
|
const themeBtns = [lightThemeBtn, darkThemeBtn, systemThemeBtn];
|
|
|
|
// Initialize button states based on current theme
|
|
const STORAGE_KEY = 'myp-dark-mode';
|
|
const savedMode = localStorage.getItem(STORAGE_KEY);
|
|
|
|
if (savedMode === 'true') {
|
|
setActiveThemeButton(darkThemeBtn);
|
|
} else if (savedMode === 'false') {
|
|
setActiveThemeButton(lightThemeBtn);
|
|
} else {
|
|
setActiveThemeButton(systemThemeBtn);
|
|
}
|
|
|
|
// Theme button click handlers
|
|
lightThemeBtn.addEventListener('click', function() {
|
|
document.documentElement.classList.remove('dark');
|
|
localStorage.setItem(STORAGE_KEY, 'false');
|
|
setActiveThemeButton(lightThemeBtn);
|
|
});
|
|
|
|
darkThemeBtn.addEventListener('click', function() {
|
|
document.documentElement.classList.add('dark');
|
|
localStorage.setItem(STORAGE_KEY, 'true');
|
|
setActiveThemeButton(darkThemeBtn);
|
|
});
|
|
|
|
systemThemeBtn.addEventListener('click', function() {
|
|
localStorage.removeItem(STORAGE_KEY);
|
|
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
if (prefersDark) {
|
|
document.documentElement.classList.add('dark');
|
|
} else {
|
|
document.documentElement.classList.remove('dark');
|
|
}
|
|
setActiveThemeButton(systemThemeBtn);
|
|
});
|
|
|
|
function setActiveThemeButton(activeBtn) {
|
|
themeBtns.forEach(btn => {
|
|
if (btn === activeBtn) {
|
|
btn.classList.add('active');
|
|
btn.classList.add('bg-blue-50');
|
|
btn.classList.add('dark:bg-blue-900/20');
|
|
btn.classList.add('text-blue-700');
|
|
btn.classList.add('dark:text-blue-300');
|
|
btn.classList.remove('bg-white');
|
|
btn.classList.remove('dark:bg-slate-800');
|
|
btn.classList.remove('text-slate-900');
|
|
btn.classList.remove('dark:text-white');
|
|
} else {
|
|
btn.classList.remove('active');
|
|
btn.classList.remove('bg-blue-50');
|
|
btn.classList.remove('dark:bg-blue-900/20');
|
|
btn.classList.remove('text-blue-700');
|
|
btn.classList.remove('dark:text-blue-300');
|
|
btn.classList.add('bg-white');
|
|
btn.classList.add('dark:bg-slate-800');
|
|
btn.classList.add('text-slate-900');
|
|
btn.classList.add('dark:text-white');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Contrast Settings
|
|
const normalContrastBtn = document.getElementById('normal-contrast-btn');
|
|
const highContrastBtn = document.getElementById('high-contrast-btn');
|
|
const contrastBtns = [normalContrastBtn, highContrastBtn];
|
|
|
|
normalContrastBtn.addEventListener('click', function() {
|
|
document.documentElement.classList.remove('high-contrast');
|
|
localStorage.setItem('myp-contrast', 'normal');
|
|
setActiveContrastButton(normalContrastBtn);
|
|
});
|
|
|
|
highContrastBtn.addEventListener('click', function() {
|
|
document.documentElement.classList.add('high-contrast');
|
|
localStorage.setItem('myp-contrast', 'high');
|
|
setActiveContrastButton(highContrastBtn);
|
|
});
|
|
|
|
function setActiveContrastButton(activeBtn) {
|
|
contrastBtns.forEach(btn => {
|
|
if (btn === activeBtn) {
|
|
btn.classList.add('active');
|
|
btn.classList.add('bg-blue-50');
|
|
btn.classList.add('dark:bg-blue-900/20');
|
|
btn.classList.add('text-blue-700');
|
|
btn.classList.add('dark:text-blue-300');
|
|
btn.classList.remove('bg-white');
|
|
btn.classList.remove('dark:bg-slate-800');
|
|
btn.classList.remove('text-slate-900');
|
|
btn.classList.remove('dark:text-white');
|
|
} else {
|
|
btn.classList.remove('active');
|
|
btn.classList.remove('bg-blue-50');
|
|
btn.classList.remove('dark:bg-blue-900/20');
|
|
btn.classList.remove('text-blue-700');
|
|
btn.classList.remove('dark:text-blue-300');
|
|
btn.classList.add('bg-white');
|
|
btn.classList.add('dark:bg-slate-800');
|
|
btn.classList.add('text-slate-900');
|
|
btn.classList.add('dark:text-white');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Save Settings Button
|
|
const saveSecurityBtn = document.getElementById('save-security-settings');
|
|
saveSecurityBtn.addEventListener('click', function() {
|
|
saveAllSettings();
|
|
});
|
|
|
|
// Toggle Switch Styling
|
|
document.querySelectorAll('.toggle-checkbox').forEach(checkbox => {
|
|
checkbox.addEventListener('change', function() {
|
|
// Auto-save bei Änderung
|
|
const settingName = this.name.replace('_', ' ');
|
|
const status = this.checked ? 'aktiviert' : 'deaktiviert';
|
|
showFlashMessage(`${settingName} wurde ${status}`, 'info');
|
|
});
|
|
});
|
|
|
|
// Sammle alle Einstellungen und speichere sie
|
|
async function saveAllSettings() {
|
|
const saveButton = document.getElementById('save-security-settings');
|
|
const originalButtonText = saveButton.innerHTML;
|
|
|
|
try {
|
|
// Show loading state
|
|
saveButton.disabled = true;
|
|
saveButton.innerHTML = `
|
|
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white inline" fill="none" viewBox="0 0 24 24">
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
|
</svg>
|
|
Speichern...
|
|
`;
|
|
|
|
// Add loading state to settings cards
|
|
const settingsCards = document.querySelectorAll('.glass-card');
|
|
settingsCards.forEach(card => card.classList.add('settings-loading'));
|
|
|
|
// Erscheinungsbild-Einstellungen
|
|
const theme = localStorage.getItem(STORAGE_KEY) === 'true' ? 'dark' :
|
|
localStorage.getItem(STORAGE_KEY) === 'false' ? 'light' : 'system';
|
|
const reducedMotion = document.getElementById('reduced-motion').checked;
|
|
const contrast = localStorage.getItem('myp-contrast') || 'normal';
|
|
|
|
// Benachrichtigungseinstellungen
|
|
const notifyNewJobs = document.getElementById('notify-new-jobs').checked;
|
|
const notifyJobUpdates = document.getElementById('notify-job-updates').checked;
|
|
const notifySystem = document.getElementById('notify-system').checked;
|
|
const notifyEmail = document.getElementById('notify-email').checked;
|
|
|
|
// Datenschutz & Sicherheitseinstellungen
|
|
const activityLogs = document.getElementById('activity-logs').checked;
|
|
const twoFactor = document.getElementById('two-factor').checked;
|
|
const autoLogout = document.getElementById('auto-logout').value;
|
|
|
|
// Validate settings
|
|
if (!validateSettings({ theme, contrast, autoLogout })) {
|
|
throw new Error('Ungültige Einstellungen erkannt');
|
|
}
|
|
|
|
// Einstellungsobjekt erstellen
|
|
const settings = {
|
|
theme: theme,
|
|
reduced_motion: reducedMotion,
|
|
contrast: contrast,
|
|
notifications: {
|
|
new_jobs: notifyNewJobs,
|
|
job_updates: notifyJobUpdates,
|
|
system: notifySystem,
|
|
email: notifyEmail
|
|
},
|
|
privacy: {
|
|
activity_logs: activityLogs,
|
|
two_factor: twoFactor,
|
|
auto_logout: autoLogout
|
|
},
|
|
timestamp: new Date().toISOString()
|
|
};
|
|
|
|
// Apply reduced motion immediately if changed
|
|
if (reducedMotion) {
|
|
document.documentElement.style.setProperty('--tw-transition-duration', '0s');
|
|
} else {
|
|
document.documentElement.style.removeProperty('--tw-transition-duration');
|
|
}
|
|
|
|
// Einstellungen an den Server senden
|
|
const response = await fetch('/api/user/settings', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
|
|
},
|
|
body: JSON.stringify(settings)
|
|
});
|
|
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
// Success animation
|
|
settingsCards.forEach(card => {
|
|
card.classList.add('settings-saved');
|
|
setTimeout(() => card.classList.remove('settings-saved'), 600);
|
|
});
|
|
|
|
showFlashMessage('Alle Einstellungen wurden erfolgreich gespeichert', 'success');
|
|
|
|
// Cache settings locally for faster access
|
|
localStorage.setItem('myp-settings-cache', JSON.stringify(settings));
|
|
|
|
} else {
|
|
throw new Error(result.error || 'Unbekannter Fehler');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Speichern der Einstellungen:', error);
|
|
showFlashMessage('Fehler beim Speichern der Einstellungen: ' + error.message, 'error');
|
|
} finally {
|
|
// Restore button and remove loading states
|
|
saveButton.disabled = false;
|
|
saveButton.innerHTML = originalButtonText;
|
|
|
|
const settingsCards = document.querySelectorAll('.glass-card');
|
|
settingsCards.forEach(card => card.classList.remove('settings-loading'));
|
|
}
|
|
}
|
|
|
|
// Validate settings before saving
|
|
function validateSettings(settings) {
|
|
const validThemes = ['light', 'dark', 'system'];
|
|
const validContrast = ['normal', 'high'];
|
|
const validLogoutValues = ['never', '30', '60', '120', '480'];
|
|
|
|
return validThemes.includes(settings.theme) &&
|
|
validContrast.includes(settings.contrast) &&
|
|
validLogoutValues.includes(settings.autoLogout);
|
|
}
|
|
|
|
// Enhanced settings loading with caching
|
|
async function loadUserSettings() {
|
|
try {
|
|
// Try to load from cache first for better performance
|
|
const cachedSettings = localStorage.getItem('myp-settings-cache');
|
|
if (cachedSettings) {
|
|
try {
|
|
const cached = JSON.parse(cachedSettings);
|
|
// Use cached settings if they're less than 5 minutes old
|
|
if (new Date() - new Date(cached.timestamp) < 5 * 60 * 1000) {
|
|
applySettings(cached);
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
localStorage.removeItem('myp-settings-cache');
|
|
}
|
|
}
|
|
|
|
const response = await fetch('/api/user/settings');
|
|
const result = await response.json();
|
|
|
|
if (result.success) {
|
|
const settings = result.settings;
|
|
applySettings(settings);
|
|
|
|
// Cache the loaded settings
|
|
settings.timestamp = new Date().toISOString();
|
|
localStorage.setItem('myp-settings-cache', JSON.stringify(settings));
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Laden der Einstellungen:', error);
|
|
// Use fallback defaults if loading fails
|
|
applyDefaultSettings();
|
|
}
|
|
}
|
|
|
|
function applySettings(settings) {
|
|
// Theme-Einstellungen anwenden
|
|
if (settings.theme === 'dark') {
|
|
localStorage.setItem(STORAGE_KEY, 'true');
|
|
document.documentElement.classList.add('dark');
|
|
setActiveThemeButton(darkThemeBtn);
|
|
} else if (settings.theme === 'light') {
|
|
localStorage.setItem(STORAGE_KEY, 'false');
|
|
document.documentElement.classList.remove('dark');
|
|
setActiveThemeButton(lightThemeBtn);
|
|
} else {
|
|
localStorage.removeItem(STORAGE_KEY);
|
|
setActiveThemeButton(systemThemeBtn);
|
|
}
|
|
|
|
// Apply reduced motion setting
|
|
if (settings.reduced_motion) {
|
|
document.documentElement.style.setProperty('--tw-transition-duration', '0s');
|
|
document.getElementById('reduced-motion').checked = true;
|
|
} else {
|
|
document.documentElement.style.removeProperty('--tw-transition-duration');
|
|
document.getElementById('reduced-motion').checked = false;
|
|
}
|
|
|
|
// Weitere Einstellungen anwenden
|
|
document.getElementById('notify-new-jobs').checked = settings.notifications?.new_jobs ?? true;
|
|
document.getElementById('notify-job-updates').checked = settings.notifications?.job_updates ?? true;
|
|
document.getElementById('notify-system').checked = settings.notifications?.system ?? true;
|
|
document.getElementById('notify-email').checked = settings.notifications?.email ?? false;
|
|
document.getElementById('activity-logs').checked = settings.privacy?.activity_logs ?? true;
|
|
document.getElementById('two-factor').checked = settings.privacy?.two_factor ?? false;
|
|
document.getElementById('auto-logout').value = settings.privacy?.auto_logout ?? '60';
|
|
|
|
// Kontrast-Einstellungen
|
|
if (settings.contrast === 'high') {
|
|
localStorage.setItem('myp-contrast', 'high');
|
|
document.documentElement.classList.add('high-contrast');
|
|
setActiveContrastButton(highContrastBtn);
|
|
} else {
|
|
localStorage.setItem('myp-contrast', 'normal');
|
|
document.documentElement.classList.remove('high-contrast');
|
|
setActiveContrastButton(normalContrastBtn);
|
|
}
|
|
}
|
|
|
|
function applyDefaultSettings() {
|
|
// Apply safe defaults if loading fails
|
|
setActiveThemeButton(systemThemeBtn);
|
|
setActiveContrastButton(normalContrastBtn);
|
|
|
|
document.getElementById('reduced-motion').checked = false;
|
|
document.getElementById('notify-new-jobs').checked = true;
|
|
document.getElementById('notify-job-updates').checked = true;
|
|
document.getElementById('notify-system').checked = true;
|
|
document.getElementById('notify-email').checked = false;
|
|
document.getElementById('activity-logs').checked = true;
|
|
document.getElementById('two-factor').checked = false;
|
|
document.getElementById('auto-logout').value = '60';
|
|
}
|
|
|
|
// Auto-logout implementation
|
|
let logoutTimer = null;
|
|
|
|
function setupAutoLogout() {
|
|
const autoLogoutSelect = document.getElementById('auto-logout');
|
|
|
|
// Event-Listener für Änderungen der Auto-Logout-Einstellung
|
|
autoLogoutSelect.addEventListener('change', async function() {
|
|
const newTimeout = this.value;
|
|
|
|
try {
|
|
const response = await fetch('/api/user/setting', {
|
|
method: 'PATCH',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
|
|
},
|
|
body: JSON.stringify({ auto_logout: newTimeout })
|
|
});
|
|
|
|
if (response.ok) {
|
|
// Globales Auto-Logout-System benachrichtigen
|
|
if (window.autoLogoutManager) {
|
|
window.autoLogoutManager.updateSettings(newTimeout);
|
|
}
|
|
showFlashMessage('Auto-Logout-Einstellung aktualisiert', 'success');
|
|
}
|
|
} catch (error) {
|
|
console.error('Fehler beim Aktualisieren der Auto-Logout-Einstellung:', error);
|
|
showFlashMessage('Fehler beim Speichern der Einstellung', 'error');
|
|
}
|
|
});
|
|
}
|
|
|
|
// Enhanced toggle switches with keyboard support
|
|
function enhanceToggleSwitches() {
|
|
document.querySelectorAll('.toggle-checkbox').forEach(checkbox => {
|
|
// Add keyboard support
|
|
checkbox.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
e.preventDefault();
|
|
this.click();
|
|
}
|
|
});
|
|
|
|
// Enhanced change handler with debouncing
|
|
let changeTimeout;
|
|
checkbox.addEventListener('change', function() {
|
|
clearTimeout(changeTimeout);
|
|
changeTimeout = setTimeout(() => {
|
|
const settingName = this.name.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
|
|
const status = this.checked ? 'aktiviert' : 'deaktiviert';
|
|
|
|
// Visual feedback
|
|
const label = this.nextElementSibling;
|
|
if (label) {
|
|
label.style.transform = 'scale(1.05)';
|
|
setTimeout(() => {
|
|
label.style.transform = '';
|
|
}, 150);
|
|
}
|
|
|
|
// Auto-save individual settings
|
|
saveIndividualSetting(this.name, this.checked);
|
|
|
|
showFlashMessage(`${settingName} wurde ${status}`, 'info');
|
|
}, 300); // Debounce to prevent spam
|
|
});
|
|
});
|
|
}
|
|
|
|
// Save individual settings for immediate feedback
|
|
async function saveIndividualSetting(settingName, value) {
|
|
try {
|
|
const response = await fetch('/api/user/setting', {
|
|
method: 'PATCH',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRFToken': document.querySelector('meta[name="csrf-token"]')?.getAttribute('content') || ''
|
|
},
|
|
body: JSON.stringify({ [settingName]: value })
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Fehler beim Speichern der Einstellung');
|
|
}
|
|
|
|
// Update cache
|
|
const cached = localStorage.getItem('myp-settings-cache');
|
|
if (cached) {
|
|
try {
|
|
const settings = JSON.parse(cached);
|
|
// Update the specific setting in cache
|
|
const keys = settingName.split('.');
|
|
let current = settings;
|
|
for (let i = 0; i < keys.length - 1; i++) {
|
|
if (!current[keys[i]]) current[keys[i]] = {};
|
|
current = current[keys[i]];
|
|
}
|
|
current[keys[keys.length - 1]] = value;
|
|
settings.timestamp = new Date().toISOString();
|
|
localStorage.setItem('myp-settings-cache', JSON.stringify(settings));
|
|
} catch (e) {
|
|
localStorage.removeItem('myp-settings-cache');
|
|
}
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('Fehler beim Speichern der Einzeleinstellung:', error);
|
|
}
|
|
}
|
|
|
|
// Performance optimization: Intersection Observer for animations
|
|
function setupIntersectionObserver() {
|
|
const observer = new IntersectionObserver((entries) => {
|
|
entries.forEach(entry => {
|
|
if (entry.isIntersecting) {
|
|
entry.target.classList.add('in-view');
|
|
}
|
|
});
|
|
}, { threshold: 0.1 });
|
|
|
|
document.querySelectorAll('.glass-card').forEach(card => {
|
|
observer.observe(card);
|
|
});
|
|
}
|
|
|
|
// Initialize all enhanced features
|
|
function initializeEnhancedFeatures() {
|
|
setupAutoLogout();
|
|
enhanceToggleSwitches();
|
|
setupIntersectionObserver();
|
|
setupNavigationLinks();
|
|
}
|
|
|
|
// Setup navigation links
|
|
function setupNavigationLinks() {
|
|
const navItems = document.querySelectorAll('.nav-item');
|
|
navItems.forEach(item => {
|
|
item.addEventListener('click', function(e) {
|
|
// If it's a real link to another page, don't preventDefault
|
|
if (this.getAttribute('href').startsWith('#')) {
|
|
e.preventDefault();
|
|
|
|
// Update active state
|
|
navItems.forEach(navItem => {
|
|
navItem.classList.remove('bg-blue-50', 'dark:bg-blue-900/20', 'text-blue-700', 'dark:text-blue-300');
|
|
navItem.classList.add('text-slate-900', 'dark:text-white', 'hover:bg-gray-100', 'dark:hover:bg-slate-800');
|
|
});
|
|
|
|
this.classList.add('bg-blue-50', 'dark:bg-blue-900/20', 'text-blue-700', 'dark:text-blue-300');
|
|
this.classList.remove('text-slate-900', 'dark:text-white', 'hover:bg-gray-100', 'dark:hover:bg-slate-800');
|
|
|
|
// Scroll to section with offset for header
|
|
const targetId = this.getAttribute('href').substring(1);
|
|
const targetElement = document.querySelector(`[id="${targetId}"]`) ||
|
|
document.querySelector(`h2:contains("${targetId}")`);
|
|
|
|
if (targetElement) {
|
|
const offsetTop = targetElement.offsetTop - 100; // Account for header
|
|
window.scrollTo({
|
|
top: offsetTop,
|
|
behavior: 'smooth'
|
|
});
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// Helper function to show flash messages
|
|
function showFlashMessage(message, type = 'info') {
|
|
// Use the global toast manager if available
|
|
if (window.showToast) {
|
|
window.showToast(message, type);
|
|
} else if (window.MYP && window.MYP.UI && window.MYP.UI.ToastManager) {
|
|
const toast = new window.MYP.UI.ToastManager();
|
|
toast.show(message, type);
|
|
} else {
|
|
// Fallback to simple notification
|
|
const notification = document.createElement('div');
|
|
notification.className = `fixed top-4 right-4 p-4 rounded-lg text-white z-50 ${
|
|
type === 'success' ? 'bg-green-500' :
|
|
type === 'error' ? 'bg-red-500' :
|
|
type === 'warning' ? 'bg-yellow-500' : 'bg-blue-500'
|
|
}`;
|
|
notification.textContent = message;
|
|
document.body.appendChild(notification);
|
|
|
|
setTimeout(() => {
|
|
notification.style.opacity = '0';
|
|
setTimeout(() => document.body.removeChild(notification), 300);
|
|
}, 3000);
|
|
}
|
|
}
|
|
|
|
// Initialize everything
|
|
loadUserSettings();
|
|
initializeEnhancedFeatures();
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
/* Glass Card Effect */
|
|
.glass-card {
|
|
background: rgba(255, 255, 255, 0.9);
|
|
backdrop-filter: blur(12px);
|
|
-webkit-backdrop-filter: blur(12px);
|
|
border: 1px solid rgba(229, 231, 235, 0.8);
|
|
border-radius: 12px;
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
padding: 1.5rem;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.dark .glass-card {
|
|
background: rgba(30, 41, 59, 0.8);
|
|
border-color: rgba(100, 116, 139, 0.3);
|
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3), 0 2px 4px -1px rgba(0, 0, 0, 0.2);
|
|
}
|
|
|
|
.glass-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
|
}
|
|
|
|
.dark .glass-card:hover {
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
/* Loading State Animations */
|
|
.settings-loading {
|
|
opacity: 0.6;
|
|
pointer-events: none;
|
|
position: relative;
|
|
}
|
|
|
|
.settings-loading::after {
|
|
content: '';
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
width: 20px;
|
|
height: 20px;
|
|
margin: -10px 0 0 -10px;
|
|
border: 2px solid #f3f3f3;
|
|
border-top: 2px solid #3498db;
|
|
border-radius: 50%;
|
|
animation: spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
|
|
/* Enhanced Toggle Switches */
|
|
.toggle-checkbox {
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
/* Toggle Switch Styling */
|
|
.toggle-checkbox:checked {
|
|
right: 0;
|
|
border-color: #3b82f6;
|
|
background-color: #3b82f6;
|
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
}
|
|
|
|
.toggle-checkbox:checked + .toggle-label {
|
|
background-color: #3b82f6;
|
|
}
|
|
|
|
.dark .toggle-checkbox:checked + .toggle-label {
|
|
background-color: #2563eb;
|
|
}
|
|
|
|
.toggle-label {
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.toggle-checkbox:focus + .toggle-label {
|
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
}
|
|
|
|
/* Button states */
|
|
.theme-btn.active,
|
|
.contrast-btn.active {
|
|
box-shadow: 0 0 0 2px #3b82f6;
|
|
}
|
|
|
|
/* High contrast mode styles */
|
|
.high-contrast {
|
|
--tw-text-opacity: 1;
|
|
}
|
|
|
|
.high-contrast * {
|
|
outline: 2px solid transparent;
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.high-contrast button:focus,
|
|
.high-contrast input:focus,
|
|
.high-contrast select:focus {
|
|
outline: 3px solid #000 !important;
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.dark.high-contrast button:focus,
|
|
.dark.high-contrast input:focus,
|
|
.dark.high-contrast select:focus {
|
|
outline: 3px solid #fff !important;
|
|
}
|
|
|
|
/* Accessibility improvements */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.glass-card,
|
|
.toggle-checkbox,
|
|
.toggle-label {
|
|
transition: none !important;
|
|
}
|
|
|
|
.settings-loading::after {
|
|
animation: none !important;
|
|
}
|
|
}
|
|
|
|
/* Success feedback animation */
|
|
.settings-saved {
|
|
animation: settingsSaved 0.6s ease-in-out;
|
|
}
|
|
|
|
@keyframes settingsSaved {
|
|
0% { transform: scale(1); }
|
|
50% { transform: scale(1.02); background-color: rgba(34, 197, 94, 0.1); }
|
|
100% { transform: scale(1); }
|
|
}
|
|
|
|
/* Smooth section transitions */
|
|
.settings-section {
|
|
scroll-margin-top: 2rem;
|
|
}
|
|
</style>
|
|
{% endblock %} |