// 작업 현황 캘린더 JavaScript // 전역 변수 let currentDate = new Date(); let monthlyData = {}; // 월별 데이터 캐시 // 작업자 데이터는 allWorkers 변수 사용 let currentModalDate = null; // DOM 요소 const elements = { monthYearTitle: null, calendarDays: null, prevMonthBtn: null, nextMonthBtn: null, todayBtn: null, dailyWorkModal: null, modalTitle: null, modalTotalWorkers: null, modalTotalHours: null, modalTotalTasks: null, modalErrorCount: null, modalWorkersList: null, statusFilter: null, loadingSpinner: null }; // 초기화 document.addEventListener('DOMContentLoaded', async function() { console.log('🚀 작업 현황 캘린더 초기화 시작'); // DOM 요소 초기화 initializeElements(); // 이벤트 리스너 등록 setupEventListeners(); // 작업자 데이터 로드 (한 번만) await loadWorkersData(); // 현재 월 캘린더 렌더링 await renderCalendar(); console.log('✅ 작업 현황 캘린더 초기화 완료'); }); // DOM 요소 초기화 function initializeElements() { elements.monthYearTitle = document.getElementById('monthYearTitle'); elements.calendarDays = document.getElementById('calendarDays'); elements.prevMonthBtn = document.getElementById('prevMonthBtn'); elements.nextMonthBtn = document.getElementById('nextMonthBtn'); elements.todayBtn = document.getElementById('todayBtn'); elements.dailyWorkModal = document.getElementById('dailyWorkModal'); elements.modalTitle = document.getElementById('modalTitle'); elements.modalSummary = document.querySelector('.daily-summary'); // 요약 섹션 elements.modalTotalWorkers = document.getElementById('modalTotalWorkers'); elements.modalTotalHours = document.getElementById('modalTotalHours'); elements.modalTotalTasks = document.getElementById('modalTotalTasks'); elements.modalErrorCount = document.getElementById('modalErrorCount'); elements.modalWorkersList = document.getElementById('modalWorkersList'); elements.modalNoData = document.getElementById('modalNoData'); elements.statusFilter = document.getElementById('statusFilter'); elements.loadingSpinner = document.getElementById('loadingSpinner'); } // 이벤트 리스너 설정 function setupEventListeners() { elements.prevMonthBtn.addEventListener('click', () => { currentDate.setMonth(currentDate.getMonth() - 1); renderCalendar(); }); elements.nextMonthBtn.addEventListener('click', () => { currentDate.setMonth(currentDate.getMonth() + 1); renderCalendar(); }); elements.todayBtn.addEventListener('click', () => { currentDate = new Date(); renderCalendar(); }); elements.statusFilter.addEventListener('change', filterWorkersList); // 모달 외부 클릭 시 닫기 elements.dailyWorkModal.addEventListener('click', (e) => { if (e.target === elements.dailyWorkModal) { closeDailyWorkModal(); } }); // ESC 키로 모달 닫기 document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && elements.dailyWorkModal.style.display !== 'none') { closeDailyWorkModal(); } }); } // 작업자 데이터 로드 (캐시) async function loadWorkersData() { if (allWorkers.length > 0) return allWorkers; try { console.log('👥 작업자 데이터 로딩...'); const response = await window.apiCall('/workers'); allWorkers = Array.isArray(response) ? response : (response.data || []); console.log(`✅ 작업자 ${allWorkers.length}명 로드 완료`); return allWorkers; } catch (error) { console.error('작업자 데이터 로딩 오류:', error); showToast('작업자 데이터를 불러오는데 실패했습니다.', 'error'); return []; } } // 월별 작업 데이터 로드 (집계 테이블 사용으로 최적화) async function loadMonthlyWorkData(year, month) { const monthKey = `${year}-${String(month + 1).padStart(2, '0')}`; if (monthlyData[monthKey]) { console.log(`📋 캐시된 ${monthKey} 데이터 사용`); return monthlyData[monthKey]; } try { console.log(`📋 ${monthKey} 집계 데이터 로딩...`); // 새로운 월별 집계 API 사용 (단일 호출) const response = await window.apiCall(`/monthly-status/calendar?year=${year}&month=${month + 1}`); if (response.success) { const calendarData = response.data; console.log(`📊 ${monthKey} 집계 데이터:`, Object.keys(calendarData).length, '일'); // 날짜별 상태 데이터로 변환 const monthData = {}; // 해당 월의 모든 날짜 초기화 const firstDay = new Date(year, month, 1); const lastDay = new Date(year, month + 1, 0); const currentDay = new Date(firstDay); while (currentDay <= lastDay) { const dateStr = currentDay.toISOString().split('T')[0]; if (calendarData[dateStr]) { // 집계 데이터가 있는 경우 const dayData = calendarData[dateStr]; monthData[dateStr] = { hasData: dayData.workingWorkers > 0, hasIssues: dayData.hasIssues, hasErrors: dayData.hasErrors, hasOvertimeWarning: dayData.hasOvertimeWarning, totalWorkers: dayData.totalWorkers, workerCount: dayData.totalWorkers, workingWorkers: dayData.workingWorkers, incompleteWorkers: dayData.incompleteWorkers, partialWorkers: dayData.partialWorkers, errorWorkers: dayData.errorWorkers, overtimeWarningWorkers: dayData.overtimeWarningWorkers, totalHours: dayData.totalHours, totalTasks: dayData.totalTasks, errorCount: dayData.errorCount, lastUpdated: dayData.lastUpdated }; } else { // 집계 데이터가 없는 경우 (작업 없음) monthData[dateStr] = { hasData: false, hasIssues: false, hasErrors: false, workerCount: 0, workingWorkers: 0, incompleteWorkers: 0, partialWorkers: 0, errorWorkers: 0, totalHours: 0, totalTasks: 0, errorCount: 0 }; } currentDay.setDate(currentDay.getDate() + 1); } // 캐시에 저장 monthlyData[monthKey] = monthData; console.log(`✅ ${monthKey} 집계 데이터 로드 완료 (${Object.keys(monthData).length}일 데이터)`); console.log('📊 월별 데이터 샘플:', Object.entries(monthData).slice(0, 5)); return monthData; } else { throw new Error(response.message || '집계 데이터 조회 실패'); } } catch (error) { console.error(`${monthKey} 집계 데이터 로딩 오류:`, error); // 폴백: 기존 방식으로 순차 로딩 console.log(`📋 폴백: ${monthKey} 기존 방식 로딩 시작...`); return await loadMonthlyWorkDataFallback(year, month); } } // 폴백: 순차적 로딩 (지연 시간 포함) async function loadMonthlyWorkDataFallback(year, month) { const monthKey = `${year}-${String(month + 1).padStart(2, '0')}`; const monthData = {}; try { const firstDay = new Date(year, month, 1); const lastDay = new Date(year, month + 1, 0); const currentDay = new Date(firstDay); let loadedCount = 0; const totalDays = lastDay.getDate(); while (currentDay <= lastDay) { const dateStr = currentDay.toISOString().split('T')[0]; try { const response = await window.apiCall(`/daily-work-reports?date=${dateStr}&view_all=true`); monthData[dateStr] = Array.isArray(response) ? response : (response.data || []); loadedCount++; // 진행률 표시 if (loadedCount % 5 === 0) { console.log(`📋 ${monthKey} 로딩 진행률: ${loadedCount}/${totalDays}`); } // API 부하 방지를 위한 지연 (100ms) await new Promise(resolve => setTimeout(resolve, 100)); } catch (error) { console.warn(`${dateStr} 데이터 로딩 실패:`, error.message); monthData[dateStr] = []; } currentDay.setDate(currentDay.getDate() + 1); } // 캐시에 저장 monthlyData[monthKey] = monthData; console.log(`✅ ${monthKey} 순차 로딩 완료 (${loadedCount}/${totalDays}일)`); return monthData; } catch (error) { console.error(`${monthKey} 순차 로딩 오류:`, error); showToast('작업 데이터를 불러오는데 실패했습니다.', 'error'); return {}; } } // 캘린더 렌더링 async function renderCalendar() { const year = currentDate.getFullYear(); const month = currentDate.getMonth(); // 헤더 업데이트 const monthNames = ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월']; const monthText = `${year}년 ${monthNames[month]}`; elements.monthYearTitle.textContent = monthText; // 로딩 표시 showLoading(true); try { // 월별 데이터 로드 const monthData = await loadMonthlyWorkData(year, month); // 캘린더 날짜 생성 const firstDay = new Date(year, month, 1); const lastDay = new Date(year, month + 1, 0); const startDate = new Date(firstDay); startDate.setDate(startDate.getDate() - firstDay.getDay()); // 주의 시작일 (일요일) const today = new Date(); const todayStr = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`; let calendarHTML = ''; const currentDay = new Date(startDate); // 6주 * 7일 = 42일 렌더링 for (let i = 0; i < 42; i++) { // 로컬 시간대로 날짜 문자열 생성 (UTC 변환 문제 방지) const year = currentDay.getFullYear(); const month_num = String(currentDay.getMonth() + 1).padStart(2, '0'); const day_num = String(currentDay.getDate()).padStart(2, '0'); const dateStr = `${year}-${month_num}-${day_num}`; const dayNumber = currentDay.getDate(); const isCurrentMonth = currentDay.getMonth() === month; const isToday = dateStr === todayStr; const isSunday = currentDay.getDay() === 0; const isSaturday = currentDay.getDay() === 6; // 해당 날짜의 작업 데이터 (집계 데이터 구조) let dayWorkData = monthData[dateStr] || { hasData: false, hasIssues: false, hasErrors: false, workerCount: 0 }; // 실제 데이터 사용 (테스트 데이터 제거) const dayStatus = analyzeDayStatus(dayWorkData); // 디버깅: 상태가 있는 날짜만 로그 if (dayStatus.hasData || dayStatus.hasIssues || dayStatus.hasIncomplete || dayStatus.hasOvertimeWarning) { let statusText = '이상없음'; if (dayStatus.hasOvertimeWarning) statusText = '확인필요'; else if (dayStatus.hasIncomplete) statusText = '미입력'; else if (dayStatus.hasIssues) statusText = '부분입력'; console.log(`📅 ${dateStr} (${dayNumber}일):`, { 상태: statusText, 작업자수: dayStatus.workerCount, dayStatus, 원본데이터: dayWorkData }); } let dayClasses = ['calendar-day']; if (!isCurrentMonth) dayClasses.push('other-month'); if (isToday) dayClasses.push('today'); if (isSunday) dayClasses.push('sunday'); if (isSaturday) dayClasses.push('saturday'); if (isSunday || isSaturday) dayClasses.push('weekend'); // 문제가 있는지 확인 const hasAnyProblem = dayStatus.hasOvertimeWarning || dayStatus.hasIncomplete || dayStatus.hasIssues; // 문제가 없으면 초록색 배경 if (dayStatus.hasData && !hasAnyProblem) { dayClasses.push('has-normal'); // 이상없음 (초록) } // 문제가 있으면 범례 아이콘들을 그대로 표시 let statusIcons = ''; if (hasAnyProblem) { // 범례와 동일한 아이콘들 표시 if (dayStatus.hasOvertimeWarning) { statusIcons += '
'; } if (dayStatus.hasIncomplete) { statusIcons += '
'; } if (dayStatus.hasIssues) { statusIcons += '
'; } } calendarHTML += `
${dayNumber}
${statusIcons}
`; currentDay.setDate(currentDay.getDate() + 1); } elements.calendarDays.innerHTML = calendarHTML; } catch (error) { console.error('캘린더 렌더링 오류:', error); showToast('캘린더를 불러오는데 실패했습니다.', 'error'); } finally { showLoading(false); } } // 일별 상태 분석 (집계 데이터 또는 원본 데이터 처리) function analyzeDayStatus(dayData) { // 새로운 집계 데이터 구조인지 확인 (monthly_summary에서 온 데이터) if (dayData && typeof dayData === 'object' && 'totalWorkers' in dayData) { // 미입력 판단: allWorkers 배열 길이와 실제 작업한 작업자 수 비교 const totalRegisteredWorkers = allWorkers ? allWorkers.length : 10; // 실제 등록된 작업자 수 const actualIncompleteWorkers = Math.max(0, totalRegisteredWorkers - dayData.workingWorkers); const result = { hasData: dayData.totalWorkers > 0, hasIssues: dayData.partialWorkers > 0, // 부분입력 작업자가 있으면 true hasIncomplete: actualIncompleteWorkers > 0 || dayData.incompleteWorkers > 0, // 실제 미입력 작업자가 있으면 true hasOvertimeWarning: dayData.hasOvertimeWarning || dayData.overtimeWarningWorkers > 0, // 12시간 초과 workerCount: dayData.totalWorkers || 0 }; // 디버깅: 모든 데이터 로그 (미입력 문제 해결용) console.log('📊 analyzeDayStatus 결과:', { dayData, result, actualIncompleteWorkers, workingWorkers: dayData.workingWorkers, totalRegisteredWorkers: totalRegisteredWorkers, allWorkersLength: allWorkers ? allWorkers.length : 'undefined' }); return result; } // 기존 hasData 구조 확인 if (dayData && typeof dayData === 'object' && dayData.hasData !== undefined) { return { hasData: dayData.hasData, hasIssues: dayData.hasIssues, hasErrors: dayData.hasErrors, workerCount: dayData.workerCount || 0 }; } // 폴백: 기존 방식으로 분석 (원본 작업 데이터 배열) if (!Array.isArray(dayData) || dayData.length === 0) { return { hasData: false, hasIssues: false, hasErrors: false, workerCount: 0 }; } // 작업자별로 그룹화 const workerGroups = {}; dayData.forEach(work => { if (!workerGroups[work.worker_id]) { workerGroups[work.worker_id] = []; } workerGroups[work.worker_id].push(work); }); const workerCount = Object.keys(workerGroups).length; let hasIssues = false; let hasErrors = false; // 각 작업자의 상태 분석 - 문제가 있는지만 확인 Object.values(workerGroups).forEach(workerWork => { const totalHours = workerWork.reduce((sum, w) => sum + parseFloat(w.work_hours || 0), 0); const hasError = workerWork.some(w => w.work_status_id === 2); const hasVacation = workerWork.some(w => w.project_id === 13); // 오류가 있는 경우 if (hasError) { hasErrors = true; } // 휴가가 아닌데 미입력이거나 부분입력인 경우 else if (!hasVacation && (totalHours === 0 || totalHours < 8)) { hasIssues = true; } }); return { hasData: true, hasIssues, hasErrors, workerCount }; } // 일일 작업 현황 모달 열기 async function openDailyWorkModal(dateStr) { console.log(`🗓️ 클릭된 날짜: ${dateStr}`); currentModalDate = dateStr; // 날짜 포맷팅 const date = new Date(dateStr + 'T00:00:00'); const year = date.getFullYear(); const month = date.getMonth() + 1; const day = date.getDate(); console.log(`📅 파싱된 날짜: ${year}년 ${month}월 ${day}일`); const dayNames = ['일', '월', '화', '수', '목', '금', '토']; const dayName = dayNames[date.getDay()]; elements.modalTitle.textContent = `${year}년 ${month}월 ${day}일 (${dayName}) 작업 현황`; try { // 새로운 집계 API로 작업자별 상세 정보 조회 const response = await window.apiCall(`/monthly-status/daily-details?date=${dateStr}`); if (response.success) { const { workers, summary } = response.data; renderModalDataFromSummary(workers, summary); } else { // 폴백: 기존 API 사용 console.log('집계 API 실패, 기존 API로 폴백'); const fallbackResponse = await window.apiCall(`/daily-work-reports?date=${dateStr}&view_all=true`); const workData = Array.isArray(fallbackResponse) ? fallbackResponse : (fallbackResponse.data || []); renderModalData(workData); } // 모달 표시 elements.dailyWorkModal.style.display = 'flex'; document.body.style.overflow = 'hidden'; } catch (error) { console.error('일일 작업 데이터 로딩 오류:', error); showToast('해당 날짜의 작업 데이터를 불러오는데 실패했습니다.', 'error'); } } // 집계 데이터로 모달 렌더링 (최적화된 버전) async function renderModalDataFromSummary(workers, summary) { // 전체 작업자 목록 가져오기 const allWorkers = await loadWorkersData(); // 작업한 작업자 ID 목록 const workedWorkerIds = new Set(workers.map(w => w.workerId)); // 미기입 작업자 추가 (대시보드와 동일한 상태 판단 로직 적용) const missingWorkers = allWorkers .filter(worker => !workedWorkerIds.has(worker.worker_id)) .map(worker => { return { workerId: worker.worker_id, workerName: worker.worker_name, jobType: worker.job_type, totalHours: 0, actualWorkHours: 0, vacationHours: 0, totalWorkCount: 0, regularWorkCount: 0, errorWorkCount: 0, status: 'incomplete', hasVacation: false, hasError: false, hasIssues: true }; }); // 전체 작업자 목록 (작업한 사람 + 미기입 사람) const allWorkersList = [...workers, ...missingWorkers]; // 요약 정보 업데이트 (전체 작업자 수 포함) if (elements.modalTotalWorkers) { elements.modalTotalWorkers.textContent = `${allWorkersList.length}명`; } if (elements.modalTotalHours) { elements.modalTotalHours.textContent = `${summary.totalHours.toFixed(1)}h`; } if (elements.modalTotalTasks) { elements.modalTotalTasks.textContent = `${summary.totalTasks}건`; } if (elements.modalErrorCount) { elements.modalErrorCount.textContent = `${summary.errorCount}건`; elements.modalErrorCount.className = summary.errorCount > 0 ? 'summary-value error' : 'summary-value'; } // 작업자 리스트 렌더링 if (allWorkersList.length === 0) { elements.modalWorkersList.innerHTML = '
등록된 작업자가 없습니다.
'; return; } const workersHtml = allWorkersList.map(worker => { // 상태 텍스트 및 색상 결정 (에러가 있어도 작업시간 기준으로 판단) let statusText = '미입력'; let statusClass = 'incomplete'; // 에러 여부와 관계없이 작업시간 기준으로 상태 결정 const totalHours = worker.totalHours || 0; const hasVacation = worker.hasVacation || false; const vacationHours = worker.vacationHours || 0; if (totalHours > 12) { statusText = '확인필요'; statusClass = 'overtime-warning'; } else if (hasVacation && vacationHours > 0) { switch (vacationHours) { case 8: statusText = '연차'; statusClass = 'vacation-full'; break; case 6: statusText = '조퇴'; statusClass = 'vacation-half-half'; break; case 4: statusText = '반차'; statusClass = 'vacation-half'; break; case 2: statusText = '반반차'; statusClass = 'vacation-quarter'; break; default: statusText = '연차'; statusClass = 'vacation-full'; } } else if (totalHours > 8) { statusText = '연장근로'; statusClass = 'overtime'; } else if (totalHours === 8) { statusText = '정시근로'; statusClass = 'complete'; } else if (totalHours > 0) { statusText = '부분입력'; statusClass = 'partial'; } else { statusText = '미입력'; statusClass = 'incomplete'; } // 작업자 이름의 첫 글자 추출 const initial = worker.workerName ? worker.workerName.charAt(0) : '?'; return `
${initial}
${worker.workerName}
${worker.jobType || '일반'}
${statusText}
작업시간 ${worker.actualWorkHours.toFixed(1)}h
정규 ${worker.regularWorkCount}건 에러 ${worker.errorWorkCount}건
`; }).join(''); elements.modalWorkersList.innerHTML = workersHtml; } // 모달 데이터 렌더링 (폴백용 - 기존 방식) function renderModalData(workData) { // 작업자별로 그룹화 const workerGroups = {}; workData.forEach(work => { if (!workerGroups[work.worker_id]) { workerGroups[work.worker_id] = { worker_id: work.worker_id, worker_name: work.worker_name, job_type: work.job_type, works: [] }; } workerGroups[work.worker_id].works.push(work); }); // 요약 정보 계산 const totalWorkers = Object.keys(workerGroups).length; const totalHours = workData.reduce((sum, w) => sum + parseFloat(w.work_hours || 0), 0); const totalTasks = workData.length; const errorCount = workData.filter(w => w.work_status_id === 2).length; // 요약 정보 업데이트 elements.modalTotalWorkers.textContent = `${totalWorkers}명`; elements.modalTotalHours.textContent = `${totalHours.toFixed(1)}h`; elements.modalTotalTasks.textContent = `${totalTasks}건`; elements.modalErrorCount.textContent = `${errorCount}건`; // 작업자 리스트 렌더링 renderWorkersList(Object.values(workerGroups)); } // 작업자 리스트 렌더링 function renderWorkersList(workerGroups) { const workersHTML = workerGroups.map(workerGroup => { const totalHours = workerGroup.works.reduce((sum, w) => sum + parseFloat(w.work_hours || 0), 0); const hasError = workerGroup.works.some(w => w.work_status_id === 2); const hasVacation = workerGroup.works.some(w => w.project_id === 13); const regularWorkCount = workerGroup.works.filter(w => w.project_id !== 13 && w.work_status_id !== 2).length; const errorWorkCount = workerGroup.works.filter(w => w.project_id !== 13 && w.work_status_id === 2).length; // 상태 결정 let status, statusText, statusBadge; if (hasVacation) { const vacationHours = workerGroup.works .filter(w => w.project_id === 13) .reduce((sum, w) => sum + parseFloat(w.work_hours || 0), 0); if (vacationHours === 8) { status = 'vacation-full'; statusText = '연차'; statusBadge = '연차'; } else if (vacationHours === 6) { status = 'vacation-half-half'; statusText = '조퇴'; statusBadge = '조퇴'; } else if (vacationHours === 4) { status = 'vacation-half'; statusText = '반차'; statusBadge = '반차'; } else if (vacationHours === 2) { status = 'vacation-quarter'; statusText = '반반차'; statusBadge = '반반차'; } } else if (totalHours > 8) { status = 'overtime'; statusText = '연장근로'; statusBadge = '연장근로'; } else if (totalHours === 8) { status = 'complete'; statusText = '정시근로'; statusBadge = '정시근로'; } else if (totalHours > 0) { status = 'partial'; statusText = '부분입력'; statusBadge = '부분입력'; } else { status = 'incomplete'; statusText = '미입력'; statusBadge = '미입력'; } return `
${workerGroup.worker_name.charAt(0)}

