📝 🚀 "Refactor backend templates
This commit is contained in:
@ -5,164 +5,592 @@
|
||||
{% block head %}
|
||||
{{ super() }}
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<style>
|
||||
/* Modern Toggle Switch */
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 52px;
|
||||
height: 28px;
|
||||
}
|
||||
|
||||
.toggle-switch input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.toggle-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(135deg, #e2e8f0, #cbd5e1);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border-radius: 28px;
|
||||
border: 2px solid rgba(148, 163, 184, 0.2);
|
||||
box-shadow:
|
||||
inset 0 2px 4px rgba(0, 0, 0, 0.1),
|
||||
0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.toggle-slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
left: 2px;
|
||||
bottom: 2px;
|
||||
background: linear-gradient(135deg, #ffffff, #f8fafc);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
border-radius: 50%;
|
||||
box-shadow:
|
||||
0 2px 8px rgba(0, 0, 0, 0.15),
|
||||
0 1px 4px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
input:checked + .toggle-slider {
|
||||
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
|
||||
border-color: rgba(59, 130, 246, 0.3);
|
||||
box-shadow:
|
||||
inset 0 2px 4px rgba(29, 78, 216, 0.2),
|
||||
0 0 0 3px rgba(59, 130, 246, 0.1),
|
||||
0 4px 12px rgba(59, 130, 246, 0.2);
|
||||
}
|
||||
|
||||
input:checked + .toggle-slider:before {
|
||||
transform: translateX(24px);
|
||||
background: linear-gradient(135deg, #ffffff, #f1f5f9);
|
||||
box-shadow:
|
||||
0 3px 12px rgba(0, 0, 0, 0.2),
|
||||
0 1px 6px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* Premium Input Fields */
|
||||
.premium-input {
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.9), rgba(248, 250, 252, 0.95));
|
||||
backdrop-filter: blur(8px);
|
||||
border: 2px solid transparent;
|
||||
background-clip: padding-box;
|
||||
position: relative;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.premium-input:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
padding: 2px;
|
||||
background: linear-gradient(135deg, #e2e8f0, #cbd5e1, #e2e8f0);
|
||||
border-radius: inherit;
|
||||
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
|
||||
mask-composite: exclude;
|
||||
-webkit-mask-composite: xor;
|
||||
}
|
||||
|
||||
.premium-input:focus {
|
||||
transform: translateY(-1px);
|
||||
box-shadow:
|
||||
0 10px 25px rgba(59, 130, 246, 0.15),
|
||||
0 4px 10px rgba(59, 130, 246, 0.1),
|
||||
0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.premium-input:focus:before {
|
||||
background: linear-gradient(135deg, #3b82f6, #1d4ed8, #3b82f6);
|
||||
}
|
||||
|
||||
/* Dark mode styles */
|
||||
.dark .premium-input {
|
||||
background: linear-gradient(135deg, rgba(30, 41, 59, 0.9), rgba(15, 23, 42, 0.95));
|
||||
color: #f1f5f9;
|
||||
}
|
||||
|
||||
.dark .premium-input:before {
|
||||
background: linear-gradient(135deg, #475569, #334155, #475569);
|
||||
}
|
||||
|
||||
.dark .toggle-slider {
|
||||
background: linear-gradient(135deg, #475569, #334155);
|
||||
border-color: rgba(71, 85, 105, 0.3);
|
||||
}
|
||||
|
||||
/* Permission Card Animations */
|
||||
.permission-card {
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.permission-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow:
|
||||
0 20px 25px -5px rgba(0, 0, 0, 0.1),
|
||||
0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
/* Floating Labels */
|
||||
.floating-label {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.floating-label input:focus + label,
|
||||
.floating-label input:not(:placeholder-shown) + label {
|
||||
transform: translateY(-24px) scale(0.875);
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.floating-label label {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
top: 14px;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
pointer-events: none;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
/* Button Hover Effects */
|
||||
.btn-gradient {
|
||||
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn-gradient:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: left 0.5s;
|
||||
}
|
||||
|
||||
.btn-gradient:hover:before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
/* Glass Effect Cards */
|
||||
.glass-card {
|
||||
background: rgba(255, 255, 255, 0.85);
|
||||
backdrop-filter: blur(16px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
box-shadow:
|
||||
0 25px 50px -12px rgba(0, 0, 0, 0.1),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05) inset;
|
||||
}
|
||||
|
||||
.dark .glass-card {
|
||||
background: rgba(15, 23, 42, 0.85);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900">
|
||||
<div class="max-w-2xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<div class="min-h-screen bg-gradient-to-br from-blue-50 via-indigo-50 to-purple-50 dark:from-slate-900 dark:via-blue-900 dark:to-indigo-900 relative overflow-hidden">
|
||||
<!-- Animated Background Elements -->
|
||||
<div class="absolute inset-0 overflow-hidden pointer-events-none">
|
||||
<div class="absolute -top-40 -right-32 w-96 h-96 bg-gradient-to-br from-blue-400/20 to-indigo-600/20 rounded-full blur-3xl animate-pulse"></div>
|
||||
<div class="absolute -bottom-40 -left-32 w-96 h-96 bg-gradient-to-tr from-purple-400/20 to-pink-600/20 rounded-full blur-3xl animate-pulse delay-1000"></div>
|
||||
<div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-64 h-64 bg-gradient-to-r from-blue-300/10 to-indigo-300/10 rounded-full blur-2xl animate-pulse delay-500"></div>
|
||||
</div>
|
||||
|
||||
<div class="relative z-10 max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
|
||||
|
||||
<!-- Header -->
|
||||
<div class="mb-8">
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 class="text-3xl font-bold text-slate-900 dark:text-white">Benutzer bearbeiten</h1>
|
||||
<p class="text-slate-600 dark:text-slate-400 mt-2">Bearbeiten Sie die Daten von {{ user.name or user.email }}</p>
|
||||
<!-- Modern Header with Glass Effect -->
|
||||
<div class="mb-12">
|
||||
<div class="glass-card rounded-3xl p-8 mb-8">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-6">
|
||||
<div class="w-16 h-16 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-2xl flex items-center justify-center shadow-xl">
|
||||
<svg class="w-8 h-8 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.232 5.232l3.536 3.536m-2.036-5.036a2.5 2.5 0 113.536 3.536L6.5 21.036H3v-3.572L16.732 3.732z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<h1 class="text-4xl font-bold bg-gradient-to-r from-slate-900 to-slate-700 dark:from-white dark:to-gray-200 bg-clip-text text-transparent">
|
||||
Benutzer bearbeiten
|
||||
</h1>
|
||||
<p class="text-lg text-slate-600 dark:text-slate-400 mt-2 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<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>
|
||||
{{ user.name or user.email }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{ url_for('admin_page', tab='users') }}"
|
||||
class="group inline-flex items-center px-6 py-3 bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm text-slate-700 dark:text-slate-300 rounded-2xl hover:bg-white dark:hover:bg-slate-700 transition-all duration-300 shadow-lg hover:shadow-xl border border-white/20 dark:border-slate-700/50">
|
||||
<svg class="w-5 h-5 mr-2 transition-transform group-hover:-translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
||||
</svg>
|
||||
<span class="font-medium">Zurück zur Verwaltung</span>
|
||||
</a>
|
||||
</div>
|
||||
<a href="{{ url_for('admin_page', tab='users') }}" class="inline-flex items-center px-4 py-2 bg-slate-200 dark:bg-slate-700 text-slate-700 dark:text-slate-300 rounded-xl hover:bg-slate-300 dark:hover:bg-slate-600 transition-all duration-300">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
|
||||
</svg>
|
||||
Zurück zur Benutzerverwaltung
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form -->
|
||||
<div class="bg-white dark:bg-slate-800 rounded-2xl shadow-xl p-8">
|
||||
<form method="POST" action="{{ url_for('admin_update_user_form', user_id=user.id) }}" class="space-y-6">
|
||||
<!-- Modern Form with Glass Effect -->
|
||||
<div class="glass-card rounded-3xl p-10 shadow-2xl">
|
||||
<form method="POST" action="{{ url_for('admin_update_user_form', user_id=user.id) }}" class="space-y-8" id="userEditForm">
|
||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
<input type="hidden" name="_method" value="PUT"/>
|
||||
|
||||
<!-- Benutzername -->
|
||||
<div>
|
||||
<label for="username" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||||
Benutzername
|
||||
</label>
|
||||
<input type="text" name="username" id="username" required
|
||||
value="{{ user.username }}"
|
||||
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white"
|
||||
placeholder="max.mustermann">
|
||||
<!-- Form Header -->
|
||||
<div class="border-b border-slate-200 dark:border-slate-700 pb-8">
|
||||
<h2 class="text-2xl font-bold text-slate-900 dark:text-white mb-3 flex items-center">
|
||||
<svg class="w-6 h-6 mr-3 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/>
|
||||
</svg>
|
||||
Benutzerdaten bearbeiten
|
||||
</h2>
|
||||
<p class="text-slate-600 dark:text-slate-400 text-lg">
|
||||
Bearbeiten Sie die Informationen und Berechtigungen für diesen Benutzer
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Name -->
|
||||
<div>
|
||||
<label for="name" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||||
Vollständiger Name
|
||||
</label>
|
||||
<input type="text" name="name" id="name"
|
||||
value="{{ user.name }}"
|
||||
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white"
|
||||
placeholder="Max Mustermann">
|
||||
</div>
|
||||
|
||||
<!-- Neues Passwort (optional) -->
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||||
Neues Passwort (leer lassen, um beizubehalten)
|
||||
</label>
|
||||
<input type="password" name="password" id="password"
|
||||
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white"
|
||||
placeholder="Neues Passwort">
|
||||
</div>
|
||||
|
||||
<!-- Rolle -->
|
||||
<div>
|
||||
<label for="role" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||||
Benutzerrolle
|
||||
</label>
|
||||
<select name="role" id="role"
|
||||
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white">
|
||||
<option value="user" {% if not user.is_admin %}selected{% endif %}>Benutzer</option>
|
||||
<option value="admin" {% if user.is_admin %}selected{% endif %}>Administrator</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Status -->
|
||||
<div>
|
||||
<label for="is_active" class="block text-sm font-medium text-slate-700 dark:text-slate-300 mb-2">
|
||||
Benutzerstatus
|
||||
</label>
|
||||
<select name="is_active" id="is_active"
|
||||
class="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent dark:bg-slate-700 dark:text-white">
|
||||
<option value="true" {% if user.active %}selected{% endif %}>Aktiv</option>
|
||||
<option value="false" {% if not user.active %}selected{% endif %}>Deaktiviert</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Berechtigungen -->
|
||||
<div class="border-t border-slate-200 dark:border-slate-600 pt-6">
|
||||
<h3 class="text-lg font-medium text-slate-900 dark:text-white mb-4">Benutzerberechtigungen</h3>
|
||||
|
||||
<div class="space-y-4">
|
||||
<!-- Jobs ohne Genehmigung starten -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label for="can_start_jobs" class="text-sm font-medium text-slate-700 dark:text-slate-300">
|
||||
Jobs ohne Genehmigung starten
|
||||
<!-- Personal Information Section -->
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
|
||||
<!-- Left Column: Basic Info -->
|
||||
<div class="space-y-6">
|
||||
<div class="bg-gradient-to-br from-blue-50 to-indigo-50 dark:from-blue-900/20 dark:to-indigo-900/20 rounded-2xl p-6 border border-blue-200/50 dark:border-blue-800/50">
|
||||
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-6 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<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>
|
||||
Persönliche Informationen
|
||||
</h3>
|
||||
|
||||
<!-- Username Field -->
|
||||
<div class="mb-6">
|
||||
<label for="username" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">
|
||||
Benutzername
|
||||
</label>
|
||||
<p class="text-xs text-slate-500 dark:text-slate-400">
|
||||
Benutzer kann eigene Druckjobs ohne Admin-Genehmigung starten
|
||||
</p>
|
||||
<div class="relative group">
|
||||
<input type="text"
|
||||
name="username"
|
||||
id="username"
|
||||
required
|
||||
value="{{ user.username }}"
|
||||
class="premium-input w-full px-5 py-4 rounded-xl text-slate-900 dark:text-white placeholder-slate-500 dark:placeholder-slate-400 focus:outline-none"
|
||||
placeholder="max.mustermann">
|
||||
<div class="absolute inset-y-0 right-4 flex items-center pointer-events-none">
|
||||
<svg class="w-5 h-5 text-slate-400 group-focus-within:text-blue-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 12a4 4 0 10-8 0 4 4 0 008 0zm0 0v1.5a2.5 2.5 0 005 0V12a9 9 0 10-9 9m4.5-1.206a8.959 8.959 0 01-4.5 1.207"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
|
||||
<input type="checkbox" name="can_start_jobs" id="can_start_jobs"
|
||||
{% if user.permissions and user.permissions.can_start_jobs %}checked{% endif %}
|
||||
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer">
|
||||
<label for="can_start_jobs" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
|
||||
|
||||
<!-- Full Name Field -->
|
||||
<div class="mb-6">
|
||||
<label for="name" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">
|
||||
Vollständiger Name
|
||||
</label>
|
||||
<div class="relative group">
|
||||
<input type="text"
|
||||
name="name"
|
||||
id="name"
|
||||
value="{{ user.name }}"
|
||||
class="premium-input w-full px-5 py-4 rounded-xl text-slate-900 dark:text-white placeholder-slate-500 dark:placeholder-slate-400 focus:outline-none"
|
||||
placeholder="Max Mustermann">
|
||||
<div class="absolute inset-y-0 right-4 flex items-center pointer-events-none">
|
||||
<svg class="w-5 h-5 text-slate-400 group-focus-within:text-blue-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password Field -->
|
||||
<div>
|
||||
<label for="password" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">
|
||||
Neues Passwort
|
||||
<span class="text-xs font-normal text-slate-500 dark:text-slate-400 ml-2">(optional)</span>
|
||||
</label>
|
||||
<div class="relative group">
|
||||
<input type="password"
|
||||
name="password"
|
||||
id="password"
|
||||
class="premium-input w-full px-5 py-4 rounded-xl text-slate-900 dark:text-white placeholder-slate-500 dark:placeholder-slate-400 focus:outline-none"
|
||||
placeholder="Leer lassen, um beizubehalten">
|
||||
<div class="absolute inset-y-0 right-4 flex items-center pointer-events-none">
|
||||
<svg class="w-5 h-5 text-slate-400 group-focus-within:text-blue-500 transition-colors" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 0h12a2 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>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-slate-500 dark:text-slate-400 mt-2 ml-1">
|
||||
Lassen Sie das Feld leer, um das aktuelle Passwort beizubehalten
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Genehmigungspflicht -->
|
||||
<div class="flex items-center justify-between">
|
||||
<div>
|
||||
<label for="needs_approval" class="text-sm font-medium text-slate-700 dark:text-slate-300">
|
||||
Genehmigungspflicht für Jobs
|
||||
<!-- Right Column: Role & Status -->
|
||||
<div class="space-y-6">
|
||||
<div class="bg-gradient-to-br from-green-50 to-emerald-50 dark:from-green-900/20 dark:to-emerald-900/20 rounded-2xl p-6 border border-green-200/50 dark:border-green-800/50">
|
||||
<h3 class="text-lg font-semibold text-slate-900 dark:text-white mb-6 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
Rolle & Status
|
||||
</h3>
|
||||
|
||||
<!-- Role Selection -->
|
||||
<div class="mb-6">
|
||||
<label for="role" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">
|
||||
Benutzerrolle
|
||||
</label>
|
||||
<p class="text-xs text-slate-500 dark:text-slate-400">
|
||||
Jobs des Benutzers müssen von einem Admin genehmigt werden
|
||||
</p>
|
||||
<div class="relative">
|
||||
<select name="role"
|
||||
id="role"
|
||||
class="premium-input w-full px-5 py-4 rounded-xl text-slate-900 dark:text-white focus:outline-none appearance-none cursor-pointer">
|
||||
<option value="user" {% if not user.is_admin %}selected{% endif %}>
|
||||
👤 Standard-Benutzer
|
||||
</option>
|
||||
<option value="admin" {% if user.is_admin %}selected{% endif %}>
|
||||
👑 Administrator
|
||||
</option>
|
||||
</select>
|
||||
<div class="absolute inset-y-0 right-4 flex items-center pointer-events-none">
|
||||
<svg class="w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
|
||||
<input type="checkbox" name="needs_approval" id="needs_approval"
|
||||
{% if not user.permissions or user.permissions.needs_approval %}checked{% endif %}
|
||||
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer">
|
||||
<label for="needs_approval" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Jobs genehmigen -->
|
||||
<div class="flex items-center justify-between">
|
||||
<!-- Status Selection -->
|
||||
<div>
|
||||
<label for="can_approve_jobs" class="text-sm font-medium text-slate-700 dark:text-slate-300">
|
||||
Jobs genehmigen
|
||||
<label for="is_active" class="block text-sm font-semibold text-slate-700 dark:text-slate-300 mb-3">
|
||||
Kontostatus
|
||||
</label>
|
||||
<p class="text-xs text-slate-500 dark:text-slate-400">
|
||||
Benutzer kann Gastanfragen und fremde Jobs genehmigen
|
||||
</p>
|
||||
</div>
|
||||
<div class="relative inline-block w-10 mr-2 align-middle select-none transition duration-200 ease-in">
|
||||
<input type="checkbox" name="can_approve_jobs" id="can_approve_jobs"
|
||||
{% if user.permissions and user.permissions.can_approve_jobs %}checked{% endif %}
|
||||
class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer">
|
||||
<label for="can_approve_jobs" class="toggle-label block overflow-hidden h-6 rounded-full bg-gray-300 cursor-pointer"></label>
|
||||
<div class="relative">
|
||||
<select name="is_active"
|
||||
id="is_active"
|
||||
class="premium-input w-full px-5 py-4 rounded-xl text-slate-900 dark:text-white focus:outline-none appearance-none cursor-pointer">
|
||||
<option value="true" {% if user.active %}selected{% endif %}>
|
||||
✅ Aktiv
|
||||
</option>
|
||||
<option value="false" {% if not user.active %}selected{% endif %}>
|
||||
❌ Deaktiviert
|
||||
</option>
|
||||
</select>
|
||||
<div class="absolute inset-y-0 right-4 flex items-center pointer-events-none">
|
||||
<svg class="w-5 h-5 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Buttons -->
|
||||
<div class="flex items-center justify-end space-x-4 pt-4">
|
||||
<a href="{{ url_for('admin_page', tab='users') }}"
|
||||
class="px-6 py-3 border border-slate-300 dark:border-slate-600 text-slate-700 dark:text-slate-300 rounded-xl hover:bg-slate-50 dark:hover:bg-slate-700 transition-all duration-300">
|
||||
Abbrechen
|
||||
</a>
|
||||
<button type="submit"
|
||||
class="px-6 py-3 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-xl hover:from-blue-600 hover:to-blue-700 transition-all duration-300 shadow-lg">
|
||||
Änderungen speichern
|
||||
</button>
|
||||
<!-- Advanced Permissions Section -->
|
||||
<div class="bg-gradient-to-br from-purple-50 to-pink-50 dark:from-purple-900/20 dark:to-pink-900/20 rounded-2xl p-8 border border-purple-200/50 dark:border-purple-800/50">
|
||||
<div class="mb-8">
|
||||
<h3 class="text-xl font-bold text-slate-900 dark:text-white mb-3 flex items-center">
|
||||
<svg class="w-6 h-6 mr-3 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 15v2m-6 0h12a2 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>
|
||||
Erweiterte Berechtigungen
|
||||
</h3>
|
||||
<p class="text-slate-600 dark:text-slate-400">
|
||||
Konfigurieren Sie die spezifischen Zugriffsrechte für diesen Benutzer
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<!-- Permission: Start Jobs -->
|
||||
<div class="permission-card bg-white/60 dark:bg-slate-800/60 backdrop-blur-sm rounded-2xl p-6 border border-white/50 dark:border-slate-700/50">
|
||||
<div class="flex items-start justify-between mb-4">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center mb-2">
|
||||
<svg class="w-5 h-5 text-green-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.828 14.828a4 4 0 01-5.656 0M9 10h1m4 0h1m-6 4h.01M19 10a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
<h4 class="font-semibold text-slate-900 dark:text-white">Jobs starten</h4>
|
||||
</div>
|
||||
<p class="text-sm text-slate-600 dark:text-slate-400 leading-relaxed">
|
||||
Benutzer kann eigene Druckjobs ohne Admin-Genehmigung starten
|
||||
</p>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox"
|
||||
name="can_start_jobs"
|
||||
{% if user.permissions and user.permissions.can_start_jobs %}checked{% endif %}>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Permission: Needs Approval -->
|
||||
<div class="permission-card bg-white/60 dark:bg-slate-800/60 backdrop-blur-sm rounded-2xl p-6 border border-white/50 dark:border-slate-700/50">
|
||||
<div class="flex items-start justify-between mb-4">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center mb-2">
|
||||
<svg class="w-5 h-5 text-orange-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
<h4 class="font-semibold text-slate-900 dark:text-white">Genehmigungspflicht</h4>
|
||||
</div>
|
||||
<p class="text-sm text-slate-600 dark:text-slate-400 leading-relaxed">
|
||||
Jobs des Benutzers müssen von einem Admin genehmigt werden
|
||||
</p>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox"
|
||||
name="needs_approval"
|
||||
{% if not user.permissions or user.permissions.needs_approval %}checked{% endif %}>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Permission: Can Approve -->
|
||||
<div class="permission-card bg-white/60 dark:bg-slate-800/60 backdrop-blur-sm rounded-2xl p-6 border border-white/50 dark:border-slate-700/50">
|
||||
<div class="flex items-start justify-between mb-4">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center mb-2">
|
||||
<svg class="w-5 h-5 text-blue-500 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z"/>
|
||||
</svg>
|
||||
<h4 class="font-semibold text-slate-900 dark:text-white">Jobs genehmigen</h4>
|
||||
</div>
|
||||
<p class="text-sm text-slate-600 dark:text-slate-400 leading-relaxed">
|
||||
Benutzer kann Gastanfragen und fremde Jobs genehmigen
|
||||
</p>
|
||||
</div>
|
||||
<div class="ml-4">
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox"
|
||||
name="can_approve_jobs"
|
||||
{% if user.permissions and user.permissions.can_approve_jobs %}checked{% endif %}>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="flex items-center justify-between pt-8 border-t border-slate-200 dark:border-slate-700">
|
||||
<div class="flex items-center space-x-4">
|
||||
<div class="flex items-center text-sm text-slate-500 dark:text-slate-400">
|
||||
<svg class="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||
</svg>
|
||||
<span>Änderungen werden sofort gespeichert</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center space-x-4">
|
||||
<a href="{{ url_for('admin_page', tab='users') }}"
|
||||
class="group inline-flex items-center px-8 py-4 bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm text-slate-700 dark:text-slate-300 rounded-2xl hover:bg-white dark:hover:bg-slate-700 transition-all duration-300 shadow-lg hover:shadow-xl border border-white/20 dark:border-slate-700/50">
|
||||
<svg class="w-5 h-5 mr-2 transition-transform group-hover:-translate-x-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
|
||||
</svg>
|
||||
<span class="font-medium">Abbrechen</span>
|
||||
</a>
|
||||
|
||||
<button type="submit"
|
||||
class="btn-gradient group inline-flex items-center px-8 py-4 text-white rounded-2xl hover:shadow-2xl transition-all duration-300 shadow-xl font-semibold text-lg relative overflow-hidden">
|
||||
<svg class="w-5 h-5 mr-3 transition-transform group-hover:scale-110" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/>
|
||||
</svg>
|
||||
<span>Änderungen speichern</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Enhanced JavaScript for better UX -->
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const form = document.getElementById('userEditForm');
|
||||
const submitButton = form.querySelector('button[type="submit"]');
|
||||
|
||||
// Add smooth loading state
|
||||
form.addEventListener('submit', function(e) {
|
||||
submitButton.disabled = true;
|
||||
submitButton.innerHTML = `
|
||||
<svg class="w-5 h-5 mr-3 animate-spin" 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>
|
||||
<span>Speichere...</span>
|
||||
`;
|
||||
});
|
||||
|
||||
// Enhanced input focus effects
|
||||
const inputs = form.querySelectorAll('.premium-input');
|
||||
inputs.forEach(input => {
|
||||
input.addEventListener('focus', function() {
|
||||
this.parentElement.classList.add('focused');
|
||||
});
|
||||
|
||||
input.addEventListener('blur', function() {
|
||||
this.parentElement.classList.remove('focused');
|
||||
});
|
||||
});
|
||||
|
||||
// Smooth scroll on form errors
|
||||
const errors = document.querySelectorAll('.error-message');
|
||||
if (errors.length > 0) {
|
||||
errors[0].scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
}
|
||||
|
||||
// Toggle switch animations
|
||||
const toggles = document.querySelectorAll('.toggle-switch input');
|
||||
toggles.forEach(toggle => {
|
||||
toggle.addEventListener('change', function() {
|
||||
const slider = this.nextElementSibling;
|
||||
if (this.checked) {
|
||||
slider.style.transform = 'scale(1.1)';
|
||||
setTimeout(() => {
|
||||
slider.style.transform = 'scale(1)';
|
||||
}, 150);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Form validation feedback
|
||||
const requiredInputs = form.querySelectorAll('input[required]');
|
||||
requiredInputs.forEach(input => {
|
||||
input.addEventListener('blur', function() {
|
||||
if (this.value.trim() === '') {
|
||||
this.classList.add('border-red-300');
|
||||
this.classList.remove('border-green-300');
|
||||
} else {
|
||||
this.classList.add('border-green-300');
|
||||
this.classList.remove('border-red-300');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Auto-save notification (mock)
|
||||
let saveTimeout;
|
||||
inputs.forEach(input => {
|
||||
input.addEventListener('input', function() {
|
||||
clearTimeout(saveTimeout);
|
||||
saveTimeout = setTimeout(() => {
|
||||
// Could implement auto-save here
|
||||
console.log('Changes detected - auto-save ready');
|
||||
}, 2000);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
Reference in New Issue
Block a user