import { useState, useEffect, useCallback } from 'react' import { useSubMenu } from '../../hooks/useSubMenu' import { fetchUsers, fetchRoles, updatePermissionsApi, updateUserApi, updateRoleDefaultApi, approveUserApi, rejectUserApi, fetchRegistrationSettings, updateRegistrationSettingsApi, type UserListItem, type RoleWithPermissions, type RegistrationSettings, } from '../../services/authApi' const roleLabels: Record = { ADMIN: { label: '관리자', color: 'var(--red)' }, MANAGER: { label: '매니저', color: 'var(--orange)' }, USER: { label: '사용자', color: 'var(--cyan)' }, VIEWER: { label: '뷰어', color: 'var(--t3)' }, } const statusLabels: Record = { PENDING: { label: '승인대기', color: 'text-yellow-400', dot: 'bg-yellow-400' }, ACTIVE: { label: '활성', color: 'text-green-400', dot: 'bg-green-400' }, LOCKED: { label: '잠김', color: 'text-red-400', dot: 'bg-red-400' }, INACTIVE: { label: '비활성', color: 'text-text-3', dot: 'bg-text-3' }, REJECTED: { label: '거절됨', color: 'text-red-300', dot: 'bg-red-300' }, } const mockMenus = [ { id: 'prediction', label: '유출유 확산예측', enabled: true, order: 1 }, { id: 'hns', label: 'HNS·대기확산', enabled: true, order: 2 }, { id: 'rescue', label: '긴급구난', enabled: true, order: 3 }, { id: 'reports', label: '보고자료', enabled: true, order: 4 }, { id: 'aerial', label: '항공탐색', enabled: true, order: 5 }, { id: 'assets', label: '방제자산 관리', enabled: true, order: 6 }, { id: 'scat', label: 'Pre-SCAT', enabled: false, order: 7 }, { id: 'incidents', label: '사고조회', enabled: true, order: 8 }, { id: 'board', label: '게시판', enabled: true, order: 9 }, { id: 'weather', label: '기상정보', enabled: true, order: 10 }, ] // ─── 사용자 관리 패널 ───────────────────────────────────────── function UsersPanel() { const [searchTerm, setSearchTerm] = useState('') const [statusFilter, setStatusFilter] = useState('') const [users, setUsers] = useState([]) const [loading, setLoading] = useState(true) const loadUsers = useCallback(async () => { setLoading(true) try { const data = await fetchUsers(searchTerm || undefined, statusFilter || undefined) setUsers(data) } catch (err) { console.error('사용자 목록 조회 실패:', err) } finally { setLoading(false) } }, [searchTerm, statusFilter]) useEffect(() => { loadUsers() }, [loadUsers]) const handleUnlock = async (userId: string) => { try { await updateUserApi(userId, { status: 'ACTIVE' }) await loadUsers() } catch (err) { console.error('계정 잠금 해제 실패:', err) } } const handleApprove = async (userId: string) => { try { await approveUserApi(userId) await loadUsers() } catch (err) { console.error('사용자 승인 실패:', err) } } const handleReject = async (userId: string) => { try { await rejectUserApi(userId) await loadUsers() } catch (err) { console.error('사용자 거절 실패:', err) } } const formatDate = (dateStr: string | null) => { if (!dateStr) return '-' return new Date(dateStr).toLocaleString('ko-KR', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', }) } const pendingCount = users.filter(u => u.status === 'PENDING').length return (

사용자 관리

총 {users.length}명

