// work-review.js - 통합 API 설정 적용 버전 // ================================================================= // 🌐 통합 API 설정 import // ================================================================= import { API, getAuthHeaders, apiCall } from '/js/api-config.js'; // 전역 변수 let currentDate = new Date(); let selectedDate = null; let selectedDateData = null; let basicData = { workTypes: [], workStatusTypes: [], errorTypes: [], projects: [] }; // 현재 사용자 정보 가져오기 function getCurrentUser() { try { const token = localStorage.getItem('token'); if (!token) return null; const payloadBase64 = token.split('.')[1]; if (payloadBase64) { const payload = JSON.parse(atob(payloadBase64)); return payload; } } catch (error) { console.log('토큰에서 사용자 정보 추출 실패:', error); } return null; } // 메시지 표시 function showMessage(message, type = 'info') { const container = document.getElementById('message-container'); container.innerHTML = `
${message}
`; if (type !== 'loading') { setTimeout(() => { container.innerHTML = ''; }, 5000); } } // 날짜 포맷팅 function formatDate(date) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } // 월 표시 업데이트 function updateMonthDisplay() { const monthElement = document.getElementById('currentMonth'); const year = currentDate.getFullYear(); const month = currentDate.getMonth() + 1; monthElement.textContent = `${year}년 ${month}월`; } // 근무 유형 분류 function classifyWorkType(totalHours) { if (totalHours === 0) return { type: 'vacation', label: '휴무' }; if (totalHours === 2) return { type: 'vacation', label: '조퇴' }; if (totalHours === 4) return { type: 'vacation', label: '반차' }; if (totalHours === 6) return { type: 'vacation', label: '반반차' }; if (totalHours === 8) return { type: 'normal-work', label: '정시근무' }; if (totalHours > 8) return { type: 'overtime', label: '잔업' }; return { type: 'vacation', label: '기타' }; } // 캘린더 렌더링 (데이터 로드 없이) function renderCalendar() { const calendar = document.getElementById('calendar'); // 기존 날짜 셀들 제거 (헤더는 유지) const dayHeaders = calendar.querySelectorAll('.day-header'); calendar.innerHTML = ''; dayHeaders.forEach(header => calendar.appendChild(header)); const year = currentDate.getFullYear(); const month = currentDate.getMonth(); // 해당 월의 첫째 날과 마지막 날 const firstDay = new Date(year, month, 1); const lastDay = new Date(year, month + 1, 0); // 첫째 주의 시작 (일요일부터 시작) const startDate = new Date(firstDay); startDate.setDate(startDate.getDate() - firstDay.getDay()); // 마지막 주의 끝 const endDate = new Date(lastDay); endDate.setDate(endDate.getDate() + (6 - lastDay.getDay())); // 오늘 날짜 const today = new Date(); const todayStr = formatDate(today); // 날짜 셀 생성 let currentCalendarDate = new Date(startDate); while (currentCalendarDate <= endDate) { const dateStr = formatDate(currentCalendarDate); const isCurrentMonth = currentCalendarDate.getMonth() === month; const isToday = dateStr === todayStr; const isSelected = selectedDate === dateStr; const dayCell = document.createElement('div'); dayCell.className = 'day-cell'; if (!isCurrentMonth) { dayCell.classList.add('other-month'); } if (isToday) { dayCell.classList.add('today'); } if (isSelected) { dayCell.classList.add('selected'); } // 날짜 번호 const dayNumber = document.createElement('div'); dayNumber.className = 'day-number'; dayNumber.textContent = currentCalendarDate.getDate(); dayCell.appendChild(dayNumber); // 클릭 이벤트 - 현재 월의 날짜만 클릭 가능 if (isCurrentMonth) { dayCell.style.cursor = 'pointer'; dayCell.addEventListener('click', () => { selectedDate = dateStr; loadDayData(dateStr); renderCalendar(); // 선택 상태 업데이트를 위해 재렌더링 }); } calendar.appendChild(dayCell); currentCalendarDate.setDate(currentCalendarDate.getDate() + 1); } } // 특정 날짜 데이터 로드 (통합 API 사용) async function loadDayData(dateStr) { try { showMessage(`${dateStr} 데이터를 불러오는 중... (통합 API)`, 'loading'); const data = await apiCall(`${API}/daily-work-reports?date=${dateStr}`); const dataArray = Array.isArray(data) ? data : (data.data || []); // 데이터 처리 processDayData(dateStr, dataArray); renderDayInfo(); document.getElementById('message-container').innerHTML = ''; } catch (error) { console.error('날짜 데이터 로드 실패:', error); showMessage('데이터를 불러올 수 없습니다: ' + error.message, 'error'); selectedDateData = null; renderDayInfo(); } } // 일별 데이터 처리 function processDayData(dateStr, works) { const dayData = { date: dateStr, totalHours: 0, workers: new Set(), reviewed: Math.random() > 0.3, // 임시: 70% 확률로 검토 완료 details: works }; works.forEach(work => { dayData.totalHours += parseFloat(work.work_hours || 0); dayData.workers.add(work.worker_name || work.worker_id); }); const workType = classifyWorkType(dayData.totalHours); dayData.workType = workType.type; dayData.workLabel = workType.label; selectedDateData = dayData; } // 선택된 날짜 정보 렌더링 function renderDayInfo() { const dayInfoContainer = document.getElementById('day-info-container'); if (!selectedDate) { dayInfoContainer.innerHTML = `

