feat: 일일순회점검 시스템 구축 및 관리 기능 개선
- 일일순회점검 시스템 신규 구현 - DB 테이블: patrol_checklist_items, daily_patrol_sessions, patrol_check_records, workplace_items, item_types - API: /api/patrol/* 엔드포인트 - 프론트엔드: 지도 기반 작업장 점검 UI - 설비 관리 기능 개선 - 구매 관련 필드 추가 (구매일, 가격, 공급업체 등) - 설비 코드 자동 생성 (TKP-XXX 형식) - 작업장 관리 개선 - 레이아웃 이미지 업로드 기능 - 마커 위치 저장 기능 - 부서 관리 기능 추가 - 사이드바 네비게이션 카테고리 재구성 - 이미지 401 오류 수정 (정적 파일 경로 처리) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -70,34 +70,12 @@ async function initializePage() {
|
||||
}
|
||||
|
||||
// ========== 사용자 정보 설정 ========== //
|
||||
// navbar/sidebar는 app-init.js에서 공통 처리
|
||||
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);
|
||||
console.log('👤 사용자 정보 로드 완료:', currentUser.name, currentUser.role);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -249,10 +227,10 @@ function renderUsersTable() {
|
||||
<button class="action-btn reset-pw" onclick="resetPassword(${user.user_id}, '${user.username}')" title="비밀번호 000000으로 초기화">
|
||||
비번초기화
|
||||
</button>
|
||||
<button class="action-btn toggle" onclick="toggleUserStatus(${user.user_id})">
|
||||
<button class="action-btn ${user.is_active ? 'deactivate' : 'activate'}" onclick="toggleUserStatus(${user.user_id})">
|
||||
${user.is_active ? '비활성화' : '활성화'}
|
||||
</button>
|
||||
<button class="action-btn delete" onclick="deleteUser(${user.user_id})">
|
||||
<button class="action-btn delete danger" onclick="permanentDeleteUser(${user.user_id}, '${user.username}')" title="영구 삭제 (복구 불가)">
|
||||
삭제
|
||||
</button>
|
||||
</div>
|
||||
@@ -325,25 +303,31 @@ function handleFilter(e) {
|
||||
// ========== 모달 관리 ========== //
|
||||
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';
|
||||
}
|
||||
@@ -373,16 +357,23 @@ function editUser(userId) {
|
||||
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';
|
||||
}
|
||||
@@ -395,14 +386,29 @@ function closeUserModal() {
|
||||
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';
|
||||
// 영구 삭제 (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');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -566,10 +572,10 @@ function showToast(message, type = 'info', duration = 3000) {
|
||||
|
||||
// ========== 전역 함수 (HTML에서 호출) ========== //
|
||||
window.editUser = editUser;
|
||||
window.deleteUser = deleteUser;
|
||||
window.toggleUserStatus = toggleUserStatus;
|
||||
window.closeUserModal = closeUserModal;
|
||||
window.closeDeleteModal = closeDeleteModal;
|
||||
window.permanentDeleteUser = permanentDeleteUser;
|
||||
|
||||
// ========== 페이지 권한 관리 ========== //
|
||||
let allPages = [];
|
||||
@@ -599,75 +605,6 @@ async function loadUserPageAccess(userId) {
|
||||
}
|
||||
}
|
||||
|
||||
// 페이지 권한 체크박스 렌더링
|
||||
function renderPageAccessList(userRole) {
|
||||
const pageAccessList = document.getElementById('pageAccessList');
|
||||
const pageAccessGroup = document.getElementById('pageAccessGroup');
|
||||
|
||||
if (!pageAccessList || !pageAccessGroup) return;
|
||||
|
||||
// Admin 사용자는 권한 설정 불필요
|
||||
if (userRole === 'admin') {
|
||||
pageAccessGroup.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
pageAccessGroup.style.display = 'block';
|
||||
|
||||
// 카테고리별로 페이지 그룹화
|
||||
const pagesByCategory = {
|
||||
'work': [],
|
||||
'admin': [],
|
||||
'common': [],
|
||||
'profile': []
|
||||
};
|
||||
|
||||
allPages.forEach(page => {
|
||||
const category = page.category || 'common';
|
||||
if (pagesByCategory[category]) {
|
||||
pagesByCategory[category].push(page);
|
||||
}
|
||||
});
|
||||
|
||||
const categoryNames = {
|
||||
'common': '공통',
|
||||
'work': '작업',
|
||||
'admin': '관리',
|
||||
'profile': '프로필'
|
||||
};
|
||||
|
||||
// HTML 생성
|
||||
let html = '';
|
||||
|
||||
Object.keys(pagesByCategory).forEach(category => {
|
||||
const pages = pagesByCategory[category];
|
||||
if (pages.length === 0) return;
|
||||
|
||||
const catName = categoryNames[category] || category;
|
||||
html += '<div class="page-access-category">';
|
||||
html += '<div class="page-access-category-title">' + catName + '</div>';
|
||||
|
||||
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;
|
||||
|
||||
html += '<div class="page-access-item"><label>';
|
||||
html += '<input type="checkbox" class="page-access-checkbox" ';
|
||||
html += 'data-page-id="' + page.id + '" ';
|
||||
html += 'data-page-key="' + page.page_key + '" ';
|
||||
html += (isChecked ? 'checked ' : '');
|
||||
html += (isAlwaysAccessible ? 'disabled ' : '');
|
||||
html += '>';
|
||||
html += '<span class="page-name">' + page.page_name + '</span>';
|
||||
html += '</label></div>';
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
pageAccessList.innerHTML = html;
|
||||
}
|
||||
|
||||
// 페이지 권한 저장
|
||||
async function savePageAccess(userId, containerId = null) {
|
||||
@@ -701,61 +638,6 @@ async function savePageAccess(userId, containerId = null) {
|
||||
}
|
||||
}
|
||||
|
||||
// editUser 함수를 수정하여 페이지 권한 로드 추가
|
||||
const originalEditUser = window.editUser;
|
||||
window.editUser = async function(userId) {
|
||||
// 페이지 목록이 없으면 로드
|
||||
if (allPages.length === 0) {
|
||||
await loadAllPages();
|
||||
}
|
||||
|
||||
// 원래 editUser 함수 실행
|
||||
if (originalEditUser) {
|
||||
originalEditUser(userId);
|
||||
}
|
||||
|
||||
// 사용자의 페이지 권한 로드
|
||||
await loadUserPageAccess(userId);
|
||||
|
||||
// 사용자 정보 가져오기
|
||||
const user = users.find(u => u.user_id === userId);
|
||||
if (!user) return;
|
||||
|
||||
// 페이지 권한 체크박스 렌더링
|
||||
const roleToValueMap = {
|
||||
'Admin': 'admin',
|
||||
'System Admin': 'admin',
|
||||
'User': 'user',
|
||||
'Guest': 'user'
|
||||
};
|
||||
const userRole = roleToValueMap[user.role] || 'user';
|
||||
renderPageAccessList(userRole);
|
||||
};
|
||||
|
||||
// saveUser 함수를 수정하여 페이지 권한 저장 추가
|
||||
const originalSaveUser = window.saveUser;
|
||||
window.saveUser = async function() {
|
||||
try {
|
||||
// 원래 saveUser 함수 실행
|
||||
if (originalSaveUser) {
|
||||
await originalSaveUser();
|
||||
}
|
||||
|
||||
// 사용자 편집 시에만 페이지 권한 저장
|
||||
if (currentEditingUser && currentEditingUser.user_id) {
|
||||
const userRole = document.getElementById('userRole')?.value;
|
||||
|
||||
// Admin이 아닌 경우에만 페이지 권한 저장
|
||||
if (userRole !== 'admin') {
|
||||
await savePageAccess(currentEditingUser.user_id);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 저장 오류:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -806,66 +688,106 @@ function closePageAccessModal() {
|
||||
currentPageAccessUser = null;
|
||||
}
|
||||
|
||||
// 페이지 권한 체크박스 렌더링 (모달용)
|
||||
// 페이지 권한 체크박스 렌더링 (모달용) - 폴더 구조 형태
|
||||
function renderPageAccessModalList() {
|
||||
const pageAccessList = document.getElementById('pageAccessModalList');
|
||||
if (!pageAccessList) return;
|
||||
|
||||
// 카테고리별로 페이지 그룹화
|
||||
const pagesByCategory = {
|
||||
'work': [],
|
||||
'admin': [],
|
||||
'common': [],
|
||||
'profile': []
|
||||
// 폴더 구조 정의 (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 category = page.category || 'common';
|
||||
if (pagesByCategory[category]) {
|
||||
pagesByCategory[category].push(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);
|
||||
}
|
||||
});
|
||||
|
||||
const categoryNames = {
|
||||
'common': '공통',
|
||||
'work': '작업',
|
||||
'admin': '관리',
|
||||
'profile': '프로필'
|
||||
};
|
||||
// HTML 생성 - 폴더 트리 형태
|
||||
let html = '<div class="folder-tree">';
|
||||
|
||||
// HTML 생성
|
||||
let html = '';
|
||||
Object.keys(folderStructure).forEach(folderKey => {
|
||||
const folder = folderStructure[folderKey];
|
||||
if (folder.pages.length === 0) return;
|
||||
|
||||
Object.keys(pagesByCategory).forEach(category => {
|
||||
const pages = pagesByCategory[category];
|
||||
if (pages.length === 0) return;
|
||||
const folderId = 'folder-' + folderKey;
|
||||
|
||||
const catName = categoryNames[category] || category;
|
||||
html += '<div class="page-access-category">';
|
||||
html += '<div class="page-access-category-title">' + catName + '</div>';
|
||||
html += '<div class="folder-group">';
|
||||
html += '<div class="folder-header" onclick="toggleFolder(\'' + folderId + '\')">';
|
||||
html += '<span class="folder-icon">' + folder.icon + '</span>';
|
||||
html += '<span class="folder-name">' + folder.name + '</span>';
|
||||
html += '<span class="folder-count">(' + folder.pages.length + ')</span>';
|
||||
html += '<span class="folder-toggle" id="toggle-' + folderId + '">▼</span>';
|
||||
html += '</div>';
|
||||
|
||||
pages.forEach(page => {
|
||||
html += '<div class="folder-content" id="' + folderId + '">';
|
||||
|
||||
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;
|
||||
|
||||
html += '<div class="page-access-item"><label>';
|
||||
// 파일명만 추출 (page_key에서)
|
||||
const fileName = page.page_key.split('.').pop() || page.page_key;
|
||||
|
||||
html += '<div class="page-item">';
|
||||
html += '<label class="page-label">';
|
||||
html += '<input type="checkbox" class="page-access-checkbox" ';
|
||||
html += 'data-page-id="' + page.id + '" ';
|
||||
html += 'data-page-key="' + page.page_key + '" ';
|
||||
html += (isChecked ? 'checked ' : '');
|
||||
html += (isAlwaysAccessible ? 'disabled ' : '');
|
||||
html += '>';
|
||||
html += '<span class="file-icon">📄</span>';
|
||||
html += '<span class="page-name">' + page.page_name + '</span>';
|
||||
html += '</label></div>';
|
||||
if (isAlwaysAccessible) {
|
||||
html += '<span class="always-access-badge">기본</span>';
|
||||
}
|
||||
html += '</label>';
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
html += '</div>'; // folder-content
|
||||
html += '</div>'; // folder-group
|
||||
});
|
||||
|
||||
html += '</div>'; // 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) {
|
||||
@@ -899,3 +821,256 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
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 = `
|
||||
<span class="worker-badge">
|
||||
<span class="worker-name">👤 ${user.worker_name}</span>
|
||||
${user.department_name ? `<span class="dept-name">(${user.department_name})</span>` : ''}
|
||||
</span>
|
||||
`;
|
||||
} else {
|
||||
linkedWorkerInfo.innerHTML = '<span class="no-worker">연결된 작업자 없음</span>';
|
||||
}
|
||||
}
|
||||
|
||||
// 작업자 선택 모달 열기
|
||||
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 = '<div class="empty-message">등록된 부서가 없습니다</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = departments.map(dept => `
|
||||
<div class="department-item" data-dept-id="${dept.department_id}" onclick="selectDepartment(${dept.department_id})">
|
||||
<span class="dept-icon">📁</span>
|
||||
<span class="dept-name">${dept.department_name}</span>
|
||||
<span class="dept-count">${dept.worker_count || 0}명</span>
|
||||
</div>
|
||||
`).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 = '<div class="empty-message">이 부서에 작업자가 없습니다</div>';
|
||||
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 `
|
||||
<div class="worker-select-item ${isSelected ? 'selected' : ''} ${isLinkedToOther ? 'disabled' : ''}"
|
||||
onclick="${isLinkedToOther ? '' : `selectWorker(${worker.worker_id}, '${worker.worker_name}')`}">
|
||||
<div class="worker-avatar">${worker.worker_name.charAt(0)}</div>
|
||||
<div class="worker-info">
|
||||
<div class="worker-name">${worker.worker_name}</div>
|
||||
<div class="worker-role">${getJobTypeName(worker.job_type)}</div>
|
||||
</div>
|
||||
${isLinkedToOther ? `<span class="already-linked">${linkedUser?.username} 연결됨</span>` : ''}
|
||||
<div class="select-indicator">${isSelected ? '✓' : ''}</div>
|
||||
</div>
|
||||
`;
|
||||
}).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;
|
||||
|
||||
Reference in New Issue
Block a user