// 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(); } // ========== 사용자 정보 설정 ========== // // navbar/sidebar는 app-init.js에서 공통 처리 function setupUserInfo() { const authData = getAuthData(); if (authData && authData.user) { currentUser = authData.user; console.log('👤 사용자 정보 로드 완료:', currentUser.name, currentUser.role); } } 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 => `
${(user.name || user.username).charAt(0)}

${user.name || user.username}

${user.email || '이메일 없음'}

${user.username} ${getRoleIcon(user.role)} ${getRoleName(user.role)} ${user.is_active ? '활성' : '비활성'} ${formatDate(user.last_login) || '로그인 기록 없음'}
${user.role !== 'Admin' && user.role !== 'admin' ? ` ` : ''}
`).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; } // 작업자 연결 섹션 숨기기 (새 사용자 추가 시) const workerLinkGroup = document.getElementById('workerLinkGroup'); if (workerLinkGroup) { workerLinkGroup.style.display = 'none'; } 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 = '사용자 정보 수정'; } // 역할 이름을 HTML select option value로 변환 const roleToValueMap = { 'Admin': 'admin', 'System Admin': 'admin', 'User': 'user', 'Guest': 'user' }; // 폼에 데이터 채우기 if (elements.userNameInput) elements.userNameInput.value = user.name || ''; if (elements.userIdInput) elements.userIdInput.value = user.username || ''; if (elements.userRoleSelect) elements.userRoleSelect.value = roleToValueMap[user.role] || 'user'; 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; } // 작업자 연결 섹션 표시 (수정 시에만) const workerLinkGroup = document.getElementById('workerLinkGroup'); if (workerLinkGroup) { workerLinkGroup.style.display = 'block'; updateLinkedWorkerDisplay(user); } if (elements.userModal) { elements.userModal.style.display = 'flex'; } } function closeUserModal() { if (elements.userModal) { elements.userModal.style.display = 'none'; } currentEditingUser = null; } // 영구 삭제 (Hard Delete) async function permanentDeleteUser(userId, username) { if (!confirm(`⚠️ 경고: "${username}" 사용자를 영구 삭제하시겠습니까?\n\n이 작업은 되돌릴 수 없습니다!\n관련된 모든 데이터(로그인 기록, 권한 설정 등)도 함께 삭제됩니다.`)) { return; } // 이중 확인 if (!confirm(`정말로 "${username}"을(를) 영구 삭제하시겠습니까?\n\n[확인]을 누르면 즉시 삭제됩니다.`)) { return; } try { const response = await window.apiCall(`/users/${userId}/permanent`, 'DELETE'); if (response.success) { showToast(`"${username}" 사용자가 영구 삭제되었습니다.`, 'success'); await loadUsers(); } else { throw new Error(response.message || '사용자 삭제에 실패했습니다.'); } } catch (error) { console.error('사용자 영구 삭제 오류:', error); showToast(`사용자 삭제 중 오류가 발생했습니다: ${error.message}`, 'error'); } } function closeDeleteModal() { if (elements.deleteModal) { elements.deleteModal.style.display = 'none'; } currentEditingUser = null; } // ========== 비밀번호 초기화 ========== // async function resetPassword(userId, username) { if (!confirm(`${username} 사용자의 비밀번호를 000000으로 초기화하시겠습니까?`)) { return; } try { const response = await window.apiCall(`/users/${userId}/reset-password`, 'POST'); if (response.success) { showToast(`${username}의 비밀번호가 000000으로 초기화되었습니다.`, 'success'); } else { showToast(response.message || '비밀번호 초기화에 실패했습니다.', 'error'); } } catch (error) { console.error('비밀번호 초기화 오류:', error); showToast('비밀번호 초기화 중 오류가 발생했습니다.', 'error'); } } window.resetPassword = resetPassword; // ========== 사용자 CRUD ========== // async function saveUser() { try { const formData = { name: elements.userNameInput?.value, username: elements.userIdInput?.value, role: elements.userRoleSelect?.value, // HTML select value는 이미 'admin' 또는 'user' email: elements.userEmailInput?.value }; console.log('저장할 데이터:', formData); // 유효성 검사 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 = `
${iconMap[type] || 'ℹ️'}
${message}
`; elements.toastContainer.appendChild(toast); // 자동 제거 setTimeout(() => { if (toast.parentElement) { toast.remove(); } }, duration); } // ========== 전역 함수 (HTML에서 호출) ========== // window.editUser = editUser; window.toggleUserStatus = toggleUserStatus; window.closeUserModal = closeUserModal; window.closeDeleteModal = closeDeleteModal; window.permanentDeleteUser = permanentDeleteUser; // ========== 페이지 권한 관리 ========== // let allPages = []; let userPageAccess = []; // 모든 페이지 목록 로드 async function loadAllPages() { try { const response = await apiCall('/pages'); allPages = response.data || response || []; console.log('📄 페이지 목록 로드:', allPages.length, '개'); } catch (error) { console.error('❌ 페이지 목록 로드 오류:', error); allPages = []; } } // 사용자의 페이지 권한 로드 async function loadUserPageAccess(userId) { try { const response = await apiCall(`/users/${userId}/page-access`); userPageAccess = response.data?.pageAccess || []; console.log(`👤 사용자 ${userId} 페이지 권한 로드:`, userPageAccess.length, '개'); } catch (error) { console.error('❌ 사용자 페이지 권한 로드 오류:', error); userPageAccess = []; } } // 페이지 권한 저장 async function savePageAccess(userId, containerId = null) { try { // 특정 컨테이너가 지정되면 그 안에서만 체크박스 선택 const container = containerId ? document.getElementById(containerId) : document; const checkboxes = container.querySelectorAll('.page-access-checkbox:not([disabled])'); // 중복 page_id 제거 (Map 사용) const pageAccessMap = new Map(); checkboxes.forEach(checkbox => { const pageId = parseInt(checkbox.dataset.pageId); pageAccessMap.set(pageId, { page_id: pageId, can_access: checkbox.checked ? 1 : 0 }); }); const pageAccessData = Array.from(pageAccessMap.values()); console.log('📤 페이지 권한 저장:', userId, pageAccessData); await apiCall(`/users/${userId}/page-access`, 'PUT', { pageAccess: pageAccessData }); console.log('✅ 페이지 권한 저장 완료'); } catch (error) { console.error('❌ 페이지 권한 저장 오류:', error); throw error; } } // ========== 페이지 권한 관리 모달 ========== // let currentPageAccessUser = null; // 페이지 권한 관리 모달 열기 async function managePageAccess(userId) { try { // 페이지 목록이 없으면 로드 if (allPages.length === 0) { await loadAllPages(); } // 사용자 정보 가져오기 const user = users.find(u => u.user_id === userId); if (!user) { showToast('사용자를 찾을 수 없습니다.', 'error'); return; } currentPageAccessUser = user; // 사용자의 페이지 권한 로드 await loadUserPageAccess(userId); // 모달 정보 업데이트 const userName = user.name || user.username; document.getElementById('pageAccessModalTitle').textContent = userName + ' - 페이지 권한 관리'; document.getElementById('pageAccessUserName').textContent = userName; document.getElementById('pageAccessUserRole').textContent = getRoleName(user.role); document.getElementById('pageAccessUserAvatar').textContent = userName.charAt(0); // 페이지 권한 체크박스 렌더링 renderPageAccessModalList(); // 모달 표시 document.getElementById('pageAccessModal').style.display = 'flex'; } catch (error) { console.error('❌ 페이지 권한 관리 모달 오류:', error); showToast('페이지 권한 관리를 열 수 없습니다.', 'error'); } } // 페이지 권한 모달 닫기 function closePageAccessModal() { document.getElementById('pageAccessModal').style.display = 'none'; currentPageAccessUser = null; } // 페이지 권한 체크박스 렌더링 (모달용) - 폴더 구조 형태 function renderPageAccessModalList() { const pageAccessList = document.getElementById('pageAccessModalList'); if (!pageAccessList) return; // 폴더 구조 정의 (page_key 패턴 기준) const folderStructure = { 'dashboard': { name: '대시보드', icon: '📊', pages: [] }, 'work': { name: '작업 관리', icon: '📋', pages: [] }, 'safety': { name: '안전 관리', icon: '🛡️', pages: [] }, 'attendance': { name: '근태 관리', icon: '📅', pages: [] }, 'admin': { name: '시스템 관리', icon: '⚙️', pages: [] }, 'profile': { name: '내 정보', icon: '👤', pages: [] } }; // 페이지를 폴더별로 분류 allPages.forEach(page => { const pageKey = page.page_key || ''; if (pageKey === 'dashboard') { folderStructure['dashboard'].pages.push(page); } else if (pageKey.startsWith('work.')) { folderStructure['work'].pages.push(page); } else if (pageKey.startsWith('safety.')) { folderStructure['safety'].pages.push(page); } else if (pageKey.startsWith('attendance.')) { folderStructure['attendance'].pages.push(page); } else if (pageKey.startsWith('admin.')) { folderStructure['admin'].pages.push(page); } else if (pageKey.startsWith('profile.')) { folderStructure['profile'].pages.push(page); } }); // HTML 생성 - 폴더 트리 형태 let html = '
'; Object.keys(folderStructure).forEach(folderKey => { const folder = folderStructure[folderKey]; if (folder.pages.length === 0) return; const folderId = 'folder-' + folderKey; html += '
'; html += '
'; html += '' + folder.icon + ''; html += '' + folder.name + ''; html += '(' + folder.pages.length + ')'; html += ''; html += '
'; html += '
'; folder.pages.forEach(page => { // 프로필과 대시보드는 모든 사용자가 접근 가능 const isAlwaysAccessible = page.page_key === 'dashboard' || page.page_key.startsWith('profile.'); const isChecked = userPageAccess.find(p => p.page_id === page.id && p.can_access === 1) || isAlwaysAccessible; // 파일명만 추출 (page_key에서) const fileName = page.page_key.split('.').pop() || page.page_key; html += '
'; html += ''; html += '
'; }); html += '
'; // folder-content html += '
'; // folder-group }); html += '
'; // folder-tree pageAccessList.innerHTML = html; } // 폴더 접기/펼치기 function toggleFolder(folderId) { const content = document.getElementById(folderId); const toggle = document.getElementById('toggle-' + folderId); if (content && toggle) { const isExpanded = content.style.display !== 'none'; content.style.display = isExpanded ? 'none' : 'block'; toggle.textContent = isExpanded ? '▶' : '▼'; } } window.toggleFolder = toggleFolder; // 페이지 권한 저장 (모달용) async function savePageAccessFromModal() { if (!currentPageAccessUser) { showToast('사용자 정보가 없습니다.', 'error'); return; } try { // 모달 컨테이너 지정 await savePageAccess(currentPageAccessUser.user_id, 'pageAccessModalList'); showToast('페이지 권한이 저장되었습니다.', 'success'); // 캐시 삭제 (사용자가 다시 로그인하거나 페이지 새로고침 필요) localStorage.removeItem('userPageAccess'); closePageAccessModal(); } catch (error) { console.error('❌ 페이지 권한 저장 오류:', error); showToast('페이지 권한 저장에 실패했습니다.', 'error'); } } // 전역 함수로 등록 window.managePageAccess = managePageAccess; window.closePageAccessModal = closePageAccessModal; // 저장 버튼 이벤트 리스너 document.addEventListener('DOMContentLoaded', () => { const saveBtn = document.getElementById('savePageAccessBtn'); if (saveBtn) { saveBtn.addEventListener('click', savePageAccessFromModal); } }); // ========== 작업자 연결 기능 ========== // let departments = []; let selectedWorkerId = null; // 연결된 작업자 정보 표시 업데이트 function updateLinkedWorkerDisplay(user) { const linkedWorkerInfo = document.getElementById('linkedWorkerInfo'); if (!linkedWorkerInfo) return; if (user.worker_id && user.worker_name) { linkedWorkerInfo.innerHTML = ` 👤 ${user.worker_name} ${user.department_name ? `(${user.department_name})` : ''} `; } else { linkedWorkerInfo.innerHTML = '연결된 작업자 없음'; } } // 작업자 선택 모달 열기 async function openWorkerSelectModal() { if (!currentEditingUser) { showToast('사용자 정보가 없습니다.', 'error'); return; } selectedWorkerId = currentEditingUser.worker_id || null; // 부서 목록 로드 await loadDepartmentsForSelect(); // 모달 표시 document.getElementById('workerSelectModal').style.display = 'flex'; } window.openWorkerSelectModal = openWorkerSelectModal; // 작업자 선택 모달 닫기 function closeWorkerSelectModal() { document.getElementById('workerSelectModal').style.display = 'none'; selectedWorkerId = null; } window.closeWorkerSelectModal = closeWorkerSelectModal; // 부서 목록 로드 async function loadDepartmentsForSelect() { try { const response = await window.apiCall('/departments'); departments = response.data || response || []; renderDepartmentList(); } catch (error) { console.error('부서 목록 로드 실패:', error); showToast('부서 목록을 불러오는데 실패했습니다.', 'error'); } } // 부서 목록 렌더링 function renderDepartmentList() { const container = document.getElementById('departmentList'); if (!container) return; if (departments.length === 0) { container.innerHTML = '
등록된 부서가 없습니다
'; return; } container.innerHTML = departments.map(dept => `
📁 ${dept.department_name} ${dept.worker_count || 0}명
`).join(''); } // 부서 선택 async function selectDepartment(departmentId) { // 활성 상태 업데이트 document.querySelectorAll('.department-item').forEach(item => { item.classList.remove('active'); }); document.querySelector(`.department-item[data-dept-id="${departmentId}"]`)?.classList.add('active'); // 해당 부서의 작업자 목록 로드 await loadWorkersForSelect(departmentId); } window.selectDepartment = selectDepartment; // 부서별 작업자 목록 로드 async function loadWorkersForSelect(departmentId) { try { const response = await window.apiCall(`/departments/${departmentId}/workers`); const workers = response.data || response || []; renderWorkerListForSelect(workers); } catch (error) { console.error('작업자 목록 로드 실패:', error); showToast('작업자 목록을 불러오는데 실패했습니다.', 'error'); } } // 작업자 목록 렌더링 (선택용) function renderWorkerListForSelect(workers) { const container = document.getElementById('workerListForSelect'); if (!container) return; if (workers.length === 0) { container.innerHTML = '
이 부서에 작업자가 없습니다
'; return; } // 이미 다른 계정에 연결된 작업자 확인을 위해 users 배열 사용 const linkedWorkerIds = users .filter(u => u.worker_id && u.user_id !== currentEditingUser?.user_id) .map(u => u.worker_id); container.innerHTML = workers.map(worker => { const isSelected = selectedWorkerId === worker.worker_id; const isLinkedToOther = linkedWorkerIds.includes(worker.worker_id); const linkedUser = isLinkedToOther ? users.find(u => u.worker_id === worker.worker_id) : null; return `
${worker.worker_name.charAt(0)}
${worker.worker_name}
${getJobTypeName(worker.job_type)}
${isLinkedToOther ? `${linkedUser?.username} 연결됨` : ''}
${isSelected ? '✓' : ''}
`; }).join(''); } // 직책 한글 변환 function getJobTypeName(jobType) { const names = { leader: '그룹장', worker: '작업자', admin: '관리자' }; return names[jobType] || jobType || '-'; } // 작업자 선택 async function selectWorker(workerId, workerName) { selectedWorkerId = workerId; // UI 업데이트 document.querySelectorAll('.worker-select-item').forEach(item => { item.classList.remove('selected'); item.querySelector('.select-indicator').textContent = ''; }); const selectedItem = document.querySelector(`.worker-select-item[onclick*="${workerId}"]`); if (selectedItem) { selectedItem.classList.add('selected'); selectedItem.querySelector('.select-indicator').textContent = '✓'; } // 서버에 저장 try { const response = await window.apiCall(`/users/${currentEditingUser.user_id}`, 'PUT', { worker_id: workerId }); if (response.success) { // currentEditingUser 업데이트 currentEditingUser.worker_id = workerId; currentEditingUser.worker_name = workerName; // 부서 정보도 업데이트 const dept = departments.find(d => document.querySelector(`.department-item.active`)?.dataset.deptId == d.department_id ); if (dept) { currentEditingUser.department_name = dept.department_name; } // users 배열 업데이트 const userIndex = users.findIndex(u => u.user_id === currentEditingUser.user_id); if (userIndex !== -1) { users[userIndex] = { ...users[userIndex], ...currentEditingUser }; } // 표시 업데이트 updateLinkedWorkerDisplay(currentEditingUser); showToast(`${workerName} 작업자가 연결되었습니다.`, 'success'); closeWorkerSelectModal(); } else { throw new Error(response.message || '작업자 연결에 실패했습니다.'); } } catch (error) { console.error('작업자 연결 오류:', error); showToast(`작업자 연결 중 오류가 발생했습니다: ${error.message}`, 'error'); } } window.selectWorker = selectWorker; // 작업자 연결 해제 async function unlinkWorker() { if (!currentEditingUser) { showToast('사용자 정보가 없습니다.', 'error'); return; } if (!currentEditingUser.worker_id) { showToast('연결된 작업자가 없습니다.', 'warning'); closeWorkerSelectModal(); return; } if (!confirm('작업자 연결을 해제하시겠습니까?')) { return; } try { const response = await window.apiCall(`/users/${currentEditingUser.user_id}`, 'PUT', { worker_id: null }); if (response.success) { // currentEditingUser 업데이트 currentEditingUser.worker_id = null; currentEditingUser.worker_name = null; currentEditingUser.department_name = null; // users 배열 업데이트 const userIndex = users.findIndex(u => u.user_id === currentEditingUser.user_id); if (userIndex !== -1) { users[userIndex] = { ...users[userIndex], worker_id: null, worker_name: null, department_name: null }; } // 표시 업데이트 updateLinkedWorkerDisplay(currentEditingUser); showToast('작업자 연결이 해제되었습니다.', 'success'); closeWorkerSelectModal(); } else { throw new Error(response.message || '연결 해제에 실패했습니다.'); } } catch (error) { console.error('작업자 연결 해제 오류:', error); showToast(`연결 해제 중 오류가 발생했습니다: ${error.message}`, 'error'); } } window.unlinkWorker = unlinkWorker; // ========== 알림 수신자 관리 ========== // let notificationRecipients = {}; let allUsersForRecipient = []; let currentNotificationType = null; const NOTIFICATION_TYPE_CONFIG = { repair: { name: '설비 수리', icon: '🔧', description: '설비 수리 신청 시 알림을 받을 사용자' }, safety: { name: '안전 신고', icon: '⚠️', description: '안전 관련 신고 시 알림을 받을 사용자' }, nonconformity: { name: '부적합 신고', icon: '🚫', description: '부적합 사항 신고 시 알림을 받을 사용자' }, equipment: { name: '설비 관련', icon: '🔩', description: '설비 관련 알림을 받을 사용자' }, maintenance: { name: '정기점검', icon: '🛠️', description: '정기점검 알림을 받을 사용자' }, system: { name: '시스템', icon: '📢', description: '시스템 알림을 받을 사용자' } }; // 알림 수신자 목록 로드 async function loadNotificationRecipients() { try { const response = await window.apiCall('/notification-recipients'); if (response.success) { notificationRecipients = response.data || {}; renderNotificationTypeCards(); } } catch (error) { console.error('알림 수신자 로드 오류:', error); } } // 알림 유형 카드 렌더링 function renderNotificationTypeCards() { const container = document.getElementById('notificationTypeCards'); if (!container) return; let html = ''; Object.keys(NOTIFICATION_TYPE_CONFIG).forEach(type => { const config = NOTIFICATION_TYPE_CONFIG[type]; const recipients = notificationRecipients[type]?.recipients || []; html += `
${config.icon} ${config.name}
${recipients.length > 0 ? recipients.map(r => ` 👤 ${r.user_name || r.username} `).join('') : '지정된 수신자 없음' }
`; }); container.innerHTML = html; } // 수신자 편집 모달 열기 async function openRecipientModal(notificationType) { currentNotificationType = notificationType; const config = NOTIFICATION_TYPE_CONFIG[notificationType]; // 모달 정보 업데이트 document.getElementById('recipientModalTitle').textContent = config.name + ' 알림 수신자'; document.getElementById('recipientModalDesc').textContent = config.description; // 사용자 목록 로드 (users가 이미 로드되어 있으면 사용) if (users.length === 0) { await loadUsers(); } allUsersForRecipient = users.filter(u => u.is_active); // 현재 수신자 목록 const currentRecipients = notificationRecipients[notificationType]?.recipients || []; const currentRecipientIds = currentRecipients.map(r => r.user_id); // 사용자 목록 렌더링 renderRecipientUserList(currentRecipientIds); // 검색 이벤트 const searchInput = document.getElementById('recipientSearchInput'); searchInput.value = ''; searchInput.oninput = (e) => { renderRecipientUserList(currentRecipientIds, e.target.value); }; // 모달 표시 document.getElementById('notificationRecipientModal').style.display = 'flex'; } window.openRecipientModal = openRecipientModal; // 수신자 사용자 목록 렌더링 function renderRecipientUserList(selectedIds, searchTerm = '') { const container = document.getElementById('recipientUserList'); if (!container) return; let filteredUsers = allUsersForRecipient; if (searchTerm) { const term = searchTerm.toLowerCase(); filteredUsers = filteredUsers.filter(u => (u.name && u.name.toLowerCase().includes(term)) || (u.username && u.username.toLowerCase().includes(term)) ); } if (filteredUsers.length === 0) { container.innerHTML = '
사용자가 없습니다
'; return; } container.innerHTML = filteredUsers.map(user => { const isSelected = selectedIds.includes(user.user_id); return `
${(user.name || user.username).charAt(0)}
${user.name || user.username}
${getRoleName(user.role)}
`; }).join(''); } // 수신자 토글 function toggleRecipientUser(userId, element) { const checkbox = element.querySelector('input[type="checkbox"]'); checkbox.checked = !checkbox.checked; element.classList.toggle('selected', checkbox.checked); } window.toggleRecipientUser = toggleRecipientUser; // 수신자 모달 닫기 function closeRecipientModal() { document.getElementById('notificationRecipientModal').style.display = 'none'; currentNotificationType = null; } window.closeRecipientModal = closeRecipientModal; // 알림 수신자 저장 async function saveNotificationRecipients() { if (!currentNotificationType) { showToast('알림 유형이 선택되지 않았습니다.', 'error'); return; } try { const checkboxes = document.querySelectorAll('#recipientUserList input[type="checkbox"]:checked'); const userIds = Array.from(checkboxes).map(cb => parseInt(cb.dataset.userId)); const response = await window.apiCall(`/notification-recipients/${currentNotificationType}`, 'PUT', { user_ids: userIds }); if (response.success) { showToast('알림 수신자가 저장되었습니다.', 'success'); closeRecipientModal(); await loadNotificationRecipients(); } else { throw new Error(response.message || '저장에 실패했습니다.'); } } catch (error) { console.error('알림 수신자 저장 오류:', error); showToast(`저장 중 오류가 발생했습니다: ${error.message}`, 'error'); } } window.saveNotificationRecipients = saveNotificationRecipients; // 초기화 시 알림 수신자 로드 const originalInitializePage = initializePage; initializePage = async function() { await originalInitializePage(); await loadNotificationRecipients(); };