import React, { useCallback, useEffect, useState } from 'react'; import { Modal, ConfirmDialog } from '../ui/Modal'; import { AvatarPicker } from '../ui/AvatarPicker'; import { UserAvatar } from '../ui/UserAvatar'; import { ColorPicker } from './ColorPicker'; import { inputClass, labelClass } from '../leads/leadUi'; import { useToast } from '../Toast'; import { useAuth } from '../../lib/auth'; import { usersApi, type UserInput } from '../../lib/api'; import { userColor, type AppUser, type Role } from '../../types'; interface UserFormState { username: string; name: string; email: string; role: Role; is_active: boolean; password: string; /** null = Farbe automatisch aus der Palette */ color: string | null; /** '' = kein Foto, dann wird die Initiale angezeigt */ avatar: string; } const emptyForm: UserFormState = { username: '', name: '', email: '', role: 'user', is_active: true, password: '', color: null, avatar: '', }; const RoleBadge: React.FC<{ role: Role }> = ({ role }) => role === 'admin' ? ( Admin ) : ( User ); export const UserManagement: React.FC = () => { const { addToast } = useToast(); const { user: currentUser, updateUser } = useAuth(); const [users, setUsers] = useState([]); const [isLoading, setIsLoading] = useState(true); const [editingUser, setEditingUser] = useState(null); const [isModalOpen, setIsModalOpen] = useState(false); const [form, setForm] = useState(emptyForm); const [isSaving, setIsSaving] = useState(false); const [userToDelete, setUserToDelete] = useState(null); const load = useCallback(async () => { setIsLoading(true); try { setUsers(await usersApi.list()); } catch (err) { addToast(err instanceof Error ? err.message : 'Benutzer konnten nicht geladen werden.', 'error'); } finally { setIsLoading(false); } }, [addToast]); useEffect(() => { load(); }, [load]); const openCreate = () => { setEditingUser(null); setForm(emptyForm); setIsModalOpen(true); }; const openEdit = (user: AppUser) => { setEditingUser(user); setForm({ username: user.username, name: user.name, email: user.email ?? '', role: user.role, is_active: user.is_active, password: '', color: user.color ?? null, avatar: user.avatar ?? '', }); setIsModalOpen(true); }; const set = (key: K, value: UserFormState[K]) => setForm((prev) => ({ ...prev, [key]: value })); const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); if (!form.username.trim() || !form.name.trim()) { addToast('Benutzername und Name sind erforderlich.', 'error'); return; } if (!editingUser && form.password.length < 8) { addToast('Das Passwort muss mindestens 8 Zeichen haben.', 'error'); return; } if (editingUser && form.password && form.password.length < 8) { addToast('Das neue Passwort muss mindestens 8 Zeichen haben.', 'error'); return; } setIsSaving(true); try { const payload: UserInput = { username: form.username.trim(), name: form.name.trim(), email: form.email.trim() || null, role: form.role, is_active: form.is_active, color: form.color, avatar: form.avatar || null, }; if (form.password) payload.password = form.password; if (editingUser) { const updated = await usersApi.update(editingUser.id, payload); addToast(`Benutzer "${updated.name}" wurde aktualisiert.`, 'success'); // Eigene Daten sofort in der laufenden Sitzung uebernehmen. if (currentUser && updated.id === currentUser.id) updateUser(updated); } else { const created = await usersApi.create(payload); addToast(`Benutzer "${created.name}" wurde angelegt.`, 'success'); } setIsModalOpen(false); await load(); } catch (err) { addToast(err instanceof Error ? err.message : 'Speichern fehlgeschlagen.', 'error'); } finally { setIsSaving(false); } }; const confirmDelete = async () => { if (!userToDelete) return; try { await usersApi.remove(userToDelete.id); addToast(`Benutzer "${userToDelete.name}" wurde gelöscht.`, 'success'); await load(); } catch (err) { addToast(err instanceof Error ? err.message : 'Löschen fehlgeschlagen.', 'error'); } finally { setUserToDelete(null); } }; return (

Benutzer ({users.length})

Rolle Admin sieht die Einstellungen, Rolle User nicht.

{isLoading ? (
) : (
    {users.map((user) => (
  • {user.name} @{user.username} {user.id === currentUser?.id && ( Ich )} {!user.is_active && ( Deaktiviert )}
    {user.email &&

    {user.email}

    }
    {user.id !== currentUser?.id && ( )}
  • ))}
)} {isModalOpen && ( setIsModalOpen(false)} size="md" footer={
} >
set('avatar', value)} initials={form.name.charAt(0).toUpperCase() || '?'} />
set('username', event.target.value)} className={inputClass} autoComplete="off" />
set('name', event.target.value)} className={inputClass} />
set('email', event.target.value)} className={inputClass} autoComplete="off" />
set('password', event.target.value)} placeholder={editingUser ? 'Leer lassen, um es nicht zu ändern' : 'Mindestens 8 Zeichen'} className={inputClass} autoComplete="new-password" />
set('color', color)} />
)} {userToDelete && ( setUserToDelete(null)} /> )}
); };