import { API, getAuthHeaders } from '/js/api-config.js'; // DOM 요소들 const startDateInput = document.getElementById('startDate'); const endDateInput = document.getElementById('endDate'); const analyzeBtn = document.getElementById('analyzeBtn'); const quickMonthBtn = document.getElementById('quickMonth'); const quickLastMonthBtn = document.getElementById('quickLastMonth'); const analysisCard = document.getElementById('analysisCard'); const summaryCards = document.getElementById('summaryCards'); // 필터 요소들 const projectFilter = document.getElementById('projectFilter'); const workerFilter = document.getElementById('workerFilter'); const taskFilter = document.getElementById('taskFilter'); const applyFilterBtn = document.getElementById('applyFilter'); // 탭 요소들 const tabButtons = document.querySelectorAll('.tab-button'); const tabContents = document.querySelectorAll('.analysis-content'); // 테이블 바디들 const projectTableBody = document.getElementById('projectTableBody'); const workerTableBody = document.getElementById('workerTableBody'); const taskTableBody = document.getElementById('taskTableBody'); const detailTableBody = document.getElementById('detailTableBody'); // 데이터 저장 let workers = []; let projects = []; // 프로젝트 데이터 추가 let tasks = []; // 작업 데이터 추가 let rawData = []; let filteredData = []; // 초기화 async function initialize() { console.log('프로젝트 분석 페이지 초기화 시작'); setDefaultDates(); console.log('기본 날짜 설정 완료'); await loadWorkers(); console.log('작업자 로딩 완료'); await loadProjects(); console.log('프로젝트 로딩 완료'); await loadTasks(); console.log('작업 로딩 완료'); setupEventListeners(); console.log('이벤트 리스너 설정 완료'); console.log('초기화 완료'); } // 기본 날짜 설정 (이번 달) function setDefaultDates() { const now = new Date(); const firstDay = new Date(now.getFullYear(), now.getMonth(), 1); const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0); startDateInput.value = formatDate(firstDay); endDateInput.value = formatDate(lastDay); } // 날짜 포맷 함수 function formatDate(date) { return date.toISOString().split('T')[0]; } // 작업자 데이터 로딩 async function loadWorkers() { try { console.log('API 주소:', API); console.log('인증 헤더:', getAuthHeaders()); const res = await fetch(`${API}/workers`, { headers: getAuthHeaders() }); console.log('작업자 API 응답 상태:', res.status); if (!res.ok) { throw new Error(`HTTP 오류: ${res.status} ${res.statusText}`); } workers = await res.json(); console.log('불러온 작업자 데이터:', workers); workers.sort((a, b) => a.worker_id - b.worker_id); } catch (err) { console.error('작업자 로딩 실패:', err); alert(`작업자 데이터를 불러오는데 실패했습니다: ${err.message}`); } } // 프로젝트 데이터 로딩 async function loadProjects() { try { const res = await fetch(`${API}/projects`, { headers: getAuthHeaders() }); console.log('프로젝트 API 응답 상태:', res.status); if (!res.ok) { throw new Error(`HTTP 오류: ${res.status} ${res.statusText}`); } projects = await res.json(); console.log('불러온 프로젝트 데이터:', projects); } catch (err) { console.error('프로젝트 로딩 실패:', err); // 프로젝트 데이터가 없어도 일단 진행 projects = []; } } // 작업 데이터 로딩 async function loadTasks() { try { const res = await fetch(`${API}/tasks`, { headers: getAuthHeaders() }); console.log('작업 API 응답 상태:', res.status); if (!res.ok) { throw new Error(`HTTP 오류: ${res.status} ${res.statusText}`); } tasks = await res.json(); console.log('불러온 작업 데이터:', tasks); } catch (err) { console.error('작업 로딩 실패:', err); // 작업 데이터가 없어도 일단 진행 tasks = []; } } // 이벤트 리스너 설정 function setupEventListeners() { analyzeBtn.addEventListener('click', analyzeData); quickMonthBtn.addEventListener('click', setThisMonth); quickLastMonthBtn.addEventListener('click', setLastMonth); applyFilterBtn.addEventListener('click', applyFilters); // 탭 전환 tabButtons.forEach(btn => { btn.addEventListener('click', () => switchTab(btn.dataset.tab)); }); } // 이번 달 설정 function setThisMonth() { const now = new Date(); const firstDay = new Date(now.getFullYear(), now.getMonth(), 1); const lastDay = new Date(now.getFullYear(), now.getMonth() + 1, 0); startDateInput.value = formatDate(firstDay); endDateInput.value = formatDate(lastDay); } // 지난 달 설정 function setLastMonth() { const now = new Date(); const firstDay = new Date(now.getFullYear(), now.getMonth() - 1, 1); const lastDay = new Date(now.getFullYear(), now.getMonth(), 0); startDateInput.value = formatDate(firstDay); endDateInput.value = formatDate(lastDay); } // 데이터 분석 실행 async function analyzeData() { const startDate = startDateInput.value; const endDate = endDateInput.value; console.log('분석 시작 - 선택된 기간:', startDate, '~', endDate); if (!startDate || !endDate) { alert('시작일과 종료일을 모두 선택해주세요.'); return; } if (startDate > endDate) { alert('시작일이 종료일보다 늦을 수 없습니다.'); return; } showLoading(); try { console.log('작업보고서 데이터 로딩 시작'); await loadWorkReports(startDate, endDate); console.log('데이터 전처리 시작'); processData(); console.log('필터 업데이트 시작'); updateFilters(); console.log('요약 정보 렌더링 시작'); renderSummary(); console.log('테이블 렌더링 시작'); renderAllTables(); analysisCard.style.display = 'block'; console.log('분석 완료'); } catch (err) { console.error('분석 실패:', err); alert(`데이터 분석 중 오류가 발생했습니다: ${err.message}`); } } // 실제 투입시간 계산 함수 function calculateActualWorkHours(workDetails, overtimeHours) { let baseHours = 8; // 기본 8시간 // 근무형태에 따른 기본시간 조정 switch(workDetails) { case '연차': baseHours = 0; break; case '반차': baseHours = 4; break; case '반반차': baseHours = 6; break; case '조퇴': baseHours = 2; break; case '휴무': case '유급': baseHours = 0; break; default: baseHours = 8; // 정상근무 } // 잔업시간 1.5배 가산 const overtimePay = (overtimeHours || 0) * 1.5; return baseHours + overtimePay; } // 작업보고서 데이터 로딩 async function loadWorkReports(startDate, endDate) { try { const url = `${API}/workreports?start=${startDate}&end=${endDate}`; console.log('작업보고서 요청 URL:', url); console.log('요청 기간:', startDate, '~', endDate); const res = await fetch(url, { headers: getAuthHeaders() }); console.log('작업보고서 API 응답 상태:', res.status); if (!res.ok) { throw new Error(`HTTP 오류: ${res.status} ${res.statusText}`); } rawData = await res.json(); console.log('불러온 작업보고서 데이터 개수:', rawData.length); console.log('작업보고서 데이터 샘플:', rawData.slice(0, 3)); // ID를 이름으로 매핑 + 실제 투입시간 계산 rawData = rawData.map(item => { const worker = workers.find(w => w.worker_id === item.worker_id); const project = projects.find(p => p.project_id === item.project_id); const task = tasks.find(t => t.task_id === item.task_id); // 실제 투입시간 계산 const actualHours = calculateActualWorkHours(item.work_details, item.overtime_hours); return { ...item, worker_name: worker ? worker.worker_name : '알 수 없음', project_name: project ? project.project_name : `프로젝트 ID ${item.project_id}`, task_category: task ? task.category : `작업 ID ${item.task_id}`, work_hours: actualHours, // 계산된 실제 투입시간 base_hours: calculateActualWorkHours(item.work_details, 0), // 기본시간만 overtime_pay: (item.overtime_hours || 0) * 1.5 // 잔업 가산시간 }; }); console.log('ID 매핑 + 투입시간 계산 후 샘플:', rawData.slice(0, 3)); filteredData = [...rawData]; } catch (err) { console.error('작업보고서 로딩 실패:', err); throw new Error(`작업보고서 데이터 로딩 실패: ${err.message}`); } } // 데이터 전처리 function processData() { console.log('전처리 전 전체 데이터 개수:', rawData.length); // 실제 투입시간이 있는 유효한 데이터만 필터링 filteredData = rawData.filter(item => { const hasProject = item.project_name && item.project_name !== `프로젝트 ID ${item.project_id}`; const hasTask = item.task_category && item.task_category !== `작업 ID ${item.task_id}`; const hasActualHours = item.work_hours > 0; // 실제 투입시간이 0보다 큰 경우만 const isNotPureLeave = !['연차', '휴무', '유급'].includes(item.work_details); // 완전 휴가가 아닌 경우 if (!hasProject) console.log('프로젝트명 없음 또는 매핑 실패:', item); if (!hasTask) console.log('작업 분류 없음 또는 매핑 실패:', item); if (!hasActualHours) console.log('실제 투입시간 없음 (휴가/휴무):', item); if (!isNotPureLeave) console.log('완전 휴가 데이터:', item); return hasProject && hasTask && hasActualHours && isNotPureLeave; }); console.log('전처리 후 유효 데이터 개수:', filteredData.length); if (filteredData.length > 0) { console.log('유효 데이터 샘플:', filteredData.slice(0, 3)); // 투입시간 계산 확인 const sampleItem = filteredData[0]; console.log('투입시간 계산 확인:', { 근무형태: sampleItem.work_details, 기본시간: sampleItem.base_hours, 잔업시간: sampleItem.overtime_hours, 잔업가산: sampleItem.overtime_pay, 총투입시간: sampleItem.work_hours, '계산공식': `${sampleItem.base_hours} + ${sampleItem.overtime_pay} = ${sampleItem.work_hours}` }); } } // 필터 옵션 업데이트 function updateFilters() { // 프로젝트 필터 const projects = [...new Set(filteredData.map(item => item.project_name))].sort(); projectFilter.innerHTML = ''; projects.forEach(project => { projectFilter.insertAdjacentHTML('beforeend', ``); }); // 작업자 필터 const workerNames = [...new Set(filteredData.map(item => item.worker_name))].sort(); workerFilter.innerHTML = ''; workerNames.forEach(name => { workerFilter.insertAdjacentHTML('beforeend', ``); }); // 작업 분류 필터 const tasks = [...new Set(filteredData.map(item => item.task_category))].sort(); taskFilter.innerHTML = ''; tasks.forEach(task => { taskFilter.insertAdjacentHTML('beforeend', ``); }); } // 필터 적용 function applyFilters() { let filtered = [...rawData]; // 유효한 데이터만 필터링 (투입시간 계산 반영) filtered = filtered.filter(item => { const hasProject = item.project_name && item.project_name !== `프로젝트 ID ${item.project_id}`; const hasTask = item.task_category && item.task_category !== `작업 ID ${item.task_id}`; const hasActualHours = item.work_hours > 0; const isNotPureLeave = !['연차', '휴무', '유급'].includes(item.work_details); return hasProject && hasTask && hasActualHours && isNotPureLeave; }); // 프로젝트 필터 if (projectFilter.value) { filtered = filtered.filter(item => item.project_name === projectFilter.value); } // 작업자 필터 if (workerFilter.value) { filtered = filtered.filter(item => item.worker_name === workerFilter.value); } // 작업 분류 필터 if (taskFilter.value) { filtered = filtered.filter(item => item.task_category === taskFilter.value); } filteredData = filtered; renderSummary(); renderAllTables(); } // 요약 정보 렌더링 function renderSummary() { const totalHours = filteredData.reduce((sum, item) => sum + parseFloat(item.work_hours || 0), 0); const totalProjects = new Set(filteredData.map(item => item.project_name)).size; const totalWorkers = new Set(filteredData.map(item => item.worker_name)).size; const totalTasks = new Set(filteredData.map(item => item.task_category)).size; summaryCards.innerHTML = `

