✨ 새로운 기능: - 작업 분석 페이지 구현 (기간별, 프로젝트별, 작업자별, 오류별) - 개별 분석 실행 버튼으로 API 부하 최적화 - 연차/휴무 집계 방식 개선 (주말 제외, 작업내용 통합) - 프로젝트 관리 시스템 (활성화/비활성화) - 작업자 관리 시스템 (CRUD 기능) - 코드 관리 시스템 (작업유형, 작업상태, 오류유형) 🎨 UI/UX 개선: - 기간별 작업 현황을 테이블 형태로 변경 - 작업자별 rowspan 그룹화로 가독성 향상 - 연차/휴무 프로젝트 하단 배치 및 시각적 구분 - 기간 확정 시스템으로 사용자 경험 개선 - 반응형 디자인 적용 🔧 기술적 개선: - Rate Limiting 제거 (내부 시스템 최적화) - 주말 연차/휴무 자동 제외 로직 - 작업공수 계산 정확도 향상 - 데이터베이스 마이그레이션 추가 - API 엔드포인트 확장 및 최적화 🐛 버그 수정: - projectSelect 요소 참조 오류 해결 - 차트 높이 무한 증가 문제 해결 - 날짜 표시 형식 단순화 - 작업보고서 저장 validation 오류 수정
321 lines
8.6 KiB
JavaScript
321 lines
8.6 KiB
JavaScript
// 작업 관리 페이지 JavaScript
|
|
|
|
// 전역 변수
|
|
let statsData = {
|
|
projects: 0,
|
|
workers: 0,
|
|
tasks: 0,
|
|
codeTypes: 0
|
|
};
|
|
|
|
// 페이지 초기화
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
console.log('🔧 작업 관리 페이지 초기화 시작');
|
|
|
|
initializePage();
|
|
loadStatistics();
|
|
loadRecentActivity();
|
|
});
|
|
|
|
// 페이지 초기화
|
|
function initializePage() {
|
|
// 시간 업데이트 시작
|
|
updateCurrentTime();
|
|
setInterval(updateCurrentTime, 1000);
|
|
|
|
// 사용자 정보 업데이트
|
|
updateUserInfo();
|
|
|
|
// 프로필 메뉴 토글
|
|
setupProfileMenu();
|
|
|
|
// 로그아웃 버튼
|
|
setupLogoutButton();
|
|
}
|
|
|
|
// 현재 시간 업데이트
|
|
function updateCurrentTime() {
|
|
const now = new Date();
|
|
const timeString = now.toLocaleTimeString('ko-KR', {
|
|
hour12: false,
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
second: '2-digit'
|
|
});
|
|
|
|
const timeElement = document.getElementById('timeValue');
|
|
if (timeElement) {
|
|
timeElement.textContent = timeString;
|
|
}
|
|
}
|
|
|
|
// 사용자 정보 업데이트
|
|
function updateUserInfo() {
|
|
let userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}');
|
|
let authUser = JSON.parse(localStorage.getItem('user') || '{}');
|
|
|
|
const finalUserInfo = {
|
|
worker_name: userInfo.worker_name || authUser.username || authUser.worker_name,
|
|
job_type: userInfo.job_type || authUser.role || authUser.job_type,
|
|
username: authUser.username || userInfo.username
|
|
};
|
|
|
|
const userNameElement = document.getElementById('userName');
|
|
const userRoleElement = document.getElementById('userRole');
|
|
const userInitialElement = document.getElementById('userInitial');
|
|
|
|
if (userNameElement) {
|
|
userNameElement.textContent = finalUserInfo.worker_name || '사용자';
|
|
}
|
|
|
|
if (userRoleElement) {
|
|
const roleMap = {
|
|
'leader': '그룹장',
|
|
'worker': '작업자',
|
|
'admin': '관리자',
|
|
'system': '시스템 관리자'
|
|
};
|
|
userRoleElement.textContent = roleMap[finalUserInfo.job_type] || finalUserInfo.job_type || '작업자';
|
|
}
|
|
|
|
if (userInitialElement) {
|
|
const name = finalUserInfo.worker_name || '사용자';
|
|
userInitialElement.textContent = name.charAt(0);
|
|
}
|
|
}
|
|
|
|
// 프로필 메뉴 설정
|
|
function setupProfileMenu() {
|
|
const userProfile = document.getElementById('userProfile');
|
|
const profileMenu = document.getElementById('profileMenu');
|
|
|
|
if (userProfile && profileMenu) {
|
|
userProfile.addEventListener('click', function(e) {
|
|
e.stopPropagation();
|
|
const isVisible = profileMenu.style.display === 'block';
|
|
profileMenu.style.display = isVisible ? 'none' : 'block';
|
|
});
|
|
|
|
// 외부 클릭 시 메뉴 닫기
|
|
document.addEventListener('click', function() {
|
|
profileMenu.style.display = 'none';
|
|
});
|
|
}
|
|
}
|
|
|
|
// 로그아웃 버튼 설정
|
|
function setupLogoutButton() {
|
|
const logoutBtn = document.getElementById('logoutBtn');
|
|
if (logoutBtn) {
|
|
logoutBtn.addEventListener('click', function() {
|
|
if (confirm('로그아웃 하시겠습니까?')) {
|
|
localStorage.removeItem('token');
|
|
localStorage.removeItem('user');
|
|
localStorage.removeItem('userInfo');
|
|
window.location.href = '/index.html';
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// 통계 데이터 로드
|
|
async function loadStatistics() {
|
|
try {
|
|
console.log('📊 통계 데이터 로딩 시작');
|
|
|
|
// 프로젝트 수 조회
|
|
try {
|
|
const projectsResponse = await apiCall('/projects', 'GET');
|
|
if (projectsResponse && Array.isArray(projectsResponse)) {
|
|
statsData.projects = projectsResponse.length;
|
|
updateStatDisplay('projectCount', statsData.projects);
|
|
}
|
|
} catch (error) {
|
|
console.warn('프로젝트 통계 로드 실패:', error);
|
|
updateStatDisplay('projectCount', '오류');
|
|
}
|
|
|
|
// 작업자 수 조회
|
|
try {
|
|
const workersResponse = await apiCall('/workers', 'GET');
|
|
if (workersResponse && Array.isArray(workersResponse)) {
|
|
const activeWorkers = workersResponse.filter(w => w.status === 'active');
|
|
statsData.workers = activeWorkers.length;
|
|
updateStatDisplay('workerCount', statsData.workers);
|
|
}
|
|
} catch (error) {
|
|
console.warn('작업자 통계 로드 실패:', error);
|
|
updateStatDisplay('workerCount', '오류');
|
|
}
|
|
|
|
// 작업 유형 수 조회
|
|
try {
|
|
const tasksResponse = await apiCall('/tasks', 'GET');
|
|
if (tasksResponse && Array.isArray(tasksResponse)) {
|
|
const activeTasks = tasksResponse.filter(t => t.is_active);
|
|
statsData.tasks = activeTasks.length;
|
|
updateStatDisplay('taskCount', statsData.tasks);
|
|
}
|
|
} catch (error) {
|
|
console.warn('작업 유형 통계 로드 실패:', error);
|
|
updateStatDisplay('taskCount', '오류');
|
|
}
|
|
|
|
// 코드 타입 수 조회 (임시로 고정값)
|
|
statsData.codeTypes = 3; // ISSUE_TYPE, ERROR_TYPE, WORK_STATUS
|
|
updateStatDisplay('codeTypeCount', statsData.codeTypes);
|
|
|
|
console.log('✅ 통계 데이터 로딩 완료:', statsData);
|
|
|
|
} catch (error) {
|
|
console.error('통계 데이터 로딩 오류:', error);
|
|
}
|
|
}
|
|
|
|
// 통계 표시 업데이트
|
|
function updateStatDisplay(elementId, value) {
|
|
const element = document.getElementById(elementId);
|
|
if (element) {
|
|
element.textContent = value;
|
|
|
|
// 애니메이션 효과
|
|
element.style.transform = 'scale(1.1)';
|
|
setTimeout(() => {
|
|
element.style.transform = 'scale(1)';
|
|
}, 200);
|
|
}
|
|
}
|
|
|
|
// 최근 활동 로드
|
|
async function loadRecentActivity() {
|
|
try {
|
|
console.log('📋 최근 활동 로딩 시작');
|
|
|
|
// 임시 데이터 (실제로는 API에서 가져와야 함)
|
|
const activities = [
|
|
{
|
|
type: 'project',
|
|
icon: '📁',
|
|
title: '효성화학 에틸렌 탱크 건설공사 프로젝트가 수정되었습니다',
|
|
user: '김두수',
|
|
time: '2시간 전'
|
|
},
|
|
{
|
|
type: 'worker',
|
|
icon: '👥',
|
|
title: '새로운 작업자가 등록되었습니다',
|
|
user: '관리자',
|
|
time: '1일 전'
|
|
},
|
|
{
|
|
type: 'task',
|
|
icon: '📋',
|
|
title: '작업 유형이 업데이트되었습니다',
|
|
user: '김두수',
|
|
time: '2일 전'
|
|
}
|
|
];
|
|
|
|
renderActivityList(activities);
|
|
|
|
} catch (error) {
|
|
console.error('최근 활동 로딩 오류:', error);
|
|
}
|
|
}
|
|
|
|
// 활동 목록 렌더링
|
|
function renderActivityList(activities) {
|
|
const activityList = document.getElementById('activityList');
|
|
if (!activityList) return;
|
|
|
|
const activitiesHtml = activities.map(activity => `
|
|
<div class="activity-item">
|
|
<div class="activity-icon">${activity.icon}</div>
|
|
<div class="activity-content">
|
|
<div class="activity-title">${activity.title}</div>
|
|
<div class="activity-meta">
|
|
<span class="activity-user">${activity.user}</span>
|
|
<span class="activity-time">${activity.time}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
|
|
activityList.innerHTML = activitiesHtml;
|
|
}
|
|
|
|
// 페이지 네비게이션
|
|
function navigateToPage(url) {
|
|
console.log(`🔗 페이지 이동: ${url}`);
|
|
|
|
// 로딩 효과
|
|
const card = event.currentTarget;
|
|
const originalContent = card.innerHTML;
|
|
|
|
card.style.opacity = '0.7';
|
|
card.style.pointerEvents = 'none';
|
|
|
|
// 잠시 후 페이지 이동
|
|
setTimeout(() => {
|
|
window.location.href = url;
|
|
}, 300);
|
|
}
|
|
|
|
// 토스트 메시지 표시
|
|
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: '1000',
|
|
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.navigateToPage = navigateToPage;
|
|
window.loadRecentActivity = loadRecentActivity;
|