"use client"; import { useCallback, useEffect, useState } from "react"; import { Icon } from "@/components/icons"; import { Modal } from "@/components/modal"; import { Badge, Button, Card, Field, Notice, Select, TextInput, } from "@/components/ui"; import { ApiError, apiJson } from "@/lib/api-client"; type Role = "ADMIN" | "EDITOR" | "VIEWER"; const ROLE_LABELS: Record = { ADMIN: "Administrator", EDITOR: "Bearbeiter", VIEWER: "Betrachter", }; const ROLE_HINTS: Record = { ADMIN: "darf auch Mitglieder verwalten", EDITOR: "darf Codes anlegen und ändern", VIEWER: "darf nur ansehen", }; type Member = { userId: string; email: string; name: string | null; role: Role }; type AdminGroup = { id: string; name: string; description: string | null; codeCount: number; canManage: boolean; members: Member[]; }; type PickerUser = { id: string; email: string; name: string | null }; export function GroupAdmin({ canCreate }: { canCreate: boolean }) { const [groups, setGroups] = useState(null); const [users, setUsers] = useState([]); const [error, setError] = useState(null); const [creating, setCreating] = useState(false); const [form, setForm] = useState({ name: "", description: "" }); const [formError, setFormError] = useState(null); const [editing, setEditing] = useState(null); const [members, setMembers] = useState([]); const [memberError, setMemberError] = useState(null); const [saving, setSaving] = useState(false); const [deleting, setDeleting] = useState(null); const load = useCallback(async () => { try { const result = await apiJson<{ groups: AdminGroup[] }>("/api/admin/groups"); setGroups(result.groups); setError(null); } catch (caught) { setGroups([]); setError( caught instanceof ApiError ? caught.message : "Die Gruppen konnten nicht geladen werden.", ); } if (canCreate) { try { const result = await apiJson<{ users: PickerUser[] }>("/api/admin/users"); setUsers(result.users); } catch { // Ohne Benutzerliste lässt sich zwar niemand hinzufügen, die // Gruppenübersicht bleibt aber brauchbar. } } }, [canCreate]); useEffect(() => { // setState folgt erst nach dem await der Netzwerkantwort. // eslint-disable-next-line react-hooks/set-state-in-effect void load(); }, [load]); const create = async () => { setFormError(null); try { await apiJson("/api/admin/groups", { method: "POST", json: form }); setCreating(false); setForm({ name: "", description: "" }); await load(); } catch (caught) { setFormError( caught instanceof ApiError ? caught.message : "Die Gruppe konnte nicht angelegt werden.", ); } }; const saveMembers = async () => { if (!editing) return; setSaving(true); setMemberError(null); try { await apiJson(`/api/admin/groups/${editing.id}`, { method: "PATCH", json: { members: members.map((m) => ({ userId: m.userId, role: m.role })) }, }); setEditing(null); await load(); } catch (caught) { setMemberError( caught instanceof ApiError ? caught.message : "Die Mitglieder konnten nicht gespeichert werden.", ); } finally { setSaving(false); } }; const remove = async () => { if (!deleting) return; try { await apiJson(`/api/admin/groups/${deleting.id}`, { method: "DELETE" }); setDeleting(null); await load(); } catch (caught) { setError( caught instanceof ApiError ? caught.message : "Die Gruppe konnte nicht gelöscht werden.", ); setDeleting(null); } }; if (groups === null) { return

Gruppen werden geladen …

; } const available = users.filter( (user) => !members.some((member) => member.userId === user.id), ); return (
{error ? {error} : null} {canCreate ? (
) : null} {groups.map((group) => (

{group.name}

{group.codeCount} {group.codeCount === 1 ? "Code" : "Codes"}
{group.description ? (

{group.description}

) : null}
    {group.members.length === 0 ? (
  • Noch keine Mitglieder.
  • ) : ( group.members.map((member) => (
  • {member.name?.trim() || member.email} {" "} — {ROLE_LABELS[member.role]}
  • )) )}
{group.canManage ? ( ) : null} {canCreate ? ( ) : null}
))} {/* ------------------------------------------------------- Anlegen */} setCreating(false)} footer={ <> } >
{formError ? {formError} : null} setForm({ ...form, name: event.target.value })} /> setForm({ ...form, description: event.target.value }) } />
{/* ---------------------------------------------------- Mitglieder */} setEditing(null)} footer={ <> } >
{memberError ? {memberError} : null} {members.length === 0 ? (

Diese Gruppe hat noch keine Mitglieder.

) : (
    {members.map((member) => (
  • {member.name?.trim() || member.email}
  • ))}
)} {available.length > 0 ? ( ) : null}
{/* -------------------------------------------------------- Löschen */} setDeleting(null)} footer={ <> } > {deleting ? (

{deleting.name} wird gelöscht und alle Mitgliedschaften entfallen. {deleting.codeCount > 0 ? ` Die Gruppe enthält noch ${deleting.codeCount} ${deleting.codeCount === 1 ? "Code" : "Codes"} — das Löschen wird deshalb abgelehnt.` : " Sie enthält keine Codes."}

) : null}
); }