"Refactor database schema for user profiles and profile templates updated to remove obsolete db table 'systades (feat): systades.db) system data)"

This commit is contained in:
2025-05-02 19:21:19 +02:00
parent d0821db983
commit 7003c89447
3 changed files with 313 additions and 63 deletions

View File

View File

@@ -241,6 +241,78 @@
background: linear-gradient(135deg, #8b5cf6, #6366f1);
box-shadow: 0 4px 12px rgba(79, 70, 229, 0.3);
}
/* Style improvements for the theme toggle button */
.theme-toggle {
position: relative;
width: 48px;
height: 24px;
border-radius: 24px;
padding: 2px;
cursor: pointer;
transition: all 0.3s ease;
overflow: hidden;
}
body.dark .theme-toggle {
background: linear-gradient(to right, #7c3aed, #3b82f6);
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.3), 0 0 10px rgba(124, 58, 237, 0.3);
border: 1px solid rgba(255, 255, 255, 0.1);
}
body:not(.dark) .theme-toggle {
background: linear-gradient(to right, #8b5cf6, #60a5fa);
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1), 0 0 10px rgba(124, 58, 237, 0.15);
border: 1px solid rgba(0, 0, 0, 0.05);
}
.theme-toggle::after {
content: '';
position: absolute;
width: 20px;
height: 20px;
border-radius: 50%;
top: 2px;
transition: all 0.3s ease;
z-index: 2;
}
body.dark .theme-toggle::after {
background: #f1f5f9 url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%237c3aed' width='14' height='14'%3E%3Cpath d='M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z'%3E%3C/path%3E%3C/svg%3E") no-repeat center center;
transform: translateX(24px);
box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
}
body:not(.dark) .theme-toggle::after {
background: #fff url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23f59e0b' width='14' height='14'%3E%3Cpath d='M12 2.25a.75.75 0 01.75.75v2.25a.75.75 0 01-1.5 0V3a.75.75 0 01.75-.75zM7.5 12a4.5 4.5 0 119 0 4.5 4.5 0 01-9 0zM18.894 6.166a.75.75 0 00-1.06-1.06l-1.591 1.59a.75.75 0 101.06 1.061l1.591-1.59zM21.75 12a.75.75 0 01-.75.75h-2.25a.75.75 0 010-1.5H21a.75.75 0 01.75.75zM17.834 18.894a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 10-1.061 1.06l1.59 1.591zM12 18a.75.75 0 01.75.75V21a.75.75 0 01-1.5 0v-2.25A.75.75 0 0112 18zM7.758 17.303a.75.75 0 00-1.061-1.06l-1.591 1.59a.75.75 0 001.06 1.061l1.591-1.59zM6 12a.75.75 0 01-.75.75H3a.75.75 0 010-1.5h2.25A.75.75 0 016 12zM6.697 7.757a.75.75 0 001.06-1.06l-1.59-1.591a.75.75 0 00-1.061 1.06l1.59 1.591z'%3E%3C/path%3E%3C/svg%3E") no-repeat center center;
transform: translateX(2px);
box-shadow: 0 0 8px rgba(124, 58, 237, 0.2);
}
.theme-toggle:hover::after {
box-shadow: 0 0 12px rgba(124, 58, 237, 0.4);
}
/* Fixes for light mode button text colors */
body:not(.dark) .btn-primary {
color: white !important;
}
/* Fix for KI-Chat container */
#chatgpt-assistant {
position: fixed;
bottom: 1.5rem;
right: 1.5rem;
z-index: 100;
}
.chat-assistant {
max-height: 80vh !important;
}
.chat-assistant .chat-messages {
max-height: calc(80vh - 160px) !important;
}
</style>
</head>
<body data-page="{{ request.endpoint }}" class="relative overflow-x-hidden dark bg-gray-900 text-white" x-data="{
@@ -369,14 +441,9 @@
<!-- Dark/Light Mode Schalter -->
<button
@click="toggleDarkMode()"
class="theme-toggle relative w-12 h-6 rounded-full bg-gradient-to-r transition-all duration-300 flex items-center"
:class="darkMode ? 'from-purple-700 to-indigo-800' : 'from-purple-400 to-indigo-500'"
class="theme-toggle relative w-12 h-6 rounded-full transition-all duration-300 flex items-center overflow-hidden"
aria-label="Dark Mode umschalten"
>
<span
class="absolute w-5 h-5 rounded-full bg-white shadow-md transform transition-transform duration-300"
:class="darkMode ? 'translate-x-7' : 'translate-x-1'"
></span>
<span class="sr-only" x-text="darkMode ? 'Zum Light Mode wechseln' : 'Zum Dark Mode wechseln'"></span>
</button>
<!-- Profil-Link oder Login -->
@@ -703,6 +770,11 @@
if (appEl && appEl.__x) {
appEl.__x.$data.darkMode = isDarkMode;
}
// Event für andere Komponenten auslösen
document.dispatchEvent(new CustomEvent('darkModeToggled', {
detail: { isDark: isDarkMode }
}));
}
window.MindMap.toggleDarkMode = function() {
@@ -712,27 +784,12 @@
// DOM aktualisieren
applyDarkModeClasses(newIsDark);
// Alpine.js Status aktualisieren (falls verfügbar)
const appEl = document.querySelector('body');
if (appEl && appEl.__x) {
appEl.__x.$data.darkMode = newIsDark;
}
// Server aktualisieren
fetch('/api/set_dark_mode', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ darkMode: newIsDark })
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Event für andere Komponenten auslösen
document.dispatchEvent(new CustomEvent('darkModeToggled', {
detail: { isDark: newIsDark }
}));
}
})
.catch(console.error);
};
@@ -754,8 +811,10 @@
fetch('/api/get_dark_mode')
.then(response => response.json())
.then(data => {
const serverDarkMode = data.darkMode === true || data.darkMode === 'true';
applyDarkModeClasses(serverDarkMode);
if (data.success) {
const serverDarkMode = data.darkMode === true || data.darkMode === 'true';
applyDarkModeClasses(serverDarkMode);
}
})
.catch(error => console.error('Fehler beim Abrufen des Dark Mode Status:', error));