총 투입 시간

${totalHours.toFixed(1)}h

참여 프로젝트

${totalProjects}개

참여 인원

${totalWorkers}명

작업 분류

${totalTasks}개
`; } // 모든 테이블 렌더링 function renderAllTables() { renderProjectTable(); renderWorkerTable(); renderTaskTable(); renderDetailTable(); } // 프로젝트별 테이블 렌더링 function renderProjectTable() { const projectData = {}; filteredData.forEach(item => { const project = item.project_name; if (!projectData[project]) { projectData[project] = { name: project, hours: 0, workers: new Set() }; } projectData[project].hours += parseFloat(item.work_hours || 0); projectData[project].workers.add(item.worker_name); }); const sortedProjects = Object.values(projectData).sort((a, b) => b.hours - a.hours); const totalHours = sortedProjects.reduce((sum, p) => sum + p.hours, 0); let html = ''; if (sortedProjects.length === 0) { html = '데이터가 없습니다'; } else { sortedProjects.forEach((project, index) => { const ratio = totalHours > 0 ? (project.hours / totalHours * 100).toFixed(1) : 0; html += ` ${index + 1} ${project.name} ${project.hours.toFixed(1)}h ${ratio}% ${project.workers.size}명 `; }); } projectTableBody.innerHTML = html; } // 작업자별 테이블 렌더링 function renderWorkerTable() { const workerData = {}; filteredData.forEach(item => { const worker = item.worker_name; if (!workerData[worker]) { workerData[worker] = { name: worker, hours: 0, projects: new Set() }; } workerData[worker].hours += parseFloat(item.work_hours || 0); workerData[worker].projects.add(item.project_name); }); const sortedWorkers = Object.values(workerData).sort((a, b) => b.hours - a.hours); const totalHours = sortedWorkers.reduce((sum, w) => sum + w.hours, 0); let html = ''; if (sortedWorkers.length === 0) { html = '데이터가 없습니다'; } else { sortedWorkers.forEach((worker, index) => { const ratio = totalHours > 0 ? (worker.hours / totalHours * 100).toFixed(1) : 0; html += ` ${index + 1} ${worker.name} ${worker.hours.toFixed(1)}h ${ratio}% ${worker.projects.size}개 `; }); } workerTableBody.innerHTML = html; } // 작업별 테이블 렌더링 function renderTaskTable() { const taskData = {}; filteredData.forEach(item => { const task = item.task_category; if (!taskData[task]) { taskData[task] = { name: task, hours: 0, workers: new Set() }; } taskData[task].hours += parseFloat(item.work_hours || 0); taskData[task].workers.add(item.worker_name); }); const sortedTasks = Object.values(taskData).sort((a, b) => b.hours - a.hours); const totalHours = sortedTasks.reduce((sum, t) => sum + t.hours, 0); let html = ''; if (sortedTasks.length === 0) { html = '데이터가 없습니다'; } else { sortedTasks.forEach((task, index) => { const ratio = totalHours > 0 ? (task.hours / totalHours * 100).toFixed(1) : 0; html += ` ${index + 1} ${task.name} ${task.hours.toFixed(1)}h ${ratio}% ${task.workers.size}명 `; }); } taskTableBody.innerHTML = html; } // 상세 내역 테이블 렌더링 function renderDetailTable() { const sortedData = [...filteredData].sort((a, b) => new Date(b.date) - new Date(a.date)); let html = ''; if (sortedData.length === 0) { html = '데이터가 없습니다'; } else { sortedData.forEach((item, index) => { const date = new Date(item.date).toLocaleDateString('ko-KR'); const memo = item.memo || '-'; // 투입시간 계산 과정을 툴팁으로 표시 const hoursBreakdown = `기본: ${item.base_hours}h + 잔업가산: ${item.overtime_pay}h = 총 ${item.work_hours}h`; const workDetailsDisplay = item.work_details || '정상근무'; html += ` ${index + 1} ${date} ${item.project_name} ${item.worker_name} ${item.task_category} ${workDetailsDisplay} ${parseFloat(item.work_hours || 0).toFixed(1)}h ${memo.length > 20 ? memo.substring(0, 20) + '...' : memo} `; }); } detailTableBody.innerHTML = html; } // 탭 전환 function switchTab(tabName) { // 모든 탭 버튼 비활성화 tabButtons.forEach(btn => btn.classList.remove('active')); // 모든 탭 콘텐츠 숨기기 tabContents.forEach(content => content.classList.remove('active')); // 선택된 탭 활성화 document.querySelector(`[data-tab="${tabName}"]`).classList.add('active'); document.getElementById(`${tabName}Tab`).classList.add('active'); } // 로딩 표시 function showLoading() { const loadingHtml = '📊 데이터 분석 중...'; projectTableBody.innerHTML = loadingHtml; workerTableBody.innerHTML = loadingHtml; taskTableBody.innerHTML = loadingHtml; detailTableBody.innerHTML = '📊 데이터 분석 중...'; } // 초기화 실행 initialize();