// /js/project-analysis-ui.js
const DOM = {
// 기간 설정
startDate: document.getElementById('startDate'),
endDate: document.getElementById('endDate'),
// 카드 및 필터
analysisCard: document.getElementById('analysisCard'),
summaryCards: document.getElementById('summaryCards'),
projectFilter: document.getElementById('projectFilter'),
workerFilter: document.getElementById('workerFilter'),
taskFilter: document.getElementById('taskFilter'),
// 탭
tabButtons: document.querySelectorAll('.tab-button'),
tabContents: document.querySelectorAll('.analysis-content'),
// 테이블 본문
projectTableBody: document.getElementById('projectTableBody'),
workerTableBody: document.getElementById('workerTableBody'),
taskTableBody: document.getElementById('taskTableBody'),
detailTableBody: document.getElementById('detailTableBody'),
};
/**
* 날짜 input 값을 YYYY-MM-DD 형식의 문자열로 반환
* @param {Date} date - 날짜 객체
* @returns {string} - 포맷된 날짜 문자열
*/
const formatDate = (date) => date.toISOString().split('T')[0];
/**
* UI상의 날짜 선택기를 기본값(이번 달)으로 설정합니다.
*/
export 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);
DOM.startDate.value = formatDate(firstDay);
DOM.endDate.value = formatDate(lastDay);
}
/**
* 분석 실행 전후의 UI 상태를 관리합니다 (로딩 표시 등)
* @param {'loading' | 'data' | 'no-data' | 'error'} state - UI 상태
*/
export function setUIState(state) {
const projectCols = 5;
const detailCols = 8;
const messages = {
loading: '📊 데이터 분석 중...',
'no-data': '해당 기간에 분석할 데이터가 없습니다.',
error: '오류가 발생했습니다. 다시 시도해주세요.',
};
if (state === 'data') {
DOM.analysisCard.style.display = 'block';
} else {
const message = messages[state];
const html = `
| ${message} |
`;
const detailHtml = `| ${message} |
`;
DOM.projectTableBody.innerHTML = html;
DOM.workerTableBody.innerHTML = html;
DOM.taskTableBody.innerHTML = html;
DOM.detailTableBody.innerHTML = detailHtml;
DOM.summaryCards.innerHTML = '';
DOM.analysisCard.style.display = 'block';
}
}
/**
* 마스터 데이터를 기반으로 필터 옵션을 채웁니다.
* @param {{workers: Array, projects: Array, tasks: Array}} masterData - 마스터 데이터
*/
export function updateFilterOptions(masterData) {
const createOptions = (items, key, value) => {
let html = '';
items.forEach(item => {
html += ``;
});
return html;
};
DOM.projectFilter.innerHTML = createOptions(masterData.projects, 'project_id', 'project_name');
DOM.workerFilter.innerHTML = createOptions(masterData.workers, 'worker_id', 'worker_name');
DOM.taskFilter.innerHTML = createOptions(masterData.tasks, 'task_id', 'category');
}
/**
* 요약 카드 데이터를 렌더링합니다.
* @param {object} summary - 요약 데이터
*/
export function renderSummary(summary) {
DOM.summaryCards.innerHTML = `
총 투입 시간
${(summary.totalHours || 0).toFixed(1)}h
참여 프로젝트
${summary.totalProjects || 0}개
참여 인원
${summary.totalWorkers || 0}명
작업 분류
${summary.totalTasks || 0}개
`;
}
/**
* 집계된 데이터를 받아 테이블을 렌더링하는 범용 함수
* @param {HTMLElement} tableBodyEl - 렌더링할 테이블의 tbody 요소
* @param {Array} data - 집계된 데이터 배열
* @param {function} rowRenderer - 각 행을 렌더링하는 함수
*/
function renderTable(tableBodyEl, data, rowRenderer) {
if (!data || data.length === 0) {
tableBodyEl.innerHTML = '| 데이터가 없습니다 |
';
return;
}
tableBodyEl.innerHTML = data.map(rowRenderer).join('');
}
/**
* 집계된 데이터를 기반으로 모든 분석 테이블을 렌더링합니다.
* @param {object} analysis - 프로젝트/작업자/작업별 집계 데이터
*/
export function renderAnalysisTables(analysis) {
renderTable(DOM.projectTableBody, analysis.byProject, (p, i) => `
| ${i + 1} | ${p.name} | ${p.hours}h |
${p.percentage}% | ${p.participants}명 |
`);
renderTable(DOM.workerTableBody, analysis.byWorker, (w, i) => `
| ${i + 1} | ${w.name} | ${w.hours}h |
${w.percentage}% | ${w.participants}개 |
`);
renderTable(DOM.taskTableBody, analysis.byTask, (t, i) => `
| ${i + 1} | ${t.name} | ${t.hours}h |
${t.percentage}% | ${t.participants}명 |
`);
}
/**
* 상세 내역 테이블을 렌더링합니다.
* @param {Array} detailData - 필터링된 상세 데이터
*/
export function renderDetailTable(detailData) {
if (!detailData || detailData.length === 0) {
DOM.detailTableBody.innerHTML = '| 데이터가 없습니다 |
';
return;
}
DOM.detailTableBody.innerHTML = detailData.map((item, index) => `
| ${index + 1} | ${formatDate(new Date(item.date))} |
${item.project_name} |
${item.worker_name} | ${item.task_category} |
${item.work_details || '정상근무'} |
${item.work_hours}h |
${(item.memo || '-').substring(0, 20)} |
`
).join('');
}
/**
* 탭 UI를 제어합니다.
* @param {string} tabName - 활성화할 탭의 이름
*/
export function switchTab(tabName) {
DOM.tabButtons.forEach(btn => btn.classList.toggle('active', btn.dataset.tab === tabName));
DOM.tabContents.forEach(content => content.classList.toggle('active', content.id === `${tabName}Tab`));
}
/**
* 사용자로부터 현재 필터 값을 가져옵니다.
* @returns {{project: string, worker: string, task: string}}
*/
export function getCurrentFilters() {
return {
project: DOM.projectFilter.value,
worker: DOM.workerFilter.value,
task: DOM.taskFilter.value,
};
}