import { useEffect, useState } from 'react' import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { Ban, CheckCircle2, Search, UserPlus } from 'lucide-react' import { listAdminTeams, listAdminUsers, updateAdminUser, type AdminUserOut } from '@/api/admin' import { ApiError } from '@/api/client' import { useAuth } from '@/auth/useAuth' import { AdminUserCreateDialog } from '@/components/admin/AdminUserCreateDialog' import { AdminUserProfileDialog } from '@/components/admin/AdminUserProfileDialog' import { Avatar } from '@/components/ui/Avatar' import { useToast } from '@/components/ui/ToastProvider' const PAGE_SIZE = 10 /** Верхний предел выборки команд для селекта — без отдельной пагинации в этом контексте. */ const TEAMS_LIMIT = 200 const STATUS_FILTERS: { value: 'active' | 'blocked' | 'all'; label: string }[] = [ { value: 'active', label: 'Активные' }, { value: 'blocked', label: 'Заблокированные' }, { value: 'all', label: 'Все' }, ] /** * Вкладка «Пользователи» админки — список с поиском, пагинацией, сменой * роли, блокировкой и командой (design/mockups/admin.html, * «table-card»/«user-cell»). Собственная учётная запись администратора * защищена от самоизменения на уровне UI (disabled) и backend (409 — на * случай гонки в двух вкладках) — но это касается только роли и блокировки: * смену собственной команды запрет не затрагивает, селект «Команда» для * своей строки не дизейблится. * * Клик по имени пользователя открывает карточку * профиля (`AdminUserProfileDialog`) — те же поля, что в собственном * профиле, плюс существующие действия роль/блокировка. */ export function AdminUsersTab() { const { user: currentUser } = useAuth() const [statusFilter, setStatusFilter] = useState<'active' | 'blocked' | 'all'>('active') const [searchInput, setSearchInput] = useState('') const [search, setSearch] = useState('') const [offset, setOffset] = useState(0) const [viewingUserId, setViewingUserId] = useState(null) const [creating, setCreating] = useState(false) const queryClient = useQueryClient() const toast = useToast() useEffect(() => { const timer = setTimeout(() => { setSearch(searchInput.trim()) setOffset(0) }, 300) return () => clearTimeout(timer) }, [searchInput]) const { data, isLoading } = useQuery({ queryKey: ['admin', 'users', statusFilter, search, offset], queryFn: () => listAdminUsers({ status: statusFilter === 'all' ? undefined : statusFilter, q: search || undefined, limit: PAGE_SIZE, offset, }), }) const { data: teamsData } = useQuery({ queryKey: ['admin', 'teams', 'all'], queryFn: () => listAdminTeams({ limit: TEAMS_LIMIT, offset: 0 }), }) const teams = teamsData?.items ?? [] const mutation = useMutation({ mutationFn: ({ id, payload }: { id: string; payload: Parameters[1] }) => updateAdminUser(id, payload), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['admin', 'users'] }) }, onError: (err: unknown) => { if (err instanceof ApiError && err.status === 409) { toast.show('Нельзя изменить собственную учётную запись', 'error') } else { toast.show('Не удалось сохранить изменения', 'error') } }, }) function handleRoleChange(target: AdminUserOut, role: 'admin' | 'user') { if (role === target.role) return mutation.mutate({ id: target.id, payload: { role } }) } function handleToggleBlock(target: AdminUserOut) { mutation.mutate({ id: target.id, payload: { is_blocked: !target.is_blocked } }) } function handleTeamChange(target: AdminUserOut, teamId: string) { const nextTeamId = teamId === '' ? null : teamId if (nextTeamId === target.team_id) return mutation.mutate({ id: target.id, payload: { team_id: nextTeamId } }) } const items = data?.items ?? [] const total = data?.total ?? 0 return ( <>
{STATUS_FILTERS.map((f) => ( ))}
{isLoading ? 'Загрузка…' : `Показано ${items.length ? offset + 1 : 0}–${offset + items.length} из ${total}`}
{items.length === 0 && !isLoading && ( )} {items.map((u) => { const isSelf = u.id === currentUser?.id return ( ) })}
Пользователь Роль Команда Регистрация Статус
Пользователи не найдены
{u.email}
{new Date(u.created_at).toLocaleDateString('ru-RU')} {u.is_blocked ? ( ) : ( )}
{total > PAGE_SIZE && (
)} {viewingUserId && setViewingUserId(null)} />} {creating && setCreating(false)} />} ) }