import React, { useCallback, useEffect, useMemo, useState } from "react"; import axios from "axios"; import { Card, CardHeader, CardBody, Typography, Chip, Button, Select, Option, Input } from "@material-tailwind/react"; import { useNavigate } from "react-router-dom"; import { PaginationControls, usePage } from "@/components/PageProvider.jsx"; import { useMaintenance } from "@/components/MaintenanceProvider.jsx"; import { useToast } from "@/components/ToastProvider.jsx"; const normalizeUserGroups = (rawGroups) => { if (!Array.isArray(rawGroups)) { return []; } return rawGroups .map((group) => { if (group && typeof group === "object") { const id = Number(group.id); const name = typeof group.name === "string" ? group.name : ""; if (!name) { return null; } return { id: Number.isFinite(id) ? id : null, name }; } if (typeof group === "string") { const name = group.trim(); return name ? { id: null, name } : null; } return null; }) .filter(Boolean); }; export function Users() { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); const [searchQuery, setSearchQuery] = useState(""); const { showToast } = useToast(); const { maintenance } = useMaintenance(); const maintenanceActive = Boolean(maintenance?.active); const navigate = useNavigate(); const noop = useCallback(() => { }, []); const { page, perPage, perPageOptions, perPageIsAll, handlePerPageChange, setPage, setTotals, resetPagination } = usePage(); useEffect(() => () => resetPagination(), [resetPagination]); const fetchUsers = useCallback(async () => { setLoading(true); setError(""); try { const response = await axios.get("/api/users"); const items = Array.isArray(response.data?.items) ? response.data.items : []; const sorted = items .map((item) => ({ id: item.id, username: item.username || "", email: item.email || "", isActive: Boolean(item.isActive), avatarColor: typeof item.avatarColor === "string" ? item.avatarColor.trim() : null, lastLogin: item.lastLogin || null, createdAt: item.createdAt || null, updatedAt: item.updatedAt || null, groups: normalizeUserGroups(item.groups) })) .sort((a, b) => a.username.localeCompare(b.username, "de-DE")); setUsers(sorted); } catch (err) { const message = err.response?.data?.error || err.message || "Unbekannter Fehler"; setError(message); showToast({ variant: "error", title: "Benutzer konnten nicht geladen werden", description: message }); } finally { setLoading(false); } }, [showToast]); useEffect(() => { fetchUsers(); }, [fetchUsers]); const handleRefresh = useCallback(() => { fetchUsers(); }, [fetchUsers]); const handleSearchChange = useCallback((event) => { setSearchQuery(event.target.value); }, []); const handleClearSearch = useCallback(() => { setSearchQuery(""); }, []); useEffect(() => { if (page !== 1) { setPage(1); } }, [searchQuery, page, setPage]); const filteredUsers = useMemo(() => { const query = searchQuery.trim().toLowerCase(); if (!query) { return users; } return users.filter((user) => { const haystacks = [ user.username, user.email, ...user.groups.map((group) => group.name) ].map((value) => String(value || "").toLowerCase()); return haystacks.some((value) => value.includes(query)); }); }, [users, searchQuery]); const perPageNumber = useMemo(() => { if (perPageIsAll) { return null; } const numeric = Number(perPage); return Number.isFinite(numeric) && numeric > 0 ? numeric : null; }, [perPage, perPageIsAll]); const paginatedUsers = useMemo(() => { if (perPageIsAll || !perPageNumber) { return filteredUsers; } const startIndex = (page - 1) * perPageNumber; return filteredUsers.slice(startIndex, startIndex + perPageNumber); }, [filteredUsers, page, perPageIsAll, perPageNumber]); const filteredCount = filteredUsers.length; const visibleCount = paginatedUsers.length; useEffect(() => { if (perPageIsAll) { setTotals(filteredCount, filteredCount); if (page !== 1) { setPage(1); } return; } const numeric = Number(perPage); if (!Number.isFinite(numeric) || numeric <= 0) { setTotals(filteredCount, filteredCount); return; } const maxPages = Math.max(1, Math.ceil(filteredCount / numeric)); if (page > maxPages) { setPage(maxPages); return; } setTotals(filteredCount, visibleCount); }, [filteredCount, visibleCount, page, perPage, perPageIsAll, setPage, setTotals]); const formatTimestamp = useCallback((value) => { if (!value) { return "–"; } const normalized = typeof value === "string" ? value.replace(" ", "T") : value; const parsed = new Date(normalized); if (Number.isNaN(parsed.getTime())) { return value; } return new Intl.DateTimeFormat("de-DE", { dateStyle: "short", timeStyle: "short" }).format(parsed); }, []); return (
Benutzerverwaltung {maintenanceActive && (
Wartungsmodus aktiv – Änderungen sind deaktiviert. Die Liste kann dennoch angezeigt werden.
)}
{/*
*/} {/*
*/} {/* {searchQuery && ( )} */}
{error && (
{error}
)}
{loading && users.length === 0 ? ( ) : paginatedUsers.length === 0 ? ( ) : ( paginatedUsers.map((user, index) => { const rowClass = index === paginatedUsers.length - 1 ? "" : "border-b border-blue-gray-50"; return ( ); }) )}
Benutzername E-Mail Gruppen Status Letzte Anmeldung Erstellt am Aktionen
Benutzerdaten werden geladen ...
Keine Benutzer gefunden.
{user.username || "–"} {user.email || "–"} {user.groups.length > 0 ? (
{user.groups.map((group) => ( ))}
) : ( )}
{formatTimestamp(user.lastLogin)} {formatTimestamp(user.createdAt)}
{""}
); } export default Users;