/** * my-monthly-confirm.js — 작업자 월간 근무 확인 (모바일 전용) * Sprint 004 Section B */ const DAYS_KR = ['일', '월', '화', '수', '목', '금', '토']; const VAC_TEXT = { 'ANNUAL_FULL': '연차', 'ANNUAL_HALF': '반차', 'ANNUAL_QUARTER': '반반차', 'EARLY_LEAVE': '조퇴', 'SICK': '병가', 'SICK_FULL': '병가', 'SICK_HALF': '병가(반)', 'SPECIAL': '경조사', 'SPOUSE_BIRTH': '출산휴가' }; let currentYear, currentMonth; let isProcessing = false; // ===== Init ===== document.addEventListener('DOMContentLoaded', function() { var now = new Date(); currentYear = now.getFullYear(); currentMonth = now.getMonth() + 1; var params = new URLSearchParams(location.search); if (params.get('year')) currentYear = parseInt(params.get('year')); if (params.get('month')) currentMonth = parseInt(params.get('month')); setTimeout(function() { if (!window.currentUser) return; updateMonthLabel(); loadData(); }, 500); }); // ===== Month Nav ===== function updateMonthLabel() { document.getElementById('monthLabel').textContent = currentYear + '년 ' + currentMonth + '월'; } function changeMonth(delta) { currentMonth += delta; if (currentMonth > 12) { currentMonth = 1; currentYear++; } if (currentMonth < 1) { currentMonth = 12; currentYear--; } updateMonthLabel(); loadData(); } // ===== Data Load ===== async function loadData() { var tableWrap = document.getElementById('tableWrap'); tableWrap.innerHTML = '
'; try { var userId = window.currentUser.user_id || window.currentUser.id; var [recordsRes, balanceRes] = await Promise.all([ window.apiCall('/monthly-comparison/my-records?year=' + currentYear + '&month=' + currentMonth), window.apiCall('/attendance/vacation-balance/' + userId).catch(function() { return { success: true, data: [] }; }) ]); if (!recordsRes || !recordsRes.success) { tableWrap.innerHTML = '

데이터가 없습니다

'; document.getElementById('bottomActions').classList.add('hidden'); return; } var data = recordsRes.data; renderUserInfo(data.user); renderAttendanceTable(data.daily_records || []); renderVacationBalance(balanceRes.data || []); renderConfirmStatus(data.confirmation); } catch (e) { tableWrap.innerHTML = '

네트워크 오류

'; } } // ===== Render ===== function renderUserInfo(user) { if (!user) return; document.getElementById('userName').textContent = user.worker_name || user.name || '-'; document.getElementById('userDept').textContent = (user.job_type ? user.job_type + ' · ' : '') + (user.department_name || ''); } function renderAttendanceTable(records) { var el = document.getElementById('tableWrap'); if (!records.length) { el.innerHTML = '

해당 월 데이터가 없습니다

