import { useEffect, useState } from 'react' import { codeGroupList, codeGroupDetail, createCodeGroup, updateCodeGroup, deleteCodeGroup, } from '../../../api/uiws' import { PageHeader, Panel, SearchBar, Input, Button, DataGrid, Modal, FormField, Spinner, YnBadge, type Column, } from '../../../components/uiws/ui' interface CodeGrp extends Record { grpCd: string; grpNm: string; useYn: string } interface CodeValue extends Record { grpCd?: string; codeVal: string; codeNm: string; sortOrd: number; useYn: string } interface CodeGrpDetail extends CodeGrp { values: CodeValue[] } function friendlyError(e: any): string { if (e?.response?.status === 403) return '권한이 없습니다 (SUPERADMIN 전용).' if (e?.response?.status === 409) return '이미 존재하는 코드입니다.' return e?.response?.data?.message || '요청을 처리하지 못했습니다.' } export default function CodeManagement() { const [groups, setGroups] = useState([]) const [activeGrp, setActiveGrp] = useState('') const [detail, setDetail] = useState(null) const [keyword, setKeyword] = useState('') const [loadingGrp, setLoadingGrp] = useState(false) const [loadingDetail, setLoadingDetail] = useState(false) const [err, setErr] = useState('') const [form, setForm] = useState<{ open: boolean; edit: CodeGrpDetail | null }>({ open: false, edit: null }) const loadGroups = async (kw = '') => { setLoadingGrp(true); setErr('') try { const res = await codeGroupList(kw || undefined) // CMS 백엔드 그룹목록은 PageResponse → data.content const list: CodeGrp[] = res.data.data?.content ?? [] setGroups(list) if (!activeGrp && list.length > 0) setActiveGrp(list[0].grpCd) } catch (e) { setErr(friendlyError(e)) } finally { setLoadingGrp(false) } } useEffect(() => { loadGroups('') }, []) const loadDetail = async (grp = activeGrp) => { if (!grp) { setDetail(null); return } setLoadingDetail(true) try { const res = await codeGroupDetail(grp) const d = res.data.data setDetail(d ? { ...d, values: d.values ?? [] } : null) } catch (e) { setErr(friendlyError(e)) } finally { setLoadingDetail(false) } } useEffect(() => { loadDetail() }, [activeGrp]) const removeGroup = async (grpCd: string) => { if (!window.confirm(`코드그룹 '${grpCd}' 와 하위 코드를 삭제하시겠습니까?`)) return try { await deleteCodeGroup(grpCd) if (activeGrp === grpCd) { setActiveGrp(''); setDetail(null) } await loadGroups(keyword) } catch (e) { setErr(friendlyError(e)) } } const codeColumns: Column[] = [ { key: 'codeVal', header: '코드값', width: 170 }, { key: 'codeNm', header: '코드명' }, { key: 'sortOrd', header: '정렬', width: 70, align: 'center' }, { key: 'useYn', header: '사용', width: 80, align: 'center', render: r => }, ] return (
setForm({ open: true, edit: null })}>+ 코드그룹 등록} /> {err &&
{err}
}
코드그룹
{loadingGrp ? : (
{groups.length === 0 &&
코드그룹이 없습니다.
} {groups.map(g => (
setActiveGrp(g.grpCd)} style={{ padding: '11px 16px', cursor: 'pointer', fontSize: 13, display: 'flex', justifyContent: 'space-between', alignItems: 'center', borderLeft: `3px solid ${activeGrp === g.grpCd ? 'var(--uiws-primary)' : 'transparent'}`, background: activeGrp === g.grpCd ? 'var(--uiws-primary-soft)' : 'transparent', }}>
{g.grpNm}
{g.grpCd}
e.stopPropagation()}>
))}
)}
코드값 {activeGrp && ({activeGrp})}
{loadingDetail ? : ( `${activeGrp}:${r.codeVal}`} empty={activeGrp ? '코드값이 없습니다.' : '코드그룹을 선택하세요.'} /> )}
{form.open && ( setForm({ open: false, edit: null })} onSaved={async (grpCd) => { setForm({ open: false, edit: null }) await loadGroups(keyword) if (grpCd) { setActiveGrp(grpCd); await loadDetail(grpCd) } else await loadDetail() }} /> )}
) } function SelectYn({ value, onChange }: { value: string; onChange: (v: string) => void }) { return ( ) } function CodeGroupForm({ grp, onClose, onSaved }: { grp: CodeGrpDetail | null; onClose: () => void; onSaved: (grpCd: string) => void }) { const editing = !!grp const [grpCd, setGrpCd] = useState(grp?.grpCd ?? '') const [grpNm, setGrpNm] = useState(grp?.grpNm ?? '') const [useYn, setUseYn] = useState(grp?.useYn ?? 'Y') const [values, setValues] = useState(grp?.values ? grp.values.map(v => ({ ...v })) : []) const [err, setErr] = useState('') const addValue = () => setValues(prev => [...prev, { codeVal: '', codeNm: '', sortOrd: prev.length + 1, useYn: 'Y' }]) const updateValue = (idx: number, field: keyof CodeValue, val: string | number) => setValues(prev => prev.map((v, i) => (i === idx ? { ...v, [field]: val } : v))) const removeValue = (idx: number) => setValues(prev => prev.filter((_, i) => i !== idx)) const save = async () => { setErr('') if (!editing && !grpCd.trim()) { setErr('그룹코드는 필수입니다.'); return } if (!grpNm.trim()) { setErr('그룹명은 필수입니다.'); return } for (const v of values) { if (!v.codeVal.trim() || !v.codeNm.trim()) { setErr('코드값과 코드명은 필수입니다.'); return } } try { const body = { grpCd, grpNm, useYn, values: values.map(v => ({ grpCd, codeVal: v.codeVal, codeNm: v.codeNm, sortOrd: Number(v.sortOrd) || 0, useYn: v.useYn })), } editing ? await updateCodeGroup(grpCd, body) : await createCodeGroup(body) onSaved(grpCd) } catch (e) { setErr(friendlyError(e)) } } const cellInput: React.CSSProperties = { width: '100%', padding: '6px 8px', borderRadius: 6, fontSize: 12, background: 'var(--uiws-input-bg)', border: '1px solid var(--uiws-border)', color: 'var(--uiws-text)', } return ( }>
코드값 ({values.length})
{values.length === 0 ? ( ) : values.map((v, idx) => ( ))}
코드값 코드명 정렬 사용
코드값이 없습니다.
updateValue(idx, 'codeVal', e.target.value)} style={cellInput} /> updateValue(idx, 'codeNm', e.target.value)} style={cellInput} /> updateValue(idx, 'sortOrd', Number(e.target.value) || 0)} style={cellInput} />
{err &&
{err}
}
) }