📅 날짜를 선택하세요

캘린더에서 날짜를 클릭하면 해당 날짜의 작업 정보를 확인할 수 있습니다.

`; return; } if (!selectedDateData) { dayInfoContainer.innerHTML = `

📅 ${selectedDate}

해당 날짜에 등록된 작업이 없습니다.

`; return; } const data = selectedDateData; // 작업자별 상세 정보 생성 const workerDetailsHtml = Array.from(data.workers).map(worker => { const workerWorks = data.details.filter(w => (w.worker_name || w.worker_id) === worker); const workerHours = workerWorks.reduce((sum, w) => sum + parseFloat(w.work_hours || 0), 0); const workerWorkItemsHtml = workerWorks.map(work => `
${work.project_name || '프로젝트'} - ${work.work_hours}시간
작업: ${work.work_type_name || '미지정'} | 상태: ${work.work_status_name || '미지정'} ${work.error_type_name ? `
에러: ${work.error_type_name}` : ''}
`).join(''); return `
👤 ${worker} - 총 ${workerHours}시간
${workerWorkItemsHtml}
`; }).join(''); dayInfoContainer.innerHTML = `

📅 ${selectedDate} 작업 정보

총 작업시간: ${data.totalHours}시간
근무 유형: ${data.workLabel}
작업자 수: ${data.workers.size}명
검토 상태: ${data.reviewed ? '✅ 검토완료' : '⏳ 미검토'}

👥 작업자별 상세

${workerDetailsHtml}
`; } // 검토 상태 토글 function toggleReview() { if (selectedDateData) { selectedDateData.reviewed = !selectedDateData.reviewed; renderDayInfo(); // TODO: 실제로는 여기서 API 호출해서 DB에 저장해야 함 console.log(`검토 상태 변경: ${selectedDate} - ${selectedDateData.reviewed ? '검토완료' : '미검토'}`); showMessage(`검토 상태가 ${selectedDateData.reviewed ? '완료' : '미완료'}로 변경되었습니다.`, 'success'); } } // 현재 날짜 새로고침 function refreshCurrentDay() { if (selectedDate) { loadDayData(selectedDate); } } // 🛠️ 작업 항목 수정 함수 (통합 API 사용) async function editWorkItem(workId) { try { console.log('수정할 작업 ID:', workId); if (!selectedDateData) { showMessage('작업 데이터를 찾을 수 없습니다.', 'error'); return; } const workData = selectedDateData.details.find(work => work.id == workId); if (!workData) { showMessage('수정할 작업 데이터를 찾을 수 없습니다.', 'error'); return; } // 기본 데이터가 없으면 로드 if (basicData.workTypes.length === 0) { showMessage('기본 데이터를 불러오는 중... (통합 API)', 'loading'); await loadBasicData(); } showEditModal(workData); document.getElementById('message-container').innerHTML = ''; } catch (error) { console.error('작업 정보 조회 오류:', error); showMessage('작업 정보를 불러올 수 없습니다: ' + error.message, 'error'); } } // 🛠️ 수정 모달 표시 (개선된 버전) function showEditModal(workData) { const modalHtml = `

✏️ 작업 수정

