feat(tkeg): tkeg BOM 자재관리 서비스 초기 세팅 (api + web + docker-compose)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
664
tkeg/web/src/pages/SystemSettingsPage.jsx
Normal file
664
tkeg/web/src/pages/SystemSettingsPage.jsx
Normal file
@@ -0,0 +1,664 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import api from '../api';
|
||||
|
||||
const SystemSettingsPage = ({ onNavigate, user }) => {
|
||||
const [users, setUsers] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
const [showCreateForm, setShowCreateForm] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState('users'); // 'users', 'login-logs', 'activity-logs'
|
||||
const [loginLogs, setLoginLogs] = useState([]);
|
||||
const [activityLogs, setActivityLogs] = useState([]);
|
||||
const [newUser, setNewUser] = useState({
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
full_name: '',
|
||||
role: 'user'
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
loadUsers();
|
||||
if (activeTab === 'login-logs') {
|
||||
loadLoginLogs();
|
||||
} else if (activeTab === 'activity-logs') {
|
||||
loadActivityLogs();
|
||||
}
|
||||
}, [activeTab]);
|
||||
|
||||
const loadUsers = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await api.get('/auth/users');
|
||||
if (response.data.success) {
|
||||
setUsers(response.data.users);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('사용자 목록 로딩 실패:', err);
|
||||
setError('사용자 목록을 불러오는데 실패했습니다.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const loadLoginLogs = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await api.get('/auth/logs/login?limit=50');
|
||||
if (response.data.success) {
|
||||
setLoginLogs(response.data.logs);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('로그인 로그 로딩 실패:', err);
|
||||
setError('로그인 로그를 불러오는데 실패했습니다.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const loadActivityLogs = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await api.get('/auth/logs/system?limit=50');
|
||||
if (response.data.success) {
|
||||
setActivityLogs(response.data.logs);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('활동 로그 로딩 실패:', err);
|
||||
setError('활동 로그를 불러오는데 실패했습니다.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleCreateUser = async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (!newUser.username || !newUser.email || !newUser.password) {
|
||||
setError('모든 필수 필드를 입력해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await api.post('/auth/register', newUser);
|
||||
|
||||
if (response.data.success) {
|
||||
alert('사용자가 성공적으로 생성되었습니다.');
|
||||
setNewUser({
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
full_name: '',
|
||||
role: 'user'
|
||||
});
|
||||
setShowCreateForm(false);
|
||||
loadUsers();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('사용자 생성 실패:', err);
|
||||
setError(err.response?.data?.detail || '사용자 생성에 실패했습니다.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteUser = async (userId) => {
|
||||
if (!confirm('정말로 이 사용자를 삭제하시겠습니까?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await api.delete(`/auth/users/${userId}`);
|
||||
|
||||
if (response.data.success) {
|
||||
alert('사용자가 삭제되었습니다.');
|
||||
loadUsers();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('사용자 삭제 실패:', err);
|
||||
setError('사용자 삭제에 실패했습니다.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getRoleDisplay = (role) => {
|
||||
switch (role) {
|
||||
case 'admin': return '관리자';
|
||||
case 'manager': return '매니저';
|
||||
case 'user': return '사용자';
|
||||
default: return role;
|
||||
}
|
||||
};
|
||||
|
||||
const getRoleBadgeColor = (role) => {
|
||||
switch (role) {
|
||||
case 'admin': return '#dc2626';
|
||||
case 'manager': return '#ea580c';
|
||||
case 'user': return '#059669';
|
||||
default: return '#6b7280';
|
||||
}
|
||||
};
|
||||
|
||||
const getActivityTypeColor = (activityType) => {
|
||||
switch (activityType) {
|
||||
case 'FILE_UPLOAD':
|
||||
return '#10b981'; // 초록색
|
||||
case '파일 정보 수정':
|
||||
return '#f59e0b'; // 주황색
|
||||
case '엑셀 내보내기':
|
||||
return '#3b82f6'; // 파란색
|
||||
case '자재 목록 조회':
|
||||
return '#8b5cf6'; // 보라색
|
||||
case 'LOGIN':
|
||||
return '#6b7280'; // 회색
|
||||
default:
|
||||
return '#6b7280';
|
||||
}
|
||||
};
|
||||
|
||||
// 관리자 권한 확인 (system이 최고 권한)
|
||||
if (user?.role !== 'admin' && user?.role !== 'system') {
|
||||
return (
|
||||
<div style={{ padding: '32px', textAlign: 'center' }}>
|
||||
<h2 style={{ color: '#dc2626', marginBottom: '16px' }}>접근 권한이 없습니다</h2>
|
||||
<p style={{ color: '#6b7280', marginBottom: '24px' }}>
|
||||
시스템 설정은 관리자만 접근할 수 있습니다.
|
||||
</p>
|
||||
<button
|
||||
onClick={() => onNavigate('dashboard')}
|
||||
style={{
|
||||
background: '#4299e1',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '6px',
|
||||
padding: '12px 24px',
|
||||
cursor: 'pointer'
|
||||
}}
|
||||
>
|
||||
대시보드로 돌아가기
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div style={{ padding: '32px', maxWidth: '1200px', margin: '0 auto' }}>
|
||||
{/* 헤더 */}
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: '32px'
|
||||
}}>
|
||||
<div>
|
||||
<h1 style={{ fontSize: '28px', fontWeight: '700', color: '#2d3748', marginBottom: '8px' }}>
|
||||
⚙️ 시스템 설정
|
||||
</h1>
|
||||
<p style={{ color: '#718096', fontSize: '16px' }}>
|
||||
사용자 계정 관리 및 시스템 로그 모니터링
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 탭 네비게이션 */}
|
||||
<div style={{ marginBottom: '24px', borderBottom: '2px solid #e2e8f0' }}>
|
||||
<div style={{ display: 'flex', gap: '0' }}>
|
||||
<button
|
||||
onClick={() => setActiveTab('users')}
|
||||
style={{
|
||||
padding: '12px 24px',
|
||||
border: 'none',
|
||||
background: activeTab === 'users' ? '#4299e1' : 'transparent',
|
||||
color: activeTab === 'users' ? 'white' : '#4a5568',
|
||||
borderRadius: '8px 8px 0 0',
|
||||
cursor: 'pointer',
|
||||
fontWeight: '600',
|
||||
fontSize: '14px',
|
||||
borderBottom: activeTab === 'users' ? '2px solid #4299e1' : '2px solid transparent'
|
||||
}}
|
||||
>
|
||||
👥 사용자 관리
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('login-logs')}
|
||||
style={{
|
||||
padding: '12px 24px',
|
||||
border: 'none',
|
||||
background: activeTab === 'login-logs' ? '#4299e1' : 'transparent',
|
||||
color: activeTab === 'login-logs' ? 'white' : '#4a5568',
|
||||
borderRadius: '8px 8px 0 0',
|
||||
cursor: 'pointer',
|
||||
fontWeight: '600',
|
||||
fontSize: '14px',
|
||||
borderBottom: activeTab === 'login-logs' ? '2px solid #4299e1' : '2px solid transparent'
|
||||
}}
|
||||
>
|
||||
🔐 로그인 로그
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('activity-logs')}
|
||||
style={{
|
||||
padding: '12px 24px',
|
||||
border: 'none',
|
||||
background: activeTab === 'activity-logs' ? '#4299e1' : 'transparent',
|
||||
color: activeTab === 'activity-logs' ? 'white' : '#4a5568',
|
||||
borderRadius: '8px 8px 0 0',
|
||||
cursor: 'pointer',
|
||||
fontWeight: '600',
|
||||
fontSize: '14px',
|
||||
borderBottom: activeTab === 'activity-logs' ? '2px solid #4299e1' : '2px solid transparent'
|
||||
}}
|
||||
>
|
||||
📊 활동 로그
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => onNavigate('dashboard')}
|
||||
style={{
|
||||
background: '#e2e8f0',
|
||||
color: '#4a5568',
|
||||
border: 'none',
|
||||
borderRadius: '6px',
|
||||
padding: '12px 20px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600'
|
||||
}}
|
||||
>
|
||||
← 대시보드
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div style={{
|
||||
background: '#fed7d7',
|
||||
color: '#c53030',
|
||||
padding: '12px 16px',
|
||||
borderRadius: '6px',
|
||||
marginBottom: '24px'
|
||||
}}>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 사용자 관리 섹션 */}
|
||||
<div style={{
|
||||
background: 'white',
|
||||
borderRadius: '12px',
|
||||
padding: '24px',
|
||||
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.07)',
|
||||
border: '1px solid #e2e8f0',
|
||||
marginBottom: '24px'
|
||||
}}>
|
||||
<div style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
marginBottom: '24px'
|
||||
}}>
|
||||
<h2 style={{ fontSize: '20px', fontWeight: '600', color: '#2d3748' }}>
|
||||
👥 사용자 관리
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => setShowCreateForm(!showCreateForm)}
|
||||
style={{
|
||||
background: '#38a169',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '6px',
|
||||
padding: '10px 16px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600'
|
||||
}}
|
||||
>
|
||||
+ 새 사용자 생성
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* 사용자 생성 폼 */}
|
||||
{showCreateForm && (
|
||||
<div style={{
|
||||
background: '#f7fafc',
|
||||
padding: '20px',
|
||||
borderRadius: '8px',
|
||||
marginBottom: '24px',
|
||||
border: '1px solid #e2e8f0'
|
||||
}}>
|
||||
<h3 style={{ fontSize: '16px', fontWeight: '600', marginBottom: '16px' }}>
|
||||
새 사용자 생성
|
||||
</h3>
|
||||
<form onSubmit={handleCreateUser}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginBottom: '16px' }}>
|
||||
<div>
|
||||
<label style={{ display: 'block', marginBottom: '4px', fontSize: '14px', fontWeight: '500' }}>
|
||||
사용자명 *
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newUser.username}
|
||||
onChange={(e) => setNewUser({...newUser, username: e.target.value})}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '8px 12px',
|
||||
border: '1px solid #d1d5db',
|
||||
borderRadius: '4px',
|
||||
fontSize: '14px'
|
||||
}}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ display: 'block', marginBottom: '4px', fontSize: '14px', fontWeight: '500' }}>
|
||||
이메일 *
|
||||
</label>
|
||||
<input
|
||||
type="email"
|
||||
value={newUser.email}
|
||||
onChange={(e) => setNewUser({...newUser, email: e.target.value})}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '8px 12px',
|
||||
border: '1px solid #d1d5db',
|
||||
borderRadius: '4px',
|
||||
fontSize: '14px'
|
||||
}}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '16px', marginBottom: '16px' }}>
|
||||
<div>
|
||||
<label style={{ display: 'block', marginBottom: '4px', fontSize: '14px', fontWeight: '500' }}>
|
||||
비밀번호 *
|
||||
</label>
|
||||
<input
|
||||
type="password"
|
||||
value={newUser.password}
|
||||
onChange={(e) => setNewUser({...newUser, password: e.target.value})}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '8px 12px',
|
||||
border: '1px solid #d1d5db',
|
||||
borderRadius: '4px',
|
||||
fontSize: '14px'
|
||||
}}
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label style={{ display: 'block', marginBottom: '4px', fontSize: '14px', fontWeight: '500' }}>
|
||||
전체 이름
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={newUser.full_name}
|
||||
onChange={(e) => setNewUser({...newUser, full_name: e.target.value})}
|
||||
style={{
|
||||
width: '100%',
|
||||
padding: '8px 12px',
|
||||
border: '1px solid #d1d5db',
|
||||
borderRadius: '4px',
|
||||
fontSize: '14px'
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ marginBottom: '16px' }}>
|
||||
<label style={{ display: 'block', marginBottom: '4px', fontSize: '14px', fontWeight: '500' }}>
|
||||
권한
|
||||
</label>
|
||||
<select
|
||||
value={newUser.role}
|
||||
onChange={(e) => setNewUser({...newUser, role: e.target.value})}
|
||||
style={{
|
||||
width: '200px',
|
||||
padding: '8px 12px',
|
||||
border: '1px solid #d1d5db',
|
||||
borderRadius: '4px',
|
||||
fontSize: '14px'
|
||||
}}
|
||||
>
|
||||
<option value="user">사용자</option>
|
||||
<option value="manager">매니저</option>
|
||||
<option value="admin">관리자</option>
|
||||
</select>
|
||||
</div>
|
||||
<div style={{ display: 'flex', gap: '8px' }}>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={loading}
|
||||
style={{
|
||||
background: '#38a169',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '6px',
|
||||
padding: '10px 16px',
|
||||
cursor: loading ? 'not-allowed' : 'pointer',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600'
|
||||
}}
|
||||
>
|
||||
{loading ? '생성 중...' : '사용자 생성'}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowCreateForm(false)}
|
||||
style={{
|
||||
background: '#e2e8f0',
|
||||
color: '#4a5568',
|
||||
border: 'none',
|
||||
borderRadius: '6px',
|
||||
padding: '10px 16px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '14px',
|
||||
fontWeight: '600'
|
||||
}}
|
||||
>
|
||||
취소
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* 탭별 콘텐츠 */}
|
||||
{loading ? (
|
||||
<div style={{ textAlign: 'center', padding: '40px' }}>
|
||||
<div style={{ fontSize: '16px', color: '#718096' }}>로딩 중...</div>
|
||||
</div>
|
||||
) : activeTab === 'users' ? (
|
||||
// 사용자 관리 탭
|
||||
<div style={{ overflowX: 'auto' }}>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr style={{ borderBottom: '2px solid #e2e8f0' }}>
|
||||
<th style={{ padding: '12px', textAlign: 'left', fontWeight: '600', color: '#4a5568' }}>
|
||||
사용자명
|
||||
</th>
|
||||
<th style={{ padding: '12px', textAlign: 'left', fontWeight: '600', color: '#4a5568' }}>
|
||||
이메일
|
||||
</th>
|
||||
<th style={{ padding: '12px', textAlign: 'left', fontWeight: '600', color: '#4a5568' }}>
|
||||
전체 이름
|
||||
</th>
|
||||
<th style={{ padding: '12px', textAlign: 'center', fontWeight: '600', color: '#4a5568' }}>
|
||||
권한
|
||||
</th>
|
||||
<th style={{ padding: '12px', textAlign: 'center', fontWeight: '600', color: '#4a5568' }}>
|
||||
상태
|
||||
</th>
|
||||
<th style={{ padding: '12px', textAlign: 'center', fontWeight: '600', color: '#4a5568' }}>
|
||||
작업
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.map((userItem) => (
|
||||
<tr key={userItem.id} style={{ borderBottom: '1px solid #e2e8f0' }}>
|
||||
<td style={{ padding: '12px', fontWeight: '500' }}>
|
||||
{userItem.username}
|
||||
</td>
|
||||
<td style={{ padding: '12px', color: '#4a5568' }}>
|
||||
{userItem.email}
|
||||
</td>
|
||||
<td style={{ padding: '12px', color: '#4a5568' }}>
|
||||
{userItem.full_name || '-'}
|
||||
</td>
|
||||
<td style={{ padding: '12px', textAlign: 'center' }}>
|
||||
<span style={{
|
||||
background: getRoleBadgeColor(userItem.role),
|
||||
color: 'white',
|
||||
padding: '4px 8px',
|
||||
borderRadius: '12px',
|
||||
fontSize: '12px',
|
||||
fontWeight: '600'
|
||||
}}>
|
||||
{getRoleDisplay(userItem.role)}
|
||||
</span>
|
||||
</td>
|
||||
<td style={{ padding: '12px', textAlign: 'center' }}>
|
||||
<span style={{
|
||||
background: userItem.is_active ? '#d1fae5' : '#fee2e2',
|
||||
color: userItem.is_active ? '#065f46' : '#dc2626',
|
||||
padding: '4px 8px',
|
||||
borderRadius: '12px',
|
||||
fontSize: '12px',
|
||||
fontWeight: '600'
|
||||
}}>
|
||||
{userItem.is_active ? '활성' : '비활성'}
|
||||
</span>
|
||||
</td>
|
||||
<td style={{ padding: '12px', textAlign: 'center' }}>
|
||||
{userItem.id !== user?.id && (
|
||||
<button
|
||||
onClick={() => handleDeleteUser(userItem.id)}
|
||||
style={{
|
||||
background: '#dc2626',
|
||||
color: 'white',
|
||||
border: 'none',
|
||||
borderRadius: '4px',
|
||||
padding: '6px 12px',
|
||||
cursor: 'pointer',
|
||||
fontSize: '12px',
|
||||
fontWeight: '600'
|
||||
}}
|
||||
>
|
||||
삭제
|
||||
</button>
|
||||
)}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : activeTab === 'login-logs' ? (
|
||||
// 로그인 로그 탭
|
||||
<div style={{ overflowX: 'auto' }}>
|
||||
<h3 style={{ marginBottom: '16px', color: '#2d3748' }}>🔐 로그인 로그</h3>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr style={{ borderBottom: '2px solid #e2e8f0' }}>
|
||||
<th style={{ padding: '12px', textAlign: 'left', fontWeight: '600', color: '#4a5568' }}>
|
||||
사용자명
|
||||
</th>
|
||||
<th style={{ padding: '12px', textAlign: 'left', fontWeight: '600', color: '#4a5568' }}>
|
||||
IP 주소
|
||||
</th>
|
||||
<th style={{ padding: '12px', textAlign: 'center', fontWeight: '600', color: '#4a5568' }}>
|
||||
상태
|
||||
</th>
|
||||
<th style={{ padding: '12px', textAlign: 'left', fontWeight: '600', color: '#4a5568' }}>
|
||||
로그인 시간
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{loginLogs.map((log, index) => (
|
||||
<tr key={index} style={{ borderBottom: '1px solid #e2e8f0' }}>
|
||||
<td style={{ padding: '12px', fontWeight: '500' }}>
|
||||
{log.username}
|
||||
</td>
|
||||
<td style={{ padding: '12px', color: '#4a5568' }}>
|
||||
{log.ip_address}
|
||||
</td>
|
||||
<td style={{ padding: '12px', textAlign: 'center' }}>
|
||||
<span style={{
|
||||
background: log.status === 'success' ? '#48bb78' : '#f56565',
|
||||
color: 'white',
|
||||
padding: '4px 8px',
|
||||
borderRadius: '12px',
|
||||
fontSize: '12px'
|
||||
}}>
|
||||
{log.status === 'success' ? '성공' : '실패'}
|
||||
</span>
|
||||
</td>
|
||||
<td style={{ padding: '12px', color: '#4a5568' }}>
|
||||
{new Date(log.login_time).toLocaleString('ko-KR')}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : activeTab === 'activity-logs' ? (
|
||||
// 활동 로그 탭
|
||||
<div style={{ overflowX: 'auto' }}>
|
||||
<h3 style={{ marginBottom: '16px', color: '#2d3748' }}>📊 활동 로그</h3>
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr style={{ borderBottom: '2px solid #e2e8f0' }}>
|
||||
<th style={{ padding: '12px', textAlign: 'left', fontWeight: '600', color: '#4a5568' }}>
|
||||
사용자명
|
||||
</th>
|
||||
<th style={{ padding: '12px', textAlign: 'left', fontWeight: '600', color: '#4a5568' }}>
|
||||
활동 유형
|
||||
</th>
|
||||
<th style={{ padding: '12px', textAlign: 'left', fontWeight: '600', color: '#4a5568' }}>
|
||||
상세 내용
|
||||
</th>
|
||||
<th style={{ padding: '12px', textAlign: 'left', fontWeight: '600', color: '#4a5568' }}>
|
||||
시간
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{activityLogs.map((log, index) => (
|
||||
<tr key={index} style={{ borderBottom: '1px solid #e2e8f0' }}>
|
||||
<td style={{ padding: '12px', fontWeight: '500' }}>
|
||||
{log.username}
|
||||
</td>
|
||||
<td style={{ padding: '12px' }}>
|
||||
<span style={{
|
||||
background: getActivityTypeColor(log.activity_type),
|
||||
color: 'white',
|
||||
padding: '4px 8px',
|
||||
borderRadius: '12px',
|
||||
fontSize: '12px'
|
||||
}}>
|
||||
{log.activity_type}
|
||||
</span>
|
||||
</td>
|
||||
<td style={{ padding: '12px', color: '#4a5568' }}>
|
||||
{log.activity_description}
|
||||
</td>
|
||||
<td style={{ padding: '12px', color: '#4a5568' }}>
|
||||
{new Date(log.created_at).toLocaleString('ko-KR')}
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SystemSettingsPage;
|
||||
|
||||
Reference in New Issue
Block a user