'; return; } var totalHours = 0; var html = ''; records.forEach(function(r) { var day = parseInt(r.date.substring(8)); var dow = r.day_of_week || DAYS_KR[new Date(r.date).getDay()]; var isHoliday = r.is_holiday; var rowClass = isHoliday ? ' class="row-holiday"' : ''; // 셀 값 결정 var val = '-'; var valClass = ''; if (r.attendance && r.attendance.vacation_type) { val = r.attendance.vacation_type; valClass = 'val-vacation'; } else if (isHoliday && (!r.attendance || !r.attendance.total_work_hours)) { val = '휴무'; valClass = 'val-holiday'; } else if (r.attendance && r.attendance.total_work_hours > 0) { var hrs = parseFloat(r.attendance.total_work_hours); val = hrs.toFixed(2); totalHours += hrs; } else if (r.work_report && r.work_report.total_hours > 0) { var hrs2 = parseFloat(r.work_report.total_hours); val = hrs2.toFixed(2); totalHours += hrs2; } // 주말 근무 if (isHoliday && r.attendance && r.attendance.total_work_hours > 0) { val = parseFloat(r.attendance.total_work_hours).toFixed(2); totalHours += parseFloat(r.attendance.total_work_hours); rowClass = ' class="row-holiday-work"'; valClass = ''; } var dowClass = (dow === '일') ? 'dow-sun' : (dow === '토') ? 'dow-sat' : ''; html += ''; html += ''; html += ''; html += ''; html += ''; }); html += ''; html += ''; html += ''; html += ''; html += '
' + day + '' + dow + '' + escHtml(val) + '
총 근무시간' + totalHours.toFixed(2) + 'h
'; el.innerHTML = html; } function renderVacationBalance(balances) { var el = document.getElementById('vacationCards'); var total = 0, used = 0; if (Array.isArray(balances)) { balances.forEach(function(b) { total += parseFloat(b.total_days || b.remaining_days_total || 0); used += parseFloat(b.used_days || 0); }); } var remaining = total - used; el.innerHTML = '
연차 현황
' + '
' + '
' + fmtNum(total) + '
신규
' + '
' + fmtNum(used) + '
사용
' + '
' + fmtNum(remaining) + '
잔여
' + '
'; } function renderConfirmStatus(conf) { var actions = document.getElementById('bottomActions'); var statusEl = document.getElementById('confirmedStatus'); var badge = document.getElementById('statusBadge'); if (!conf || conf.status === 'pending') { actions.classList.remove('hidden'); statusEl.classList.add('hidden'); badge.textContent = '미확인'; badge.className = 'mmc-status-badge pending'; return; } if (conf.status === 'confirmed') { actions.classList.add('hidden'); statusEl.classList.remove('hidden'); badge.textContent = '확인완료'; badge.className = 'mmc-status-badge confirmed'; var dt = conf.confirmed_at ? new Date(conf.confirmed_at).toLocaleDateString('ko') : ''; document.getElementById('confirmedText').textContent = dt + ' 확인 완료'; } else if (conf.status === 'rejected') { actions.classList.remove('hidden'); statusEl.classList.add('hidden'); badge.textContent = '반려'; badge.className = 'mmc-status-badge rejected'; } } // ===== Actions ===== async function confirmMonth() { if (isProcessing) return; if (!confirm(currentYear + '년 ' + currentMonth + '월 근무 내역을 확인하시겠습니까?')) return; isProcessing = true; try { var res = await window.apiCall('/monthly-comparison/confirm', 'POST', { year: currentYear, month: currentMonth, status: 'confirmed' }); if (res && res.success) { showToast(res.message || '확인 완료', 'success'); loadData(); } else { showToast(res && res.message || '처리 실패', 'error'); } } catch (e) { showToast('네트워크 오류', 'error'); } finally { isProcessing = false; } } function openRejectModal() { document.getElementById('rejectReason').value = ''; document.getElementById('rejectModal').classList.remove('hidden'); } function closeRejectModal() { document.getElementById('rejectModal').classList.add('hidden'); } async function submitReject() { if (isProcessing) return; var reason = document.getElementById('rejectReason').value.trim(); if (!reason) { showToast('반려 사유를 입력해주세요', 'error'); return; } isProcessing = true; try { var res = await window.apiCall('/monthly-comparison/confirm', 'POST', { year: currentYear, month: currentMonth, status: 'rejected', reject_reason: reason }); if (res && res.success) { showToast(res.message || '반려 제출 완료', 'success'); closeRejectModal(); loadData(); } else { showToast(res && res.message || '처리 실패', 'error'); } } catch (e) { showToast('네트워크 오류', 'error'); } finally { isProcessing = false; } } // ===== Helpers ===== function fmtNum(v) { var n = parseFloat(v) || 0; return n % 1 === 0 ? n.toString() : n.toFixed(1); } function escHtml(s) { return (s || '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); } function showToast(msg, type) { if (window.showToast) { window.showToast(msg, type); return; } var c = document.getElementById('toastContainer'); var t = document.createElement('div'); t.className = 'toast toast-' + (type || 'info'); t.textContent = msg; c.appendChild(t); setTimeout(function() { t.remove(); }, 3000); } document.addEventListener('keydown', function(e) { if (e.key === 'Escape') closeRejectModal(); });