import React, { useEffect, useState } from 'react' import api, { adminOtpReset } from '../api/client' export default function AdminPage() { const [users, setUsers] = useState([]) const [auditLogs, setAuditLogs] = useState([]) const [settings, setSettings] = useState([]) const [tab, setTab] = useState<'users'|'audit'|'settings'>('users') const [showForm, setShowForm] = useState(false) const [form, setForm] = useState({ username: '', password: '', fullName: '', email: '', role: 'HR_STAFF' }) const loadUsers = () => api.get('/admin/users').then(r => setUsers(r.data.data || [])).catch(() => {}) const loadAudit = () => api.get('/admin/audit-logs', { params: { page: 1, size: 30 } }).then(r => setAuditLogs(r.data.data.items || [])).catch(() => {}) const loadSettings = () => api.get('/admin/settings').then(r => setSettings(r.data.data || [])).catch(() => {}) useEffect(() => { loadUsers(); loadAudit(); loadSettings() }, []) const saveUser = async () => { try { await api.post('/admin/users', form) setShowForm(false); setForm({ username: '', password: '', fullName: '', email: '', role: 'HR_STAFF' }) loadUsers() } catch (e: any) { alert(e.response?.data?.message || '저장 실패') } } const toggleUser = async (id: number, active: boolean) => { await api.patch(`/admin/users/${id}/status`, null, { params: { active: !active } }) loadUsers() } // 관리자 OTP 초기화(SUPERADMIN). 대상 사용자는 다음 로그인 시 OTP_SETUP(QR 재등록)을 탄다. const resetOtp = async (id: number, username: string) => { if (!window.confirm( `'${username}' 사용자의 OTP를 초기화하시겠습니까?\n초기화하면 다음 로그인 시 재등록해야 합니다.` )) return try { await adminOtpReset(id) alert('OTP가 초기화되었습니다.') } catch (e: any) { alert(e?.response?.status === 403 ? '권한이 없습니다 (SUPERADMIN 전용).' : (e?.response?.data?.message || 'OTP 초기화에 실패했습니다.')) } } const saveSetting = async (key: string, value: string) => { await api.put(`/admin/settings/${key}`, null, { params: { value } }) loadSettings() } const roleBadge = (r: string) => { const m: any = { SUPERADMIN: 'bg-red-100 text-red-700', MANAGER: 'bg-orange-100 text-orange-700', HR_STAFF: 'bg-blue-100 text-blue-700', VIEWER: 'bg-slate-100 text-slate-600' } return {r} } const methodBadge = (m: string) => { const c: any = { GET: 'bg-green-100 text-green-700', POST: 'bg-blue-100 text-blue-700', PUT: 'bg-yellow-100 text-yellow-700', PATCH: 'bg-yellow-100 text-yellow-700', DELETE: 'bg-red-100 text-red-700' } return {m} } return (

시스템 관리

{tab === 'users' && }
{[{v:'users',l:'사용자 관리'},{v:'audit',l:'감사 로그'},{v:'settings',l:'시스템 설정'}].map(t => ( ))}
{tab === 'users' && (
{['사용자명','이름','이메일','역할','상태','마지막 로그인','관리'].map(h=>)} {users.map((u: any) => ( ))} {users.length === 0 && }
{h}
{u.username} {u.full_name} {u.email || '-'} {roleBadge(u.role)} {u.is_active ? '활성' : '비활성'} {u.last_login_at?.slice(0,16) || '-'}
사용자가 없습니다
)} {tab === 'audit' && (
{['시간','사용자','메서드','경로','상태','IP'].map(h=>)} {auditLogs.map((l: any) => ( ))} {auditLogs.length === 0 && }
{h}
{l.created_at?.slice(0,19)} {l.username || '-'} {methodBadge(l.method)} {l.path} {l.status_code} {l.ip_address || '-'}
감사 로그가 없습니다
)} {tab === 'settings' && (

시스템 설정

{settings.map((s: any) => (

{s.key}

{s.description &&

{s.description}

}
{ if (e.target.value !== s.value) saveSetting(s.key, e.target.value) }} className="border border-slate-200 rounded-lg px-3 py-1.5 text-sm focus:outline-none w-40 text-right" />
))} {settings.length === 0 &&

설정이 없습니다

}
)} {showForm && (

사용자 추가

setForm({...form, username: e.target.value})} className="w-full border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none" />
setForm({...form, password: e.target.value})} className="w-full border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none" />
setForm({...form, fullName: e.target.value})} className="w-full border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none" />
setForm({...form, email: e.target.value})} className="w-full border border-slate-200 rounded-lg px-3 py-2 text-sm focus:outline-none" />
)}
) }