Files
TK-FB-Project/web-ui/js/admin-settings.js
Hyungi Ahn a9bce9d20b fix: 캘린더 모달 중복 카드 문제 및 삭제 권한 개선
- monthly_worker_status 조회 시 GROUP BY로 중복 데이터 합산
- 작업보고서 삭제 권한을 그룹장 이상으로 제한 (admin, system, group_leader)
- 중복 데이터 정리를 위한 마이그레이션 SQL 추가 (009_fix_duplicate_monthly_status.sql)
- synology_deployment 버전에도 동일 수정 적용
2025-12-02 13:08:44 +09:00

535 lines
15 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// admin-settings.js - 관리자 설정 페이지
// 전역 변수
let currentUser = null;
let users = [];
let filteredUsers = [];
let currentEditingUser = null;
// DOM 요소
const elements = {
// 시간
timeValue: document.getElementById('timeValue'),
// 사용자 정보
userName: document.getElementById('userName'),
userRole: document.getElementById('userRole'),
userInitial: document.getElementById('userInitial'),
// 검색 및 필터
userSearch: document.getElementById('userSearch'),
filterButtons: document.querySelectorAll('.filter-btn'),
// 테이블
usersTableBody: document.getElementById('usersTableBody'),
emptyState: document.getElementById('emptyState'),
// 버튼
addUserBtn: document.getElementById('addUserBtn'),
saveUserBtn: document.getElementById('saveUserBtn'),
confirmDeleteBtn: document.getElementById('confirmDeleteBtn'),
// 모달
userModal: document.getElementById('userModal'),
deleteModal: document.getElementById('deleteModal'),
modalTitle: document.getElementById('modalTitle'),
// 폼
userForm: document.getElementById('userForm'),
userNameInput: document.getElementById('userName'),
userIdInput: document.getElementById('userId'),
userPasswordInput: document.getElementById('userPassword'),
userRoleSelect: document.getElementById('userRole'),
userEmailInput: document.getElementById('userEmail'),
userPhoneInput: document.getElementById('userPhone'),
passwordGroup: document.getElementById('passwordGroup'),
// 토스트
toastContainer: document.getElementById('toastContainer')
};
// ========== 초기화 ========== //
document.addEventListener('DOMContentLoaded', async () => {
console.log('🔧 관리자 설정 페이지 초기화 시작');
try {
await initializePage();
console.log('✅ 관리자 설정 페이지 초기화 완료');
} catch (error) {
console.error('❌ 페이지 초기화 오류:', error);
showToast('페이지를 불러오는 중 오류가 발생했습니다.', 'error');
}
});
async function initializePage() {
// 이벤트 리스너 설정
setupEventListeners();
// 사용자 목록 로드
await loadUsers();
}
// ========== 사용자 정보 설정 ========== //
function setupUserInfo() {
const authData = getAuthData();
if (authData && authData.user) {
currentUser = authData.user;
// 사용자 이름 설정
if (elements.userName) {
elements.userName.textContent = currentUser.name || currentUser.username;
}
// 사용자 역할 설정
const roleMap = {
'admin': '관리자',
'system': '시스템 관리자',
'leader': '그룹장',
'user': '작업자'
};
if (elements.userRole) {
elements.userRole.textContent = roleMap[currentUser.role] || '작업자';
}
// 아바타 초기값 설정
if (elements.userInitial) {
const initial = (currentUser.name || currentUser.username).charAt(0);
elements.userInitial.textContent = initial;
}
console.log('👤 사용자 정보 설정 완료:', currentUser.name);
}
}
function getAuthData() {
const token = localStorage.getItem('token');
const user = localStorage.getItem('user');
return {
token,
user: user ? JSON.parse(user) : null
};
}
// ========== 시간 업데이트 ========== //
function updateCurrentTime() {
const now = new Date();
const timeString = now.toLocaleTimeString('ko-KR', {
hour12: false,
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
if (elements.timeValue) {
elements.timeValue.textContent = timeString;
}
}
// ========== 이벤트 리스너 ========== //
function setupEventListeners() {
// 검색
if (elements.userSearch) {
elements.userSearch.addEventListener('input', handleSearch);
}
// 필터 버튼
elements.filterButtons.forEach(btn => {
btn.addEventListener('click', handleFilter);
});
// 사용자 추가 버튼
if (elements.addUserBtn) {
elements.addUserBtn.addEventListener('click', openAddUserModal);
}
// 사용자 저장 버튼
if (elements.saveUserBtn) {
elements.saveUserBtn.addEventListener('click', saveUser);
}
// 삭제 확인 버튼
if (elements.confirmDeleteBtn) {
elements.confirmDeleteBtn.addEventListener('click', confirmDeleteUser);
}
// 로그아웃 버튼
const logoutBtn = document.getElementById('logoutBtn');
if (logoutBtn) {
logoutBtn.addEventListener('click', handleLogout);
}
// 프로필 드롭다운
const userProfile = document.getElementById('userProfile');
const profileMenu = document.getElementById('profileMenu');
if (userProfile && profileMenu) {
userProfile.addEventListener('click', (e) => {
e.stopPropagation();
profileMenu.style.display = profileMenu.style.display === 'block' ? 'none' : 'block';
});
document.addEventListener('click', () => {
profileMenu.style.display = 'none';
});
}
}
// ========== 사용자 관리 ========== //
async function loadUsers() {
try {
console.log('👥 사용자 목록 로딩...');
// 실제 API에서 사용자 데이터 가져오기
const response = await window.apiCall('/users');
users = Array.isArray(response) ? response : (response.data || []);
console.log(`✅ 사용자 ${users.length}명 로드 완료`);
// 필터링된 사용자 목록 초기화
filteredUsers = [...users];
// 테이블 렌더링
renderUsersTable();
} catch (error) {
console.error('❌ 사용자 목록 로딩 오류:', error);
showToast('사용자 목록을 불러오는 중 오류가 발생했습니다.', 'error');
users = [];
filteredUsers = [];
renderUsersTable();
}
}
function renderUsersTable() {
if (!elements.usersTableBody) return;
if (filteredUsers.length === 0) {
elements.usersTableBody.innerHTML = '';
if (elements.emptyState) {
elements.emptyState.style.display = 'block';
}
return;
}
if (elements.emptyState) {
elements.emptyState.style.display = 'none';
}
elements.usersTableBody.innerHTML = filteredUsers.map(user => `
<tr>
<td>
<div class="user-info">
<div class="user-avatar-small">${(user.name || user.username).charAt(0)}</div>
<div class="user-details">
<h4>${user.name || user.username}</h4>
<p>${user.email || '이메일 없음'}</p>
</div>
</div>
</td>
<td><strong>${user.username}</strong></td>
<td>
<span class="role-badge ${user.role}">
${getRoleIcon(user.role)} ${getRoleName(user.role)}
</span>
</td>
<td>
<span class="status-badge ${user.is_active ? 'active' : 'inactive'}">
${user.is_active ? '활성' : '비활성'}
</span>
</td>
<td>${formatDate(user.last_login) || '로그인 기록 없음'}</td>
<td>
<div class="action-buttons">
<button class="action-btn edit" onclick="editUser(${user.user_id})">
수정
</button>
<button class="action-btn toggle" onclick="toggleUserStatus(${user.user_id})">
${user.is_active ? '비활성화' : '활성화'}
</button>
<button class="action-btn delete" onclick="deleteUser(${user.user_id})">
삭제
</button>
</div>
</td>
</tr>
`).join('');
}
function getRoleIcon(role) {
const icons = {
admin: '👑',
leader: '👨‍💼',
user: '👤'
};
return icons[role] || '👤';
}
function getRoleName(role) {
const names = {
admin: '관리자',
leader: '그룹장',
user: '작업자'
};
return names[role] || '작업자';
}
function formatDate(dateString) {
if (!dateString) return null;
const date = new Date(dateString);
return date.toLocaleDateString('ko-KR', {
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
}
// ========== 검색 및 필터링 ========== //
function handleSearch(e) {
const searchTerm = e.target.value.toLowerCase();
filteredUsers = users.filter(user => {
return (user.name && user.name.toLowerCase().includes(searchTerm)) ||
(user.username && user.username.toLowerCase().includes(searchTerm)) ||
(user.email && user.email.toLowerCase().includes(searchTerm));
});
renderUsersTable();
}
function handleFilter(e) {
const filterType = e.target.dataset.filter;
// 활성 버튼 변경
elements.filterButtons.forEach(btn => btn.classList.remove('active'));
e.target.classList.add('active');
// 필터링
if (filterType === 'all') {
filteredUsers = [...users];
} else {
filteredUsers = users.filter(user => user.role === filterType);
}
renderUsersTable();
}
// ========== 모달 관리 ========== //
function openAddUserModal() {
currentEditingUser = null;
if (elements.modalTitle) {
elements.modalTitle.textContent = '새 사용자 추가';
}
// 폼 초기화
if (elements.userForm) {
elements.userForm.reset();
}
// 비밀번호 필드 표시
if (elements.passwordGroup) {
elements.passwordGroup.style.display = 'block';
}
if (elements.userPasswordInput) {
elements.userPasswordInput.required = true;
}
if (elements.userModal) {
elements.userModal.style.display = 'flex';
}
}
function editUser(userId) {
const user = users.find(u => u.user_id === userId);
if (!user) return;
currentEditingUser = user;
if (elements.modalTitle) {
elements.modalTitle.textContent = '사용자 정보 수정';
}
// 폼에 데이터 채우기
if (elements.userNameInput) elements.userNameInput.value = user.name || '';
if (elements.userIdInput) elements.userIdInput.value = user.username || '';
if (elements.userRoleSelect) elements.userRoleSelect.value = user.role || '';
if (elements.userEmailInput) elements.userEmailInput.value = user.email || '';
if (elements.userPhoneInput) elements.userPhoneInput.value = user.phone || '';
// 비밀번호 필드 숨기기 (수정 시에는 선택사항)
if (elements.passwordGroup) {
elements.passwordGroup.style.display = 'none';
}
if (elements.userPasswordInput) {
elements.userPasswordInput.required = false;
}
if (elements.userModal) {
elements.userModal.style.display = 'flex';
}
}
function closeUserModal() {
if (elements.userModal) {
elements.userModal.style.display = 'none';
}
currentEditingUser = null;
}
function deleteUser(userId) {
const user = users.find(u => u.user_id === userId);
if (!user) return;
currentEditingUser = user;
if (elements.deleteModal) {
elements.deleteModal.style.display = 'flex';
}
}
function closeDeleteModal() {
if (elements.deleteModal) {
elements.deleteModal.style.display = 'none';
}
currentEditingUser = null;
}
// ========== 사용자 CRUD ========== //
async function saveUser() {
try {
const formData = {
name: elements.userNameInput?.value,
username: elements.userIdInput?.value,
role: elements.userRoleSelect?.value,
email: elements.userEmailInput?.value,
phone: elements.userPhoneInput?.value
};
// 유효성 검사
if (!formData.name || !formData.username || !formData.role) {
showToast('필수 항목을 모두 입력해주세요.', 'error');
return;
}
// 비밀번호 처리
if (!currentEditingUser && elements.userPasswordInput?.value) {
formData.password = elements.userPasswordInput.value;
} else if (currentEditingUser && elements.userPasswordInput?.value) {
formData.password = elements.userPasswordInput.value;
}
let response;
if (currentEditingUser) {
// 수정
response = await window.apiCall(`/users/${currentEditingUser.user_id}`, 'PUT', formData);
} else {
// 생성
response = await window.apiCall('/users', 'POST', formData);
}
if (response.success || response.user_id) {
const action = currentEditingUser ? '수정' : '생성';
showToast(`사용자가 성공적으로 ${action}되었습니다.`, 'success');
closeUserModal();
await loadUsers();
} else {
throw new Error(response.message || '사용자 저장에 실패했습니다.');
}
} catch (error) {
console.error('사용자 저장 오류:', error);
showToast(`사용자 저장 중 오류가 발생했습니다: ${error.message}`, 'error');
}
}
async function confirmDeleteUser() {
if (!currentEditingUser) return;
try {
const response = await window.apiCall(`/users/${currentEditingUser.user_id}`, 'DELETE');
if (response.success) {
showToast('사용자가 성공적으로 삭제되었습니다.', 'success');
closeDeleteModal();
await loadUsers();
} else {
throw new Error(response.message || '사용자 삭제에 실패했습니다.');
}
} catch (error) {
console.error('사용자 삭제 오류:', error);
showToast(`사용자 삭제 중 오류가 발생했습니다: ${error.message}`, 'error');
}
}
async function toggleUserStatus(userId) {
try {
const user = users.find(u => u.user_id === userId);
if (!user) return;
const newStatus = !user.is_active;
const response = await window.apiCall(`/users/${userId}/status`, 'PUT', { is_active: newStatus });
if (response.success) {
const action = newStatus ? '활성화' : '비활성화';
showToast(`사용자가 성공적으로 ${action}되었습니다.`, 'success');
await loadUsers();
} else {
throw new Error(response.message || '사용자 상태 변경에 실패했습니다.');
}
} catch (error) {
console.error('사용자 상태 변경 오류:', error);
showToast(`사용자 상태 변경 중 오류가 발생했습니다: ${error.message}`, 'error');
}
}
// ========== 로그아웃 ========== //
function handleLogout() {
if (confirm('로그아웃하시겠습니까?')) {
localStorage.clear();
window.location.href = '/index.html';
}
}
// ========== 토스트 알림 ========== //
function showToast(message, type = 'info', duration = 3000) {
if (!elements.toastContainer) return;
const toast = document.createElement('div');
toast.className = `toast ${type}`;
const iconMap = {
success: '✅',
error: '❌',
warning: '⚠️',
info: ''
};
toast.innerHTML = `
<div class="toast-icon">${iconMap[type] || ''}</div>
<div class="toast-message">${message}</div>
<button class="toast-close" onclick="this.parentElement.remove()">×</button>
`;
elements.toastContainer.appendChild(toast);
// 자동 제거
setTimeout(() => {
if (toast.parentElement) {
toast.remove();
}
}, duration);
}
// ========== 전역 함수 (HTML에서 호출) ========== //
window.editUser = editUser;
window.deleteUser = deleteUser;
window.toggleUserStatus = toggleUserStatus;
window.closeUserModal = closeUserModal;
window.closeDeleteModal = closeDeleteModal;