${workerGroup.worker_name}

${workerGroup.job_type || '작업자'}

${statusBadge}
작업시간 ${totalHours.toFixed(1)}h
정규 ${regularWorkCount}건
${errorWorkCount > 0 ? `
에러 ${errorWorkCount}건
` : ''}
`; }).join(''); elements.modalWorkersList.innerHTML = workersHTML; } // 작업자 리스트 필터링 function filterWorkersList() { const filterValue = elements.statusFilter.value; const workerRows = elements.modalWorkersList.querySelectorAll('.worker-status-row'); workerRows.forEach(row => { const status = row.dataset.status; if (filterValue === 'all' || status === filterValue || (filterValue === 'vacation' && status.startsWith('vacation'))) { row.style.display = 'flex'; } else { row.style.display = 'none'; } }); } // 모달 닫기 function closeDailyWorkModal() { elements.dailyWorkModal.style.display = 'none'; document.body.style.overflow = ''; currentModalDate = null; } // 로딩 표시 function showLoading(show) { if (elements.loadingSpinner) { elements.loadingSpinner.style.display = show ? 'flex' : 'none'; } } // 토스트 메시지 (간단한 구현) function showToast(message, type = 'info') { // 기존 토스트가 있으면 제거 const existingToast = document.querySelector('.toast-message'); if (existingToast) { existingToast.remove(); } const toast = document.createElement('div'); toast.className = `toast-message toast-${type}`; toast.textContent = message; toast.style.cssText = ` position: fixed; top: 20px; right: 20px; padding: 12px 24px; background: ${type === 'error' ? '#ef4444' : type === 'success' ? '#10b981' : '#3b82f6'}; color: white; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); z-index: 10000; font-weight: 500; max-width: 400px; `; document.body.appendChild(toast); setTimeout(() => { toast.remove(); }, 3000); } // 작업자 개별 작업 모달 열기 async function openWorkerModal(workerId, date) { try { // 작업자 정보 찾기 const worker = allWorkers.find(w => w.worker_id === workerId); if (!worker) { showToast('작업자 정보를 찾을 수 없습니다.', 'error'); return; } // 작업 입력 모달 열기 await openWorkEntryModal(workerId, worker.worker_name, date); } catch (error) { console.error('작업자 모달 열기 오류:', error); showToast('작업 입력 모달을 여는데 실패했습니다.', 'error'); } } // 작업 입력 모달 열기 async function openWorkEntryModal(workerId, workerName, date) { try { // 모달 요소들 가져오기 const modal = document.getElementById('workEntryModal'); const titleElement = document.getElementById('workEntryModalTitle'); const workerNameDisplay = document.getElementById('workerNameDisplay'); const workerIdInput = document.getElementById('workerId'); const workDateInput = document.getElementById('workDate'); if (!modal) { showToast('작업 입력 모달을 찾을 수 없습니다.', 'error'); return; } // 모달 제목 및 정보 설정 titleElement.textContent = `${workerName} - 작업 입력`; workerNameDisplay.value = workerName; workerIdInput.value = workerId; workDateInput.value = date; // 프로젝트 및 상태 데이터 로드 await loadModalData(); // 모달 표시 modal.style.display = 'flex'; document.body.style.overflow = 'hidden'; } catch (error) { console.error('작업 입력 모달 열기 오류:', error); showToast('작업 입력 모달을 여는데 실패했습니다.', 'error'); } } // 모달 데이터 로드 (프로젝트, 작업 상태) async function loadModalData() { try { // 프로젝트 목록 로드 const projectsResponse = await window.apiCall('/projects'); const projects = Array.isArray(projectsResponse) ? projectsResponse : (projectsResponse.data || []); const projectSelect = document.getElementById('projectSelect'); projectSelect.innerHTML = ''; projects.forEach(project => { const option = document.createElement('option'); option.value = project.project_id; option.textContent = project.project_name; projectSelect.appendChild(option); }); // 작업 상태 목록 로드 (하드코딩으로 대체) const statuses = [ { status_id: 1, status_name: '완료' }, { status_id: 2, status_name: '오류' }, { status_id: 3, status_name: '진행중' } ]; const statusSelect = document.getElementById('workStatusSelect'); statusSelect.innerHTML = ''; statuses.forEach(status => { const option = document.createElement('option'); option.value = status.status_id; option.textContent = status.status_name; statusSelect.appendChild(option); }); } catch (error) { console.error('모달 데이터 로드 오류:', error); showToast('데이터를 불러오는데 실패했습니다.', 'error'); } } // 작업 입력 모달 닫기 function closeWorkEntryModal() { const modal = document.getElementById('workEntryModal'); if (modal) { modal.style.display = 'none'; document.body.style.overflow = 'auto'; // 폼 초기화 const form = document.getElementById('workEntryForm'); if (form) { form.reset(); } } } // 휴가 처리 function handleVacation(type) { const projectSelect = document.getElementById('projectSelect'); const workHours = document.getElementById('workHours'); const workStatusSelect = document.getElementById('workStatusSelect'); const workDescription = document.getElementById('workDescription'); // 연차/휴무 프로젝트 선택 (project_id: 13) projectSelect.value = '13'; // 휴가 유형에 따른 시간 설정 switch (type) { case 'full': // 연차 workHours.value = '8'; workDescription.value = '연차'; break; case 'half': // 반차 workHours.value = '4'; workDescription.value = '반차'; break; case 'quarter': // 반반차 workHours.value = '2'; workDescription.value = '반반차'; break; case 'early': // 조퇴 workHours.value = '6'; workDescription.value = '조퇴'; break; } // 완료 상태로 설정 (status_id: 1) workStatusSelect.value = '1'; } // 작업 저장 async function saveWorkEntry() { try { const form = document.getElementById('workEntryForm'); const formData = new FormData(form); const workData = { worker_id: document.getElementById('workerId').value, project_id: document.getElementById('projectSelect').value, work_hours: document.getElementById('workHours').value, work_status_id: document.getElementById('workStatusSelect').value, description: document.getElementById('workDescription').value, report_date: document.getElementById('workDate').value }; // 필수 필드 검증 if (!workData.project_id || !workData.work_hours || !workData.work_status_id) { showToast('필수 항목을 모두 입력해주세요.', 'error'); return; } // API 호출 const response = await window.apiCall('/daily-work-reports', 'POST', workData); if (response.success || response.id) { showToast('작업이 성공적으로 저장되었습니다.', 'success'); closeWorkEntryModal(); // 캘린더 새로고침 await renderCalendar(); // 현재 열린 모달이 있다면 새로고침 if (currentModalDate) { await openDailyWorkModal(currentModalDate); } } else { throw new Error(response.message || '저장에 실패했습니다.'); } } catch (error) { console.error('작업 저장 오류:', error); showToast(error.message || '작업 저장에 실패했습니다.', 'error'); } } // 모달 닫기 함수 function closeDailyWorkModal() { if (elements.dailyWorkModal) { elements.dailyWorkModal.style.display = 'none'; document.body.style.overflow = 'auto'; } } // 전역 변수로 작업자 목록 저장 let allWorkers = []; // 시간 업데이트 함수 function updateCurrentTime() { const now = new Date(); const timeString = now.toLocaleTimeString('ko-KR', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit' }); const timeValueElement = document.getElementById('timeValue'); if (timeValueElement) { timeValueElement.textContent = timeString; } } // 사용자 정보 업데이트 함수 function updateUserInfo() { const userInfo = JSON.parse(localStorage.getItem('userInfo') || '{}'); console.log('👤 localStorage userInfo:', userInfo); 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; } else { userNameElement.textContent = '사용자'; } } if (userRoleElement) { if (userInfo.job_type) { userRoleElement.textContent = userInfo.job_type; } else { userRoleElement.textContent = '작업자'; } } if (userInitialElement) { if (userInfo.worker_name) { userInitialElement.textContent = userInfo.worker_name.charAt(0); } else { userInitialElement.textContent = '사'; } } } // 페이지 초기화 개선 function initializePage() { // 시간 업데이트 시작 updateCurrentTime(); setInterval(updateCurrentTime, 1000); // 사용자 정보 업데이트 updateUserInfo(); // 프로필 메뉴 토글 const userProfile = document.getElementById('userProfile'); const profileMenu = document.getElementById('profileMenu'); if (userProfile && profileMenu) { userProfile.addEventListener('click', (e) => { e.stopPropagation(); profileMenu.style.display = profileMenu.style.display === 'block' ? 'none' : 'block'; }); // 외부 클릭 시 메뉴 닫기 document.addEventListener('click', () => { profileMenu.style.display = 'none'; }); } // 로그아웃 버튼 const logoutBtn = document.getElementById('logoutBtn'); if (logoutBtn) { logoutBtn.addEventListener('click', () => { localStorage.removeItem('token'); localStorage.removeItem('userInfo'); window.location.href = '/pages/auth/login.html'; }); } } // DOMContentLoaded 이벤트에 초기화 함수 추가 document.addEventListener('DOMContentLoaded', function() { initializePage(); }); // 전역 함수로 노출 window.openDailyWorkModal = openDailyWorkModal; window.closeDailyWorkModal = closeDailyWorkModal; window.openWorkerModal = openWorkerModal; window.openWorkEntryModal = openWorkEntryModal; window.closeWorkEntryModal = closeWorkEntryModal; window.handleVacation = handleVacation; window.saveWorkEntry = saveWorkEntry;