## 수정 내용 ### 1. JavaScript 모듈 로딩 문제 수정 - ES6 import 사용 파일에 type="module" 속성 추가 - api-config.js, load-navbar.js, worker-management.js, project-management.js ### 2. DB 스키마 불일치 해결 - workers 테이블 실제 구조에 맞게 코드 수정 - 존재하지 않는 컬럼 제거: phone_number, email, hire_date, department, notes - 실제 컬럼 사용: join_date, salary, annual_leave ### 3. 백엔드 수정 - workerModel.js: create, update 함수를 실제 테이블 구조에 맞게 수정 - workerController.js: 상세 로깅 추가 ### 4. 프론트엔드 수정 - worker-management.js: 데이터 전송 구조 수정 - api-config.js: 에러 로깅 개선 - HTML 파일: 스크립트 type="module" 추가 및 버전 업데이트 ### 5. 개발 문서 - 개발로그 추가: 2026-01-19_작업자관리_스키마_동기화.md ## 영향 범위 - 작업자 관리 페이지: 상태 변경 기능 정상화 - 프로젝트 관리 페이지: 모듈 로딩 오류 수정 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
747 lines
24 KiB
JavaScript
747 lines
24 KiB
JavaScript
// 작업자 관리 페이지 JavaScript
|
|
|
|
// 전역 변수
|
|
let allWorkers = [];
|
|
let filteredWorkers = [];
|
|
let currentEditingWorker = null;
|
|
let currentStatusFilter = 'all'; // 'all', 'active', 'inactive'
|
|
|
|
// 페이지 초기화
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log('👥 작업자 관리 페이지 초기화 시작');
|
|
|
|
initializePage();
|
|
loadWorkers();
|
|
});
|
|
|
|
// 페이지 초기화
|
|
function initializePage() {
|
|
// 시간 업데이트 시작
|
|
updateCurrentTime();
|
|
setInterval(updateCurrentTime, 1000);
|
|
|
|
// 사용자 정보 업데이트
|
|
updateUserInfo();
|
|
|
|
// 프로필 메뉴 토글
|
|
setupProfileMenu();
|
|
|
|
// 로그아웃 버튼
|
|
setupLogoutButton();
|
|
|
|
// 검색 입력 이벤트
|
|
setupSearchInput();
|
|
}
|
|
|
|
// 현재 시간 업데이트
|
|
function updateCurrentTime() {
|
|
const now = new Date();
|
|
const timeString = now.toLocaleTimeString('ko-KR', {
|
|
hour12: false,
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit'
|
|
});
|
|
|
|
const timeElement = document.getElementById('timeValue');
|
|
if (timeElement) {
|
|
timeElement.textContent = timeString;
|
|
}
|
|
}
|
|
|
|
// 사용자 정보 업데이트
|
|
function updateUserInfo() {
|
|
let userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}');
|
|
let authUser = JSON.parse(localStorage.getItem('user') || '{}');
|
|
|
|
const finalUserInfo = {
|
|
worker_name: userInfo.worker_name || authUser.username || authUser.worker_name,
|
|
job_type: userInfo.job_type || authUser.role || authUser.job_type,
|
|
username: authUser.username || userInfo.username
|
|
};
|
|
|
|
const userNameElement = document.getElementById('userName');
|
|
const userRoleElement = document.getElementById('userRole');
|
|
const userInitialElement = document.getElementById('userInitial');
|
|
|
|
if (userNameElement) {
|
|
userNameElement.textContent = finalUserInfo.worker_name || '사용자';
|
|
}
|
|
|
|
if (userRoleElement) {
|
|
const roleMap = {
|
|
'leader': '그룹장',
|
|
'worker': '작업자',
|
|
'admin': '관리자',
|
|
'system': '시스템 관리자'
|
|
};
|
|
userRoleElement.textContent = roleMap[finalUserInfo.job_type] || finalUserInfo.job_type || '작업자';
|
|
}
|
|
|
|
if (userInitialElement) {
|
|
const name = finalUserInfo.worker_name || '사용자';
|
|
userInitialElement.textContent = name.charAt(0);
|
|
}
|
|
}
|
|
|
|
// 프로필 메뉴 설정
|
|
function setupProfileMenu() {
|
|
const userProfile = document.getElementById('userProfile');
|
|
const profileMenu = document.getElementById('profileMenu');
|
|
|
|
if (userProfile && profileMenu) {
|
|
userProfile.addEventListener('click', function(e) {
|
|
e.stopPropagation();
|
|
const isVisible = profileMenu.style.display === 'block';
|
|
profileMenu.style.display = isVisible ? 'none' : 'block';
|
|
});
|
|
|
|
// 외부 클릭 시 메뉴 닫기
|
|
document.addEventListener('click', function() {
|
|
profileMenu.style.display = 'none';
|
|
});
|
|
}
|
|
}
|
|
|
|
// 로그아웃 버튼 설정
|
|
function setupLogoutButton() {
|
|
const logoutBtn = document.getElementById('logoutBtn');
|
|
if (logoutBtn) {
|
|
logoutBtn.addEventListener('click', function() {
|
|
if (confirm('로그아웃 하시겠습니까?')) {
|
|
localStorage.removeItem('token');
|
|
localStorage.removeItem('user');
|
|
localStorage.removeItem('userInfo');
|
|
window.location.href = '/index.html';
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// 검색 입력 설정
|
|
function setupSearchInput() {
|
|
const searchInput = document.getElementById('searchInput');
|
|
if (searchInput) {
|
|
searchInput.addEventListener('input', function() {
|
|
searchWorkers();
|
|
});
|
|
|
|
searchInput.addEventListener('keypress', function(e) {
|
|
if (e.key === 'Enter') {
|
|
searchWorkers();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// 작업자 목록 로드
|
|
async function loadWorkers() {
|
|
try {
|
|
console.log('📊 작업자 목록 로딩 시작');
|
|
|
|
const response = await apiCall('/workers?limit=1000', 'GET'); // 모든 작업자 조회
|
|
|
|
console.log('📊 API 응답 구조:', response);
|
|
|
|
// API 응답이 { success: true, data: [...] } 형태인 경우 처리
|
|
let workerData = [];
|
|
if (response && response.success && Array.isArray(response.data)) {
|
|
workerData = response.data;
|
|
} else if (Array.isArray(response)) {
|
|
workerData = response;
|
|
} else {
|
|
console.warn('작업자 데이터가 배열이 아닙니다:', response);
|
|
workerData = [];
|
|
}
|
|
|
|
allWorkers = workerData;
|
|
|
|
console.log(`✅ 작업자 ${allWorkers.length}명 로드 완료`);
|
|
|
|
// 초기 필터 적용
|
|
applyAllFilters();
|
|
updateStatCardActiveState();
|
|
|
|
} catch (error) {
|
|
console.error('작업자 로딩 오류:', error);
|
|
showToast('작업자 목록을 불러오는데 실패했습니다.', 'error');
|
|
allWorkers = [];
|
|
filteredWorkers = [];
|
|
renderWorkers();
|
|
}
|
|
}
|
|
|
|
// 작업자 목록 렌더링
|
|
function renderWorkers() {
|
|
const workersGrid = document.getElementById('workersGrid');
|
|
const emptyState = document.getElementById('emptyState');
|
|
|
|
if (!workersGrid || !emptyState) return;
|
|
|
|
if (filteredWorkers.length === 0) {
|
|
workersGrid.style.display = 'none';
|
|
emptyState.style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
workersGrid.style.display = 'grid';
|
|
emptyState.style.display = 'none';
|
|
|
|
const workersHtml = filteredWorkers.map(worker => {
|
|
// 작업자 상태 및 직책 아이콘
|
|
const jobTypeMap = {
|
|
'worker': { icon: '👷', text: '작업자', color: '#6b7280' },
|
|
'leader': { icon: '👨💼', text: '그룹장', color: '#3b82f6' },
|
|
'admin': { icon: '👨💻', text: '관리자', color: '#8b5cf6' }
|
|
};
|
|
|
|
const jobType = jobTypeMap[worker.job_type] || jobTypeMap['worker'];
|
|
const isInactive = worker.status === 'inactive' || worker.is_active === 0 || worker.is_active === false;
|
|
|
|
console.log('🎨 카드 렌더링:', {
|
|
worker_id: worker.worker_id,
|
|
worker_name: worker.worker_name,
|
|
status: worker.status,
|
|
is_active: worker.is_active,
|
|
isInactive: isInactive
|
|
});
|
|
|
|
return `
|
|
<div class="project-card worker-card ${isInactive ? 'inactive' : ''}" onclick="editWorker(${worker.worker_id})">
|
|
${isInactive ? '<div class="inactive-overlay"><span class="inactive-badge">🚫 비활성화됨</span></div>' : ''}
|
|
<div class="project-header">
|
|
<div class="project-info">
|
|
<div class="worker-avatar">
|
|
<span class="avatar-initial">${worker.worker_name.charAt(0)}</span>
|
|
</div>
|
|
<h3 class="project-name">
|
|
${worker.worker_name}
|
|
${isInactive ? '<span class="inactive-label">(비활성)</span>' : ''}
|
|
</h3>
|
|
<div class="project-meta">
|
|
<span style="color: ${jobType.color}; font-weight: 500;">${jobType.icon} ${jobType.text}</span>
|
|
${worker.phone_number ? `<span>📞 ${worker.phone_number}</span>` : ''}
|
|
${worker.email ? `<span>📧 ${worker.email}</span>` : ''}
|
|
${worker.department ? `<span>🏢 ${worker.department}</span>` : ''}
|
|
${worker.hire_date ? `<span>📅 입사: ${formatDate(worker.hire_date)}</span>` : ''}
|
|
${isInactive ? '<span class="inactive-notice">⚠️ 작업보고서에서 숨김</span>' : ''}
|
|
</div>
|
|
</div>
|
|
<div class="project-actions">
|
|
<button class="btn-toggle ${isInactive ? 'btn-activate' : 'btn-deactivate'}"
|
|
onclick="event.stopPropagation(); toggleWorkerStatus(${worker.worker_id})"
|
|
title="${isInactive ? '활성화' : '비활성화'}">
|
|
${isInactive ? '✅' : '❌'}
|
|
</button>
|
|
<button class="btn-edit" onclick="event.stopPropagation(); editWorker(${worker.worker_id})" title="수정">
|
|
✏️
|
|
</button>
|
|
<button class="btn-delete" onclick="event.stopPropagation(); confirmDeleteWorker(${worker.worker_id})" title="삭제">
|
|
🗑️
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
workersGrid.innerHTML = workersHtml;
|
|
}
|
|
|
|
// 작업자 통계 업데이트
|
|
function updateWorkerStats() {
|
|
const activeWorkers = filteredWorkers.filter(w => w.status !== 'inactive' && w.is_active !== 0 && w.is_active !== false);
|
|
const inactiveWorkers = filteredWorkers.filter(w => w.status === 'inactive' || w.is_active === 0 || w.is_active === false);
|
|
|
|
const activeWorkersElement = document.getElementById('activeWorkers');
|
|
const inactiveWorkersElement = document.getElementById('inactiveWorkers');
|
|
const totalWorkersElement = document.getElementById('totalWorkers');
|
|
|
|
if (activeWorkersElement) {
|
|
activeWorkersElement.textContent = activeWorkers.length;
|
|
}
|
|
|
|
if (inactiveWorkersElement) {
|
|
inactiveWorkersElement.textContent = inactiveWorkers.length;
|
|
}
|
|
|
|
if (totalWorkersElement) {
|
|
totalWorkersElement.textContent = filteredWorkers.length;
|
|
}
|
|
|
|
console.log('📊 작업자 통계:', {
|
|
전체: filteredWorkers.length,
|
|
활성: activeWorkers.length,
|
|
비활성: inactiveWorkers.length
|
|
});
|
|
}
|
|
|
|
// 날짜 포맷팅
|
|
function formatDate(dateString) {
|
|
if (!dateString) return '';
|
|
|
|
const date = new Date(dateString);
|
|
return date.toLocaleDateString('ko-KR', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit'
|
|
});
|
|
}
|
|
|
|
// 상태별 필터링
|
|
function filterByStatus(status) {
|
|
currentStatusFilter = status;
|
|
|
|
// 통계 카드 활성화 상태 업데이트
|
|
updateStatCardActiveState();
|
|
|
|
// 필터링 적용
|
|
applyAllFilters();
|
|
|
|
console.log(`🔍 상태 필터 적용: ${status}`);
|
|
}
|
|
|
|
// 통계 카드 활성화 상태 업데이트
|
|
function updateStatCardActiveState() {
|
|
// 모든 통계 카드에서 active 클래스 제거
|
|
document.querySelectorAll('.stat-item').forEach(item => {
|
|
item.classList.remove('active');
|
|
});
|
|
|
|
// 현재 선택된 필터에 active 클래스 추가
|
|
const activeCard = document.querySelector(`.${currentStatusFilter === 'active' ? 'active-stat' :
|
|
currentStatusFilter === 'inactive' ? 'inactive-stat' : 'total-stat'}`);
|
|
if (activeCard) {
|
|
activeCard.classList.add('active');
|
|
}
|
|
}
|
|
|
|
// 모든 필터 적용 (검색 + 상태 + 직책)
|
|
function applyAllFilters() {
|
|
const searchInput = document.getElementById('searchInput');
|
|
const jobTypeFilter = document.getElementById('jobTypeFilter');
|
|
const statusFilter = document.getElementById('statusFilter');
|
|
|
|
const searchTerm = searchInput ? searchInput.value.toLowerCase().trim() : '';
|
|
const jobTypeValue = jobTypeFilter ? jobTypeFilter.value : '';
|
|
const statusValue = statusFilter ? statusFilter.value : '';
|
|
|
|
// 1단계: 상태 필터링 (통계 카드 클릭)
|
|
let statusFiltered = [...allWorkers];
|
|
if (currentStatusFilter === 'active') {
|
|
statusFiltered = allWorkers.filter(w => w.status !== 'inactive' && w.is_active !== 0 && w.is_active !== false);
|
|
} else if (currentStatusFilter === 'inactive') {
|
|
statusFiltered = allWorkers.filter(w => w.status === 'inactive' || w.is_active === 0 || w.is_active === false);
|
|
}
|
|
|
|
// 2단계: 드롭다운 상태 필터링
|
|
if (statusValue) {
|
|
if (statusValue === 'active') {
|
|
statusFiltered = statusFiltered.filter(w => w.status !== 'inactive' && w.is_active !== 0 && w.is_active !== false);
|
|
} else if (statusValue === 'inactive') {
|
|
statusFiltered = statusFiltered.filter(w => w.status === 'inactive' || w.is_active === 0 || w.is_active === false);
|
|
}
|
|
}
|
|
|
|
// 3단계: 직책 필터링
|
|
let jobTypeFiltered = statusFiltered;
|
|
if (jobTypeValue) {
|
|
jobTypeFiltered = statusFiltered.filter(w => w.job_type === jobTypeValue);
|
|
}
|
|
|
|
// 4단계: 검색 필터링
|
|
if (!searchTerm) {
|
|
filteredWorkers = jobTypeFiltered;
|
|
} else {
|
|
filteredWorkers = jobTypeFiltered.filter(worker =>
|
|
worker.worker_name.toLowerCase().includes(searchTerm) ||
|
|
(worker.phone_number && worker.phone_number.toLowerCase().includes(searchTerm)) ||
|
|
(worker.email && worker.email.toLowerCase().includes(searchTerm)) ||
|
|
(worker.department && worker.department.toLowerCase().includes(searchTerm))
|
|
);
|
|
}
|
|
|
|
renderWorkers();
|
|
updateWorkerStats();
|
|
}
|
|
|
|
// 작업자 검색
|
|
function searchWorkers() {
|
|
applyAllFilters();
|
|
}
|
|
|
|
// 작업자 필터링
|
|
function filterWorkers() {
|
|
applyAllFilters();
|
|
}
|
|
|
|
// 작업자 정렬
|
|
function sortWorkers() {
|
|
const sortBy = document.getElementById('sortBy');
|
|
const sortField = sortBy ? sortBy.value : 'created_at';
|
|
|
|
filteredWorkers.sort((a, b) => {
|
|
switch (sortField) {
|
|
case 'worker_name':
|
|
return a.worker_name.localeCompare(b.worker_name);
|
|
case 'job_type':
|
|
const jobOrder = { 'admin': 0, 'leader': 1, 'worker': 2 };
|
|
return (jobOrder[a.job_type] || 3) - (jobOrder[b.job_type] || 3);
|
|
case 'created_at':
|
|
default:
|
|
return new Date(b.created_at || 0) - new Date(a.created_at || 0);
|
|
}
|
|
});
|
|
|
|
renderWorkers();
|
|
}
|
|
|
|
// 작업자 목록 새로고침
|
|
async function refreshWorkerList() {
|
|
const refreshBtn = document.querySelector('.btn-secondary');
|
|
if (refreshBtn) {
|
|
const originalText = refreshBtn.innerHTML;
|
|
refreshBtn.innerHTML = '<span class="btn-icon">⏳</span>새로고침 중...';
|
|
refreshBtn.disabled = true;
|
|
|
|
await loadWorkers();
|
|
|
|
refreshBtn.innerHTML = originalText;
|
|
refreshBtn.disabled = false;
|
|
} else {
|
|
await loadWorkers();
|
|
}
|
|
|
|
showToast('작업자 목록이 새로고침되었습니다.', 'success');
|
|
}
|
|
|
|
// 작업자 모달 열기
|
|
function openWorkerModal(worker = null) {
|
|
const modal = document.getElementById('workerModal');
|
|
const modalTitle = document.getElementById('modalTitle');
|
|
const deleteBtn = document.getElementById('deleteWorkerBtn');
|
|
|
|
if (!modal) return;
|
|
|
|
currentEditingWorker = worker;
|
|
|
|
if (worker) {
|
|
// 수정 모드
|
|
modalTitle.textContent = '작업자 정보 수정';
|
|
deleteBtn.style.display = 'inline-flex';
|
|
|
|
// 폼에 데이터 채우기 (실제 테이블 구조에 맞게)
|
|
document.getElementById('workerId').value = worker.worker_id;
|
|
document.getElementById('workerName').value = worker.worker_name || '';
|
|
document.getElementById('jobType').value = worker.job_type || '';
|
|
|
|
// 옵션 필드들 - 존재하는 경우에만 설정
|
|
const joinDateElem = document.getElementById('joinDate');
|
|
if (joinDateElem) joinDateElem.value = worker.join_date || '';
|
|
|
|
const salaryElem = document.getElementById('salary');
|
|
if (salaryElem) salaryElem.value = worker.salary || '';
|
|
|
|
const annualLeaveElem = document.getElementById('annualLeave');
|
|
if (annualLeaveElem) annualLeaveElem.value = worker.annual_leave || '';
|
|
|
|
// is_active 값 처리 (DB에서 0/1로 오는 경우 대비)
|
|
const isActiveValue = worker.status !== 'inactive' && worker.is_active !== 0 && worker.is_active !== false;
|
|
document.getElementById('isActive').checked = isActiveValue;
|
|
|
|
console.log('🔧 작업자 로드:', {
|
|
worker_id: worker.worker_id,
|
|
worker_name: worker.worker_name,
|
|
job_type: worker.job_type,
|
|
status: worker.status,
|
|
is_active_raw: worker.is_active,
|
|
is_active_processed: isActiveValue
|
|
});
|
|
} else {
|
|
// 신규 등록 모드
|
|
modalTitle.textContent = '새 작업자 등록';
|
|
deleteBtn.style.display = 'none';
|
|
|
|
// 폼 초기화
|
|
document.getElementById('workerForm').reset();
|
|
document.getElementById('workerId').value = '';
|
|
document.getElementById('isActive').checked = true;
|
|
}
|
|
|
|
modal.style.display = 'flex';
|
|
document.body.style.overflow = 'hidden';
|
|
|
|
// 첫 번째 입력 필드에 포커스
|
|
setTimeout(() => {
|
|
const firstInput = document.getElementById('workerName');
|
|
if (firstInput) firstInput.focus();
|
|
}, 100);
|
|
}
|
|
|
|
// 작업자 모달 닫기
|
|
function closeWorkerModal() {
|
|
const modal = document.getElementById('workerModal');
|
|
if (modal) {
|
|
modal.style.display = 'none';
|
|
document.body.style.overflow = '';
|
|
currentEditingWorker = null;
|
|
}
|
|
}
|
|
|
|
// 작업자 편집
|
|
function editWorker(workerId) {
|
|
const worker = allWorkers.find(w => w.worker_id === workerId);
|
|
if (worker) {
|
|
openWorkerModal(worker);
|
|
} else {
|
|
showToast('작업자를 찾을 수 없습니다.', 'error');
|
|
}
|
|
}
|
|
|
|
// 작업자 저장
|
|
async function saveWorker() {
|
|
try {
|
|
const form = document.getElementById('workerForm');
|
|
|
|
// 실제 테이블 구조에 맞는 필드만 사용
|
|
const workerData = {
|
|
worker_name: document.getElementById('workerName').value.trim(),
|
|
job_type: document.getElementById('jobType').value || null,
|
|
join_date: document.getElementById('joinDate')?.value || null,
|
|
salary: document.getElementById('salary')?.value || null,
|
|
annual_leave: document.getElementById('annualLeave')?.value || null,
|
|
status: document.getElementById('isActive').checked ? 'active' : 'inactive'
|
|
};
|
|
|
|
console.log('💾 저장할 작업자 데이터:', JSON.stringify(workerData, null, 2));
|
|
|
|
// 필수 필드 검증
|
|
if (!workerData.worker_name) {
|
|
showToast('작업자명은 필수 입력 항목입니다.', 'error');
|
|
return;
|
|
}
|
|
|
|
const workerId = document.getElementById('workerId').value;
|
|
let response;
|
|
|
|
if (workerId) {
|
|
// 수정
|
|
response = await apiCall(`/workers/${workerId}`, 'PUT', workerData);
|
|
} else {
|
|
// 신규 등록
|
|
response = await apiCall('/workers', 'POST', workerData);
|
|
}
|
|
|
|
if (response && (response.success || response.worker_id)) {
|
|
const action = workerId ? '수정' : '등록';
|
|
showToast(`작업자가 성공적으로 ${action}되었습니다.`, 'success');
|
|
|
|
closeWorkerModal();
|
|
await loadWorkers();
|
|
} else {
|
|
throw new Error(response?.message || '저장에 실패했습니다.');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('작업자 저장 오류:', error);
|
|
showToast(error.message || '작업자 저장 중 오류가 발생했습니다.', 'error');
|
|
}
|
|
}
|
|
|
|
// 작업자 상태 토글 (활성화/비활성화)
|
|
async function toggleWorkerStatus(workerId) {
|
|
const worker = allWorkers.find(w => w.worker_id === workerId);
|
|
if (!worker) {
|
|
showToast('작업자를 찾을 수 없습니다.', 'error');
|
|
return;
|
|
}
|
|
|
|
const isCurrentlyInactive = worker.status === 'inactive' || worker.is_active === 0 || worker.is_active === false;
|
|
const newStatus = isCurrentlyInactive ? 'active' : 'inactive';
|
|
const actionText = isCurrentlyInactive ? '활성화' : '비활성화';
|
|
|
|
if (!confirm(`"${worker.worker_name}" 작업자를 ${actionText}하시겠습니까?`)) {
|
|
return;
|
|
}
|
|
|
|
console.log(`🔄 작업자 상태 변경: ${worker.worker_name} → ${newStatus}`);
|
|
|
|
try {
|
|
// 실제 테이블 구조에 맞는 필드만 전송
|
|
const updateData = {
|
|
worker_name: worker.worker_name,
|
|
job_type: worker.job_type || null,
|
|
status: newStatus,
|
|
join_date: worker.join_date || null,
|
|
salary: worker.salary || null,
|
|
annual_leave: worker.annual_leave || null
|
|
};
|
|
|
|
console.log('📤 전송 데이터:', JSON.stringify(updateData, null, 2));
|
|
|
|
const response = await window.apiCall(`/workers/${workerId}`, 'PUT', updateData);
|
|
|
|
if (response) {
|
|
// 로컬 데이터 업데이트
|
|
const workerIndex = allWorkers.findIndex(w => w.worker_id === workerId);
|
|
if (workerIndex !== -1) {
|
|
allWorkers[workerIndex].status = newStatus;
|
|
allWorkers[workerIndex].is_active = newStatus === 'active' ? 1 : 0;
|
|
}
|
|
|
|
// UI 새로고침
|
|
applyAllFilters();
|
|
updateWorkerStats();
|
|
|
|
showToast(`${worker.worker_name} 작업자가 ${actionText}되었습니다.`, 'success');
|
|
console.log(`✅ 작업자 상태 변경 완료: ${worker.worker_name} → ${newStatus}`);
|
|
}
|
|
} catch (error) {
|
|
console.error('작업자 상태 변경 오류:', error);
|
|
showToast(`작업자 상태 변경에 실패했습니다: ${error.message}`, 'error');
|
|
}
|
|
}
|
|
|
|
// 작업자 삭제 확인
|
|
function confirmDeleteWorker(workerId) {
|
|
console.log('🔍 삭제 요청된 작업자 ID:', workerId);
|
|
|
|
const worker = allWorkers.find(w => w.worker_id === workerId);
|
|
if (!worker) {
|
|
console.error('❌ 작업자를 찾을 수 없음:', workerId);
|
|
showToast('작업자를 찾을 수 없습니다.', 'error');
|
|
return;
|
|
}
|
|
|
|
console.log('👤 삭제 대상 작업자:', {
|
|
worker_id: worker.worker_id,
|
|
worker_name: worker.worker_name,
|
|
job_type: worker.job_type
|
|
});
|
|
|
|
// 더 명확한 확인 메시지
|
|
const confirmMessage = `⚠️ 작업자 삭제 확인 ⚠️
|
|
|
|
삭제할 작업자: ${worker.worker_name} (ID: ${worker.worker_id})
|
|
직책: ${worker.job_type || '미지정'}
|
|
|
|
정말로 이 작업자를 삭제하시겠습니까?
|
|
|
|
⚠️ 주의: 삭제된 작업자와 관련된 모든 데이터가 함께 삭제됩니다.
|
|
- 작업 보고서
|
|
- 이슈 보고서
|
|
- 월별 통계
|
|
- 그룹 소속 정보
|
|
|
|
이 작업은 되돌릴 수 없습니다!`;
|
|
|
|
if (confirm(confirmMessage)) {
|
|
console.log('✅ 사용자가 삭제를 확인함');
|
|
deleteWorkerById(workerId);
|
|
} else {
|
|
console.log('❌ 사용자가 삭제를 취소함');
|
|
}
|
|
}
|
|
|
|
// 작업자 삭제 (수정 모드에서)
|
|
function deleteWorker() {
|
|
if (currentEditingWorker) {
|
|
confirmDeleteWorker(currentEditingWorker.worker_id);
|
|
}
|
|
}
|
|
|
|
// 작업자 삭제 실행
|
|
async function deleteWorkerById(workerId) {
|
|
console.log('🗑️ 작업자 삭제 실행 시작:', workerId);
|
|
|
|
try {
|
|
const worker = allWorkers.find(w => w.worker_id === workerId);
|
|
console.log('🔍 삭제 실행 전 작업자 정보:', worker);
|
|
|
|
const response = await window.apiCall(`${window.API}/workers/${workerId}`, 'DELETE');
|
|
console.log('📡 삭제 API 응답:', response);
|
|
|
|
if (response && (response.success || response.message)) {
|
|
console.log('✅ 작업자 삭제 성공');
|
|
showToast(`작업자 "${worker?.worker_name || workerId}"가 성공적으로 삭제되었습니다.`, 'success');
|
|
|
|
closeWorkerModal();
|
|
await loadWorkers();
|
|
} else {
|
|
throw new Error(response?.message || '삭제에 실패했습니다.');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('❌ 작업자 삭제 오류:', error);
|
|
showToast(error.message || '작업자 삭제 중 오류가 발생했습니다.', 'error');
|
|
}
|
|
}
|
|
|
|
// 토스트 메시지 표시
|
|
function showToast(message, type = 'info') {
|
|
// 기존 토스트 제거
|
|
const existingToast = document.querySelector('.toast');
|
|
if (existingToast) {
|
|
existingToast.remove();
|
|
}
|
|
|
|
// 새 토스트 생성
|
|
const toast = document.createElement('div');
|
|
toast.className = `toast toast-${type}`;
|
|
toast.textContent = message;
|
|
|
|
// 스타일 적용
|
|
Object.assign(toast.style, {
|
|
position: 'fixed',
|
|
top: '20px',
|
|
right: '20px',
|
|
padding: '12px 24px',
|
|
borderRadius: '8px',
|
|
color: 'white',
|
|
fontWeight: '500',
|
|
zIndex: '1000',
|
|
transform: 'translateX(100%)',
|
|
transition: 'transform 0.3s ease'
|
|
});
|
|
|
|
// 타입별 배경색
|
|
const colors = {
|
|
success: '#10b981',
|
|
error: '#ef4444',
|
|
warning: '#f59e0b',
|
|
info: '#3b82f6'
|
|
};
|
|
toast.style.backgroundColor = colors[type] || colors.info;
|
|
|
|
document.body.appendChild(toast);
|
|
|
|
// 애니메이션
|
|
setTimeout(() => {
|
|
toast.style.transform = 'translateX(0)';
|
|
}, 100);
|
|
|
|
// 자동 제거
|
|
setTimeout(() => {
|
|
toast.style.transform = 'translateX(100%)';
|
|
setTimeout(() => {
|
|
if (toast.parentNode) {
|
|
toast.remove();
|
|
}
|
|
}, 300);
|
|
}, 3000);
|
|
}
|
|
|
|
// 전역 함수로 노출
|
|
window.openWorkerModal = openWorkerModal;
|
|
window.closeWorkerModal = closeWorkerModal;
|
|
window.editWorker = editWorker;
|
|
window.saveWorker = saveWorker;
|
|
window.deleteWorker = deleteWorker;
|
|
window.confirmDeleteWorker = confirmDeleteWorker;
|
|
window.searchWorkers = searchWorkers;
|
|
window.filterWorkers = filterWorkers;
|
|
window.sortWorkers = sortWorkers;
|
|
window.refreshWorkerList = refreshWorkerList;
|
|
window.filterByStatus = filterByStatus;
|
|
window.toggleWorkerStatus = toggleWorkerStatus;
|