page.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. 'use client'
  2. import { useState, useEffect, useCallback } from 'react'
  3. import { api } from '@/lib/api'
  4. import { useAuth } from '@/hooks/useAuth'
  5. import type { User } from '@/types'
  6. const roleLabels: Record<string, string> = {
  7. superadmin: 'Суперадмин',
  8. moderator: 'Модератор',
  9. landlord: 'Арендодатель',
  10. executor: 'Исполнитель',
  11. customer: 'Заказчик',
  12. }
  13. const statusLabels: Record<string, string> = {
  14. active: 'Активен',
  15. banned: 'Заблокирован',
  16. pending_verification: 'Ожидает проверки',
  17. }
  18. /**
  19. * Страница управления пользователями. Позволяет фильтровать
  20. * пользователей по роли, изменять роль, блокировать/разблокировать.
  21. * Загружает до 100 пользователей через API /admin/users.
  22. */
  23. export default function AdminUsersPage() {
  24. const { user: currentUser } = useAuth()
  25. const [users, setUsers] = useState<User[]>([])
  26. const [loading, setLoading] = useState(true)
  27. const [roleFilter, setRoleFilter] = useState('')
  28. const [msg, setMsg] = useState('')
  29. const fetchUsers = useCallback(async () => {
  30. const params = new URLSearchParams()
  31. if (roleFilter) params.set('role', roleFilter)
  32. params.set('limit', '100')
  33. try {
  34. const res = await api.get<{ data: User[] }>(`/admin/users?${params}`)
  35. setUsers(res.data)
  36. } catch {
  37. setMsg('Ошибка загрузки')
  38. } finally {
  39. setLoading(false)
  40. }
  41. }, [roleFilter])
  42. useEffect(() => { fetchUsers() }, [fetchUsers])
  43. const updateUser = async (id: string, data: Record<string, string>) => {
  44. try {
  45. await api.patch(`/admin/users/${id}`, data)
  46. setMsg('Обновлено')
  47. fetchUsers()
  48. } catch (err: any) {
  49. setMsg(err.message || 'Ошибка')
  50. }
  51. }
  52. if (loading) return <p className="text-white/60">Загрузка...</p>
  53. return (
  54. <div>
  55. <h1 className="mb-6 text-2xl font-bold text-white">Управление пользователями</h1>
  56. {msg && <p className="mb-4 rounded-lg bg-blue-500/10 px-4 py-2 text-sm text-blue-400">{msg}</p>}
  57. <div className="mb-4 flex flex-wrap gap-2">
  58. {['', 'customer', 'landlord', 'executor', 'moderator'].map((r) => (
  59. <button key={r} onClick={() => setRoleFilter(r)}
  60. className={`rounded-lg px-3 py-1.5 text-sm transition ${
  61. roleFilter === r ? 'bg-green-600 text-white' : 'bg-white/10 text-white/60 hover:bg-white/20'
  62. }`}
  63. >
  64. {r ? roleLabels[r] : 'Все'}
  65. </button>
  66. ))}
  67. </div>
  68. {(() => {
  69. return (
  70. <div className="space-y-2">
  71. {users.map((user) => {
  72. const isSelf = user.id === currentUser?.id
  73. return (
  74. <div key={user.id} className="flex items-center justify-between rounded-lg border border-white/10 bg-white/5 p-4">
  75. <div className="flex-1">
  76. <div className="flex items-center gap-2">
  77. <span className="font-medium text-white">{user.name || user.email}</span>
  78. {isSelf && <span className="rounded bg-green-600/30 px-1.5 py-0.5 text-xs text-green-300">это вы</span>}
  79. <span className={`rounded-full px-2 py-0.5 text-xs ${
  80. user.status === 'active' ? 'bg-green-500/20 text-green-400' :
  81. user.status === 'banned' ? 'bg-red-500/20 text-red-400' :
  82. 'bg-yellow-500/20 text-yellow-400'
  83. }`}>
  84. {statusLabels[user.status]}
  85. </span>
  86. </div>
  87. <p className="text-sm text-white/40">{user.email}</p>
  88. </div>
  89. {!isSelf && (
  90. <div className="flex items-center gap-2">
  91. <select value={user.role}
  92. onChange={(e) => updateUser(user.id, { role: e.target.value })}
  93. className="rounded-lg border border-white/10 bg-white/5 px-2 py-1.5 text-sm text-white outline-none"
  94. >
  95. {Object.entries(roleLabels).map(([k, v]) => (
  96. <option key={k} value={k} className="bg-[#1e293b]">{v}</option>
  97. ))}
  98. </select>
  99. <button onClick={() => updateUser(user.id, {
  100. status: user.status === 'banned' ? 'active' : 'banned'
  101. })}
  102. className={`rounded-lg px-3 py-1.5 text-sm transition ${
  103. user.status === 'banned' ? 'bg-green-600/80 text-white hover:bg-green-500' :
  104. 'bg-red-600/80 text-white hover:bg-red-500'
  105. }`}
  106. >
  107. {user.status === 'banned' ? 'Разблокировать' : 'Заблокировать'}
  108. </button>
  109. </div>
  110. )}
  111. </div>
  112. )
  113. })}
  114. </div>
  115. )
  116. })()}
  117. </div>
  118. )
  119. }