goldenChat base source add

This commit is contained in:
aidev
2026-05-23 15:11:48 +09:00
commit a4ea7762b5
2081 changed files with 1155760 additions and 0 deletions
@@ -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;