import { type FormEvent, type ReactNode, useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useAuth } from '../auth/auth-context'; import { Button } from '../../components/ui/button'; import { Input } from '../../components/ui/input'; import { Modal } from '../../components/ui/modal'; import { Select } from '../../components/ui/select'; import { useToast } from '../../components/ui/toast'; import { ApiError } from '../../lib/api-client'; import { createUser, deleteUser, fetchUsers, resetUserPassword, updateUser, } from '../../lib/users-api'; import { UserPermissionsModal } from './user-permissions-modal'; import { createUserSchema, resetPasswordSchema, ROLE_NAMES, type RoleName, type User, } from '../../lib/schemas'; /** Formular-Fehler aus Zod-Issues (nur erste Meldung pro Feld). */ function fieldErrorsFromZod( issues: Array<{ path: (string | number | symbol)[]; message: string }>, ): Record { const errors: Record = {}; for (const issue of issues) { const key = String(issue.path[0] ?? 'form'); if (!errors[key]) { errors[key] = issue.message; } } return errors; } /** API-Fehler-Details in einfache Feldmeldungen umwandeln. */ function fieldErrorsFromApi(details: Record | undefined): Record { if (!details) { return {}; } const errors: Record = {}; for (const [key, value] of Object.entries(details)) { errors[key] = Array.isArray(value) ? value[0] : value; } return errors; } /** Datumsformat für Tabellenanzeigen. */ function formatDate(isoDate: string | null): string { if (!isoDate) { return '–'; } return new Date(isoDate).toLocaleDateString('de-DE', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', }); } /** Formular: Neuen Benutzer anlegen. */ function CreateUserModal({ open, onClose, onCreated, }: { open: boolean; onClose: () => void; onCreated: () => void; }): ReactNode { const { showToast } = useToast(); const [fieldErrors, setFieldErrors] = useState>({}); const [formError, setFormError] = useState(null); const createMutation = useMutation({ mutationFn: createUser, onSuccess: (user) => { showToast('success', `Benutzer "${user.username}" wurde angelegt`); onCreated(); onClose(); }, onError: (error) => { if (error instanceof ApiError) { setFormError(error.message); setFieldErrors(fieldErrorsFromApi(error.details)); } else { setFormError('Benutzer konnte nicht angelegt werden'); } }, }); function handleSubmit(event: FormEvent): void { event.preventDefault(); setFieldErrors({}); setFormError(null); const formData = new FormData(event.currentTarget); const parsed = createUserSchema.safeParse({ username: formData.get('username'), email: formData.get('email'), displayName: formData.get('displayName'), password: formData.get('password'), role: formData.get('role'), }); if (!parsed.success) { setFieldErrors(fieldErrorsFromZod(parsed.error.issues)); return; } createMutation.mutate(parsed.data); } return (
{formError && (

{formError}

)}
); } /** Bestätigungsdialog: Benutzer löschen. */ function DeleteUserModal({ user, onClose }: { user: User; onClose: () => void }): ReactNode { const { showToast } = useToast(); const queryClient = useQueryClient(); const [formError, setFormError] = useState(null); const deleteMutation = useMutation({ mutationFn: () => deleteUser(user.id), onSuccess: () => { void queryClient.invalidateQueries({ queryKey: ['users'] }); showToast('success', `Benutzer "${user.username}" wurde gelöscht`); onClose(); }, onError: (error) => { setFormError(error instanceof ApiError ? error.message : 'Löschen fehlgeschlagen'); }, }); return ( {formError && (

{formError}

)}
); } /** Benutzerverwaltung (nur Admin): Liste, Anlegen, Bearbeiten, Passwort, Löschen. */ export function UsersPage(): ReactNode { const { user: currentUser } = useAuth(); const queryClient = useQueryClient(); const { showToast } = useToast(); const [createOpen, setCreateOpen] = useState(false); const [editUser, setEditUser] = useState(null); const [resetPasswordUser, setResetPasswordUser] = useState(null); const [deleteTarget, setDeleteTarget] = useState(null); const [permissionsUser, setPermissionsUser] = useState(null); const usersQuery = useQuery({ queryKey: ['users'], queryFn: fetchUsers, }); const toggleActiveMutation = useMutation({ mutationFn: (target: User) => updateUser(target.id, { isActive: !target.isActive }), onSuccess: () => { void queryClient.invalidateQueries({ queryKey: ['users'] }); showToast('success', 'Status wurde aktualisiert'); }, onError: (error) => { showToast('error', error instanceof ApiError ? error.message : 'Aktualisierung fehlgeschlagen'); }, }); return (

Benutzerverwaltung

Benutzer anlegen, bearbeiten und verwalten.

{usersQuery.isLoading && ( )} {usersQuery.isError && ( )} {usersQuery.data?.map((user) => ( ))}
Benutzer Rolle Status Letzter Login Aktionen
Benutzer werden geladen…
Benutzer konnten nicht geladen werden.
{user.displayName}
@{user.username} · {user.email}
{user.role === 'ADMIN' ? 'Administrator' : 'Benutzer'} {user.isActive ? 'Aktiv' : 'Deaktiviert'} {formatDate(user.lastLoginAt)}
{user.id !== currentUser?.id && ( <> )}
{usersQuery.data?.length === 0 && (

Noch keine Benutzer vorhanden.

)}
{createOpen && ( setCreateOpen(false)} onCreated={() => void queryClient.invalidateQueries({ queryKey: ['users'] })} /> )} {editUser && setEditUser(null)} />} {resetPasswordUser && ( setResetPasswordUser(null)} /> )} {deleteTarget && ( setDeleteTarget(null)} /> )} {permissionsUser && ( setPermissionsUser(null)} /> )}
); }