/**
* 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 ? `
| ${worker.name} |
` : ''}
${project.name} |
${workType.name} |
${workType.hours}h |
${isFirstProject ? `
${worker.totalManDays.toFixed(1)} |
${worker.workDays}일 / ${worker.avgHours.toFixed(1)}h |
` : ''}
`;
});
});
});
tableHTML += `
| 총 공수 |
${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는 브라우저 환경에서 제거됨