351 lines
12 KiB
TypeScript
351 lines
12 KiB
TypeScript
/**
|
|
* Settings Page
|
|
*
|
|
* Benutzer-Einstellungen (System-Level, ohne Instanz-Kontext).
|
|
*/
|
|
|
|
import React, { useState, useCallback } from 'react';
|
|
import { useLanguage } from '../providers/language/LanguageContext';
|
|
import { useCurrentUser, useUser } from '../hooks/useUsers';
|
|
import { setUserDataCache, getUserDataCache } from '../utils/userCache';
|
|
import { FormGeneratorForm } from '../components/FormGenerator/FormGeneratorForm/FormGeneratorForm';
|
|
import type { AttributeDefinition } from '../components/FormGenerator/FormGeneratorForm/FormGeneratorForm';
|
|
import styles from './Settings.module.css';
|
|
|
|
// =============================================================================
|
|
// PROFILE EDIT MODAL
|
|
// =============================================================================
|
|
|
|
interface ProfileEditModalProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
userData: any;
|
|
onSave: (data: any) => Promise<void>;
|
|
}
|
|
|
|
const ProfileEditModal: React.FC<ProfileEditModalProps> = ({ isOpen, onClose, userData, onSave }) => {
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
// Define editable profile fields
|
|
const profileAttributes: AttributeDefinition[] = [
|
|
{
|
|
name: 'fullName',
|
|
type: 'string',
|
|
label: 'Vollständiger Name',
|
|
description: 'Ihr vollständiger Name',
|
|
required: false,
|
|
placeholder: 'Max Mustermann'
|
|
},
|
|
{
|
|
name: 'email',
|
|
type: 'email',
|
|
label: 'E-Mail-Adresse',
|
|
description: 'Ihre E-Mail-Adresse für Benachrichtigungen',
|
|
required: true,
|
|
placeholder: 'name@example.com'
|
|
},
|
|
{
|
|
name: 'language',
|
|
type: 'select',
|
|
label: 'Sprache',
|
|
description: 'Anzeigesprache der Anwendung',
|
|
required: true,
|
|
options: [
|
|
{ value: 'de', label: 'Deutsch' },
|
|
{ value: 'en', label: 'English' },
|
|
{ value: 'fr', label: 'Français' }
|
|
]
|
|
}
|
|
];
|
|
|
|
const handleSubmit = async (formData: any) => {
|
|
setIsSaving(true);
|
|
setError(null);
|
|
try {
|
|
await onSave(formData);
|
|
onClose();
|
|
} catch (err: any) {
|
|
setError(err.message || 'Fehler beim Speichern des Profils');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
};
|
|
|
|
if (!isOpen) return null;
|
|
|
|
return (
|
|
<div className={styles.modalOverlay} onClick={onClose}>
|
|
<div className={styles.modalContent} onClick={(e) => e.stopPropagation()}>
|
|
<div className={styles.modalHeader}>
|
|
<h2>Profil bearbeiten</h2>
|
|
<button className={styles.closeButton} onClick={onClose}>×</button>
|
|
</div>
|
|
<div className={styles.modalBody}>
|
|
{error && <div className={styles.errorMessage}>{error}</div>}
|
|
<FormGeneratorForm
|
|
attributes={profileAttributes}
|
|
data={userData}
|
|
mode="edit"
|
|
onSubmit={handleSubmit}
|
|
onCancel={onClose}
|
|
submitButtonText={isSaving ? 'Speichern...' : 'Speichern'}
|
|
cancelButtonText="Abbrechen"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
// =============================================================================
|
|
// SETTINGS PAGE
|
|
// =============================================================================
|
|
|
|
export const SettingsPage: React.FC = () => {
|
|
const { currentLanguage, setLanguage } = useLanguage();
|
|
const { user: currentUser, refetch: refetchUser } = useCurrentUser();
|
|
const { updateUser } = useUser();
|
|
|
|
const [theme, setTheme] = useState<'light' | 'dark'>(
|
|
() => (localStorage.getItem('theme') as 'light' | 'dark') || 'light'
|
|
);
|
|
const [isProfileModalOpen, setIsProfileModalOpen] = useState(false);
|
|
const [isSavingLanguage, setIsSavingLanguage] = useState(false);
|
|
const [languageError, setLanguageError] = useState<string | null>(null);
|
|
|
|
// Handle theme change
|
|
const handleThemeChange = (newTheme: 'light' | 'dark') => {
|
|
setTheme(newTheme);
|
|
localStorage.setItem('theme', newTheme);
|
|
|
|
if (newTheme === 'dark') {
|
|
document.documentElement.classList.add('dark-theme');
|
|
document.documentElement.classList.remove('light-theme');
|
|
} else {
|
|
document.documentElement.classList.add('light-theme');
|
|
document.documentElement.classList.remove('dark-theme');
|
|
}
|
|
document.documentElement.setAttribute('data-theme', newTheme);
|
|
};
|
|
|
|
// Handle language change - save to backend and update cache
|
|
const handleLanguageChange = useCallback(async (newLanguage: 'de' | 'en' | 'fr') => {
|
|
if (!currentUser?.id || !currentUser?.username) return;
|
|
|
|
setIsSavingLanguage(true);
|
|
setLanguageError(null);
|
|
|
|
try {
|
|
// 1. Build full user object for update (backend requires full User model)
|
|
const userUpdateData = {
|
|
id: currentUser.id,
|
|
username: currentUser.username,
|
|
email: currentUser.email,
|
|
fullName: currentUser.fullName,
|
|
language: newLanguage,
|
|
enabled: currentUser.enabled ?? true,
|
|
authenticationAuthority: currentUser.authenticationAuthority || 'local'
|
|
};
|
|
|
|
// 2. Save to backend
|
|
await updateUser(currentUser.id, userUpdateData);
|
|
|
|
// 3. Update sessionStorage cache
|
|
const cachedUser = getUserDataCache();
|
|
if (cachedUser) {
|
|
setUserDataCache({ ...cachedUser, language: newLanguage });
|
|
}
|
|
|
|
// 4. Update UI language context
|
|
setLanguage(newLanguage);
|
|
|
|
// 5. Dispatch event to notify other components
|
|
window.dispatchEvent(new CustomEvent('userInfoUpdated'));
|
|
|
|
console.log('Language updated successfully to:', newLanguage);
|
|
} catch (err: any) {
|
|
console.error('Failed to update language:', err);
|
|
setLanguageError('Sprache konnte nicht gespeichert werden');
|
|
} finally {
|
|
setIsSavingLanguage(false);
|
|
}
|
|
}, [currentUser, updateUser, setLanguage]);
|
|
|
|
// Handle profile save
|
|
const handleProfileSave = useCallback(async (formData: any) => {
|
|
if (!currentUser?.id || !currentUser?.username) throw new Error('Nicht angemeldet');
|
|
|
|
// Get the new language (from form or current user)
|
|
const newLanguage = formData.language || currentUser.language || 'de';
|
|
|
|
// Build full user object for update (backend requires full User model)
|
|
const userUpdateData = {
|
|
id: currentUser.id,
|
|
username: currentUser.username,
|
|
email: formData.email || currentUser.email,
|
|
fullName: formData.fullName || currentUser.fullName,
|
|
language: newLanguage,
|
|
enabled: currentUser.enabled ?? true,
|
|
authenticationAuthority: currentUser.authenticationAuthority || 'local'
|
|
};
|
|
|
|
// Update user via API
|
|
const updatedUser = await updateUser(currentUser.id, userUpdateData);
|
|
|
|
// Update sessionStorage cache
|
|
const cachedUser = getUserDataCache();
|
|
if (cachedUser) {
|
|
setUserDataCache({
|
|
...cachedUser,
|
|
fullName: updatedUser.fullName || cachedUser.fullName,
|
|
email: updatedUser.email || cachedUser.email,
|
|
language: newLanguage
|
|
});
|
|
}
|
|
|
|
// Update UI language if changed
|
|
if (newLanguage !== currentLanguage) {
|
|
setLanguage(newLanguage as 'de' | 'en' | 'fr');
|
|
}
|
|
|
|
// Refetch user data
|
|
if (refetchUser) {
|
|
await refetchUser();
|
|
}
|
|
|
|
// Dispatch event to notify other components (e.g., sidebar)
|
|
window.dispatchEvent(new CustomEvent('userInfoUpdated'));
|
|
|
|
}, [currentUser, updateUser, refetchUser, currentLanguage, setLanguage]);
|
|
|
|
return (
|
|
<div className={styles.settings}>
|
|
<header className={styles.header}>
|
|
<h1>Einstellungen</h1>
|
|
<p className={styles.subtitle}>Persönliche Einstellungen und Präferenzen</p>
|
|
</header>
|
|
|
|
<main className={styles.content}>
|
|
{/* Darstellung */}
|
|
<section className={styles.section}>
|
|
<h2 className={styles.sectionTitle}>Darstellung</h2>
|
|
|
|
<div className={styles.settingRow}>
|
|
<div className={styles.settingInfo}>
|
|
<label className={styles.settingLabel}>Theme</label>
|
|
<p className={styles.settingDescription}>
|
|
Wähle zwischen hellem und dunklem Design.
|
|
</p>
|
|
</div>
|
|
<div className={styles.settingControl}>
|
|
<div className={styles.themeToggle}>
|
|
<button
|
|
className={`${styles.themeButton} ${theme === 'light' ? styles.active : ''}`}
|
|
onClick={() => handleThemeChange('light')}
|
|
>
|
|
Hell
|
|
</button>
|
|
<button
|
|
className={`${styles.themeButton} ${theme === 'dark' ? styles.active : ''}`}
|
|
onClick={() => handleThemeChange('dark')}
|
|
>
|
|
Dunkel
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className={styles.settingRow}>
|
|
<div className={styles.settingInfo}>
|
|
<label className={styles.settingLabel}>Sprache</label>
|
|
<p className={styles.settingDescription}>
|
|
Wähle die Anzeigesprache der Anwendung.
|
|
{languageError && <span className={styles.errorText}> {languageError}</span>}
|
|
</p>
|
|
</div>
|
|
<div className={styles.settingControl}>
|
|
<select
|
|
className={styles.select}
|
|
value={currentLanguage}
|
|
onChange={(e) => handleLanguageChange(e.target.value as 'de' | 'en' | 'fr')}
|
|
disabled={isSavingLanguage}
|
|
>
|
|
<option value="de">Deutsch</option>
|
|
<option value="en">English</option>
|
|
<option value="fr">Français</option>
|
|
</select>
|
|
{isSavingLanguage && <span className={styles.savingIndicator}>Speichern...</span>}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Konto */}
|
|
<section className={styles.section}>
|
|
<h2 className={styles.sectionTitle}>Konto</h2>
|
|
|
|
<div className={styles.settingRow}>
|
|
<div className={styles.settingInfo}>
|
|
<label className={styles.settingLabel}>Profil bearbeiten</label>
|
|
<p className={styles.settingDescription}>
|
|
Ändere deinen Namen und E-Mail-Adresse.
|
|
</p>
|
|
</div>
|
|
<div className={styles.settingControl}>
|
|
<button
|
|
className={styles.button}
|
|
onClick={() => setIsProfileModalOpen(true)}
|
|
>
|
|
Profil öffnen
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Current user info display */}
|
|
{currentUser && (
|
|
<div className={styles.userInfoCard}>
|
|
<div className={styles.userInfoRow}>
|
|
<span className={styles.userInfoLabel}>Benutzername</span>
|
|
<span className={styles.userInfoValue}>{currentUser.username}</span>
|
|
</div>
|
|
<div className={styles.userInfoRow}>
|
|
<span className={styles.userInfoLabel}>Name</span>
|
|
<span className={styles.userInfoValue}>{currentUser.fullName || '-'}</span>
|
|
</div>
|
|
<div className={styles.userInfoRow}>
|
|
<span className={styles.userInfoLabel}>E-Mail</span>
|
|
<span className={styles.userInfoValue}>{currentUser.email || '-'}</span>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</section>
|
|
|
|
{/* Info */}
|
|
<section className={styles.section}>
|
|
<h2 className={styles.sectionTitle}>Über</h2>
|
|
|
|
<div className={styles.infoCard}>
|
|
<div className={styles.infoRow}>
|
|
<span className={styles.infoLabel}>Version</span>
|
|
<span className={styles.infoValue}>2.0.0</span>
|
|
</div>
|
|
<div className={styles.infoRow}>
|
|
<span className={styles.infoLabel}>Build</span>
|
|
<span className={styles.infoValue}>2026.01.20</span>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
{/* Profile Edit Modal */}
|
|
<ProfileEditModal
|
|
isOpen={isProfileModalOpen}
|
|
onClose={() => setIsProfileModalOpen(false)}
|
|
userData={currentUser}
|
|
onSave={handleProfileSave}
|
|
/>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default SettingsPage;
|