/
aaronair
/
Test_task_rt_solution
Обзор
Документация
Войти
/
aaronair
/
Test_task_rt_solution
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
src/components/AdminPanel.tsx
183 строки
9 KB
aaronair
Решение
03 июн 2026, 15:04
03 июн 2026, 15:04
72ced40
Код
Авторство
О чём код?
import React, { useEffect, useState } from 'react'; export const AdminPanel: React.FC = () => { const [users, setUsers] = useState<any[]>([]); const [requests, setRequests] = useState<any[]>([]); const [loading, setLoading] = useState<boolean>(true); const [error, setError] = useState<string>(''); // Адрес нашего бэкенда Fastify const BACKEND_URL = 'http://localhost:3000/api/v1'; const loadAdminData = async () => { try { setLoading(true); setError(''); // 1. Получаем список пользователей const usersRes = await fetch(`${BACKEND_URL}/admin/users`); // 2. Получаем список заявок на инициативы const reqsRes = await fetch(`${BACKEND_URL}/admin/requests`); if (!usersRes.ok || !reqsRes.ok) { throw new Error('Не удалось загрузить данные с сервера администрирования'); } const usersData = await usersRes.json(); const reqsData = await reqsRes.json(); setUsers(usersData); setRequests(reqsData); } catch (err: any) { setError(err.message || 'Ошибка сети'); } finally { setLoading(false); } }; useEffect(() => { loadAdminData(); }, []); // Изменение роли (Пункт 3 ТЗ: user <-> admin) const toggleRole = async (userId: string, currentRole: string) => { const newRole = currentRole === 'admin' ? 'user' : 'admin'; try { const res = await fetch(`${BACKEND_URL}/admin/users/${userId}/role`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ role: newRole }) }); if (res.ok) { loadAdminData(); // Обновляем данные на экране } } catch (err) { alert('Ошибка при изменении роли'); } }; // Изменение статуса заявки (Пункт 3 ТЗ: подтверждение / отклонение) const handleModerate = async (requestId: string, status: 'approved' | 'rejected') => { try { const res = await fetch(`${BACKEND_URL}/admin/requests/${requestId}/status`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ status }) }); if (res.ok) { loadAdminData(); // Перезагружаем списки } } catch (err) { alert('Ошибка при модерации заявки'); } }; if (loading) return <div className="text-center p-10 font-medium text-slate-500">Загрузка данных панели управления...</div>; if (error) return <div className="bg-red-50 text-red-600 p-4 rounded-lg border border-red-200 my-4">{error}</div>; return ( <div className="space-y-10"> {/* ТАБЛИЦА ПОЛЬЗОВАТЕЛЕЙ */} <div className="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden"> <div className="px-6 py-4 border-b border-slate-200 bg-slate-50"> <h2 className="text-lg font-bold text-slate-900">Управление пользователями ({users.length})</h2> <p className="text-xs text-slate-500 mt-0.5">Список зарегистрированных аккаунтов и изменение их ролей</p> </div> <div className="overflow-x-auto"> <table className="w-full text-left border-collapse"> <thead> <tr className="bg-slate-100 text-slate-700 text-xs uppercase tracking-wider font-semibold border-b border-slate-200"> <th className="p-4">Имя пользователя</th> <th className="p-4">Email адрес</th> <th className="p-4">Текущая роль</th> <th className="p-4 text-right">Действие</th> </tr> </thead> <tbody className="divide-y divide-slate-200 text-sm text-slate-600"> {users.length === 0 ? ( <tr> <td colSpan={4} className="p-4 text-center text-slate-400">Нет зарегистрированных пользователей</td> </tr> ) : ( users.map((u: any) => ( <tr key={u.id} className="hover:bg-slate-50 transition-colors"> <td className="p-4 font-medium text-slate-900">{u.name}</td> <td className="p-4">{u.email}</td> <td className="p-4"> <span className={`px-2.5 py-1 rounded-full text-xs font-bold ${ u.role === 'admin' ? 'bg-red-100 text-red-700' : 'bg-blue-100 text-blue-700' }`}> {u.role.toUpperCase()} </span> </td> <td className="p-4 text-right"> <button onClick={() => toggleRole(u.id, u.role)} className="text-xs bg-slate-100 border border-slate-300 px-3 py-1.5 rounded hover:bg-indigo-50 hover:text-indigo-600 hover:border-indigo-200 transition-all font-medium" > Сменить на {u.role === 'admin' ? 'USER' : 'ADMIN'} </button> </td> </tr> )) )} </tbody> </table> </div> </div> {/* СПИСОК ЗАЯВОК НА ИНИЦИАТИВЫ */} <div className="bg-white rounded-xl shadow-sm border border-slate-200 overflow-hidden"> <div className="px-6 py-4 border-b border-slate-200 bg-slate-50"> <h2 className="text-lg font-bold text-slate-900">Модерация заявок на добавление инициатив в реестр</h2> <p className="text-xs text-slate-500 mt-0.5">Рассмотрение предложений, отправленных пользователями из личных кабинетов</p> </div> <div className="p-6 space-y-4"> {requests.length === 0 ? ( <p className="text-center text-slate-400 text-sm py-4">Активные заявки на верификацию отсутствуют</p> ) : ( requests.map((req: any) => ( <div key={req.id} className="p-5 border border-slate-200 rounded-xl bg-slate-50 flex flex-col md:flex-row md:justify-between md:items-center gap-4 hover:border-slate-300 transition-all"> <div className="space-y-1"> <div className="flex items-center gap-3"> <h3 className="font-bold text-slate-900 text-base">{req.title}</h3> <span className={`px-2 py-0.5 rounded text-xxs font-extrabold uppercase tracking-wide ${ req.status === 'pending' ? 'bg-amber-100 text-amber-800' : req.status === 'approved' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' }`}> {req.status} </span> </div> <p className="text-slate-600 text-sm leading-relaxed">{req.description}</p> <div className="text-xxs text-slate-400 font-medium pt-1"> ID инициатора: <code className="bg-slate-200 px-1 rounded text-slate-700">{req.userId}</code> </div> </div> {/* Кнопки управления статусом отображаются только для ожидающих заявок */} {req.status === 'pending' && ( <div className="flex sm:space-x-2 gap-2 mt-2 md:mt-0 self-end md:self-center"> <button onClick={() => handleModerate(req.id, 'approved')} className="bg-green-600 text-white text-xs font-semibold px-4 py-2 rounded-md hover:bg-green-500 shadow-sm transition-colors" > Одобрить и внести в реестр </button> <button onClick={() => handleModerate(req.id, 'rejected')} className="bg-white border border-red-200 text-red-600 text-xs font-semibold px-4 py-2 rounded-md hover:bg-red-50 transition-colors" > Отклонить </button> </div> )} </div> )) )} </div> </div> </div> ); };