132 lines
5.5 KiB
TypeScript
132 lines
5.5 KiB
TypeScript
import { useEffect, useState } from 'react'
|
|
import {
|
|
roleList, createRole, updateRole, deleteRoles,
|
|
} from '../../../api/uiws'
|
|
import {
|
|
PageHeader, SearchBar, Input, Button, DataGrid, Modal, FormField, Spinner, YnBadge, type Column,
|
|
} from '../../../components/uiws/ui'
|
|
|
|
interface RoleRow extends Record<string, unknown> {
|
|
roleId: string; roleNm: string; roleDesc: string; useYn: string
|
|
}
|
|
|
|
function friendlyError(e: any): string {
|
|
if (e?.response?.status === 403) return '권한이 없습니다 (관리자 전용).'
|
|
if (e?.response?.status === 409) return '이미 존재하는 권한ID입니다.'
|
|
return e?.response?.data?.message || '요청을 처리하지 못했습니다. 잠시 후 다시 시도하세요.'
|
|
}
|
|
|
|
export default function RoleManagement() {
|
|
const [rows, setRows] = useState<RoleRow[]>([])
|
|
const [keyword, setKeyword] = useState('')
|
|
const [loading, setLoading] = useState(false)
|
|
const [err, setErr] = useState('')
|
|
const [selected, setSelected] = useState<Set<string>>(new Set())
|
|
const [edit, setEdit] = useState<RoleRow | null>(null)
|
|
const [showForm, setShowForm] = useState(false)
|
|
|
|
const load = async (kw = keyword) => {
|
|
setLoading(true); setErr('')
|
|
try {
|
|
const res = await roleList(kw || undefined)
|
|
setRows(res.data.data?.content ?? [])
|
|
setSelected(new Set())
|
|
} catch (e) { setErr(friendlyError(e)) }
|
|
finally { setLoading(false) }
|
|
}
|
|
useEffect(() => { load('') }, [])
|
|
|
|
const toggleSel = (id: string) =>
|
|
setSelected(prev => { const n = new Set(prev); n.has(id) ? n.delete(id) : n.add(id); return n })
|
|
|
|
const removeSelected = async () => {
|
|
if (selected.size === 0) return
|
|
if (!window.confirm(`선택한 권한 ${selected.size}건을 삭제하시겠습니까?`)) return
|
|
try { await deleteRoles([...selected]); await load() }
|
|
catch (e) { setErr(friendlyError(e)) }
|
|
}
|
|
|
|
const columns: Column<RoleRow>[] = [
|
|
{
|
|
key: 'sel', header: '', width: 44, align: 'center',
|
|
render: r => (
|
|
<input type="checkbox" checked={selected.has(r.roleId)}
|
|
onClick={e => e.stopPropagation()} onChange={() => toggleSel(r.roleId)} />
|
|
),
|
|
},
|
|
{ key: 'roleId', header: '권한ID', width: 180 },
|
|
{ key: 'roleNm', header: '권한명' },
|
|
{ key: 'roleDesc', header: '설명' },
|
|
{ key: 'useYn', header: '사용', width: 80, align: 'center', render: r => <YnBadge yn={r.useYn} yes="사용" no="미사용" /> },
|
|
]
|
|
|
|
return (
|
|
<div className="uiws-scope">
|
|
<PageHeader title="권한 관리" subtitle="시스템관리(권한) · 역할(Role) 등록/수정/삭제 — RBAC"
|
|
actions={
|
|
<>
|
|
<Button variant="danger" onClick={removeSelected} disabled={selected.size === 0}>선택 삭제</Button>
|
|
<Button onClick={() => { setEdit(null); setShowForm(true) }}>+ 권한 등록</Button>
|
|
</>
|
|
} />
|
|
|
|
{err && <div style={{ color: 'var(--uiws-danger)', fontSize: 13, marginBottom: 12 }}>{err}</div>}
|
|
|
|
<SearchBar>
|
|
<Input value={keyword} onChange={setKeyword} placeholder="권한ID/권한명 검색" style={{ minWidth: 240 }} />
|
|
<Button variant="ghost" onClick={() => load()}>검색</Button>
|
|
</SearchBar>
|
|
|
|
{loading ? <Spinner /> : (
|
|
<DataGrid columns={columns} rows={rows} rowKey={r => r.roleId}
|
|
onRowClick={r => { setEdit(r); setShowForm(true) }} empty="등록된 권한이 없습니다." />
|
|
)}
|
|
|
|
{showForm && (
|
|
<RoleForm role={edit} onClose={() => setShowForm(false)}
|
|
onSaved={() => { setShowForm(false); load() }} />
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
function RoleForm({ role, onClose, onSaved }: { role: RoleRow | null; onClose: () => void; onSaved: () => void }) {
|
|
const editing = !!role
|
|
const [roleId, setRoleId] = useState(role?.roleId ?? '')
|
|
const [roleNm, setRoleNm] = useState(role?.roleNm ?? '')
|
|
const [roleDesc, setRoleDesc] = useState(role?.roleDesc ?? '')
|
|
const [useYn, setUseYn] = useState(role?.useYn ?? 'Y')
|
|
const [err, setErr] = useState('')
|
|
|
|
const save = async () => {
|
|
setErr('')
|
|
if (!roleNm.trim()) { setErr('권한명은 필수입니다.'); return }
|
|
if (!editing && !roleId.trim()) { setErr('권한ID는 필수입니다.'); return }
|
|
try {
|
|
const body = { roleId, roleNm, roleDesc, useYn }
|
|
editing ? await updateRole(roleId, body) : await createRole(body)
|
|
onSaved()
|
|
} catch (e) { setErr(friendlyError(e)) }
|
|
}
|
|
|
|
return (
|
|
<Modal title={editing ? '권한 수정' : '권한 등록'} onClose={onClose}
|
|
footer={<><Button variant="ghost" onClick={onClose}>취소</Button><Button onClick={save}>저장</Button></>}>
|
|
<FormField label="권한ID">
|
|
<Input value={roleId} onChange={setRoleId} style={{ width: '100%', opacity: editing ? 0.6 : 1 }} />
|
|
</FormField>
|
|
<FormField label="권한명"><Input value={roleNm} onChange={setRoleNm} style={{ width: '100%' }} /></FormField>
|
|
<FormField label="설명"><Input value={roleDesc} onChange={setRoleDesc} style={{ width: '100%' }} /></FormField>
|
|
<FormField label="사용 여부">
|
|
<select value={useYn} onChange={e => setUseYn(e.target.value)}
|
|
style={{ width: '100%', padding: '9px 12px', borderRadius: 8, fontSize: 13,
|
|
background: 'var(--uiws-input-bg)', border: '1px solid var(--uiws-border)', color: 'var(--uiws-text)' }}>
|
|
<option value="Y">사용</option>
|
|
<option value="N">미사용</option>
|
|
</select>
|
|
</FormField>
|
|
{err && <div style={{ color: 'var(--uiws-danger)', fontSize: 13 }}>{err}</div>}
|
|
</Modal>
|
|
)
|
|
}
|