`; document.body.insertAdjacentHTML('beforeend', modalHtml); // 업무 상태 변경 이벤트 document.getElementById('editWorkStatus').addEventListener('change', (e) => { const errorTypeGroup = document.getElementById('editErrorTypeGroup'); if (e.target.value === '2') { errorTypeGroup.style.display = 'block'; } else { errorTypeGroup.style.display = 'none'; } }); } // 🛠️ 수정 모달 닫기 function closeEditModal() { const modal = document.getElementById('editModal'); if (modal) { modal.remove(); } } // 🛠️ 수정된 작업 저장 (통합 API 사용) async function saveEditedWork(workId) { try { // 입력값 검증 const projectId = document.getElementById('editProject').value; const workTypeId = document.getElementById('editWorkType').value; const workStatusId = document.getElementById('editWorkStatus').value; const errorTypeId = document.getElementById('editErrorType').value; const workHours = document.getElementById('editWorkHours').value; // 필수값 체크 if (!projectId) { showMessage('프로젝트를 선택해주세요.', 'error'); document.getElementById('editProject').focus(); return; } if (!workTypeId) { showMessage('작업 유형을 선택해주세요.', 'error'); document.getElementById('editWorkType').focus(); return; } if (!workStatusId) { showMessage('업무 상태를 선택해주세요.', 'error'); document.getElementById('editWorkStatus').focus(); return; } if (!workHours || workHours <= 0) { showMessage('작업 시간을 올바르게 입력해주세요.', 'error'); document.getElementById('editWorkHours').focus(); return; } if (workStatusId === '2' && !errorTypeId) { showMessage('에러 상태인 경우 에러 유형을 선택해주세요.', 'error'); document.getElementById('editErrorType').focus(); return; } const updateData = { project_id: parseInt(projectId), work_type_id: parseInt(workTypeId), work_status_id: parseInt(workStatusId), error_type_id: errorTypeId ? parseInt(errorTypeId) : null, work_hours: parseFloat(workHours) }; showMessage('작업을 수정하는 중... (통합 API)', 'loading'); // 저장 버튼 비활성화 const saveBtn = document.querySelector('.btn-success'); const originalText = saveBtn.textContent; saveBtn.textContent = '저장 중...'; saveBtn.disabled = true; const result = await apiCall(`${API}/daily-work-reports/my-entry/${workId}`, { method: 'PUT', body: JSON.stringify(updateData) }); console.log('✅ 수정 성공 (통합 API):', result); showMessage('✅ 작업이 성공적으로 수정되었습니다!', 'success'); closeEditModal(); refreshCurrentDay(); // 현재 날짜 데이터 새로고침 } catch (error) { console.error('❌ 수정 실패:', error); showMessage('수정 중 오류가 발생했습니다: ' + error.message, 'error'); // 버튼 복원 const saveBtn = document.querySelector('.btn-success'); if (saveBtn) { saveBtn.textContent = '💾 저장'; saveBtn.disabled = false; } } } // 🗑️ 작업 항목 삭제 (통합 API 사용) async function deleteWorkItem(workId) { // 확인 대화상자 const confirmDelete = await showConfirmDialog( '작업 삭제 확인', '정말로 이 작업을 삭제하시겠습니까?', '삭제된 작업은 복구할 수 없습니다.' ); if (!confirmDelete) return; try { console.log('삭제할 작업 ID:', workId); showMessage('작업을 삭제하는 중... (통합 API)', 'loading'); const result = await apiCall(`${API}/daily-work-reports/my-entry/${workId}`, { method: 'DELETE' }); console.log('✅ 삭제 성공 (통합 API):', result); showMessage('✅ 작업이 성공적으로 삭제되었습니다!', 'success'); refreshCurrentDay(); // 현재 날짜 데이터 새로고침 } catch (error) { console.error('❌ 삭제 실패:', error); showMessage('삭제 중 오류가 발생했습니다: ' + error.message, 'error'); } } // 🗑️ 작업자의 모든 작업 삭제 (통합 API 사용) async function deleteWorkerAllWorks(date, workerName) { // 확인 대화상자 const confirmDelete = await showConfirmDialog( '전체 작업 삭제 확인', `정말로 ${workerName}님의 ${date} 모든 작업을 삭제하시겠습니까?`, '삭제된 작업들은 복구할 수 없습니다.' ); if (!confirmDelete) return; try { if (!selectedDateData) return; const workerWorks = selectedDateData.details.filter(w => (w.worker_name || w.worker_id) === workerName); if (workerWorks.length === 0) { showMessage('삭제할 작업이 없습니다.', 'error'); return; } showMessage(`${workerName}님의 작업들을 삭제하는 중... (통합 API)`, 'loading'); // 순차적으로 삭제 (병렬 처리하면 서버 부하 발생 가능) let successCount = 0; let failCount = 0; for (const work of workerWorks) { try { await apiCall(`${API}/daily-work-reports/my-entry/${work.id}`, { method: 'DELETE' }); successCount++; } catch (error) { console.error(`작업 ${work.id} 삭제 실패:`, error); failCount++; } } if (failCount === 0) { showMessage(`✅ ${workerName}님의 모든 작업(${successCount}개)이 삭제되었습니다!`, 'success'); } else { showMessage(`⚠️ ${successCount}개 삭제 완료, ${failCount}개 삭제 실패`, 'warning'); } refreshCurrentDay(); // 현재 날짜 데이터 새로고침 } catch (error) { console.error('❌ 전체 삭제 실패:', error); showMessage('작업 삭제 중 오류가 발생했습니다: ' + error.message, 'error'); } } // 확인 대화상자 표시 function showConfirmDialog(title, message, warning) { return new Promise((resolve) => { const modalHtml = `

