diff --git a/src/components/Sidebar/SidebarUser.tsx b/src/components/Sidebar/SidebarUser.tsx index fc9eade..df046d2 100644 --- a/src/components/Sidebar/SidebarUser.tsx +++ b/src/components/Sidebar/SidebarUser.tsx @@ -2,16 +2,44 @@ import React, { useState, useEffect, useRef } from 'react' import { useMsal } from '@azure/msal-react' import { FaSignOutAlt } from 'react-icons/fa' import styles from './SidebarStyles/SidebarUser.module.css' -import { useCurrentUser } from '../../hooks/useUsers' +import { useCurrentUser, useUser, User } from '../../hooks/useUsers' import { SidebarUserProps } from './sidebarTypes'; const SidebarUser: React.FC = ({ isMinimized = false }) => { const { instance } = useMsal(); - const { user, isLoading, error, logout } = useCurrentUser(); + const { user: currentUser, isLoading: currentUserLoading, logout } = useCurrentUser(); + const { getUser } = useUser(); + + // Local state for user data fetched directly via API + const [user, setUser] = useState(null); + const [userLoading, setUserLoading] = useState(false); + const [userError, setUserError] = useState(null); + const hasLoadedUser = useRef(false); + const [showLogoutMenu, setShowLogoutMenu] = useState(false); const [isLoggingOut, setIsLoggingOut] = useState(false); const userSectionRef = useRef(null); + // Fetch user data directly using the /api/users/{userId} endpoint + const fetchUserData = async () => { + if (!currentUser?.id || hasLoadedUser.current) return; + + hasLoadedUser.current = true; + setUserLoading(true); + setUserError(null); + + try { + const userData = await getUser(currentUser.id); + setUser(userData); + } catch (error) { + console.error('Failed to fetch user data in sidebar:', error); + setUserError(typeof error === 'string' ? error : 'Failed to load user data'); + hasLoadedUser.current = false; // Reset on error to allow retry + } finally { + setUserLoading(false); + } + }; + // Function to get initials from full name const getInitials = (fullName: string): string => { return fullName @@ -44,6 +72,28 @@ const SidebarUser: React.FC = ({ isMinimized = false }) => { } }; + // Fetch user data when currentUser is available + useEffect(() => { + if (currentUser?.id && !hasLoadedUser.current) { + fetchUserData(); + } + }, [currentUser?.id]); + + // Listen for user updates from settings page + useEffect(() => { + const handleUserUpdate = () => { + hasLoadedUser.current = false; // Reset flag + if (currentUser?.id) { + fetchUserData(); + } + }; + + window.addEventListener('userInfoUpdated', handleUserUpdate); + return () => { + window.removeEventListener('userInfoUpdated', handleUserUpdate); + }; + }, [currentUser?.id]); + // Close popup when clicking outside useEffect(() => { const handleClickOutside = (event: MouseEvent) => { @@ -61,7 +111,7 @@ const SidebarUser: React.FC = ({ isMinimized = false }) => { }; }, [showLogoutMenu]); - if (isLoading) { + if (currentUserLoading || userLoading) { return (
Lädt...
@@ -69,7 +119,7 @@ const SidebarUser: React.FC = ({ isMinimized = false }) => { ); } - if (error) { + if (userError) { return (
Fehler beim Laden des Benutzerprofils
diff --git a/src/components/settings/settingsUser.module.css b/src/components/settings/settingsUser.module.css new file mode 100644 index 0000000..e69de29 diff --git a/src/components/settings/settingsUser.tsx b/src/components/settings/settingsUser.tsx new file mode 100644 index 0000000..e69de29 diff --git a/src/hooks/useUsers.ts b/src/hooks/useUsers.ts index ace7029..716c08e 100644 --- a/src/hooks/useUsers.ts +++ b/src/hooks/useUsers.ts @@ -111,7 +111,7 @@ export function useOrgUsers() { } }; - const updateUser = async (userId: string, userData: UserUpdateData) => { + const updateUser = async (userId: string, userData: User) => { await request({ url: `/api/users/${userId}`, method: 'put', @@ -161,7 +161,7 @@ export function useUser() { }); }; - const updateUser = async (userId: string, userData: UserUpdateData): Promise => { + const updateUser = async (userId: string, userData: User): Promise => { return await request({ url: `/api/users/${userId}`, method: 'put', diff --git a/src/locales/de.ts b/src/locales/de.ts index 3052317..220539a 100644 --- a/src/locales/de.ts +++ b/src/locales/de.ts @@ -21,6 +21,25 @@ export default { 'settings.theme.dark': 'Dunkel', 'settings.theme.toggle.light': 'Zu hellem Modus wechseln', 'settings.theme.toggle.dark': 'Zu dunklem Modus wechseln', + 'settings.userinfo': 'Benutzerinformationen', + 'settings.userinfo.description': 'Verwalten Sie Ihre Kontoinformationen', + 'settings.userinfo.username': 'Benutzername', + 'settings.userinfo.fullname': 'Vollständiger Name', + 'settings.userinfo.email': 'E-Mail-Adresse', + 'settings.userinfo.language': 'Sprache', + 'settings.userinfo.privilege': 'Berechtigungsstufe', + 'settings.userinfo.enabled': 'Kontostatus', + 'settings.userinfo.auth_authority': 'Authentifizierungsanbieter', + 'settings.userinfo.enabled.true': 'Aktiv', + 'settings.userinfo.enabled.false': 'Inaktiv', + 'settings.userinfo.loading': 'Benutzerinformationen werden geladen...', + 'settings.userinfo.error': 'Fehler beim Laden der Benutzerinformationen', + 'settings.userinfo.save': 'Änderungen speichern', + 'settings.userinfo.saving': 'Speichern...', + 'settings.userinfo.success': 'Benutzerinformationen erfolgreich aktualisiert', + 'settings.userinfo.update_error': 'Fehler beim Aktualisieren der Benutzerinformationen', + 'settings.userinfo.managed_by': 'Verwaltet von {provider}', + 'settings.userinfo.managed_note': 'Dieses Feld wird von {provider} verwaltet und kann nicht geändert werden', // Languages 'language.german': 'Deutsch', @@ -305,6 +324,12 @@ export default { 'files.upload.error': 'Beim Hochladen ist ein Fehler aufgetreten.', 'files.upload.unexpected_error': 'Beim Hochladen ist ein unerwarteter Fehler aufgetreten.', + // Files Page Upload Actions + 'files.drop_zone': 'Dateien hier ablegen', + 'files.upload_button': 'Dateien hochladen', + 'files.uploading_button': 'Wird hochgeladen...', + 'files.upload_aria_label': 'Dateien hochladen', + // Files Page 'files.title': 'Dateien', 'files.table.title': 'Dateien', diff --git a/src/locales/en.ts b/src/locales/en.ts index 5c0eeb2..8c2d68a 100644 --- a/src/locales/en.ts +++ b/src/locales/en.ts @@ -21,6 +21,25 @@ export default { 'settings.theme.dark': 'Dark', 'settings.theme.toggle.light': 'Switch to light mode', 'settings.theme.toggle.dark': 'Switch to dark mode', + 'settings.userinfo': 'User Information', + 'settings.userinfo.description': 'Manage your account information', + 'settings.userinfo.username': 'Username', + 'settings.userinfo.fullname': 'Full Name', + 'settings.userinfo.email': 'Email Address', + 'settings.userinfo.language': 'Language', + 'settings.userinfo.privilege': 'Privilege Level', + 'settings.userinfo.enabled': 'Account Status', + 'settings.userinfo.auth_authority': 'Authentication Provider', + 'settings.userinfo.enabled.true': 'Active', + 'settings.userinfo.enabled.false': 'Inactive', + 'settings.userinfo.loading': 'Loading user information...', + 'settings.userinfo.error': 'Error loading user information', + 'settings.userinfo.save': 'Save Changes', + 'settings.userinfo.saving': 'Saving...', + 'settings.userinfo.success': 'User information updated successfully', + 'settings.userinfo.update_error': 'Error updating user information', + 'settings.userinfo.managed_by': 'Managed by {provider}', + 'settings.userinfo.managed_note': 'This field is managed by {provider} and cannot be changed', // Languages 'language.german': 'Deutsch', @@ -306,6 +325,12 @@ export default { 'files.upload.error': 'An error occurred while uploading.', 'files.upload.unexpected_error': 'An unexpected error occurred while uploading.', + // Files Page Upload Actions + 'files.drop_zone': 'Drop files here', + 'files.upload_button': 'Upload Files', + 'files.uploading_button': 'Uploading...', + 'files.upload_aria_label': 'Upload files', + // Files Page 'files.title': 'Files', 'files.table.title': 'Files', diff --git a/src/locales/fr.ts b/src/locales/fr.ts index 1d991cc..6c36af4 100644 --- a/src/locales/fr.ts +++ b/src/locales/fr.ts @@ -21,6 +21,25 @@ export default { 'settings.theme.dark': 'Sombre', 'settings.theme.toggle.light': 'Passer en mode clair', 'settings.theme.toggle.dark': 'Passer en mode sombre', + 'settings.userinfo': 'Informations utilisateur', + 'settings.userinfo.description': 'Gérez vos informations de compte', + 'settings.userinfo.username': 'Nom d\'utilisateur', + 'settings.userinfo.fullname': 'Nom complet', + 'settings.userinfo.email': 'Adresse e-mail', + 'settings.userinfo.language': 'Langue', + 'settings.userinfo.privilege': 'Niveau de privilège', + 'settings.userinfo.enabled': 'Statut du compte', + 'settings.userinfo.auth_authority': 'Fournisseur d\'authentification', + 'settings.userinfo.enabled.true': 'Actif', + 'settings.userinfo.enabled.false': 'Inactif', + 'settings.userinfo.loading': 'Chargement des informations utilisateur...', + 'settings.userinfo.error': 'Erreur lors du chargement des informations utilisateur', + 'settings.userinfo.save': 'Enregistrer les modifications', + 'settings.userinfo.saving': 'Enregistrement...', + 'settings.userinfo.success': 'Informations utilisateur mises à jour avec succès', + 'settings.userinfo.update_error': 'Erreur lors de la mise à jour des informations utilisateur', + 'settings.userinfo.managed_by': 'Géré par {provider}', + 'settings.userinfo.managed_note': 'Ce champ est géré par {provider} et ne peut pas être modifié', // Languages 'language.german': 'Deutsch', @@ -306,6 +325,12 @@ export default { 'files.upload.error': 'Une erreur s\'est produite lors du téléchargement.', 'files.upload.unexpected_error': 'Une erreur inattendue s\'est produite lors du téléchargement.', + // Files Page Upload Actions + 'files.drop_zone': 'Déposer les fichiers ici', + 'files.upload_button': 'Télécharger des fichiers', + 'files.uploading_button': 'Téléchargement...', + 'files.upload_aria_label': 'Télécharger des fichiers', + // Files Page 'files.title': 'Fichiers', 'files.table.title': 'Fichiers', diff --git a/src/pages/Home/Dateien.tsx b/src/pages/Home/Dateien.tsx index 895d864..139c229 100644 --- a/src/pages/Home/Dateien.tsx +++ b/src/pages/Home/Dateien.tsx @@ -74,17 +74,17 @@ function Dateien() { onClick={triggerFilePicker} > - {isDragOver ? 'Drop files here' : 'Drop files here'} + {t('files.drop_zone')}
(null); + const [userLoading, setUserLoading] = useState(false); + const [userError, setUserError] = useState(null); + + // Form state for user info + const [userForm, setUserForm] = useState({ + username: '', + fullName: '', + email: '', + language: 'en' as Language, + privilege: '', + enabled: true + }); + const [updateMessage, setUpdateMessage] = useState<{ type: 'success' | 'error', text: string } | null>(null); + const [isUpdating, setIsUpdating] = useState(false); // Flag to prevent form reset during update + const hasLoadedUser = useRef(false); + + // Fetch user data directly using the /api/users/{userId} endpoint + const fetchUserData = async () => { + if (!currentUser?.id || hasLoadedUser.current) return; + + hasLoadedUser.current = true; + setUserLoading(true); + setUserError(null); + + try { + const userData = await getUser(currentUser.id); + setUser(userData); + } catch (error) { + console.error('Failed to fetch user data:', error); + setUserError(typeof error === 'string' ? error : 'Failed to load user data'); + hasLoadedUser.current = false; // Reset on error to allow retry + } finally { + setUserLoading(false); + } + }; // Sync component state with current theme on mount useEffect(() => { @@ -13,6 +54,28 @@ function Einstellungen() { const prefersDark = savedTheme === 'dark' || (!savedTheme && window.matchMedia('(prefers-color-scheme: dark)').matches); setIsDarkMode(prefersDark); }, []); + + // Fetch user data when currentUser is available + useEffect(() => { + if (currentUser?.id && !hasLoadedUser.current) { + fetchUserData(); + } + }, [currentUser?.id]); + + // Update form when user data is loaded (but not during an active update) + useEffect(() => { + if (user && !isUpdating) { + console.log('🔄 Updating form with user data:', user); + setUserForm({ + username: user.username || '', + fullName: user.fullName || '', + email: user.email || '', + language: (user.language as Language) || currentLanguage, + privilege: user.privilege || '', + enabled: user.enabled + }); + } + }, [user, currentLanguage, isUpdating]); const applyTheme = (isDark: boolean) => { if (isDark) { @@ -31,14 +94,93 @@ function Einstellungen() { applyTheme(newIsDarkMode); localStorage.setItem('theme', newIsDarkMode ? 'dark' : 'light'); }; - - const handleLanguageChange = async (language: Language) => { - if (language === currentLanguage) return; + + const handleUserFormChange = (field: keyof typeof userForm, value: string | boolean) => { + setUserForm(prev => ({ ...prev, [field]: value })); + setUpdateMessage(null); // Clear any previous messages + + // Language change will be handled when the form is submitted, not immediately + }; + + const handleSaveUserInfo = async () => { + if (!user) return; + + setIsUpdating(true); // Prevent form reset during update try { - await setLanguage(language); + // Create complete User object with updated form data + // Only include editable fields based on authentication authority + const completeUserData: User = { + id: user.id, + username: user.authenticationAuthority === 'local' ? userForm.username : user.username, + fullName: user.authenticationAuthority === 'local' ? userForm.fullName : user.fullName, + email: user.authenticationAuthority === 'local' ? userForm.email : user.email, + language: userForm.language, // Language is always editable + privilege: userForm.privilege, + enabled: userForm.enabled, + authenticationAuthority: user.authenticationAuthority, + mandateId: user.mandateId + }; + + // Update user via API - this returns the updated user + const updatedUser = await updateUser(user.id, completeUserData); + + if (updatedUser) { + console.log('✅ User update successful:', updatedUser); + + // Update local user state with the returned data + setUser(updatedUser); + + // Update frontend language if it was changed + const newLanguage = updatedUser.language as Language; + if (newLanguage && newLanguage !== currentLanguage) { + try { + await setLanguage(newLanguage); + console.log('🌍 Frontend language updated to:', newLanguage); + } catch (error) { + console.error('Failed to change frontend language:', error); + } + } + + // Success: Update form with the actual returned data to ensure consistency + setUserForm({ + username: updatedUser.username || '', + fullName: updatedUser.fullName || '', + email: updatedUser.email || '', + language: newLanguage || currentLanguage, + privilege: updatedUser.privilege || '', + enabled: updatedUser.enabled + }); + + console.log('📝 Form updated with new data'); + + // Dispatch event to notify other components (like sidebar) that user data was updated + window.dispatchEvent(new CustomEvent('userInfoUpdated')); + + setUpdateMessage({ type: 'success', text: t('settings.userinfo.success') }); + } else { + throw new Error('No updated user data returned from server'); + } + + // Clear message after 3 seconds + setTimeout(() => setUpdateMessage(null), 3000); } catch (error) { - console.error('Failed to change language:', error); + console.error('Failed to update user info:', error); + setUpdateMessage({ type: 'error', text: t('settings.userinfo.update_error') }); + + // Reset form to original user data on error + if (user) { + setUserForm({ + username: user.username || '', + fullName: user.fullName || '', + email: user.email || '', + language: (user.language as Language) || currentLanguage, + privilege: user.privilege || '', + enabled: user.enabled + }); + } + } finally { + setIsUpdating(false); // Re-enable form sync } }; @@ -51,12 +193,12 @@ function Einstellungen() { } }; - if (isLoading) { + if (isLoading || currentUserLoading || userLoading) { return (
- {t('common.loading')} + {isLoading ? t('common.loading') : t('settings.userinfo.loading')}
@@ -70,6 +212,146 @@ function Einstellungen() {
+ {/* User Information Section */} + + {userError && ( +
+ {t('settings.userinfo.error')}: {typeof userError === 'string' ? userError : 'An error occurred'} +
+ )} + + {user && ( +
+ {t('settings.userinfo')} + + {t('settings.userinfo.description')} + +
+
+ + handleUserFormChange('username', e.target.value)} + placeholder={t('settings.userinfo.username')} + readOnly={user.authenticationAuthority !== 'local'} + title={user.authenticationAuthority !== 'local' ? + t('settings.userinfo.managed_note').replace('{provider}', user.authenticationAuthority) : + undefined} + /> +
+ +
+ + handleUserFormChange('fullName', e.target.value)} + placeholder={t('settings.userinfo.fullname')} + readOnly={user.authenticationAuthority !== 'local'} + title={user.authenticationAuthority !== 'local' ? + t('settings.userinfo.managed_note').replace('{provider}', user.authenticationAuthority) : + undefined} + /> +
+
+ +
+
+ + handleUserFormChange('email', e.target.value)} + placeholder={t('settings.userinfo.email')} + readOnly={user.authenticationAuthority !== 'local'} + title={user.authenticationAuthority !== 'local' ? + t('settings.userinfo.managed_note').replace('{provider}', user.authenticationAuthority) : + undefined} + /> +
+ +
+ + +
+
+ +
+
+ + +
+ +
+ + +
+
+ + {updateMessage && ( +
+ {updateMessage.text} +
+ )} + +
+ +
+
+ )} + + {/* Theme Setting */}
{t('settings.theme')} @@ -94,35 +376,7 @@ function Einstellungen() {
-
-
- {t('settings.language')} - - {t('settings.language.description')} - -
- - -
- -
-

{t('settings.about')}

-
-
- {t('settings.version')} - 1.0.0 -
-
-
+
diff --git a/src/pages/Home/HomeStyles/Einstellungen.module.css b/src/pages/Home/HomeStyles/Einstellungen.module.css index 8558f69..d254bd5 100644 --- a/src/pages/Home/HomeStyles/Einstellungen.module.css +++ b/src/pages/Home/HomeStyles/Einstellungen.module.css @@ -12,8 +12,8 @@ align-items: center; padding: 20px; background: var(--color-bg); - border-radius: 20px; - border: 2px solid var(--color-surface); + border-radius: 25px; + border: 1px solid var(--color-primary); gap: 20px; } @@ -44,7 +44,7 @@ gap: 12px; padding: 12px 20px; border-radius: 25px; - border: 2px solid var(--color-primary); + border: 1px solid var(--color-primary); background: var(--color-bg); color: var(--color-text); cursor: pointer; @@ -57,8 +57,7 @@ .themeToggle:hover { border-color: var(--color-secondary); - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(63, 81, 181, 0.15); + box-shadow: 0 4px 12px rgba(63, 81, 181, 0.1); } @@ -123,6 +122,140 @@ padding: 10px; } +/* User Information Form Styles */ +.userInfoForm { + display: flex; + flex-direction: column; + gap: 20px; + padding: 20px; + background: var(--color-bg); + border-radius: 25px; + border: 1px solid var(--color-primary); + margin-bottom: 20px; +} + +.formRow { + display: flex; + gap: 20px; + flex-wrap: wrap; +} + +.formField { + display: flex; + flex-direction: column; + gap: 8px; + flex: 1; + min-width: 250px; +} + +.fieldLabel { + font-size: 0.875rem; + font-weight: 500; + color: var(--color-text); + font-family: var(--font-family); +} + +.fieldNote { + font-size: 0.75rem; + font-weight: 400; + color: var(--color-primary); + font-style: italic; +} + +.formInput, +.formSelect { + padding: 12px 16px; + border-radius: 25px; + border: 1px solid var(--color-primary); + background: var(--color-bg); + color: var(--color-text); + font-family: var(--font-family); + font-size: 0.875rem; + transition: all 0.3s ease; + outline: none; +} + +.formInput:focus, +.formSelect:focus { + border-color: var(--color-primary); + box-shadow: 0 0 0 3px rgba(63, 81, 181, 0.1); +} + +.formInput:hover, +.formSelect:hover { + border-color: var(--color-secondary); +} + +.formInput[readonly] { + background: var(--color-gray-light); + cursor: not-allowed; + opacity: 0.7; +} + +.formSelect option { + background: var(--color-bg); + color: var(--color-text); + padding: 10px; +} + +.formActions { + display: flex; + justify-content: flex-end; + padding-top: 10px; +} + +.saveButton { + padding: 12px 24px; + border-radius: 25px; + border: none; + background: var(--color-secondary); + color: white; + cursor: pointer; + transition: all 0.3s ease; + font-family: var(--font-family); + font-size: 0.875rem; + font-weight: 500; + min-width: 120px; +} + +.saveButton:hover:not(:disabled) { + background: var(--color-secondary); + border-color: var(--color-secondary); + box-shadow: 0 4px 12px rgba(63, 81, 181, 0.3); +} + +.saveButton:disabled { + opacity: 0.6; + cursor: not-allowed; + transform: none; + box-shadow: none; +} + +.updateMessage { + padding: 12px 16px; + border-radius: 12px; + font-size: 0.875rem; + font-weight: 500; + margin-bottom: 10px; +} + +.successMessage { + background-color: #e8f5e8; + color: #2e7d32; + border: 1px solid #81c784; +} + +.errorMessage { + background-color: #fce4ec; + color: #c2185b; + border: 1px solid #f48fb1; + padding: 12px 16px; + border-radius: 12px; + font-size: 0.875rem; + font-weight: 500; + margin-bottom: 20px; +} + /* Responsive design */ @media (max-width: 768px) { .einstellungenContainer { @@ -142,4 +275,22 @@ .themeToggle { align-self: flex-end; } + + .formRow { + flex-direction: column; + gap: 15px; + } + + .formField { + min-width: unset; + } + + .formActions { + justify-content: center; + } + + .saveButton { + width: 100%; + max-width: 200px; + } } \ No newline at end of file