feat: 작업 분석 시스템 및 관리 기능 대폭 개선
✨ 새로운 기능: - 작업 분석 페이지 구현 (기간별, 프로젝트별, 작업자별, 오류별) - 개별 분석 실행 버튼으로 API 부하 최적화 - 연차/휴무 집계 방식 개선 (주말 제외, 작업내용 통합) - 프로젝트 관리 시스템 (활성화/비활성화) - 작업자 관리 시스템 (CRUD 기능) - 코드 관리 시스템 (작업유형, 작업상태, 오류유형) 🎨 UI/UX 개선: - 기간별 작업 현황을 테이블 형태로 변경 - 작업자별 rowspan 그룹화로 가독성 향상 - 연차/휴무 프로젝트 하단 배치 및 시각적 구분 - 기간 확정 시스템으로 사용자 경험 개선 - 반응형 디자인 적용 🔧 기술적 개선: - Rate Limiting 제거 (내부 시스템 최적화) - 주말 연차/휴무 자동 제외 로직 - 작업공수 계산 정확도 향상 - 데이터베이스 마이그레이션 추가 - API 엔드포인트 확장 및 최적화 🐛 버그 수정: - projectSelect 요소 참조 오류 해결 - 차트 높이 무한 증가 문제 해결 - 날짜 표시 형식 단순화 - 작업보고서 저장 validation 오류 수정
This commit is contained in:
@@ -5,6 +5,8 @@ let currentDate = new Date();
|
||||
let monthlyData = {}; // 월별 데이터 캐시
|
||||
// 작업자 데이터는 allWorkers 변수 사용
|
||||
let currentModalDate = null;
|
||||
let currentEditingWork = null;
|
||||
let existingWorks = [];
|
||||
|
||||
// DOM 요소
|
||||
const elements = {
|
||||
@@ -230,8 +232,8 @@ async function loadMonthlyWorkDataFallback(year, month) {
|
||||
console.log(`📋 ${monthKey} 로딩 진행률: ${loadedCount}/${totalDays}`);
|
||||
}
|
||||
|
||||
// API 부하 방지를 위한 지연 (100ms)
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
// API 부하 방지를 위한 지연 (500ms)
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`${dateStr} 데이터 로딩 실패:`, error.message);
|
||||
@@ -846,14 +848,20 @@ async function openWorkEntryModal(workerId, workerName, date) {
|
||||
}
|
||||
|
||||
// 모달 제목 및 정보 설정
|
||||
titleElement.textContent = `${workerName} - 작업 입력`;
|
||||
titleElement.textContent = `${workerName} - 작업 관리`;
|
||||
workerNameDisplay.value = workerName;
|
||||
workerIdInput.value = workerId;
|
||||
workDateInput.value = date;
|
||||
|
||||
// 기존 작업 데이터 로드
|
||||
await loadExistingWorks(workerId, date);
|
||||
|
||||
// 프로젝트 및 상태 데이터 로드
|
||||
await loadModalData();
|
||||
|
||||
// 기본적으로 기존 작업 탭 활성화
|
||||
switchTab('existing');
|
||||
|
||||
// 모달 표시
|
||||
modal.style.display = 'flex';
|
||||
document.body.style.overflow = 'hidden';
|
||||
@@ -867,8 +875,8 @@ async function openWorkEntryModal(workerId, workerName, date) {
|
||||
// 모달 데이터 로드 (프로젝트, 작업 상태)
|
||||
async function loadModalData() {
|
||||
try {
|
||||
// 프로젝트 목록 로드
|
||||
const projectsResponse = await window.apiCall('/projects');
|
||||
// 활성 프로젝트 목록 로드
|
||||
const projectsResponse = await window.apiCall('/projects/active/list');
|
||||
const projects = Array.isArray(projectsResponse) ? projectsResponse : (projectsResponse.data || []);
|
||||
|
||||
const projectSelect = document.getElementById('projectSelect');
|
||||
@@ -960,24 +968,64 @@ async function saveWorkEntry() {
|
||||
const workData = {
|
||||
worker_id: document.getElementById('workerId').value,
|
||||
project_id: document.getElementById('projectSelect').value,
|
||||
work_type_id: document.getElementById('workTypeSelect').value, // 추가된 필드
|
||||
work_hours: document.getElementById('workHours').value,
|
||||
work_status_id: document.getElementById('workStatusSelect').value,
|
||||
error_type_id: document.getElementById('errorTypeSelect')?.value || null, // 추가된 필드
|
||||
description: document.getElementById('workDescription').value,
|
||||
report_date: document.getElementById('workDate').value
|
||||
};
|
||||
|
||||
const editingWorkId = document.getElementById('editingWorkId').value;
|
||||
|
||||
// 필수 필드 검증
|
||||
if (!workData.project_id || !workData.work_hours || !workData.work_status_id) {
|
||||
if (!workData.project_id || !workData.work_type_id || !workData.work_hours || !workData.work_status_id) {
|
||||
showToast('필수 항목을 모두 입력해주세요.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// API 호출
|
||||
const response = await window.apiCall('/daily-work-reports', 'POST', workData);
|
||||
// API 호출 (수정 또는 신규)
|
||||
let response;
|
||||
if (editingWorkId) {
|
||||
// 수정 모드 - 서버가 기대하는 형태로 데이터 변환
|
||||
const updateData = {
|
||||
project_id: workData.project_id,
|
||||
work_type_id: workData.work_type_id, // 실제 테이블 컬럼명 사용
|
||||
work_hours: workData.work_hours,
|
||||
work_status_id: workData.work_status_id, // 실제 테이블 컬럼명 사용
|
||||
error_type_id: workData.error_type_id // 실제 테이블 컬럼명 사용
|
||||
};
|
||||
|
||||
console.log('🔄 수정용 서버로 전송할 데이터:', updateData);
|
||||
response = await window.apiCall(`/daily-work-reports/${editingWorkId}`, 'PUT', updateData);
|
||||
} else {
|
||||
// 신규 추가 모드 - 서버가 기대하는 형태로 데이터 변환
|
||||
const serverData = {
|
||||
report_date: workData.report_date,
|
||||
worker_id: workData.worker_id,
|
||||
work_entries: [{
|
||||
project_id: workData.project_id,
|
||||
task_id: workData.work_type_id, // work_type_id를 task_id로 매핑
|
||||
work_hours: workData.work_hours,
|
||||
work_status_id: workData.work_status_id,
|
||||
error_type_id: workData.error_type_id,
|
||||
description: workData.description
|
||||
}]
|
||||
};
|
||||
|
||||
console.log('🔄 서버로 전송할 데이터:', serverData);
|
||||
response = await window.apiCall('/daily-work-reports', 'POST', serverData);
|
||||
}
|
||||
|
||||
if (response.success || response.id) {
|
||||
showToast('작업이 성공적으로 저장되었습니다.', 'success');
|
||||
closeWorkEntryModal();
|
||||
const action = editingWorkId ? '수정' : '저장';
|
||||
showToast(`작업이 성공적으로 ${action}되었습니다.`, 'success');
|
||||
|
||||
// 기존 작업 목록 새로고침
|
||||
await loadExistingWorks(workData.worker_id, workData.report_date);
|
||||
|
||||
// 기존 작업 탭으로 전환
|
||||
switchTab('existing');
|
||||
|
||||
// 캘린더 새로고침
|
||||
await renderCalendar();
|
||||
@@ -987,7 +1035,8 @@ async function saveWorkEntry() {
|
||||
await openDailyWorkModal(currentModalDate);
|
||||
}
|
||||
} else {
|
||||
throw new Error(response.message || '저장에 실패했습니다.');
|
||||
const action = editingWorkId ? '수정' : '저장';
|
||||
throw new Error(response.message || `${action}에 실패했습니다.`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
@@ -1025,32 +1074,51 @@ function updateCurrentTime() {
|
||||
|
||||
// 사용자 정보 업데이트 함수
|
||||
function updateUserInfo() {
|
||||
const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}');
|
||||
// auth-check.js에서 사용하는 'user' 키와 기존 'userInfo' 키 모두 확인
|
||||
let userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}');
|
||||
let authUser = JSON.parse(localStorage.getItem('user') || '{}');
|
||||
|
||||
console.log('👤 localStorage userInfo:', userInfo);
|
||||
console.log('👤 localStorage user (auth):', authUser);
|
||||
|
||||
// 두 소스에서 사용자 정보 통합
|
||||
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
|
||||
};
|
||||
|
||||
console.log('👤 최종 사용자 정보:', finalUserInfo);
|
||||
|
||||
const userNameElement = document.getElementById('userName');
|
||||
const userRoleElement = document.getElementById('userRole');
|
||||
const userInitialElement = document.getElementById('userInitial');
|
||||
|
||||
if (userNameElement) {
|
||||
if (userInfo.worker_name) {
|
||||
userNameElement.textContent = userInfo.worker_name;
|
||||
if (finalUserInfo.worker_name) {
|
||||
userNameElement.textContent = finalUserInfo.worker_name;
|
||||
} else {
|
||||
userNameElement.textContent = '사용자';
|
||||
}
|
||||
}
|
||||
|
||||
if (userRoleElement) {
|
||||
if (userInfo.job_type) {
|
||||
userRoleElement.textContent = userInfo.job_type;
|
||||
if (finalUserInfo.job_type) {
|
||||
// role을 한글로 변환
|
||||
const roleMap = {
|
||||
'leader': '그룹장',
|
||||
'worker': '작업자',
|
||||
'admin': '관리자'
|
||||
};
|
||||
userRoleElement.textContent = roleMap[finalUserInfo.job_type] || finalUserInfo.job_type;
|
||||
} else {
|
||||
userRoleElement.textContent = '작업자';
|
||||
}
|
||||
}
|
||||
|
||||
if (userInitialElement) {
|
||||
if (userInfo.worker_name) {
|
||||
userInitialElement.textContent = userInfo.worker_name.charAt(0);
|
||||
if (finalUserInfo.worker_name) {
|
||||
userInitialElement.textContent = finalUserInfo.worker_name.charAt(0);
|
||||
} else {
|
||||
userInitialElement.textContent = '사';
|
||||
}
|
||||
@@ -1098,7 +1166,494 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
initializePage();
|
||||
});
|
||||
|
||||
// ========== 작업 입력 모달 개선 기능들 ==========
|
||||
|
||||
// 탭 전환 함수
|
||||
function switchTab(tabName) {
|
||||
// 모든 탭 버튼 비활성화
|
||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
});
|
||||
|
||||
// 모든 탭 콘텐츠 숨기기
|
||||
document.querySelectorAll('.tab-content').forEach(content => {
|
||||
content.classList.remove('active');
|
||||
});
|
||||
|
||||
// 선택된 탭 활성화
|
||||
const selectedTabBtn = document.querySelector(`[data-tab="${tabName}"]`);
|
||||
const selectedTabContent = document.getElementById(`${tabName}WorkTab`);
|
||||
|
||||
if (selectedTabBtn) selectedTabBtn.classList.add('active');
|
||||
if (selectedTabContent) selectedTabContent.classList.add('active');
|
||||
|
||||
// 새 작업 탭으로 전환 시 폼 초기화
|
||||
if (tabName === 'new') {
|
||||
resetWorkForm();
|
||||
}
|
||||
}
|
||||
|
||||
// 기존 작업 데이터 로드
|
||||
async function loadExistingWorks(workerId, date) {
|
||||
try {
|
||||
console.log(`📋 기존 작업 로드: 작업자 ${workerId}, 날짜 ${date}`);
|
||||
|
||||
let workerWorks = [];
|
||||
|
||||
try {
|
||||
// 방법 1: 날짜별 작업 보고서 조회 시도
|
||||
const response = await apiCall(`/daily-work-reports/date/${date}`, 'GET');
|
||||
|
||||
if (response && Array.isArray(response)) {
|
||||
console.log(`📊 방법1 - 전체 응답 데이터 (${response.length}건):`, response);
|
||||
|
||||
// 김두수(작업자 ID 1)의 모든 작업 확인
|
||||
const allWorkerOneWorks = response.filter(work => work.worker_id == 1);
|
||||
console.log(`🔍 김두수(ID=1)의 모든 작업 (${allWorkerOneWorks.length}건):`, allWorkerOneWorks);
|
||||
|
||||
// 해당 작업자의 작업만 필터링
|
||||
workerWorks = response.filter(work => {
|
||||
const isMatch = work.worker_id == workerId;
|
||||
console.log(`🔍 작업 필터링: ID=${work.id}, worker_id=${work.worker_id}, 대상=${workerId}, 일치=${isMatch}`);
|
||||
return isMatch;
|
||||
});
|
||||
|
||||
console.log(`✅ 방법1 성공: 작업자 ${workerId}의 ${date} 작업 ${workerWorks.length}건 로드`);
|
||||
console.log('📋 필터링된 작업 목록:', workerWorks);
|
||||
}
|
||||
} catch (dateApiError) {
|
||||
console.warn('📅 날짜별 API 실패, 범위 조회 시도:', dateApiError.message);
|
||||
|
||||
try {
|
||||
// 방법 2: 범위 조회로 fallback (해당 날짜만)
|
||||
const response = await apiCall(`/daily-work-reports?start=${date}&end=${date}`, 'GET');
|
||||
|
||||
if (response && Array.isArray(response)) {
|
||||
console.log(`📊 방법2 - 전체 응답 데이터 (${response.length}건):`, response);
|
||||
|
||||
workerWorks = response.filter(work => {
|
||||
const isMatch = work.worker_id == workerId;
|
||||
console.log(`🔍 작업 필터링: ID=${work.id}, worker_id=${work.worker_id}, 대상=${workerId}, 일치=${isMatch}`);
|
||||
return isMatch;
|
||||
});
|
||||
|
||||
console.log(`✅ 방법2 성공: 작업자 ${workerId}의 ${date} 작업 ${workerWorks.length}건 로드`);
|
||||
console.log('📋 필터링된 작업 목록:', workerWorks);
|
||||
}
|
||||
} catch (rangeApiError) {
|
||||
console.warn('📊 범위 조회도 실패:', rangeApiError.message);
|
||||
// 최종적으로 빈 배열로 처리
|
||||
workerWorks = [];
|
||||
}
|
||||
}
|
||||
|
||||
existingWorks = workerWorks;
|
||||
renderExistingWorks();
|
||||
updateTabCounter();
|
||||
|
||||
} catch (error) {
|
||||
console.error('기존 작업 로드 오류:', error);
|
||||
existingWorks = [];
|
||||
renderExistingWorks();
|
||||
updateTabCounter();
|
||||
}
|
||||
}
|
||||
|
||||
// 기존 작업 목록 렌더링
|
||||
function renderExistingWorks() {
|
||||
console.log('🎨 작업 목록 렌더링 시작:', existingWorks);
|
||||
|
||||
const existingWorkList = document.getElementById('existingWorkList');
|
||||
const noExistingWork = document.getElementById('noExistingWork');
|
||||
const totalWorkCount = document.getElementById('totalWorkCount');
|
||||
const totalWorkHours = document.getElementById('totalWorkHours');
|
||||
|
||||
if (!existingWorkList) {
|
||||
console.error('❌ existingWorkList 요소를 찾을 수 없습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 총 작업 시간 계산
|
||||
const totalHours = existingWorks.reduce((sum, work) => sum + parseFloat(work.work_hours || 0), 0);
|
||||
|
||||
console.log(`📊 작업 통계: ${existingWorks.length}건, 총 ${totalHours}시간`);
|
||||
|
||||
// 요약 정보 업데이트
|
||||
if (totalWorkCount) totalWorkCount.textContent = existingWorks.length;
|
||||
if (totalWorkHours) totalWorkHours.textContent = totalHours.toFixed(1);
|
||||
|
||||
if (existingWorks.length === 0) {
|
||||
existingWorkList.style.display = 'none';
|
||||
if (noExistingWork) noExistingWork.style.display = 'block';
|
||||
console.log('ℹ️ 작업이 없어서 빈 상태 표시');
|
||||
return;
|
||||
}
|
||||
|
||||
existingWorkList.style.display = 'block';
|
||||
if (noExistingWork) noExistingWork.style.display = 'none';
|
||||
|
||||
// 각 작업 데이터 상세 로그
|
||||
existingWorks.forEach((work, index) => {
|
||||
console.log(`📋 작업 ${index + 1}:`, {
|
||||
id: work.id,
|
||||
project_name: work.project_name,
|
||||
work_hours: work.work_hours,
|
||||
work_status_name: work.work_status_name,
|
||||
created_at: work.created_at,
|
||||
description: work.description
|
||||
});
|
||||
});
|
||||
|
||||
// 작업 목록 HTML 생성
|
||||
const worksHtml = existingWorks.map((work, index) => {
|
||||
const workItemHtml = `
|
||||
<div class="work-item" data-work-id="${work.id}">
|
||||
<div class="work-item-header">
|
||||
<div class="work-item-info">
|
||||
<div class="work-item-title">${work.project_name || '프로젝트 정보 없음'}</div>
|
||||
<div class="work-item-meta">
|
||||
<span>⏰ ${work.work_hours}시간</span>
|
||||
<span>📊 ${work.work_status_name || '상태 정보 없음'}</span>
|
||||
<span>📅 ${new Date(work.created_at).toLocaleString('ko-KR')}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="work-item-actions">
|
||||
<button class="btn-edit" onclick="editWork(${work.id})" title="수정">
|
||||
✏️ 수정
|
||||
</button>
|
||||
<button class="btn-delete" onclick="confirmDeleteWork(${work.id})" title="삭제">
|
||||
🗑️ 삭제
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
${work.description ? `<div class="work-item-description">${work.description}</div>` : ''}
|
||||
</div>`;
|
||||
|
||||
console.log(`🏗️ 작업 ${index + 1} HTML 생성 완료`);
|
||||
return workItemHtml;
|
||||
}).join('');
|
||||
|
||||
console.log(`📝 최종 HTML 길이: ${worksHtml.length} 문자`);
|
||||
console.log('🎯 HTML 내용 미리보기:', worksHtml.substring(0, 200) + '...');
|
||||
|
||||
existingWorkList.innerHTML = worksHtml;
|
||||
|
||||
// 렌더링 후 실제 DOM 요소 확인
|
||||
const renderedItems = existingWorkList.querySelectorAll('.work-item');
|
||||
console.log(`✅ 렌더링 완료: ${renderedItems.length}개 작업 아이템이 DOM에 추가됨`);
|
||||
|
||||
if (renderedItems.length !== existingWorks.length) {
|
||||
console.error(`⚠️ 렌더링 불일치: 데이터 ${existingWorks.length}건 vs DOM ${renderedItems.length}개`);
|
||||
}
|
||||
}
|
||||
|
||||
// 탭 카운터 업데이트
|
||||
function updateTabCounter() {
|
||||
const existingTabBtn = document.querySelector('[data-tab="existing"]');
|
||||
if (existingTabBtn) {
|
||||
existingTabBtn.innerHTML = `📋 기존 작업 (${existingWorks.length}건)`;
|
||||
}
|
||||
}
|
||||
|
||||
// 작업 수정
|
||||
function editWork(workId) {
|
||||
const work = existingWorks.find(w => w.id === workId);
|
||||
if (!work) {
|
||||
showToast('작업 정보를 찾을 수 없습니다.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// 수정 모드로 전환
|
||||
currentEditingWork = work;
|
||||
|
||||
// 새 작업 탭으로 전환
|
||||
switchTab('new');
|
||||
|
||||
// 폼에 기존 데이터 채우기
|
||||
document.getElementById('editingWorkId').value = work.id;
|
||||
document.getElementById('projectSelect').value = work.project_id;
|
||||
document.getElementById('workHours').value = work.work_hours;
|
||||
document.getElementById('workStatusSelect').value = work.work_status_id;
|
||||
document.getElementById('workDescription').value = work.description || '';
|
||||
|
||||
// UI 업데이트
|
||||
document.getElementById('workContentTitle').textContent = '작업 내용 수정';
|
||||
document.getElementById('saveWorkBtn').innerHTML = '💾 수정 완료';
|
||||
document.getElementById('deleteWorkBtn').style.display = 'inline-block';
|
||||
|
||||
// 휴가 섹션 숨기기 (수정 시에는 휴가 처리 불가)
|
||||
document.getElementById('vacationSection').style.display = 'none';
|
||||
}
|
||||
|
||||
// 작업 삭제 확인
|
||||
function confirmDeleteWork(workId) {
|
||||
const work = existingWorks.find(w => w.id === workId);
|
||||
if (!work) {
|
||||
showToast('작업 정보를 찾을 수 없습니다.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (confirm(`"${work.project_name}" 작업을 정말 삭제하시겠습니까?\n\n⚠️ 삭제된 작업은 복구할 수 없습니다.`)) {
|
||||
deleteWorkById(workId);
|
||||
}
|
||||
}
|
||||
|
||||
// 작업 삭제 실행
|
||||
async function deleteWorkById(workId) {
|
||||
try {
|
||||
const response = await apiCall(`/daily-work-reports/${workId}`, 'DELETE');
|
||||
|
||||
if (response.success) {
|
||||
showToast('작업이 성공적으로 삭제되었습니다.', 'success');
|
||||
|
||||
// 기존 작업 목록 새로고침
|
||||
const workerId = document.getElementById('workerId').value;
|
||||
const date = document.getElementById('workDate').value;
|
||||
await loadExistingWorks(workerId, date);
|
||||
|
||||
// 현재 열린 모달이 있다면 새로고침
|
||||
if (currentModalDate) {
|
||||
await openDailyWorkModal(currentModalDate);
|
||||
}
|
||||
} else {
|
||||
showToast(response.message || '작업 삭제에 실패했습니다.', 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('작업 삭제 오류:', error);
|
||||
showToast('작업 삭제 중 오류가 발생했습니다.', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 작업 폼 초기화
|
||||
function resetWorkForm() {
|
||||
currentEditingWork = null;
|
||||
|
||||
// 폼 필드 초기화
|
||||
document.getElementById('editingWorkId').value = '';
|
||||
document.getElementById('projectSelect').value = '';
|
||||
document.getElementById('workHours').value = '';
|
||||
document.getElementById('workStatusSelect').value = '';
|
||||
document.getElementById('workDescription').value = '';
|
||||
|
||||
// UI 초기화
|
||||
document.getElementById('workContentTitle').textContent = '작업 내용';
|
||||
document.getElementById('saveWorkBtn').innerHTML = '💾 저장';
|
||||
document.getElementById('deleteWorkBtn').style.display = 'none';
|
||||
document.getElementById('vacationSection').style.display = 'block';
|
||||
}
|
||||
|
||||
// 작업 삭제 (수정 모드에서)
|
||||
function deleteWork() {
|
||||
if (currentEditingWork) {
|
||||
confirmDeleteWork(currentEditingWork.id);
|
||||
}
|
||||
}
|
||||
|
||||
// 휴가 처리 함수
|
||||
function handleVacation(vacationType) {
|
||||
const workHours = document.getElementById('workHours');
|
||||
const projectSelect = document.getElementById('projectSelect');
|
||||
const workTypeSelect = document.getElementById('workTypeSelect');
|
||||
const workStatusSelect = document.getElementById('workStatusSelect');
|
||||
const errorTypeSelect = document.getElementById('errorTypeSelect');
|
||||
const workDescription = document.getElementById('workDescription');
|
||||
|
||||
// 휴가 시간 설정
|
||||
const vacationHours = {
|
||||
'full': 8, // 연차
|
||||
'half': 4, // 반차
|
||||
'quarter': 2, // 반반차
|
||||
'early': 6 // 조퇴
|
||||
};
|
||||
|
||||
const vacationNames = {
|
||||
'full': '연차',
|
||||
'half': '반차',
|
||||
'quarter': '반반차',
|
||||
'early': '조퇴'
|
||||
};
|
||||
|
||||
// 시간 설정
|
||||
if (workHours) {
|
||||
workHours.value = vacationHours[vacationType] || 8;
|
||||
}
|
||||
|
||||
// 휴가용 기본값 설정 (휴가 관련 항목 찾아서 자동 선택)
|
||||
if (projectSelect && projectSelect.options.length > 1) {
|
||||
// "휴가", "연차", "관리" 등의 키워드가 포함된 프로젝트 찾기
|
||||
let vacationProjectFound = false;
|
||||
for (let i = 1; i < projectSelect.options.length; i++) {
|
||||
const optionText = projectSelect.options[i].textContent.toLowerCase();
|
||||
if (optionText.includes('휴가') || optionText.includes('연차') || optionText.includes('관리')) {
|
||||
projectSelect.selectedIndex = i;
|
||||
vacationProjectFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!vacationProjectFound) {
|
||||
projectSelect.selectedIndex = 1; // 첫 번째 프로젝트 선택
|
||||
}
|
||||
}
|
||||
|
||||
if (workTypeSelect && workTypeSelect.options.length > 1) {
|
||||
// "휴가", "연차", "관리" 등의 키워드가 포함된 작업 유형 찾기
|
||||
let vacationWorkTypeFound = false;
|
||||
for (let i = 1; i < workTypeSelect.options.length; i++) {
|
||||
const optionText = workTypeSelect.options[i].textContent.toLowerCase();
|
||||
if (optionText.includes('휴가') || optionText.includes('연차') || optionText.includes('관리')) {
|
||||
workTypeSelect.selectedIndex = i;
|
||||
vacationWorkTypeFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!vacationWorkTypeFound) {
|
||||
workTypeSelect.selectedIndex = 1; // 첫 번째 작업 유형 선택
|
||||
}
|
||||
}
|
||||
|
||||
if (workStatusSelect && workStatusSelect.options.length > 1) {
|
||||
// "정상", "완료" 등의 키워드가 포함된 상태 찾기
|
||||
let normalStatusFound = false;
|
||||
for (let i = 1; i < workStatusSelect.options.length; i++) {
|
||||
const optionText = workStatusSelect.options[i].textContent.toLowerCase();
|
||||
if (optionText.includes('정상') || optionText.includes('완료') || optionText.includes('normal')) {
|
||||
workStatusSelect.selectedIndex = i;
|
||||
normalStatusFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!normalStatusFound) {
|
||||
workStatusSelect.selectedIndex = 1; // 첫 번째 상태 선택
|
||||
}
|
||||
}
|
||||
|
||||
// 오류 유형은 선택하지 않음
|
||||
if (errorTypeSelect) {
|
||||
errorTypeSelect.selectedIndex = 0;
|
||||
}
|
||||
|
||||
// 작업 설명에 휴가 정보 입력
|
||||
if (workDescription) {
|
||||
workDescription.value = `${vacationNames[vacationType]} (${vacationHours[vacationType]}시간)`;
|
||||
}
|
||||
|
||||
// 사용자에게 알림
|
||||
showToast(`${vacationNames[vacationType]} (${vacationHours[vacationType]}시간)이 설정되었습니다.`, 'success');
|
||||
}
|
||||
|
||||
// 탭 전환 함수
|
||||
function switchTab(tabName) {
|
||||
// 탭 버튼 활성화 상태 변경
|
||||
document.querySelectorAll('.tab-btn').forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
});
|
||||
document.querySelector(`[data-tab="${tabName}"]`).classList.add('active');
|
||||
|
||||
// 탭 콘텐츠 표시/숨김
|
||||
document.querySelectorAll('.tab-content').forEach(content => {
|
||||
content.classList.remove('active');
|
||||
});
|
||||
document.getElementById(`${tabName}WorkTab`).classList.add('active');
|
||||
|
||||
// 새 작업 탭으로 전환할 때 드롭다운 데이터 로드
|
||||
if (tabName === 'new') {
|
||||
loadDropdownData();
|
||||
}
|
||||
}
|
||||
|
||||
// 전역 함수로 노출
|
||||
// 드롭다운 로딩 함수들
|
||||
async function loadDropdownData() {
|
||||
try {
|
||||
console.log('🔄 드롭다운 데이터 로딩 시작...');
|
||||
|
||||
// 프로젝트 로드
|
||||
console.log('📡 프로젝트 로딩 중...');
|
||||
const projectsRes = await window.apiCall('/projects/active/list');
|
||||
const projects = Array.isArray(projectsRes) ? projectsRes : (projectsRes.data || []);
|
||||
console.log('📁 로드된 프로젝트:', projects.length, '개');
|
||||
|
||||
const projectSelect = document.getElementById('projectSelect');
|
||||
if (projectSelect) {
|
||||
projectSelect.innerHTML = '<option value="">프로젝트를 선택하세요</option>';
|
||||
projects.forEach(project => {
|
||||
const option = document.createElement('option');
|
||||
option.value = project.project_id;
|
||||
option.textContent = project.project_name;
|
||||
projectSelect.appendChild(option);
|
||||
});
|
||||
console.log('✅ 프로젝트 드롭다운 업데이트 완료');
|
||||
} else {
|
||||
console.error('❌ projectSelect 요소를 찾을 수 없음');
|
||||
}
|
||||
|
||||
// 작업 유형 로드
|
||||
console.log('📡 작업 유형 로딩 중...');
|
||||
const workTypesRes = await window.apiCall('/daily-work-reports/work-types');
|
||||
const workTypes = Array.isArray(workTypesRes) ? workTypesRes : (workTypesRes.data || []);
|
||||
console.log('🔧 로드된 작업 유형:', workTypes.length, '개');
|
||||
|
||||
const workTypeSelect = document.getElementById('workTypeSelect');
|
||||
if (workTypeSelect) {
|
||||
workTypeSelect.innerHTML = '<option value="">작업 유형을 선택하세요</option>';
|
||||
workTypes.forEach(workType => {
|
||||
const option = document.createElement('option');
|
||||
option.value = workType.id; // work_type_id → id
|
||||
option.textContent = workType.name; // work_type_name → name
|
||||
workTypeSelect.appendChild(option);
|
||||
});
|
||||
console.log('✅ 작업 유형 드롭다운 업데이트 완료');
|
||||
} else {
|
||||
console.error('❌ workTypeSelect 요소를 찾을 수 없음');
|
||||
}
|
||||
|
||||
// 작업 상태 로드
|
||||
console.log('📡 작업 상태 로딩 중...');
|
||||
const workStatusRes = await window.apiCall('/daily-work-reports/work-status-types');
|
||||
const workStatuses = Array.isArray(workStatusRes) ? workStatusRes : (workStatusRes.data || []);
|
||||
console.log('📊 로드된 작업 상태:', workStatuses.length, '개');
|
||||
|
||||
const workStatusSelect = document.getElementById('workStatusSelect');
|
||||
if (workStatusSelect) {
|
||||
workStatusSelect.innerHTML = '<option value="">상태를 선택하세요</option>';
|
||||
workStatuses.forEach(status => {
|
||||
const option = document.createElement('option');
|
||||
option.value = status.id; // work_status_id → id
|
||||
option.textContent = status.name; // status_name → name
|
||||
workStatusSelect.appendChild(option);
|
||||
});
|
||||
console.log('✅ 작업 상태 드롭다운 업데이트 완료');
|
||||
} else {
|
||||
console.error('❌ workStatusSelect 요소를 찾을 수 없음');
|
||||
}
|
||||
|
||||
// 오류 유형 로드
|
||||
console.log('📡 오류 유형 로딩 중...');
|
||||
const errorTypesRes = await window.apiCall('/daily-work-reports/error-types');
|
||||
const errorTypes = Array.isArray(errorTypesRes) ? errorTypesRes : (errorTypesRes.data || []);
|
||||
console.log('⚠️ 로드된 오류 유형:', errorTypes.length, '개');
|
||||
|
||||
const errorTypeSelect = document.getElementById('errorTypeSelect');
|
||||
if (errorTypeSelect) {
|
||||
errorTypeSelect.innerHTML = '<option value="">오류 유형 (선택사항)</option>';
|
||||
errorTypes.forEach(errorType => {
|
||||
const option = document.createElement('option');
|
||||
option.value = errorType.id; // error_type_id → id
|
||||
option.textContent = errorType.name; // error_type_name → name
|
||||
errorTypeSelect.appendChild(option);
|
||||
});
|
||||
console.log('✅ 오류 유형 드롭다운 업데이트 완료');
|
||||
} else {
|
||||
console.error('❌ errorTypeSelect 요소를 찾을 수 없음');
|
||||
}
|
||||
|
||||
console.log('🎉 모든 드롭다운 데이터 로딩 완료!');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 드롭다운 데이터 로딩 오류:', error);
|
||||
}
|
||||
}
|
||||
|
||||
window.openDailyWorkModal = openDailyWorkModal;
|
||||
window.closeDailyWorkModal = closeDailyWorkModal;
|
||||
window.openWorkerModal = openWorkerModal;
|
||||
@@ -1106,3 +1661,8 @@ window.openWorkEntryModal = openWorkEntryModal;
|
||||
window.closeWorkEntryModal = closeWorkEntryModal;
|
||||
window.handleVacation = handleVacation;
|
||||
window.saveWorkEntry = saveWorkEntry;
|
||||
window.switchTab = switchTab;
|
||||
window.editWork = editWork;
|
||||
window.confirmDeleteWork = confirmDeleteWork;
|
||||
window.deleteWork = deleteWork;
|
||||
window.loadDropdownData = loadDropdownData;
|
||||
|
||||
Reference in New Issue
Block a user