`;
});
grid.innerHTML = gridHtml;
}
// 프로젝트별 분석 표시
function displayProjectAnalysis(projectStats) {
const detailsContainer = document.getElementById('projectDetails');
console.log('📁 프로젝트 분석 데이터 확인:', projectStats);
console.log('📁 데이터 타입:', typeof projectStats);
console.log('📁 배열 여부:', Array.isArray(projectStats));
console.log('📁 길이:', projectStats ? projectStats.length : 'undefined');
if (projectStats && projectStats.length > 0) {
console.log('📁 첫 번째 프로젝트 데이터:', projectStats[0]);
}
if (!projectStats || projectStats.length === 0) {
console.log('📁 빈 데이터로 인한 empty-state 표시');
detailsContainer.innerHTML = `
📁
분석할 프로젝트 데이터가 없습니다.
`;
return;
}
// 프로젝트 상세 정보 표시
let detailsHtml = '';
// 전체 시간 계산 (퍼센트 계산용)
const totalAllHours = projectStats.reduce((sum, p) => {
return sum + (p.totalHours || p.total_hours || p.hours || 0);
}, 0);
projectStats.forEach(project => {
console.log('📁 개별 프로젝트 처리:', project);
const projectName = project.project_name || project.name || project.projectName || '프로젝트';
const totalHours = project.totalHours || project.total_hours || project.hours || 0;
// 퍼센트 계산
let percentage = project.percentage || project.percent || 0;
if (percentage === 0 && totalAllHours > 0) {
percentage = Math.round((totalHours / totalAllHours) * 100);
}
detailsHtml += `
${projectName}
${percentage}%
${totalHours}시간
`;
});
detailsContainer.innerHTML = detailsHtml;
// 차트 업데이트
updateProjectChart(projectStats);
}
// 프로젝트 차트 업데이트
function updateProjectChart(projectStats) {
const ctx = document.getElementById('projectChart');
if (projectChart) {
projectChart.destroy();
}
const labels = projectStats.map(p => p.project_name || p.name || p.projectName || '프로젝트');
const data = projectStats.map(p => p.totalHours || p.total_hours || p.hours || 0);
console.log('📊 차트 라벨:', labels);
console.log('📊 차트 데이터:', data);
const colors = [
'#3b82f6', '#10b981', '#f59e0b', '#ef4444', '#8b5cf6',
'#06b6d4', '#84cc16', '#f97316', '#ec4899', '#6366f1'
];
projectChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: labels,
datasets: [{
data: data,
backgroundColor: colors.slice(0, data.length),
borderWidth: 2,
borderColor: '#ffffff'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
padding: 20,
usePointStyle: true
}
}
}
}
});
}
// 오류 분석 표시
function displayErrorAnalysis(errorStats, allData) {
console.log('⚠️ 오류 분석 데이터 확인:', errorStats);
console.log('⚠️ 데이터 타입:', typeof errorStats);
console.log('⚠️ 배열 여부:', Array.isArray(errorStats));
// errorStats가 배열인 경우 첫 번째 요소 사용
let errorData = errorStats;
if (Array.isArray(errorStats) && errorStats.length > 0) {
errorData = errorStats[0];
console.log('⚠️ 배열에서 첫 번째 요소 사용:', errorData);
}
// 오류 요약 업데이트 - 실제 데이터 구조에 맞게 수정
const errorHours = errorData.totalHours || errorData.total_hours || errorData.error_hours || 0;
// 전체 작업 시간에서 오류 시간을 빼서 정규 시간 계산
// 요약 통계에서 전체 시간을 가져와서 계산
const totalHours = allData && allData.summary ? allData.summary.totalHours : 0;
const normalHours = Math.max(0, totalHours - errorHours);
console.log('⚠️ 정규 시간:', normalHours, '오류 시간:', errorHours);
document.getElementById('normalHours').textContent = `${normalHours}h`;
document.getElementById('errorHours').textContent = `${errorHours}h`;
// 프로젝트별 에러율 차트
if (errorStats.projectErrorRates) {
updateErrorByProjectChart(errorStats.projectErrorRates);
}
// 일별 오류 추이 차트
if (errorStats.dailyErrorTrend) {
updateErrorTimelineChart(errorStats.dailyErrorTrend);
}
// 오류 유형별 분석
if (errorStats.errorTypes) {
displayErrorTypes(errorStats.errorTypes);
}
}
// 프로젝트별 에러율 차트 업데이트
function updateErrorByProjectChart(projectErrorRates) {
const ctx = document.getElementById('errorByProjectChart');
if (errorByProjectChart) {
errorByProjectChart.destroy();
}
const labels = projectErrorRates.map(p => p.project_name);
const data = projectErrorRates.map(p => p.error_rate);
errorByProjectChart = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: '에러율 (%)',
data: data,
backgroundColor: 'rgba(239, 68, 68, 0.8)',
borderColor: 'rgba(239, 68, 68, 1)',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
max: 100,
ticks: {
callback: function(value) {
return value + '%';
}
}
}
},
plugins: {
legend: {
display: false
}
}
}
});
}
// 일별 오류 추이 차트 업데이트
function updateErrorTimelineChart(dailyErrorTrend) {
const ctx = document.getElementById('errorTimelineChart');
if (errorTimelineChart) {
errorTimelineChart.destroy();
}
const labels = dailyErrorTrend.map(d => formatDate(new Date(d.date)));
const errorData = dailyErrorTrend.map(d => d.error_count);
const totalData = dailyErrorTrend.map(d => d.total_count);
errorTimelineChart = new Chart(ctx, {
type: 'line',
data: {
labels: labels,
datasets: [
{
label: '총 작업',
data: totalData,
borderColor: 'rgba(59, 130, 246, 1)',
backgroundColor: 'rgba(59, 130, 246, 0.1)',
fill: true
},
{
label: '오류 작업',
data: errorData,
borderColor: 'rgba(239, 68, 68, 1)',
backgroundColor: 'rgba(239, 68, 68, 0.1)',
fill: true
}
]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true
}
},
plugins: {
legend: {
position: 'top'
}
}
}
});
}
// 오류 유형별 분석 표시
function displayErrorTypes(errorTypes) {
const container = document.getElementById('errorTypesAnalysis');
if (!errorTypes || errorTypes.length === 0) {
container.innerHTML = `
⚠️
오류 유형 데이터가 없습니다.
`;
return;
}
let html = '
🔍 오류 유형별 상세 분석
';
errorTypes.forEach(errorType => {
html += `
⚠️
${errorType.error_name}
${errorType.count}건
${errorType.percentage}%
`;
});
container.innerHTML = html;
}
// 프로젝트별 분석 로드
async function loadProjectAnalysis() {
const projectId = document.getElementById('projectModeSelect').value;
const startDate = document.getElementById('projectStartDate').value;
const endDate = document.getElementById('projectEndDate').value;
if (!projectId) {
showToast('프로젝트를 선택해주세요.', 'error');
return;
}
showLoading(true);
try {
console.log('📁 프로젝트별 분석 데이터 로딩 시작');
// API 호출 파라미터 구성
const params = new URLSearchParams({
project_id: projectId
});
if (startDate) params.append('start', startDate);
if (endDate) params.append('end', endDate);
// 프로젝트별 상세 분석 데이터 로드
const response = await apiCall(`/work-analysis/project-worktype-analysis?${params}`, 'GET');
const projectAnalysisData = response.data || response;
console.log('📁 프로젝트 분석 데이터:', projectAnalysisData);
// 결과 표시
displayProjectModeAnalysis(projectAnalysisData);
// 결과 섹션 표시
document.getElementById('projectModeResults').style.display = 'block';
showToast('프로젝트 분석이 완료되었습니다.', 'success');
} catch (error) {
console.error('프로젝트별 분석 오류:', error);
showToast('프로젝트 분석 중 오류가 발생했습니다.', 'error');
} finally {
showLoading(false);
}
}
// 프로젝트별 분석 결과 표시
function displayProjectModeAnalysis(data) {
const container = document.getElementById('projectModeResults');
// 프로젝트별 분석 결과 HTML 생성
let html = `
📁 ${data.project_name} 분석 결과
`;
container.innerHTML = html;
}
// 로딩 상태 표시/숨김
function showLoading(show) {
const overlay = document.getElementById('loadingOverlay');
if (overlay) {
overlay.style.display = show ? 'flex' : 'none';
}
}
// 날짜 포맷팅
function formatDate(date) {
if (!date) return '';
const d = new Date(date);
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
// 토스트 메시지 표시
function showToast(message, type = 'info') {
// 기존 토스트 제거
const existingToast = document.querySelector('.toast');
if (existingToast) {
existingToast.remove();
}
// 새 토스트 생성
const toast = document.createElement('div');
toast.className = `toast toast-${type}`;
toast.textContent = message;
// 스타일 적용
Object.assign(toast.style, {
position: 'fixed',
top: '20px',
right: '20px',
padding: '12px 24px',
borderRadius: '8px',
color: 'white',
fontWeight: '500',
zIndex: '10000',
transform: 'translateX(100%)',
transition: 'transform 0.3s ease'
});
// 타입별 배경색
const colors = {
success: '#10b981',
error: '#ef4444',
warning: '#f59e0b',
info: '#3b82f6'
};
toast.style.backgroundColor = colors[type] || colors.info;
document.body.appendChild(toast);
// 애니메이션
setTimeout(() => {
toast.style.transform = 'translateX(0)';
}, 100);
// 자동 제거
setTimeout(() => {
toast.style.transform = 'translateX(100%)';
setTimeout(() => {
if (toast.parentNode) {
toast.remove();
}
}, 300);
}, 3000);
}
// 전역 함수로 노출
window.switchAnalysisMode = switchAnalysisMode;
window.switchAnalysisTab = switchAnalysisTab;
window.loadPeriodAnalysis = loadPeriodAnalysis;
window.loadProjectAnalysis = loadProjectAnalysis;