feat: 작업 분석 시스템 및 관리 기능 대폭 개선

 새로운 기능:
- 작업 분석 페이지 구현 (기간별, 프로젝트별, 작업자별, 오류별)
- 개별 분석 실행 버튼으로 API 부하 최적화
- 연차/휴무 집계 방식 개선 (주말 제외, 작업내용 통합)
- 프로젝트 관리 시스템 (활성화/비활성화)
- 작업자 관리 시스템 (CRUD 기능)
- 코드 관리 시스템 (작업유형, 작업상태, 오류유형)

🎨 UI/UX 개선:
- 기간별 작업 현황을 테이블 형태로 변경
- 작업자별 rowspan 그룹화로 가독성 향상
- 연차/휴무 프로젝트 하단 배치 및 시각적 구분
- 기간 확정 시스템으로 사용자 경험 개선
- 반응형 디자인 적용

🔧 기술적 개선:
- Rate Limiting 제거 (내부 시스템 최적화)
- 주말 연차/휴무 자동 제외 로직
- 작업공수 계산 정확도 향상
- 데이터베이스 마이그레이션 추가
- API 엔드포인트 확장 및 최적화

🐛 버그 수정:
- projectSelect 요소 참조 오류 해결
- 차트 높이 무한 증가 문제 해결
- 날짜 표시 형식 단순화
- 작업보고서 저장 validation 오류 수정
This commit is contained in:
Hyungi Ahn
2025-11-04 16:56:47 +09:00
parent 746e09420b
commit de427c457b
46 changed files with 10912 additions and 530 deletions

View File

@@ -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;