"use client"; import { zodResolver } from "@hookform/resolvers/zod"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Check, KeyRound, RotateCcw, Search, UserPlus2 } from "lucide-react"; import { useMemo, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { useAuth } from "@/components/auth"; import { apiDelete, apiFetch, apiGet, Paginated, User } from "@/lib/api"; const schema = z.object({ first_name: z.string().min(1, "Vorname erforderlich"), last_name: z.string().min(1, "Nachname erforderlich"), email: z.string().email("Gueltige E-Mail erforderlich"), role: z.enum(["admin", "pruefer", "mitarbeiter", "leser"]), is_active: z.boolean().default(true), must_change_password: z.boolean().default(true), temporary_password: z.string().min(12, "Mindestens 12 Zeichen") }); type UserForm = z.infer; const roleLabels: Record = { admin: "ADMIN", pruefer: "PRUEFER", mitarbeiter: "MITARBEITER", leser: "LESER" }; export default function UsersPage() { const { token, user } = useAuth(); const client = useQueryClient(); const [search, setSearch] = useState(""); const [role, setRole] = useState(""); const [active, setActive] = useState(""); const [page, setPage] = useState(1); const [sortBy, setSortBy] = useState("created_at"); const [sortOrder, setSortOrder] = useState<"asc" | "desc">("desc"); const [editing, setEditing] = useState(null); const [temporaryPassword, setTemporaryPassword] = useState(""); const [message, setMessage] = useState(""); const [error, setError] = useState(""); const form = useForm({ resolver: zodResolver(schema), defaultValues: { first_name: "", last_name: "", email: "", role: "mitarbeiter", is_active: true, must_change_password: true, temporary_password: "" } }); const queryKey = useMemo( () => ["users", token, page, search, role, active, sortBy, sortOrder], [token, page, search, role, active, sortBy, sortOrder] ); const users = useQuery({ queryKey, queryFn: () => apiGet>( `/users?page=${page}&page_size=10&search=${encodeURIComponent(search)}&role=${encodeURIComponent(role)}&active=${encodeURIComponent(active)}&sort_by=${encodeURIComponent(sortBy)}&sort_order=${sortOrder}`, token ?? "" ), enabled: Boolean(token) }); const saveMutation = useMutation({ mutationFn: async (values: UserForm) => { const payload = { ...values, email: values.email.toLowerCase() }; if (editing) { return apiFetch(`/users/${editing.id}`, token ?? "", { method: "PUT", body: JSON.stringify(payload) }); } return apiFetch<{ temporary_password: string; user: User }>(`/users`, token ?? "", { method: "POST", body: JSON.stringify(payload) }); }, onSuccess: async (created) => { if (!editing) { setTemporaryPassword((created as { temporary_password?: string }).temporary_password ?? ""); } setMessage(editing ? "Benutzer gespeichert" : "Benutzer angelegt"); setError(""); setEditing(null); form.reset(); await client.invalidateQueries({ queryKey: ["users"] }); }, onError: (err: Error) => setError(err.message) }); const deactivateMutation = useMutation({ mutationFn: (item: User) => apiFetch(`/users/${item.id}/deactivate`, token ?? "", { method: "POST" }), onSuccess: () => client.invalidateQueries({ queryKey: ["users"] }) }); const activateMutation = useMutation({ mutationFn: (item: User) => apiFetch(`/users/${item.id}/activate`, token ?? "", { method: "POST" }), onSuccess: () => client.invalidateQueries({ queryKey: ["users"] }) }); const resetMutation = useMutation({ mutationFn: (item: User) => apiFetch<{ temporary_password: string }>(`/users/${item.id}/reset-password`, token ?? "", { method: "POST" }), onSuccess: (result) => { setTemporaryPassword(result.temporary_password); setMessage("Temporäres Passwort erzeugt"); } }); if (user?.role !== "admin") { return
Nur fuer Administratoren verfuegbar.
; } function startCreate() { setEditing(null); form.reset({ first_name: "", last_name: "", email: "", role: "mitarbeiter", is_active: true, must_change_password: true, temporary_password: "" }); } function startEdit(item: User) { setEditing(item); form.reset({ first_name: item.first_name, last_name: item.last_name, email: item.email, role: item.role, is_active: item.is_active, must_change_password: item.must_change_password, temporary_password: "" }); } return (

Benutzer

Verwaltung von Rollen, Aktivstatus und Passworten.

{(users.data?.items ?? []).map((item) => ( ))}
Name E-Mail Rolle Status Letzter Login Passwort zuletzt geaendert Aktionen
{item.first_name} {item.last_name} {item.email} {roleLabels[item.role]} {item.is_active ? "Aktiv" : "Inaktiv"} {item.last_login_at ?? "nicht erfasst"} {item.password_changed_at ?? "nicht erfasst"}
{item.is_active ? ( ) : ( )}
{users.data?.total ?? 0} Datensätze
Seite {page}
{temporaryPassword &&

Temporäres Passwort

{temporaryPassword}

} {message &&
{message}
} {error &&
{error}
}
saveMutation.mutate(values))} className="rounded-lg border border-border bg-surface p-5 shadow-soft">

{editing ? "Benutzer bearbeiten" : "Benutzer anlegen"}

{(["first_name", "last_name", "email", "temporary_password"] as const).map((field) => ( ))}
); }