'use client' import { useState, useEffect, useCallback } from 'react' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '@/components/ui/dialog' import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select' import { useToast } from '@/components/ui/use-toast' import { useAuth } from '@/components/providers/auth-provider' import { UserPlus, Pencil, Trash2, KeyRound, ShieldCheck, CheckCircle, Ban, Search, Loader2 } from 'lucide-react' import { formatDateTime } from '@/lib/utils' import { ROLES, type UserRecord, type TenantRecord } from './admin-constants' export function UsersTab() { const { toast } = useToast() const { user } = useAuth() const isServerAdmin = user?.role === 'SERVER_ADMIN' const [users, setUsers] = useState([]) const [tenants, setTenants] = useState([]) const [isLoading, setIsLoading] = useState(true) const [search, setSearch] = useState('') // User Dialog const [isUserDialogOpen, setIsUserDialogOpen] = useState(false) const [editingUser, setEditingUser] = useState(null) const [userName, setUserName] = useState('') const [userEmail, setUserEmail] = useState('') const [userPassword, setUserPassword] = useState('') const [userPasswordConfirm, setUserPasswordConfirm] = useState('') const [userRole, setUserRole] = useState('OPERATOR') const [userTenantId, setUserTenantId] = useState('') const fetchUsers = useCallback(async () => { setIsLoading(true) try { const res = await fetch('/api/admin/users') if (res.ok) setUsers((await res.json()).users || []) } catch (e) { console.error('Error fetching users:', e) } finally { setIsLoading(false) } }, []) const fetchTenants = useCallback(async () => { if (!isServerAdmin) return try { const res = await fetch('/api/admin/tenants') if (res.ok) setTenants((await res.json()).tenants || []) } catch {} }, [isServerAdmin]) useEffect(() => { fetchUsers(); fetchTenants() }, [fetchUsers, fetchTenants]) const openNewUser = () => { setEditingUser(null); setUserName(''); setUserEmail(''); setUserPassword('') setUserPasswordConfirm(''); setUserRole('OPERATOR'); setUserTenantId('') setIsUserDialogOpen(true) } const openEditUser = (u: UserRecord) => { setEditingUser(u); setUserName(u.name); setUserEmail(u.email); setUserPassword('') setUserPasswordConfirm(''); setUserRole(u.role) setUserTenantId(u.memberships?.[0]?.tenant?.id || '') setIsUserDialogOpen(true) } const handleSaveUser = async () => { try { if (userPassword && userPassword !== userPasswordConfirm) { toast({ title: 'Fehler', description: 'Passwörter stimmen nicht überein.', variant: 'destructive' }) return } const url = editingUser ? `/api/admin/users/${editingUser.id}` : '/api/admin/users' const method = editingUser ? 'PATCH' : 'POST' const body: any = { name: userName, email: userEmail, role: userRole } if (userPassword) body.password = userPassword // Mandanten-Zuordnung nur beim Anlegen durch den Super-Admin (verhindert verwaiste User) if (!editingUser && isServerAdmin && userRole !== 'SERVER_ADMIN' && userTenantId) { body.tenantId = userTenantId } const res = await fetch(url, { method, headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }) if (res.ok) { toast({ title: editingUser ? 'Benutzer aktualisiert' : 'Benutzer erstellt' }) setIsUserDialogOpen(false) fetchUsers() } else { const err = await res.json(); throw new Error(err.error) } } catch (error) { toast({ title: 'Fehler', description: error instanceof Error ? error.message : 'Fehler', variant: 'destructive' }) } } const handleDeleteUser = async (id: string) => { if (!confirm('Benutzer wirklich löschen?')) return try { const res = await fetch(`/api/admin/users/${id}`, { method: 'DELETE' }) if (res.ok) { toast({ title: 'Benutzer gelöscht' }); fetchUsers() } } catch { toast({ title: 'Fehler', variant: 'destructive' }) } } const handleResetUserMfa = async (targetUser: UserRecord) => { if (!confirm(`Zwei-Faktor (2FA) von "${targetUser.name}" zurücksetzen? Der Benutzer meldet sich danach nur mit Passwort an, bis er 2FA neu einrichtet.`)) return try { const res = await fetch(`/api/admin/users/${targetUser.id}/mfa-reset`, { method: 'POST' }) if (res.ok) { toast({ title: '2FA zurückgesetzt' }) } else { const err = await res.json(); toast({ title: 'Fehler', description: err.error, variant: 'destructive' }) } } catch { toast({ title: 'Fehler', variant: 'destructive' }) } } const handleToggleUserVerified = async (targetUser: UserRecord) => { try { const newVal = !targetUser.emailVerified const res = await fetch(`/api/admin/users/${targetUser.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ emailVerified: newVal }), }) if (res.ok) { toast({ title: newVal ? 'Benutzer freigeschaltet' : 'Verifizierung entfernt' }); fetchUsers() } else { const err = await res.json(); toast({ title: 'Fehler', description: err.error, variant: 'destructive' }) } } catch { toast({ title: 'Fehler', variant: 'destructive' }) } } const handleToggleUserActive = async (targetUser: UserRecord) => { const willDeactivate = targetUser.isActive !== false if (willDeactivate && !confirm(`"${targetUser.name}" sperren? Der Benutzer kann sich dann nicht mehr anmelden.`)) return try { const res = await fetch(`/api/admin/users/${targetUser.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ isActive: !willDeactivate }), }) if (res.ok) { toast({ title: willDeactivate ? 'Benutzer gesperrt' : 'Benutzer entsperrt' }); fetchUsers() } else { const err = await res.json(); toast({ title: 'Fehler', description: err.error, variant: 'destructive' }) } } catch { toast({ title: 'Fehler', variant: 'destructive' }) } } const handleResetUserPassword = async (targetUser: UserRecord) => { try { const res = await fetch(`/api/admin/users/${targetUser.id}/reset-password`, { method: 'POST' }) const data = await res.json() if (res.ok && data.success) { if (data.emailSent) { toast({ title: 'Reset-Link gesendet', description: data.message }) } else if (data.resetUrl) { await navigator.clipboard.writeText(data.resetUrl).catch(() => {}) toast({ title: 'Reset-Link generiert', description: 'Link wurde in die Zwischenablage kopiert. Kein SMTP konfiguriert.' }) } } else { toast({ title: 'Fehler', description: data.error, variant: 'destructive' }) } } catch { toast({ title: 'Fehler', variant: 'destructive' }) } } const q = search.trim().toLowerCase() const filteredUsers = q ? users.filter(u => u.name.toLowerCase().includes(q) || u.email.toLowerCase().includes(q) || (u.memberships || []).some(m => m.tenant?.name?.toLowerCase().includes(q))) : users const lockedCount = users.filter(u => u.isActive === false).length return (
setSearch(e.target.value)} placeholder="Suche Name, E-Mail, Mandant…" className="pl-8 w-[260px]" />

