2025-06-04 10:03:22 +02:00

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 %}