import { API, getAuthHeaders, ensureAuthenticated } from '/js/api-config.js'; // 인증 확인 const token = ensureAuthenticated(); const accessLabels = { worker: '작업자', group_leader: '그룹장', support_team: '지원팀', admin: '관리자', system: '시스템' }; // 현재 사용자 정보 가져오기 const currentUser = JSON.parse(localStorage.getItem('user') || '{}'); const isSystemUser = currentUser.access_level === 'system'; function createRow(item, cols, delHandler) { const tr = document.createElement('tr'); cols.forEach(key => { const td = document.createElement('td'); td.textContent = item[key] || '-'; tr.appendChild(td); }); const delBtn = document.createElement('button'); delBtn.textContent = '삭제'; delBtn.className = 'btn-delete'; delBtn.onclick = () => delHandler(item); const td = document.createElement('td'); td.appendChild(delBtn); tr.appendChild(td); return tr; } // 내 비밀번호 변경 const myPasswordForm = document.getElementById('myPasswordForm'); myPasswordForm?.addEventListener('submit', async e => { e.preventDefault(); const currentPassword = document.getElementById('currentPassword').value; const newPassword = document.getElementById('newPassword').value; const confirmPassword = document.getElementById('confirmPassword').value; // 새 비밀번호 확인 if (newPassword !== confirmPassword) { alert('❌ 새 비밀번호가 일치하지 않습니다.'); return; } // 비밀번호 강도 검사 if (newPassword.length < 6) { alert('❌ 비밀번호는 최소 6자 이상이어야 합니다.'); return; } try { const res = await fetch(`${API}/auth/change-password`, { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify({ currentPassword, newPassword }) }); const result = await res.json(); if (res.ok && result.success) { showToast('✅ 비밀번호가 변경되었습니다.'); myPasswordForm.reset(); // 3초 후 로그인 페이지로 이동 setTimeout(() => { alert('비밀번호가 변경되어 다시 로그인해주세요.'); localStorage.removeItem('token'); localStorage.removeItem('user'); window.location.href = '/index.html'; }, 2000); } else { alert('❌ 비밀번호 변경 실패: ' + (result.error || '현재 비밀번호가 올바르지 않습니다.')); } } catch (error) { console.error('Password change error:', error); alert('🚨 서버 오류: ' + error.message); } }); // 시스템 권한자만 볼 수 있는 사용자 비밀번호 변경 섹션 if (isSystemUser) { const systemCard = document.getElementById('systemPasswordChangeCard'); if (systemCard) { systemCard.style.display = 'block'; } // 사용자 비밀번호 변경 (시스템 권한자) const userPasswordForm = document.getElementById('userPasswordForm'); userPasswordForm?.addEventListener('submit', async e => { e.preventDefault(); const targetUserId = document.getElementById('targetUserId').value; const newPassword = document.getElementById('targetNewPassword').value; if (!targetUserId) { alert('❌ 사용자를 선택해주세요.'); return; } if (newPassword.length < 6) { alert('❌ 비밀번호는 최소 6자 이상이어야 합니다.'); return; } if (!confirm('정말로 이 사용자의 비밀번호를 변경하시겠습니까?')) { return; } try { const res = await fetch(`${API}/auth/admin/change-password`, { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify({ userId: targetUserId, newPassword }) }); const result = await res.json(); if (res.ok && result.success) { showToast('✅ 사용자 비밀번호가 변경되었습니다.'); userPasswordForm.reset(); } else { alert('❌ 비밀번호 변경 실패: ' + (result.error || '권한이 없습니다.')); } } catch (error) { console.error('Admin password change error:', error); alert('🚨 서버 오류: ' + error.message); } }); } // 사용자 등록 const userForm = document.getElementById('userForm'); userForm?.addEventListener('submit', async e => { e.preventDefault(); const body = { username: document.getElementById('username').value.trim(), password: document.getElementById('password').value.trim(), name: document.getElementById('name').value.trim(), access_level: document.getElementById('access_level').value, worker_id: document.getElementById('worker_id').value || null }; try { const res = await fetch(`${API}/auth/register`, { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify(body) }); const result = await res.json(); if (res.ok && result.success) { showToast('✅ 등록 완료'); userForm.reset(); loadUsers(); } else { alert('❌ 실패: ' + (result.error || '알 수 없는 오류')); } } catch (error) { console.error('Registration error:', error); alert('🚨 서버 오류: ' + error.message); } }); async function loadUsers() { const tbody = document.getElementById('userTableBody'); tbody.innerHTML = '불러오는 중...'; try { const res = await fetch(`${API}/auth/users`, { headers: getAuthHeaders() }); if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); } const list = await res.json(); tbody.innerHTML = ''; if (Array.isArray(list)) { // 시스템 권한자용 사용자 선택 옵션도 업데이트 if (isSystemUser) { const targetUserSelect = document.getElementById('targetUserId'); if (targetUserSelect) { targetUserSelect.innerHTML = ''; list.forEach(user => { // 본인은 제외 if (user.user_id !== currentUser.user_id) { const opt = document.createElement('option'); opt.value = user.user_id; opt.textContent = `${user.name} (${user.username})`; targetUserSelect.appendChild(opt); } }); } } list.forEach(item => { item.access_level = accessLabels[item.access_level] || item.access_level; item.worker_id = item.worker_id || '-'; // 행 생성 const tr = document.createElement('tr'); // 데이터 컬럼 ['user_id', 'username', 'name', 'access_level', 'worker_id'].forEach(key => { const td = document.createElement('td'); td.textContent = item[key] || '-'; tr.appendChild(td); }); // 작업 컬럼 (페이지 권한 버튼 + 삭제 버튼) const actionTd = document.createElement('td'); // 페이지 권한 버튼 (Admin/System이 아닌 경우에만) if (item.access_level !== '관리자' && item.access_level !== '시스템') { const pageAccessBtn = document.createElement('button'); pageAccessBtn.textContent = '페이지 권한'; pageAccessBtn.className = 'btn btn-info btn-sm'; pageAccessBtn.style.marginRight = '5px'; pageAccessBtn.onclick = () => openPageAccessModal(item.user_id, item.username, item.name); actionTd.appendChild(pageAccessBtn); } // 삭제 버튼 const delBtn = document.createElement('button'); delBtn.textContent = '삭제'; delBtn.className = 'btn-delete'; delBtn.onclick = async () => { if (!confirm('삭제하시겠습니까?')) return; try { const delRes = await fetch(`${API}/auth/users/${item.user_id}`, { method: 'DELETE', headers: getAuthHeaders() }); if (delRes.ok) { showToast('✅ 삭제 완료'); loadUsers(); } else { alert('❌ 삭제 실패'); } } catch (error) { alert('🚨 삭제 중 오류 발생'); } }; actionTd.appendChild(delBtn); tr.appendChild(actionTd); tbody.appendChild(tr); }); } else { tbody.innerHTML = '데이터 형식 오류'; } } catch (error) { console.error('Load users error:', error); tbody.innerHTML = '로드 실패: ' + error.message + ''; } } async function loadWorkerOptions() { const select = document.getElementById('worker_id'); if (!select) return; try { const res = await fetch(`${API}/workers`, { headers: getAuthHeaders() }); if (!res.ok) { throw new Error(`HTTP error! status: ${res.status}`); } const allWorkers = await res.json(); // 활성화된 작업자만 필터링 const workers = allWorkers.filter(worker => { return worker.status === 'active' || worker.is_active === 1 || worker.is_active === true; }); if (Array.isArray(workers)) { workers.forEach(w => { const opt = document.createElement('option'); opt.value = w.worker_id; opt.textContent = `${w.worker_name} (${w.worker_id})`; select.appendChild(opt); }); } } catch (error) { console.warn('작업자 목록 불러오기 실패:', error); } } function showToast(message) { const toast = document.createElement('div'); toast.textContent = message; toast.style.position = 'fixed'; toast.style.bottom = '30px'; toast.style.left = '50%'; toast.style.transform = 'translateX(-50%)'; toast.style.background = '#323232'; toast.style.color = '#fff'; toast.style.padding = '10px 20px'; toast.style.borderRadius = '6px'; toast.style.fontSize = '14px'; toast.style.zIndex = 9999; document.body.appendChild(toast); setTimeout(() => toast.remove(), 2000); } // ========== 페이지 접근 권한 관리 ========== let currentEditingUserId = null; let currentUserPageAccess = []; /** * 페이지 권한 관리 모달 열기 */ async function openPageAccessModal(userId, username, name) { currentEditingUserId = userId; const modal = document.getElementById('pageAccessModal'); const modalUserInfo = document.getElementById('modalUserInfo'); const modalUserRole = document.getElementById('modalUserRole'); modalUserInfo.textContent = `${name} (${username})`; modalUserRole.textContent = `사용자 ID: ${userId}`; try { // 사용자의 페이지 접근 권한 조회 const res = await fetch(`${API}/users/${userId}/page-access`, { headers: getAuthHeaders() }); if (!res.ok) { throw new Error('페이지 접근 권한을 불러오는데 실패했습니다.'); } const result = await res.json(); if (result.success) { currentUserPageAccess = result.data.pageAccess; renderPageAccessList(result.data.pageAccess); modal.style.display = 'block'; } else { throw new Error(result.error || '데이터 로드 실패'); } } catch (error) { console.error('페이지 권한 로드 오류:', error); alert('❌ 페이지 권한을 불러오는데 실패했습니다: ' + error.message); } } /** * 페이지 접근 권한 목록 렌더링 */ function renderPageAccessList(pageAccess) { const categories = { dashboard: document.getElementById('dashboardPageList'), management: document.getElementById('managementPageList'), common: document.getElementById('commonPageList') }; // 카테고리별로 초기화 Object.values(categories).forEach(el => { if (el) el.innerHTML = ''; }); // 카테고리별로 그룹화 const grouped = pageAccess.reduce((acc, page) => { if (!acc[page.category]) acc[page.category] = []; acc[page.category].push(page); return acc; }, {}); // 각 카테고리별로 렌더링 Object.keys(grouped).forEach(category => { const container = categories[category]; if (!container) return; grouped[category].forEach(page => { const pageItem = document.createElement('div'); pageItem.className = 'page-item'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.id = `page_${page.page_id}`; checkbox.checked = page.can_access === 1 || page.can_access === true; checkbox.dataset.pageId = page.page_id; const label = document.createElement('label'); label.htmlFor = `page_${page.page_id}`; label.textContent = page.page_name; const pathSpan = document.createElement('span'); pathSpan.className = 'page-path'; pathSpan.textContent = page.page_path; pageItem.appendChild(checkbox); pageItem.appendChild(label); pageItem.appendChild(pathSpan); container.appendChild(pageItem); }); }); } /** * 페이지 권한 변경 사항 저장 */ async function savePageAccessChanges() { if (!currentEditingUserId) { alert('사용자 정보가 없습니다.'); return; } // 모든 체크박스 상태 가져오기 const checkboxes = document.querySelectorAll('.page-item input[type="checkbox"]'); const pageAccessUpdates = {}; checkboxes.forEach(checkbox => { const pageId = parseInt(checkbox.dataset.pageId); const canAccess = checkbox.checked; pageAccessUpdates[pageId] = canAccess; }); try { // 변경된 페이지 권한을 서버로 전송 const pageIds = Object.keys(pageAccessUpdates).map(id => parseInt(id)); const canAccessValues = pageIds.map(id => pageAccessUpdates[id]); // 접근 가능한 페이지 const accessiblePages = pageIds.filter((id, index) => canAccessValues[index]); // 접근 불가능한 페이지 const inaccessiblePages = pageIds.filter((id, index) => !canAccessValues[index]); // 접근 가능 페이지 업데이트 if (accessiblePages.length > 0) { await fetch(`${API}/users/${currentEditingUserId}/page-access`, { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify({ pageIds: accessiblePages, canAccess: true }) }); } // 접근 불가능 페이지 업데이트 if (inaccessiblePages.length > 0) { await fetch(`${API}/users/${currentEditingUserId}/page-access`, { method: 'POST', headers: getAuthHeaders(), body: JSON.stringify({ pageIds: inaccessiblePages, canAccess: false }) }); } showToast('✅ 페이지 접근 권한이 저장되었습니다.'); closePageAccessModal(); } catch (error) { console.error('페이지 권한 저장 오류:', error); alert('❌ 페이지 권한 저장에 실패했습니다: ' + error.message); } } /** * 페이지 권한 관리 모달 닫기 */ function closePageAccessModal() { const modal = document.getElementById('pageAccessModal'); modal.style.display = 'none'; currentEditingUserId = null; currentUserPageAccess = []; } // 모달 닫기 버튼 이벤트 document.addEventListener('DOMContentLoaded', () => { const modal = document.getElementById('pageAccessModal'); const closeBtn = modal?.querySelector('.close'); if (closeBtn) { closeBtn.onclick = closePageAccessModal; } // 모달 외부 클릭 시 닫기 window.onclick = (event) => { if (event.target === modal) { closePageAccessModal(); } }; }); // 전역 함수로 노출 window.openPageAccessModal = openPageAccessModal; window.closePageAccessModal = closePageAccessModal; window.savePageAccessChanges = savePageAccessChanges; window.addEventListener('DOMContentLoaded', () => { loadUsers(); loadWorkerOptions(); });