goldenChat base source add
This commit is contained in:
@@ -0,0 +1,257 @@
|
||||
/**
|
||||
* 관리자 설정 — 사용자 CRUD · 역할별 메뉴 권한
|
||||
*/
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import {
|
||||
createAdminUser,
|
||||
deleteAdminUser,
|
||||
fetchRolePermissions,
|
||||
listAdminUsers,
|
||||
saveRolePermissions,
|
||||
updateAdminUser,
|
||||
type UserDto,
|
||||
} from '../utils/backendApi';
|
||||
import {
|
||||
ALL_MENU_IDS,
|
||||
MENU_LABELS,
|
||||
ROLE_LABELS,
|
||||
type UserRole,
|
||||
} from '../utils/permissions';
|
||||
|
||||
const ROLES: UserRole[] = ['ADMIN', 'USER', 'GUEST'];
|
||||
|
||||
const AdminSettingsPanel: React.FC = () => {
|
||||
const [users, setUsers] = useState<UserDto[]>([]);
|
||||
const [rolePerms, setRolePerms] = useState<Record<string, Record<string, boolean>>>({});
|
||||
const [permRole, setPermRole] = useState<UserRole>('USER');
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [msg, setMsg] = useState<string | null>(null);
|
||||
const [err, setErr] = useState<string | null>(null);
|
||||
|
||||
const [formMode, setFormMode] = useState<'create' | 'edit' | null>(null);
|
||||
const [editId, setEditId] = useState<number | null>(null);
|
||||
const [fUsername, setFUsername] = useState('');
|
||||
const [fPassword, setFPassword] = useState('');
|
||||
const [fDisplayName, setFDisplayName] = useState('');
|
||||
const [fRole, setFRole] = useState<UserRole>('USER');
|
||||
const [fEnabled, setFEnabled] = useState(true);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
setLoading(true);
|
||||
setErr(null);
|
||||
try {
|
||||
const [u, p] = await Promise.all([listAdminUsers(), fetchRolePermissions()]);
|
||||
setUsers(u);
|
||||
setRolePerms(p);
|
||||
} catch (e) {
|
||||
setErr(e instanceof Error ? e.message : '데이터 로드 실패');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => { void refresh(); }, [refresh]);
|
||||
|
||||
const openCreate = () => {
|
||||
setFormMode('create');
|
||||
setEditId(null);
|
||||
setFUsername('');
|
||||
setFPassword('');
|
||||
setFDisplayName('');
|
||||
setFRole('USER');
|
||||
setFEnabled(true);
|
||||
};
|
||||
|
||||
const openEdit = (u: UserDto) => {
|
||||
setFormMode('edit');
|
||||
setEditId(u.id);
|
||||
setFUsername(u.username);
|
||||
setFPassword('');
|
||||
setFDisplayName(u.displayName);
|
||||
setFRole((u.role as UserRole) || 'USER');
|
||||
setFEnabled(u.enabled);
|
||||
};
|
||||
|
||||
const submitUser = async () => {
|
||||
setErr(null);
|
||||
setMsg(null);
|
||||
try {
|
||||
if (formMode === 'create') {
|
||||
await createAdminUser({
|
||||
username: fUsername.trim(),
|
||||
password: fPassword,
|
||||
displayName: fDisplayName.trim() || fUsername.trim(),
|
||||
role: fRole,
|
||||
enabled: fEnabled,
|
||||
});
|
||||
setMsg('사용자가 등록되었습니다.');
|
||||
} else if (formMode === 'edit' && editId != null) {
|
||||
await updateAdminUser(editId, {
|
||||
displayName: fDisplayName.trim(),
|
||||
role: fRole,
|
||||
enabled: fEnabled,
|
||||
...(fPassword ? { password: fPassword } : {}),
|
||||
});
|
||||
setMsg('사용자 정보가 저장되었습니다.');
|
||||
}
|
||||
setFormMode(null);
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
setErr(e instanceof Error ? e.message : '저장 실패');
|
||||
}
|
||||
};
|
||||
|
||||
const removeUser = async (id: number) => {
|
||||
if (!window.confirm('이 사용자를 삭제하시겠습니까?')) return;
|
||||
setErr(null);
|
||||
try {
|
||||
await deleteAdminUser(id);
|
||||
setMsg('삭제되었습니다.');
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
setErr(e instanceof Error ? e.message : '삭제 실패');
|
||||
}
|
||||
};
|
||||
|
||||
const savePerms = async () => {
|
||||
const perms = rolePerms[permRole];
|
||||
if (!perms) return;
|
||||
setErr(null);
|
||||
try {
|
||||
await saveRolePermissions(permRole, perms);
|
||||
setMsg(`${ROLE_LABELS[permRole]} 역할 권한이 저장되었습니다.`);
|
||||
await refresh();
|
||||
} catch (e) {
|
||||
setErr(e instanceof Error ? e.message : '권한 저장 실패');
|
||||
}
|
||||
};
|
||||
|
||||
const togglePerm = (menuId: string) => {
|
||||
setRolePerms(prev => ({
|
||||
...prev,
|
||||
[permRole]: {
|
||||
...(prev[permRole] ?? {}),
|
||||
[menuId]: !(prev[permRole]?.[menuId]),
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
if (loading && users.length === 0) {
|
||||
return <p className="stg-loading">관리자 데이터 로딩 중…</p>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="admin-settings">
|
||||
{msg && <p className="admin-msg admin-msg--ok">{msg}</p>}
|
||||
{err && <p className="admin-msg admin-msg--err">{err}</p>}
|
||||
|
||||
<section className="stg-section">
|
||||
<h3 className="stg-section-title">사용자 관리</h3>
|
||||
<div className="admin-toolbar">
|
||||
<button type="button" className="stg-btn stg-btn--primary" onClick={openCreate}>
|
||||
+ 사용자 등록
|
||||
</button>
|
||||
</div>
|
||||
<div className="admin-table-wrap">
|
||||
<table className="admin-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>아이디</th>
|
||||
<th>표시명</th>
|
||||
<th>역할</th>
|
||||
<th>상태</th>
|
||||
<th />
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map(u => (
|
||||
<tr key={u.id}>
|
||||
<td>{u.username}</td>
|
||||
<td>{u.displayName}</td>
|
||||
<td><span className={`admin-role admin-role--${u.role.toLowerCase()}`}>{ROLE_LABELS[u.role as UserRole] ?? u.role}</span></td>
|
||||
<td>{u.enabled ? '활성' : '비활성'}</td>
|
||||
<td className="admin-actions">
|
||||
<button type="button" className="admin-link" onClick={() => openEdit(u)}>수정</button>
|
||||
<button type="button" className="admin-link admin-link--danger" onClick={() => removeUser(u.id)}>삭제</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{formMode && (
|
||||
<section className="stg-section admin-form-section">
|
||||
<h3 className="stg-section-title">{formMode === 'create' ? '사용자 등록' : '사용자 수정'}</h3>
|
||||
<div className="admin-form-grid">
|
||||
{formMode === 'create' && (
|
||||
<label>
|
||||
<span>아이디</span>
|
||||
<input value={fUsername} onChange={e => setFUsername(e.target.value)} />
|
||||
</label>
|
||||
)}
|
||||
<label>
|
||||
<span>{formMode === 'create' ? '비밀번호' : '새 비밀번호 (변경 시만)'}</span>
|
||||
<input type="text" autoComplete="off" className="stg-input stg-input--sensitive" value={fPassword} onChange={e => setFPassword(e.target.value)} />
|
||||
</label>
|
||||
<label>
|
||||
<span>표시명</span>
|
||||
<input value={fDisplayName} onChange={e => setFDisplayName(e.target.value)} />
|
||||
</label>
|
||||
<label>
|
||||
<span>역할</span>
|
||||
<select value={fRole} onChange={e => setFRole(e.target.value as UserRole)}>
|
||||
{ROLES.map(r => (
|
||||
<option key={r} value={r}>{ROLE_LABELS[r]}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="admin-check">
|
||||
<input type="checkbox" checked={fEnabled} onChange={e => setFEnabled(e.target.checked)} />
|
||||
<span>활성 계정</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="admin-form-actions">
|
||||
<button type="button" className="stg-btn stg-btn--primary" onClick={() => void submitUser()}>저장</button>
|
||||
<button type="button" className="stg-btn" onClick={() => setFormMode(null)}>취소</button>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<section className="stg-section">
|
||||
<h3 className="stg-section-title">역할별 메뉴 접근 권한</h3>
|
||||
<p className="stg-row-desc" style={{ marginBottom: 12 }}>
|
||||
역할마다 상단 메뉴·설정 하위 메뉴·알림 접근을 제어합니다. 비로그인 사용자는 게스트(GUEST) 권한이 적용됩니다.
|
||||
</p>
|
||||
<div className="admin-perm-role-tabs">
|
||||
{ROLES.map(r => (
|
||||
<button
|
||||
key={r}
|
||||
type="button"
|
||||
className={`admin-perm-tab${permRole === r ? ' active' : ''}`}
|
||||
onClick={() => setPermRole(r)}
|
||||
>{ROLE_LABELS[r]}</button>
|
||||
))}
|
||||
</div>
|
||||
<div className="admin-perm-grid">
|
||||
{ALL_MENU_IDS.map(menuId => (
|
||||
<label key={menuId} className="admin-perm-item">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={rolePerms[permRole]?.[menuId] === true}
|
||||
onChange={() => togglePerm(menuId)}
|
||||
/>
|
||||
<span>{MENU_LABELS[menuId] ?? menuId}</span>
|
||||
</label>
|
||||
))}
|
||||
</div>
|
||||
<button type="button" className="stg-btn stg-btn--primary" style={{ marginTop: 12 }} onClick={() => void savePerms()}>
|
||||
{ROLE_LABELS[permRole]} 권한 저장
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminSettingsPanel;
|
||||
Reference in New Issue
Block a user