- 600줄에 달하는 project-analysis.js 파일을 API, Data, UI, Controller 네 개의 모듈로 분리 - 복잡한 데이터 처리 로직을 data 모듈로 위임하고, UI 렌더링 코드를 ui 모듈로 분리하여 관심사 분리 원칙(SoC) 적용 - 전역 상태를 최소화하고 데이터 흐름을 명확하게 개선하여 유지보수성 및 안정성 향상
114 lines
4.5 KiB
JavaScript
114 lines
4.5 KiB
JavaScript
// /js/project-analysis-data.js
|
|
|
|
/**
|
|
* 근무 형태에 따른 실제 투입 시간을 계산합니다.
|
|
* 잔업(OT)은 1.5배 가산됩니다.
|
|
* @param {string} workDetails - 근무 형태 (예: '정상', '연차', '반차')
|
|
* @param {number} overtimeHours - 잔업 시간
|
|
* @returns {number} - 실제 투입 시간
|
|
*/
|
|
function calculateActualWorkHours(workDetails, overtimeHours) {
|
|
let baseHours = 8;
|
|
switch(workDetails) {
|
|
case '연차':
|
|
case '휴무':
|
|
case '유급': baseHours = 0; break;
|
|
case '반차': baseHours = 4; break;
|
|
case '반반차': baseHours = 6; break;
|
|
case '조퇴': baseHours = 2; break;
|
|
default: baseHours = 8; // 정상근무
|
|
}
|
|
return baseHours + (overtimeHours || 0) * 1.5;
|
|
}
|
|
|
|
/**
|
|
* 원본 작업 보고서 데이터에 마스터 데이터를 매핑하고 가공합니다.
|
|
* @param {Array} rawReports - 원본 작업 보고서
|
|
* @param {{workers: Array, projects: Array, tasks: Array}} masterData - 마스터 데이터
|
|
* @returns {Array} - 가공된 데이터
|
|
*/
|
|
export function processRawData(rawReports, masterData) {
|
|
if (!rawReports || !masterData) return [];
|
|
|
|
const { workers, projects, tasks } = masterData;
|
|
const workerMap = new Map(workers.map(w => [w.worker_id, w.worker_name]));
|
|
const projectMap = new Map(projects.map(p => [p.project_id, p.project_name]));
|
|
const taskMap = new Map(tasks.map(t => [t.task_id, t.category]));
|
|
|
|
return rawReports
|
|
.map(item => ({
|
|
...item,
|
|
worker_name: workerMap.get(item.worker_id) || '알 수 없음',
|
|
project_name: projectMap.get(item.project_id) || `프로젝트 ID ${item.project_id}`,
|
|
task_category: taskMap.get(item.task_id) || `작업 ID ${item.task_id}`,
|
|
work_hours: calculateActualWorkHours(item.work_details, item.overtime_hours),
|
|
}))
|
|
// 실제 투입 시간이 있고, 완전 휴가가 아닌 유효한 데이터만 필터링
|
|
.filter(item => item.work_hours > 0 && !['연차', '휴무', '유급'].includes(item.work_details));
|
|
}
|
|
|
|
/**
|
|
* 주어진 데이터셋을 필터링합니다.
|
|
* @param {Array} data - 가공된 전체 데이터
|
|
* @param {{project: string, worker: string, task: string}} filters - 필터 조건
|
|
* @returns {Array} - 필터링된 데이터
|
|
*/
|
|
export function applyFilters(data, filters) {
|
|
return data.filter(item => {
|
|
const projectMatch = !filters.project || item.project_name === filters.project;
|
|
const workerMatch = !filters.worker || item.worker_name === filters.worker;
|
|
const taskMatch = !filters.task || item.task_category === filters.task;
|
|
return projectMatch && workerMatch && taskMatch;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 데이터를 특정 키(프로젝트, 작업자, 작업)로 집계합니다.
|
|
* @param {Array} data - 집계할 데이터
|
|
* @param {'project_name' | 'worker_name' | 'task_category'} key - 집계 기준 키
|
|
* @returns {Array} - 집계된 데이터
|
|
*/
|
|
function aggregateData(data, key) {
|
|
const aggregated = data.reduce((acc, item) => {
|
|
const group = item[key];
|
|
if (!acc[group]) {
|
|
acc[group] = {
|
|
name: group,
|
|
hours: 0,
|
|
// 참여자 또는 참여 프로젝트를 추적하기 위한 Set
|
|
participants: new Set(),
|
|
};
|
|
}
|
|
acc[group].hours += item.work_hours;
|
|
// 집계 키에 따라 다른 종류의 참여자를 추가
|
|
if (key === 'project_name') acc[group].participants.add(item.worker_name);
|
|
else if (key === 'worker_name') acc[group].participants.add(item.project_name);
|
|
else if (key === 'task_category') acc[group].participants.add(item.worker_name);
|
|
return acc;
|
|
}, {});
|
|
|
|
return Object.values(aggregated).sort((a, b) => b.hours - a.hours);
|
|
}
|
|
|
|
|
|
/**
|
|
* 필터링된 데이터를 기반으로 모든 분석 데이터를 생성합니다.
|
|
* @param {Array} filteredData - 필터링된 데이터
|
|
* @returns {object} - 요약, 프로젝트별, 작업자별, 작업별 집계 데이터
|
|
*/
|
|
export function getAnalysis(filteredData) {
|
|
const totalHours = filteredData.reduce((sum, item) => sum + (item.work_hours || 0), 0);
|
|
|
|
const summary = {
|
|
totalHours,
|
|
totalProjects: new Set(filteredData.map(item => item.project_name)).size,
|
|
totalWorkers: new Set(filteredData.map(item => item.worker_name)).size,
|
|
totalTasks: new Set(filteredData.map(item => item.task_category)).size,
|
|
};
|
|
|
|
const byProject = aggregateData(filteredData, 'project_name');
|
|
const byWorker = aggregateData(filteredData, 'worker_name');
|
|
const byTask = aggregateData(filteredData, 'task_category');
|
|
|
|
return { summary, byProject, byWorker, byTask };
|
|
}
|