Files
a301_client/src/components/admin/UserAdmin.jsx
tolelom 1a3be5f76b
All checks were successful
Client CI/CD / test (push) Successful in 12s
Client CI/CD / deploy (push) Successful in 38s
fix: ESLint react-hooks 최신 규칙 호환
- useCallback → 일반 함수 + eslint-disable (초기 데이터 fetch)
- ToastProvider: useCallback 프로퍼티 할당 → useMemo 객체 패턴
- SSAFYCallbackPage: eslint-disable-line 추가

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-15 23:45:10 +09:00

86 lines
3.2 KiB
JavaScript

// TODO: Add tests for CRUD operations (list users, update role, delete user)
import { useState, useEffect } from 'react';
import { getUsers, updateUserRole, deleteUser } from '../../api/users';
import { useAuth } from '../../context/useAuth';
import { useToast } from '../toast/useToast';
import './AdminCommon.css';
export default function UserAdmin() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [fetchError, setFetchError] = useState(false);
const { user: me } = useAuth();
const toast = useToast();
const load = () => {
setLoading(true);
setFetchError(false);
getUsers()
.then(setUsers)
.catch((err) => {
console.error('유저 목록 로드 실패:', err);
setFetchError(true);
})
.finally(() => setLoading(false));
};
// eslint-disable-next-line react-hooks/set-state-in-effect -- initial data fetch on mount
useEffect(() => { load(); }, []);
const handleRoleToggle = async (u) => {
const newRole = u.role === 'admin' ? 'user' : 'admin';
// TODO: Replace window.confirm() with a custom confirmation modal for consistent UI
if (!confirm(`${u.username}의 권한을 ${newRole}로 변경하시겠습니까?`)) return;
try {
await updateUserRole(u.id, newRole);
toast.success(`${u.username}의 권한이 ${newRole}로 변경되었습니다.`);
load();
} catch (err) {
toast.error(err.message || '권한 변경에 실패했습니다.');
}
};
const handleDelete = async (u) => {
// TODO: Replace window.confirm() with a custom confirmation modal for consistent UI
if (!confirm(`${u.username} 계정을 삭제하시겠습니까?`)) return;
try {
await deleteUser(u.id);
toast.success(`${u.username} 계정이 삭제되었습니다.`);
load();
} catch (err) {
toast.error(err.message || '삭제에 실패했습니다.');
}
};
return (
<div className="admin-section">
<h2 className="admin-section-title">유저 관리</h2>
{fetchError && (
<div className="admin-error-block">
<p className="admin-error">유저 목록을 불러올 없습니다.</p>
<button className="btn-admin-secondary" onClick={load}>다시 시도</button>
</div>
)}
{loading && <p className="admin-list-empty">불러오는 ...</p>}
{!loading && users.length === 0 && <p className="admin-list-empty">등록된 유저가 없습니다.</p>}
<ul className="admin-list">
{users.map((u) => (
<li key={u.id} className="admin-list-item">
<div className="admin-list-info">
<span className="admin-list-title">{u.username}</span>
<span className={`admin-role-badge ${u.role}`}>{u.role}</span>
</div>
{u.username !== me?.username && (
<div className="admin-list-actions">
<button className="btn-admin-edit" onClick={() => handleRoleToggle(u)}>
{u.role === 'admin' ? '일반으로' : '관리자로'}
</button>
<button className="btn-admin-delete" onClick={() => handleDelete(u)}>삭제</button>
</div>
)}
</li>
))}
</ul>
</div>
);
}