guardia-mall/frontend/src/pages/uiws/system/RoleManagement.tsx

131 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)
// Mall 계약: 목록은 PageResponse { content, total, page, size }
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>
)
}