/** * Work Analysis Table Renderer Module * 작업 분석 테이블 렌더링을 담당하는 모듈 */ class WorkAnalysisTableRenderer { constructor() { this.dataProcessor = window.WorkAnalysisDataProcessor; } // ========== 프로젝트 분포 테이블 ========== /** * 프로젝트 분포 테이블 렌더링 (Production Report 스타일) * @param {Array} projectData - 프로젝트 데이터 * @param {Array} workerData - 작업자 데이터 */ renderProjectDistributionTable(projectData, workerData) { console.log('📋 프로젝트별 분포 테이블 렌더링 시작'); const tbody = document.getElementById('projectDistributionTableBody'); const tfoot = document.getElementById('projectDistributionTableFooter'); if (!tbody) { console.error('❌ projectDistributionTableBody 요소를 찾을 수 없습니다'); return; } // 프로젝트 데이터가 없으면 작업자 데이터로 대체 if (!projectData || !projectData.projects || projectData.projects.length === 0) { console.log('⚠️ 프로젝트 데이터가 없어서 작업자 데이터로 대체합니다.'); this._renderFallbackTable(workerData, tbody, tfoot); return; } let tableRows = []; let grandTotalHours = 0; let grandTotalManDays = 0; let grandTotalLaborCost = 0; // 공수당 인건비 (350,000원) const manDayRate = 350000; // 먼저 전체 시간을 계산 (부하율 계산용) projectData.projects.forEach(project => { project.workTypes.forEach(workType => { grandTotalHours += workType.totalHours; }); }); // 프로젝트별로 렌더링 projectData.projects.forEach(project => { const projectName = project.project_name || '알 수 없는 프로젝트'; const jobNo = project.job_no || 'N/A'; const workTypes = project.workTypes || []; if (workTypes.length === 0) { // 작업유형이 없는 경우 const projectHours = project.totalHours || 0; const manDays = Math.round((projectHours / 8) * 100) / 100; const laborCost = manDays * manDayRate; const loadRate = grandTotalHours > 0 ? ((projectHours / grandTotalHours) * 100).toFixed(2) : '0.00'; grandTotalManDays += manDays; grandTotalLaborCost += laborCost; const isVacation = project.project_id === 'vacation'; const displayText = isVacation ? projectName : jobNo; tableRows.push(` ${displayText} 데이터 없음 ${manDays} ${loadRate}% ₩${laborCost.toLocaleString()} `); } else { // 작업유형별 렌더링 workTypes.forEach((workType, index) => { const isFirstWorkType = index === 0; const rowspan = workTypes.length; const workTypeHours = workType.totalHours || 0; const manDays = Math.round((workTypeHours / 8) * 100) / 100; const laborCost = manDays * manDayRate; const loadRate = grandTotalHours > 0 ? ((workTypeHours / grandTotalHours) * 100).toFixed(2) : '0.00'; grandTotalManDays += manDays; grandTotalLaborCost += laborCost; const isVacation = project.project_id === 'vacation'; const displayText = isVacation ? projectName : jobNo; tableRows.push(` ${isFirstWorkType ? `${displayText}` : ''} ${workType.work_type_name} ${manDays} ${loadRate}% ₩${laborCost.toLocaleString()} `); }); // 프로젝트 소계 행 추가 const projectTotalHours = workTypes.reduce((sum, wt) => sum + (wt.totalHours || 0), 0); const projectTotalManDays = Math.round((projectTotalHours / 8) * 100) / 100; const projectTotalLaborCost = projectTotalManDays * manDayRate; const projectLoadRate = grandTotalHours > 0 ? ((projectTotalHours / grandTotalHours) * 100).toFixed(2) : '0.00'; tableRows.push(` ${projectName} 소계 ${projectTotalManDays} ${projectLoadRate}% ₩${projectTotalLaborCost.toLocaleString()} `); } }); // 테이블 업데이트 tbody.innerHTML = tableRows.join(''); // 총계 업데이트 if (tfoot) { document.getElementById('totalManDays').textContent = grandTotalManDays.toFixed(2); document.getElementById('totalLaborCost').textContent = `₩${grandTotalLaborCost.toLocaleString()}`; tfoot.style.display = 'table-footer-group'; } console.log('✅ 프로젝트별 분포 테이블 렌더링 완료'); } /** * 대체 테이블 렌더링 (작업자 데이터 기반) */ _renderFallbackTable(workerData, tbody, tfoot) { if (!workerData || workerData.length === 0) { tbody.innerHTML = ` 해당 기간에 데이터가 없습니다 `; if (tfoot) tfoot.style.display = 'none'; return; } const manDayRate = 350000; let totalManDays = 0; let totalLaborCost = 0; const tableRows = workerData.map(worker => { const hours = worker.totalHours || 0; const manDays = Math.round((hours / 8) * 100) / 100; const laborCost = manDays * manDayRate; totalManDays += manDays; totalLaborCost += laborCost; return ` 작업자 기반 ${worker.worker_name} ${manDays} - ₩${laborCost.toLocaleString()} `; }); tbody.innerHTML = tableRows.join(''); // 총계 업데이트 if (tfoot) { document.getElementById('totalManDays').textContent = totalManDays.toFixed(2); document.getElementById('totalLaborCost').textContent = `₩${totalLaborCost.toLocaleString()}`; tfoot.style.display = 'table-footer-group'; } } // ========== 오류 분석 테이블 ========== /** * 오류 분석 테이블 렌더링 * @param {Array} recentWorkData - 최근 작업 데이터 */ renderErrorAnalysisTable(recentWorkData) { console.log('📊 오류 분석 테이블 렌더링 시작'); console.log('📊 받은 데이터:', recentWorkData); const tableBody = document.getElementById('errorAnalysisTableBody'); const tableFooter = document.getElementById('errorAnalysisTableFooter'); console.log('📊 DOM 요소 확인:', { tableBody, tableFooter }); // DOM 요소 존재 확인 if (!tableBody) { console.error('❌ errorAnalysisTableBody 요소를 찾을 수 없습니다'); return; } if (!recentWorkData || recentWorkData.length === 0) { tableBody.innerHTML = ` 해당 기간에 오류 데이터가 없습니다 `; if (tableFooter) { tableFooter.style.display = 'none'; } return; } // 작업 형태별 오류 데이터 집계 const errorData = this.dataProcessor.aggregateErrorData(recentWorkData); let tableRows = []; let grandTotalHours = 0; let grandTotalRegularHours = 0; let grandTotalErrorHours = 0; // 프로젝트별로 그룹화 const projectGroups = new Map(); errorData.forEach(workType => { const projectKey = workType.isVacation ? 'vacation' : workType.project_id; if (!projectGroups.has(projectKey)) { projectGroups.set(projectKey, []); } projectGroups.get(projectKey).push(workType); }); // 프로젝트별로 렌더링 Array.from(projectGroups.entries()).forEach(([projectKey, workTypes]) => { workTypes.forEach((workType, index) => { grandTotalHours += workType.totalHours; grandTotalRegularHours += workType.regularHours; grandTotalErrorHours += workType.errorHours; const rowClass = workType.isVacation ? 'vacation-project' : 'project-group'; const isFirstWorkType = index === 0; const rowspan = workTypes.length; // 세부시간 구성 let detailHours = []; if (workType.regularHours > 0) { detailHours.push(`정규: ${workType.regularHours}h`); } // 오류 세부사항 추가 workType.errorDetails.forEach(error => { detailHours.push(`오류: ${error.type} ${error.hours}h`); }); // 작업 타입 구성 (단순화) let workTypeDisplay = ''; if (workType.regularHours > 0) { workTypeDisplay += `
정규시간
`; } workType.errorDetails.forEach(error => { workTypeDisplay += `
오류: ${error.type}
`; }); tableRows.push(` ${isFirstWorkType ? `${workType.isVacation ? '연차/휴무' : (workType.project_name || 'N/A')}` : ''} ${workType.work_type_name} ${workType.totalHours}h ${detailHours.join('
')}
${workTypeDisplay}
${workType.errorRate}% `); }); }); if (tableRows.length === 0) { tableBody.innerHTML = ` 해당 기간에 작업 데이터가 없습니다 `; if (tableFooter) { tableFooter.style.display = 'none'; } } else { tableBody.innerHTML = tableRows.join(''); // 총계 업데이트 const totalErrorRate = grandTotalHours > 0 ? ((grandTotalErrorHours / grandTotalHours) * 100).toFixed(1) : '0.0'; // 안전한 DOM 요소 접근 const totalErrorHoursElement = document.getElementById('totalErrorHours'); if (totalErrorHoursElement) { totalErrorHoursElement.textContent = `${grandTotalHours}h`; } if (tableFooter) { const detailHoursCell = tableFooter.querySelector('.total-row td:nth-child(4)'); const errorRateCell = tableFooter.querySelector('.total-row td:nth-child(6)'); if (detailHoursCell) { detailHoursCell.innerHTML = ` 정규: ${grandTotalRegularHours}h
오류: ${grandTotalErrorHours}h
`; } if (errorRateCell) { errorRateCell.innerHTML = `${totalErrorRate}%`; } tableFooter.style.display = 'table-footer-group'; } } console.log('✅ 오류 분석 테이블 렌더링 완료'); } // ========== 기간별 작업 현황 테이블 ========== /** * 기간별 작업 현황 테이블 렌더링 * @param {Array} projectData - 프로젝트 데이터 * @param {Array} workerData - 작업자 데이터 * @param {Array} recentWorkData - 최근 작업 데이터 */ renderWorkStatusTable(projectData, workerData, recentWorkData) { console.log('📈 기간별 작업 현황 테이블 렌더링 시작'); const tableContainer = document.querySelector('#work-status-tab .table-container'); if (!tableContainer) { console.error('❌ 작업 현황 테이블 컨테이너를 찾을 수 없습니다'); return; } // 데이터가 없는 경우 처리 if (!workerData || workerData.length === 0) { tableContainer.innerHTML = `
📊
데이터가 없습니다
선택한 기간에 작업 데이터가 없습니다.
`; return; } // 작업자별 데이터 처리 const workerStats = this._processWorkerStats(workerData, recentWorkData); let tableHTML = ` `; let totalHours = 0; let totalManDays = 0; workerStats.forEach(worker => { worker.projects.forEach((project, projectIndex) => { project.workTypes.forEach((workType, workTypeIndex) => { const isFirstProject = projectIndex === 0 && workTypeIndex === 0; const workerRowspan = worker.totalRowspan; totalHours += workType.hours; totalManDays += workType.manDays; tableHTML += ` ${isFirstProject ? ` ` : ''} ${isFirstProject ? ` ` : ''} `; }); }); }); tableHTML += `
작업자 분류(프로젝트) 작업내용 투입시간 작업공수 작업일/일평균시간 비고
${worker.name}${project.name} ${workType.name} ${workType.hours}h${worker.totalManDays.toFixed(1)} ${worker.workDays}일 / ${worker.avgHours.toFixed(1)}h${workType.remarks}
총 공수 ${totalHours}h ${totalManDays.toFixed(1)}
`; tableContainer.innerHTML = tableHTML; console.log('✅ 기간별 작업 현황 테이블 렌더링 완료'); } /** * 작업자별 통계 처리 (내부 헬퍼) */ _processWorkerStats(workerData, recentWorkData) { if (!workerData || workerData.length === 0) { return []; } return workerData.map(worker => { // 해당 작업자의 작업 데이터 필터링 const workerWork = recentWorkData ? recentWorkData.filter(work => work.worker_id === worker.worker_id) : []; // 프로젝트별로 그룹화 const projectMap = new Map(); workerWork.forEach(work => { const projectKey = work.project_id || 'unknown'; if (!projectMap.has(projectKey)) { projectMap.set(projectKey, { name: work.project_name || `프로젝트 ${projectKey}`, workTypes: new Map() }); } const project = projectMap.get(projectKey); const workTypeKey = work.work_type_id || 'unknown'; const workTypeName = work.work_type_name || `작업유형 ${workTypeKey}`; if (!project.workTypes.has(workTypeKey)) { project.workTypes.set(workTypeKey, { name: workTypeName, hours: 0, remarks: '정상' }); } const workType = project.workTypes.get(workTypeKey); workType.hours += parseFloat(work.work_hours) || 0; // 오류가 있으면 비고 업데이트 if (work.work_status === 'error' || work.error_type_id) { workType.remarks = work.error_type_name || work.error_description || '오류'; } }); // 프로젝트 배열로 변환 const projects = Array.from(projectMap.values()).map(project => ({ ...project, workTypes: Array.from(project.workTypes.values()).map(wt => ({ ...wt, manDays: Math.round((wt.hours / 8) * 10) / 10 })) })); // 전체 행 수 계산 const totalRowspan = projects.reduce((sum, p) => sum + p.workTypes.length, 0); return { name: worker.worker_name, totalHours: worker.totalHours || 0, totalManDays: (worker.totalHours || 0) / 8, workDays: worker.workingDays || 0, avgHours: worker.avgHours || 0, projects, totalRowspan: Math.max(totalRowspan, 1) }; }); } } // 전역 인스턴스 생성 window.WorkAnalysisTableRenderer = new WorkAnalysisTableRenderer(); // Export는 브라우저 환경에서 제거됨