{pendingCount > 0 && ( 승인대기 {pendingCount}명 )}
setSearchTerm(e.target.value)} className="w-56 px-3 py-2 text-xs bg-bg-2 border border-border rounded-md text-text-1 placeholder-text-3 focus:border-primary-cyan focus:outline-none font-korean" />
{loading ? (
불러오는 중...
) : ( {users.map((user) => { const primaryRole = user.roles[0] || 'USER' const roleInfo = roleLabels[primaryRole] || roleLabels.USER const statusInfo = statusLabels[user.status] || statusLabels.INACTIVE return ( ) })}
이름 계정 소속 역할 상태 최근 로그인 관리
{user.name} {user.account} {user.orgAbbr || '-'} {roleInfo.label} {statusInfo.label} {formatDate(user.lastLogin)}
{user.status === 'PENDING' && ( <> )} {user.status === 'LOCKED' && ( )}
)}
) } // ─── 권한 관리 패널 ───────────────────────────────────────── const PERM_RESOURCES = [ { id: 'prediction', label: '유출유 확산예측', desc: '확산 예측 실행 및 결과 조회' }, { id: 'hns', label: 'HNS·대기확산', desc: '대기확산 분석 실행 및 조회' }, { id: 'rescue', label: '긴급구난', desc: '구난 예측 실행 및 조회' }, { id: 'reports', label: '보고자료', desc: '보고자료 생성 및 관리' }, { id: 'aerial', label: '항공탐색', desc: '항공탐색 계획 및 결과 조회' }, { id: 'assets', label: '방제자산 관리', desc: '방제자산 등록 및 관리' }, { id: 'scat', label: '해안평가', desc: '해안 SCAT 조사 접근' }, { id: 'incidents', label: '사고조회', desc: '사고 정보 등록 및 조회' }, { id: 'board', label: '게시판', desc: '게시판 접근' }, { id: 'weather', label: '기상정보', desc: '기상 정보 조회' }, { id: 'admin', label: '관리자 설정', desc: '시스템 관리 기능 접근' }, ] function PermissionsPanel() { const [roles, setRoles] = useState([]) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) const [dirty, setDirty] = useState(false) useEffect(() => { loadRoles() }, []) const loadRoles = async () => { setLoading(true) try { const data = await fetchRoles() setRoles(data) setDirty(false) } catch (err) { console.error('역할 목록 조회 실패:', err) } finally { setLoading(false) } } const getPermGranted = (roleSn: number, resourceCode: string): boolean => { const role = roles.find(r => r.sn === roleSn) if (!role) return false const perm = role.permissions.find(p => p.resourceCode === resourceCode) return perm?.granted ?? false } const togglePerm = (roleSn: number, resourceCode: string) => { setRoles(prev => prev.map(role => { if (role.sn !== roleSn) return role const perms = role.permissions.map(p => p.resourceCode === resourceCode ? { ...p, granted: !p.granted } : p ) if (!perms.find(p => p.resourceCode === resourceCode)) { perms.push({ sn: 0, resourceCode, granted: true }) } return { ...role, permissions: perms } })) setDirty(true) } const toggleDefault = async (roleSn: number) => { const role = roles.find(r => r.sn === roleSn) if (!role) return const newValue = !role.isDefault try { await updateRoleDefaultApi(roleSn, newValue) setRoles(prev => prev.map(r => r.sn === roleSn ? { ...r, isDefault: newValue } : r )) } catch (err) { console.error('기본 역할 변경 실패:', err) } } const handleSave = async () => { setSaving(true) try { for (const role of roles) { const permissions = PERM_RESOURCES.map(r => ({ resourceCode: r.id, granted: getPermGranted(role.sn, r.id), })) await updatePermissionsApi(role.sn, permissions) } setDirty(false) } catch (err) { console.error('권한 저장 실패:', err) } finally { setSaving(false) } } if (loading) { return
불러오는 중...
} return (

사용자 권한 관리

역할별 메뉴 접근 권한을 설정합니다

{roles.map(role => { const info = roleLabels[role.code] || { label: role.name, color: 'var(--t3)' } return ( ) })} {PERM_RESOURCES.map((perm) => ( {roles.map(role => ( ))} ))}
기능
{info.label}
{perm.label}
{perm.desc}
) } // ─── 메뉴 관리 패널 ───────────────────────────────────────── function MenusPanel() { const [menus, setMenus] = useState(mockMenus) const toggleMenu = (id: string) => { setMenus(prev => prev.map(m => m.id === id ? { ...m, enabled: !m.enabled } : m)) } return (

메뉴 관리

메뉴 표시 여부 및 순서를 관리합니다

{menus.map((menu, idx) => (
{idx + 1}
{menu.label}
{menu.id}
))}
) } // ─── 시스템 설정 패널 ──────────────────────────────────────── function SettingsPanel() { const [settings, setSettings] = useState(null) const [loading, setLoading] = useState(true) const [saving, setSaving] = useState(false) useEffect(() => { loadSettings() }, []) const loadSettings = async () => { setLoading(true) try { const data = await fetchRegistrationSettings() setSettings(data) } catch (err) { console.error('설정 조회 실패:', err) } finally { setLoading(false) } } const handleToggle = async (key: keyof RegistrationSettings) => { if (!settings) return const newValue = !settings[key] setSaving(true) try { const updated = await updateRegistrationSettingsApi({ [key]: newValue }) setSettings(updated) } catch (err) { console.error('설정 변경 실패:', err) } finally { setSaving(false) } } if (loading) { return
불러오는 중...
} return (

시스템 설정

사용자 등록 및 권한 관련 시스템 설정을 관리합니다

{/* 사용자 등록 설정 */}

사용자 등록 설정

신규 사용자 등록 시 적용되는 정책을 설정합니다

{/* 자동 승인 */}
자동 승인

활성화하면 신규 사용자가 등록 즉시 ACTIVE 상태가 됩니다. 비활성화하면 관리자 승인 전까지 PENDING 상태로 대기합니다.

{/* 기본 역할 자동 할당 */}
기본 역할 자동 할당

활성화하면 신규 사용자에게 기본 역할이 자동으로 할당됩니다. 기본 역할은 권한 관리 탭에서 설정할 수 있습니다.

{/* 현재 설정 상태 요약 */}

설정 상태 요약

신규 사용자 등록 시{' '} {settings?.autoApprove ? ( 즉시 활성화 ) : ( 관리자 승인 필요 )}
기본 역할 자동 할당{' '} {settings?.defaultRole ? ( 활성 ) : ( 비활성 )}
) } // ─── AdminView ──────────────────────────────────────────── export function AdminView() { const { activeSubTab } = useSubMenu('admin') return (
{activeSubTab === 'users' && } {activeSubTab === 'permissions' && } {activeSubTab === 'menus' && } {activeSubTab === 'settings' && }
) }