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. Benutzer anlegen {isLoading ? ( ) : ( {users.map((user) => ( {user.name} @{user.username} {user.id === currentUser?.id && ( Ich )} {!user.is_active && ( Deaktiviert )} {user.email && {user.email}} openEdit(user)} className="rounded-lg border border-ink-200 px-3 py-1.5 text-xs font-bold text-ink-600 transition hover:border-brand-300 hover:text-brand-700" > Bearbeiten {user.id !== currentUser?.id && ( setUserToDelete(user)} className="rounded-lg p-2 text-ink-400 transition hover:bg-red-50 hover:text-red-600" aria-label={`${user.name} löschen`} > )} ))} )} {isModalOpen && ( setIsModalOpen(false)} size="md" footer={ setIsModalOpen(false)} className="flex-1 rounded-xl border border-ink-200 py-3 text-sm font-bold text-ink-600 transition hover:bg-ink-100" > Abbrechen {isSaving ? 'Speichere...' : 'Speichern'} } > Foto set('avatar', value)} initials={form.name.charAt(0).toUpperCase() || '?'} /> Benutzername * set('username', event.target.value)} className={inputClass} autoComplete="off" /> Anzeigename * set('name', event.target.value)} className={inputClass} /> E-Mail set('email', event.target.value)} className={inputClass} autoComplete="off" /> Rolle set('role', event.target.value as Role)} className={inputClass} > User – ohne Einstellungen Admin – mit Einstellungen {editingUser ? 'Neues Passwort (optional)' : 'Passwort *'} set('password', event.target.value)} placeholder={editingUser ? 'Leer lassen, um es nicht zu ändern' : 'Mindestens 8 Zeichen'} className={inputClass} autoComplete="new-password" /> Kalenderfarbe set('color', color)} /> set('is_active', event.target.checked)} className="h-4 w-4 accent-brand-500" /> Konto aktiv )} {userToDelete && ( setUserToDelete(null)} /> )} ); };
Rolle Admin sieht die Einstellungen, Rolle User nicht.
{user.email}