⚠️ ${title}

${message}

${warning}

`; document.body.insertAdjacentHTML('beforeend', modalHtml); // 전역 함수로 resolve 함수 노출 window.resolveConfirm = (result) => { const modal = document.getElementById('confirmModal'); if (modal) modal.remove(); delete window.resolveConfirm; resolve(result); }; }); } // 기본 데이터 로드 (통합 API 사용) async function loadBasicData() { try { console.log('🔗 통합 API 설정을 사용한 기본 데이터 로딩...'); const promises = [ // 프로젝트 로드 apiCall(`${API}/projects`) .then(data => Array.isArray(data) ? data : (data.projects || [])) .catch(() => []), // 작업 유형 로드 apiCall(`${API}/daily-work-reports/work-types`) .then(data => Array.isArray(data) ? data : [ {id: 1, name: 'Base'}, {id: 2, name: 'Vessel'}, {id: 3, name: 'Piping'} ]) .catch(() => [ {id: 1, name: 'Base'}, {id: 2, name: 'Vessel'}, {id: 3, name: 'Piping'} ]), // 업무 상태 유형 로드 apiCall(`${API}/daily-work-reports/work-status-types`) .then(data => Array.isArray(data) ? data : [ {id: 1, name: '정규'}, {id: 2, name: '에러'} ]) .catch(() => [ {id: 1, name: '정규'}, {id: 2, name: '에러'} ]), // 에러 유형 로드 apiCall(`${API}/daily-work-reports/error-types`) .then(data => Array.isArray(data) ? data : [ {id: 1, name: '설계미스'}, {id: 2, name: '외주작업 불량'}, {id: 3, name: '입고지연'}, {id: 4, name: '작업 불량'} ]) .catch(() => [ {id: 1, name: '설계미스'}, {id: 2, name: '외주작업 불량'}, {id: 3, name: '입고지연'}, {id: 4, name: '작업 불량'} ]) ]; const [projects, workTypes, workStatusTypes, errorTypes] = await Promise.all(promises); basicData = { projects, workTypes, workStatusTypes, errorTypes }; console.log('✅ 기본 데이터 로드 완료 (통합 API):', basicData); } catch (error) { console.error('기본 데이터 로드 실패:', error); } } // 이벤트 리스너 설정 function setupEventListeners() { document.getElementById('prevMonth').addEventListener('click', () => { currentDate.setMonth(currentDate.getMonth() - 1); updateMonthDisplay(); selectedDate = null; selectedDateData = null; renderCalendar(); renderDayInfo(); }); document.getElementById('nextMonth').addEventListener('click', () => { currentDate.setMonth(currentDate.getMonth() + 1); updateMonthDisplay(); selectedDate = null; selectedDateData = null; renderCalendar(); renderDayInfo(); }); // 오늘 날짜로 이동 버튼 추가 document.getElementById('goToday')?.addEventListener('click', () => { const today = new Date(); currentDate = new Date(today); updateMonthDisplay(); renderCalendar(); // 오늘 날짜 자동 선택 const todayStr = formatDate(today); selectedDate = todayStr; loadDayData(todayStr); }); } // 전역 함수로 노출 window.toggleReview = toggleReview; window.refreshCurrentDay = refreshCurrentDay; window.editWorkItem = editWorkItem; window.deleteWorkItem = deleteWorkItem; window.deleteWorkerAllWorks = deleteWorkerAllWorks; window.closeEditModal = closeEditModal; window.saveEditedWork = saveEditedWork; // 초기화 async function init() { try { const token = localStorage.getItem('token'); if (!token || token === 'undefined') { showMessage('로그인이 필요합니다.', 'error'); setTimeout(() => { window.location.href = '/'; }, 2000); return; } updateMonthDisplay(); setupEventListeners(); renderCalendar(); renderDayInfo(); // 기본 데이터 미리 로드 await loadBasicData(); console.log('✅ 검토 페이지 초기화 완료 (통합 API 설정 적용)'); } catch (error) { console.error('초기화 오류:', error); showMessage('초기화 중 오류가 발생했습니다.', 'error'); } } // 페이지 로드 시 초기화 document.addEventListener('DOMContentLoaded', init);