{filteredUsers.length}{q ? ` / ${users.length}` : ''} Benutzer {lockedCount > 0 && ` · ${lockedCount} gesperrt`}

{isLoading ? (
) : (
{filteredUsers.map(u => { const inactive = u.isActive === false const tenantNames = (u.memberships || []).map(m => m.tenant?.name).filter(Boolean) return ( )})} {filteredUsers.length === 0 && ( )}
Name E-Mail Rolle Mandant Letzte Anmeldung Status Aktionen
{u.name.charAt(0).toUpperCase()}
{u.name}
{u.email} {ROLES.find(r => r.value === u.role)?.label || u.role} {tenantNames.length > 0 ? tenantNames.join(', ') : } {u.lastLoginAt ? ( {formatDateTime(u.lastLoginAt)} ) : ( Nie eingeloggt )} {inactive ? ( Gesperrt ) : u.emailVerified === false ? ( Unverifiziert ) : ( Aktiv )}
{u.emailVerified === false && ( )}
Keine Benutzer gefunden.
)} {/* User Dialog */} {editingUser ? 'Benutzer bearbeiten' : 'Neuer Benutzer'}
setUserName(e.target.value)} placeholder="Vor- und Nachname" />
setUserEmail(e.target.value)} placeholder="name@example.com" />
setUserPassword(e.target.value)} placeholder={editingUser ? '••••••••' : 'Min. 6 Zeichen'} />
setUserPasswordConfirm(e.target.value)} placeholder="Passwort wiederholen" /> {userPassword && userPasswordConfirm && userPassword !== userPasswordConfirm && (

Passwörter stimmen nicht überein

)}
{/* Mandanten-Auswahl: nur beim Anlegen durch Super-Admin für Nicht-Server-Admins */} {!editingUser && isServerAdmin && userRole !== 'SERVER_ADMIN' && (

Ohne Mandant hat der Benutzer keine Organisation.

)}
) }