View File

@@ -985,52 +985,173 @@
});
}
// Einstellungen-Formular-Handling
const settingsForm = document.querySelector('.settings-card form');
const saveSettingsBtn = document.querySelector('.settings-card .profile-action-btn.primary');
if (saveSettingsBtn && !settingsForm) {
saveSettingsBtn.addEventListener('click', function() {
// Sammle Daten aus den Eingabefeldern
const formData = new FormData();
formData.append('action', 'update_profile');
formData.append('bio', document.getElementById('bio').value);
formData.append('location', document.getElementById('location').value);
formData.append('website', document.getElementById('website').value || '');
// Avatar-Bearbeitung
const avatarEditBtn = document.querySelector('.avatar-edit');
if (avatarEditBtn) {
avatarEditBtn.addEventListener('click', function() {
// Dateiauwahl öffnen
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = 'image/*';
fileInput.style.display = 'none';
document.body.appendChild(fileInput);
// AJAX-Anfrage senden
fetch('{{ url_for("settings") }}', {
method: 'POST',
body: formData,
credentials: 'same-origin'
})
.then(response => response.json())
.catch(error => {
console.error('Fehler beim Speichern der Profileinstellungen:', error);
})
.then(data => {
// Erfolgsanimation
const originalText = this.innerHTML;
this.innerHTML = '<i class="fas fa-check mr-1"></i> Gespeichert';
fileInput.click();
fileInput.addEventListener('change', function() {
if (this.files && this.files[0]) {
// Anzeigen des gewählten Bildes
const avatarImg = document.querySelector('.avatar');
// FileReader zum Einlesen des Bildes
const reader = new FileReader();
reader.onload = function(e) {
// Vorschau anzeigen
avatarImg.src = e.target.result;
// Avatar-URL im Einstellungsbereich speichern
const avatarUrlInput = document.createElement('input');
avatarUrlInput.type = 'hidden';
avatarUrlInput.name = 'avatar_url';
avatarUrlInput.id = 'avatar_url';
avatarUrlInput.value = e.target.result;
// Entferne vorhandenes Input, falls vorhanden
const existingInput = document.getElementById('avatar_url');
if (existingInput) {
existingInput.remove();
}
// Zum Formular hinzufügen
const settingsForm = document.querySelector('.settings-card');
if (settingsForm) {
settingsForm.appendChild(avatarUrlInput);
}
// Erfolgsmeldung anzeigen
showNotification('Avatar wurde aktualisiert! Bitte speichere deine Änderungen.', 'success');
};
reader.readAsDataURL(this.files[0]);
}
setTimeout(() => {
this.innerHTML = originalText;
}, 2000);
// Input entfernen
document.body.removeChild(fileInput);
});
});
}
// Mindmap-Karten mit Hover-Effekten
const mindmapItems = document.querySelectorAll('.mindmap-item');
mindmapItems.forEach(item => {
item.addEventListener('mouseenter', () => {
item.style.transform = 'translateY(-5px)';
item.style.boxShadow = '0 12px 30px rgba(0, 0, 0, 0.15)';
});
item.addEventListener('mouseleave', () => {
item.style.transform = 'translateY(0)';
item.style.boxShadow = 'none';
// Einstellungen-Formular-Handling
const saveSettingsBtn = document.querySelectorAll('.settings-card .profile-action-btn.primary');
saveSettingsBtn.forEach(btn => {
btn.addEventListener('click', function() {
const isPasswordUpdate = this.textContent.includes('Passwort');
// Passwort-Update
if (isPasswordUpdate) {
const currentPassword = document.getElementById('password').value;
const newPassword = document.getElementById('password_confirm').value;
if (!currentPassword || !newPassword) {
showNotification('Bitte fülle alle Passwortfelder aus', 'error');
return;
}
// AJAX-Anfrage senden
const formData = new FormData();
formData.append('action', 'update_password');
formData.append('current_password', currentPassword);
formData.append('new_password', newPassword);
formData.append('confirm_password', newPassword);
// Visuelle Rückmeldung
const originalText = this.innerHTML;
this.innerHTML = '<i class="fas fa-circle-notch fa-spin mr-1"></i> Speichern...';
this.disabled = true;
fetch('{{ url_for("settings") }}', {
method: 'POST',
body: formData
})
.then(response => response.json())
.catch(error => {
console.error('Fehler beim Aktualisieren des Passworts:', error);
return { success: false, message: 'Netzwerkfehler. Bitte versuche es erneut.' };
})
.then(data => {
this.innerHTML = originalText;
this.disabled = false;
if (data && data.success) {
showNotification('Passwort erfolgreich aktualisiert!', 'success');
document.getElementById('password').value = '';
document.getElementById('password_confirm').value = '';
} else {
showNotification(data?.message || 'Fehler beim Aktualisieren des Passworts', 'error');
}
});
}
// Profil-Update
else {
// Sammle Daten aus den Eingabefeldern
const formData = new FormData();
formData.append('action', 'update_profile');
formData.append('bio', document.getElementById('bio').value || '');
formData.append('location', document.getElementById('location').value || '');
formData.append('website', document.getElementById('website').value || '');
// Avatar hinzufügen, falls vorhanden
const avatarUrlInput = document.getElementById('avatar_url');
if (avatarUrlInput) {
formData.append('avatar_url', avatarUrlInput.value);
}
// Visuelle Rückmeldung
const originalText = this.innerHTML;
this.innerHTML = '<i class="fas fa-circle-notch fa-spin mr-1"></i> Speichern...';
this.disabled = true;
// AJAX-Anfrage senden
fetch('{{ url_for("settings") }}', {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error('Fehler beim Speichern');
}
return response.json();
})
.catch(error => {
console.error('Fehler beim Speichern der Profileinstellungen:', error);
return { success: false, message: 'Netzwerkfehler. Bitte versuche es erneut.' };
})
.then(data => {
this.innerHTML = originalText;
this.disabled = false;
if (data && data.success) {
// Erfolgsanimation
showNotification('Profil erfolgreich aktualisiert!', 'success');
// UI aktualisieren ohne Neuladen
const bioElement = document.querySelector('.user-bio');
const locationElement = document.querySelector('.user-meta span:first-child');
if (bioElement) {
bioElement.textContent = document.getElementById('bio').value || 'Keine Bio vorhanden. Klicke auf bearbeiten, um eine hinzuzufügen.';
}
if (locationElement) {
const location = document.getElementById('location').value;
locationElement.innerHTML = `<i class="fas fa-map-marker-alt"></i> ${location || 'Kein Standort angegeben'}`;
}
} else {
showNotification(data?.message || 'Fehler beim Aktualisieren des Profils', 'error');
}
});
}
});
});
@@ -1054,6 +1175,76 @@
borderElem.style.borderLeftColor = borderElem.dataset.color;
}
});
// Mindmap-Karten mit Hover-Effekten
const mindmapItems = document.querySelectorAll('.mindmap-item');
mindmapItems.forEach(item => {
item.addEventListener('mouseenter', () => {
item.style.transform = 'translateY(-5px)';
item.style.boxShadow = '0 12px 30px rgba(0, 0, 0, 0.15)';
});
item.addEventListener('mouseleave', () => {
item.style.transform = 'translateY(0)';
item.style.boxShadow = 'none';
});
});
// Benachrichtigungsfunktion
function showNotification(message, type = 'info') {
// Bestehende Benachrichtigung entfernen
const existingNotification = document.getElementById('notification');
if (existingNotification) {
existingNotification.remove();
}
// Neue Benachrichtigung erstellen
const notification = document.createElement('div');
notification.id = 'notification';
notification.className = `fixed top-4 right-4 p-4 rounded-lg shadow-lg z-50 flex items-center transform transition-all duration-300 translate-y-0 opacity-0`;
// Typ-basierte Stile
if (type === 'success') {
notification.classList.add('bg-green-500', 'text-white');
notification.innerHTML = `<i class="fas fa-check-circle mr-2"></i> ${message}`;
} else if (type === 'error') {
notification.classList.add('bg-red-500', 'text-white');
notification.innerHTML = `<i class="fas fa-exclamation-circle mr-2"></i> ${message}`;
} else {
notification.classList.add('bg-blue-500', 'text-white');
notification.innerHTML = `<i class="fas fa-info-circle mr-2"></i> ${message}`;
}
// Close-Button
const closeBtn = document.createElement('button');
closeBtn.className = 'ml-4 text-white opacity-75 hover:opacity-100';
closeBtn.innerHTML = '<i class="fas fa-times"></i>';
closeBtn.addEventListener('click', () => {
notification.classList.add('opacity-0', 'translate-y-[-10px]');
setTimeout(() => notification.remove(), 300);
});
notification.appendChild(closeBtn);
document.body.appendChild(notification);
// Animation starten
setTimeout(() => {
notification.classList.remove('opacity-0');
notification.classList.add('opacity-100');
}, 10);
// Automatisch ausblenden nach 5 Sekunden
setTimeout(() => {
if (document.body.contains(notification)) {
notification.classList.add('opacity-0', 'translate-y-[-10px]');
setTimeout(() => {
if (document.body.contains(notification)) {
notification.remove();
}
}, 300);
}
}, 5000);
}
});
</script>
{% endblock %}