/** * 나의 출근 현황 페이지 * 본인의 출근 기록과 근태 현황을 조회하고 표시합니다 */ // 전역 상태 let currentYear = new Date().getFullYear(); let currentMonth = new Date().getMonth() + 1; let attendanceData = []; let vacationBalance = null; let monthlyStats = null; // 페이지 로드 시 초기화 document.addEventListener('DOMContentLoaded', () => { initializePage(); }); /** * 페이지 초기화 */ function initializePage() { initializeYearMonthSelects(); setupEventListeners(); loadAttendanceData(); } /** * 년도/월 선택 옵션 초기화 */ function initializeYearMonthSelects() { const yearSelect = document.getElementById('yearSelect'); const monthSelect = document.getElementById('monthSelect'); // 년도 옵션 (현재 년도 기준 ±2년) const currentYearValue = new Date().getFullYear(); for (let year = currentYearValue - 2; year <= currentYearValue + 2; year++) { const option = document.createElement('option'); option.value = year; option.textContent = `${year}년`; if (year === currentYear) option.selected = true; yearSelect.appendChild(option); } // 월 옵션 for (let month = 1; month <= 12; month++) { const option = document.createElement('option'); option.value = month; option.textContent = `${month}월`; if (month === currentMonth) option.selected = true; monthSelect.appendChild(option); } } /** * 이벤트 리스너 설정 */ function setupEventListeners() { // 조회 버튼 document.getElementById('loadAttendance').addEventListener('click', () => { currentYear = parseInt(document.getElementById('yearSelect').value); currentMonth = parseInt(document.getElementById('monthSelect').value); loadAttendanceData(); }); // 탭 전환 document.querySelectorAll('.tab-btn').forEach(btn => { btn.addEventListener('click', (e) => { const tabName = e.currentTarget.dataset.tab; switchTab(tabName); }); }); // 달력 네비게이션 document.getElementById('prevMonth').addEventListener('click', () => { changeMonth(-1); }); document.getElementById('nextMonth').addEventListener('click', () => { changeMonth(1); }); } /** * 출근 데이터 로드 */ async function loadAttendanceData() { try { showLoading(); // 병렬로 데이터 로드 const [attendanceRes, vacationRes, statsRes] = await Promise.all([ window.apiGet(`/users/me/attendance-records?year=${currentYear}&month=${currentMonth}`), window.apiGet(`/users/me/vacation-balance?year=${currentYear}`), window.apiGet(`/users/me/monthly-stats?year=${currentYear}&month=${currentMonth}`) ]); attendanceData = attendanceRes.data || attendanceRes || []; vacationBalance = vacationRes.data || vacationRes; monthlyStats = statsRes.data || statsRes; // UI 업데이트 updateStats(); renderTable(); renderCalendar(); } catch (error) { console.error('출근 데이터 로드 실패:', error); showError('출근 데이터를 불러오는데 실패했습니다.'); } } /** * 통계 업데이트 */ function updateStats() { // 총 근무시간 (API는 month_hours 반환) const totalHours = monthlyStats?.month_hours || monthlyStats?.total_work_hours || 0; document.getElementById('totalHours').textContent = `${totalHours}시간`; // 근무일수 const totalDays = monthlyStats?.work_days || 0; document.getElementById('totalDays').textContent = `${totalDays}일`; // 잔여 연차 const remaining = vacationBalance?.remaining_annual_leave || (vacationBalance?.total_annual_leave || 0) - (vacationBalance?.used_annual_leave || 0); document.getElementById('remainingLeave').textContent = `${remaining}일`; } /** * 테이블 렌더링 */ function renderTable() { const tbody = document.getElementById('attendanceTableBody'); tbody.innerHTML = ''; if (!attendanceData || attendanceData.length === 0) { tbody.innerHTML = '출근 기록이 없습니다.'; return; } attendanceData.forEach(record => { const tr = document.createElement('tr'); tr.className = `attendance-row ${getStatusClass(record.attendance_type_code || record.type_code)}`; tr.onclick = () => showDetailModal(record); const date = new Date(record.record_date); const dayOfWeek = ['일', '월', '화', '수', '목', '금', '토'][date.getDay()]; tr.innerHTML = ` ${formatDate(record.record_date)} ${dayOfWeek} ${record.check_in_time || '-'} ${record.check_out_time || '-'} ${record.total_work_hours ? `${record.total_work_hours}h` : '-'} ${getStatusText(record)} ${record.notes || '-'} `; tbody.appendChild(tr); }); } /** * 달력 렌더링 */ function renderCalendar() { const calendarTitle = document.getElementById('calendarTitle'); const calendarGrid = document.getElementById('calendarGrid'); calendarTitle.textContent = `${currentYear}년 ${currentMonth}월`; // 달력 그리드 초기화 calendarGrid.innerHTML = ''; // 요일 헤더 const weekdays = ['일', '월', '화', '수', '목', '금', '토']; weekdays.forEach(day => { const dayHeader = document.createElement('div'); dayHeader.className = 'calendar-day-header'; dayHeader.textContent = day; calendarGrid.appendChild(dayHeader); }); // 해당 월의 첫날과 마지막 날 const firstDay = new Date(currentYear, currentMonth - 1, 1); const lastDay = new Date(currentYear, currentMonth, 0); const daysInMonth = lastDay.getDate(); const startDayOfWeek = firstDay.getDay(); // 출근 데이터를 날짜별로 매핑 const attendanceMap = {}; if (attendanceData) { attendanceData.forEach(record => { const date = new Date(record.record_date); const day = date.getDate(); attendanceMap[day] = record; }); } // 빈 칸 (이전 달) for (let i = 0; i < startDayOfWeek; i++) { const emptyCell = document.createElement('div'); emptyCell.className = 'calendar-day empty'; calendarGrid.appendChild(emptyCell); } // 날짜 칸 for (let day = 1; day <= daysInMonth; day++) { const dayCell = document.createElement('div'); dayCell.className = 'calendar-day'; const record = attendanceMap[day]; if (record) { dayCell.classList.add('has-record', getStatusClass(record.attendance_type_code || record.type_code)); dayCell.onclick = () => showDetailModal(record); } dayCell.innerHTML = `
${day}
${record ? `
${getStatusIcon(record)}
` : ''} `; calendarGrid.appendChild(dayCell); } } /** * 탭 전환 */ function switchTab(tabName) { // 탭 버튼 활성화 토글 document.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.tab === tabName); }); // 탭 컨텐츠 토글 document.querySelectorAll('.tab-content').forEach(content => { content.classList.remove('active'); }); if (tabName === 'list') { document.getElementById('listView').classList.add('active'); } else if (tabName === 'calendar') { document.getElementById('calendarView').classList.add('active'); } } /** * 월 변경 */ function changeMonth(offset) { currentMonth += offset; if (currentMonth < 1) { currentMonth = 12; currentYear--; } else if (currentMonth > 12) { currentMonth = 1; currentYear++; } // Select 박스 업데이트 document.getElementById('yearSelect').value = currentYear; document.getElementById('monthSelect').value = currentMonth; loadAttendanceData(); } /** * 상세 모달 표시 */ function showDetailModal(record) { const modal = document.getElementById('detailModal'); const modalBody = document.getElementById('modalBody'); const modalTitle = document.getElementById('modalTitle'); const date = new Date(record.record_date); modalTitle.textContent = `${formatDate(record.record_date)} 출근 상세`; modalBody.innerHTML = `
${formatDate(record.record_date)}
${getStatusText(record)}
${record.check_in_time || '기록 없음'}
${record.check_out_time || '기록 없음'}
${record.total_work_hours ? `${record.total_work_hours} 시간` : '계산 불가'}
${record.vacation_type_name ? `
${record.vacation_type_name}
` : ''} ${record.notes ? `
${record.notes}
` : ''}
`; modal.style.display = 'block'; } /** * 모달 닫기 */ function closeDetailModal() { document.getElementById('detailModal').style.display = 'none'; } // 모달 외부 클릭 시 닫기 window.onclick = function(event) { const modal = document.getElementById('detailModal'); if (event.target === modal) { closeDetailModal(); } }; /** * 유틸리티 함수들 */ function formatDate(dateString) { const date = new Date(dateString); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${month}/${day}`; } function getStatusClass(typeCode) { const typeMap = { 'NORMAL': 'normal', 'LATE': 'late', 'EARLY_LEAVE': 'early', 'ABSENT': 'absent', 'VACATION': 'vacation' }; return typeMap[typeCode] || 'normal'; } function getStatusText(record) { if (record.vacation_type_name) { return record.vacation_type_name; } return record.attendance_type_name || record.type_name || '정상'; } function getStatusIcon(record) { const typeCode = record.attendance_type_code || record.type_code; const iconMap = { 'NORMAL': '✓', 'LATE': '⚠', 'EARLY_LEAVE': '⏰', 'ABSENT': '✗', 'VACATION': '🌴' }; return iconMap[typeCode] || '✓'; } function showLoading() { const tbody = document.getElementById('attendanceTableBody'); tbody.innerHTML = '데이터를 불러오는 중...'; } function showError(message) { const tbody = document.getElementById('attendanceTableBody'); tbody.innerHTML = `${message}`; // 통계 초기화 document.getElementById('totalHours').textContent = '-'; document.getElementById('totalDays').textContent = '-'; document.getElementById('remainingLeave').textContent = '-'; }