/* ===== Visit Management (출입 관리 - 관리자) ===== */ let allRequests = []; let actionRequestId = null; /* ===== Status badge for visit requests ===== */ function vrStatusBadge(s) { const m = { pending: ['badge-amber', '대기중'], approved: ['badge-green', '승인됨'], rejected: ['badge-red', '반려됨'], training_completed: ['badge-blue', '교육완료'], checked_in: ['badge-blue', '체크인'], checked_out: ['badge-gray', '체크아웃'] }; const [cls, label] = m[s] || ['badge-gray', s]; return `${label}`; } function requestTypeBadge(t) { return t === 'internal' ? '내부' : '외부'; } /* ===== Load requests ===== */ async function loadRequests() { try { const params = new URLSearchParams(); const status = document.getElementById('filterStatus').value; const dateFrom = document.getElementById('filterDateFrom').value; const dateTo = document.getElementById('filterDateTo').value; const type = document.getElementById('filterType').value; if (status) params.set('status', status); if (dateFrom) params.set('start_date', dateFrom); if (dateTo) params.set('end_date', dateTo); if (type) params.set('request_type', type); const res = await api('/visit-requests/requests?' + params.toString()); allRequests = res.data || []; renderStats(); renderRequestsTable(); } catch (e) { showToast('데이터 로드 실패: ' + e.message, 'error'); } } function renderStats() { const counts = { pending: 0, approved: 0, rejected: 0, training_completed: 0, checked_in: 0, checked_out: 0 }; allRequests.forEach(r => { if (counts[r.status] !== undefined) counts[r.status]++; }); document.getElementById('statPending').textContent = counts.pending; document.getElementById('statApproved').textContent = counts.approved; document.getElementById('statRejected').textContent = counts.rejected; document.getElementById('statTrainingDone').textContent = counts.training_completed; document.getElementById('statCheckedIn').textContent = counts.checked_in; document.getElementById('statCheckedOut').textContent = counts.checked_out; } function renderRequestsTable() { const tbody = document.getElementById('requestsTableBody'); if (!allRequests.length) { tbody.innerHTML = '신청 내역이 없습니다'; return; } tbody.innerHTML = allRequests.map(r => { let actions = ''; // 승인/반려 (pending만) if (r.status === 'pending') { actions = ` `; } // 체크인 버튼 (approved 또는 training_completed) const canCheckIn = (r.request_type === 'internal' && r.status === 'approved') || (['approved', 'training_completed'].includes(r.status)); if (canCheckIn) { actions += ` `; } // 체크아웃 버튼 (checked_in) if (r.status === 'checked_in') { actions += ` `; } actions += ` `; if (r.status === 'pending') { actions += ` `; } const displayName = r.request_type === 'internal' ? escapeHtml(r.visitor_name || r.requester_full_name || '-') : escapeHtml(r.visitor_company); return ` ${requestTypeBadge(r.request_type)} ${escapeHtml(r.requester_full_name || r.requester_name || '-')} ${displayName} ${r.visitor_count} ${escapeHtml(r.workplace_name || '-')} ${formatDate(r.visit_date)} ${r.visit_time ? String(r.visit_time).substring(0, 5) : '-'} ${escapeHtml(r.purpose_name || '-')} ${vrStatusBadge(r.status)} ${actions} `; }).join(''); } /* ===== Check-in / Check-out ===== */ async function doCheckIn(id) { if (!confirm('체크인 처리하시겠습니까?')) return; try { await api('/visit-requests/requests/' + id + '/check-in', { method: 'PUT', body: JSON.stringify({}) }); showToast('체크인 완료'); await loadRequests(); } catch (e) { showToast(e.message, 'error'); } } async function doCheckOut(id) { if (!confirm('체크아웃 처리하시겠습니까?')) return; try { await api('/visit-requests/requests/' + id + '/check-out', { method: 'PUT', body: JSON.stringify({}) }); showToast('체크아웃 완료'); await loadRequests(); } catch (e) { showToast(e.message, 'error'); } } /* ===== Approve Modal ===== */ function openApproveModal(id) { const r = allRequests.find(x => x.request_id === id); if (!r) return; actionRequestId = id; const displayName = r.request_type === 'internal' ? escapeHtml(r.visitor_name || r.requester_full_name || '-') : escapeHtml(r.visitor_company); document.getElementById('approveDetail').innerHTML = `

유형: ${r.request_type === 'internal' ? '내부 출입' : '외부 방문'}

업체/이름: ${displayName}

방문일: ${formatDate(r.visit_date)} ${r.visit_time ? String(r.visit_time).substring(0, 5) : ''}

작업장: ${escapeHtml(r.workplace_name || '-')}

인원: ${r.visitor_count}명

이 출입 신청을 승인하시겠습니까?

`; document.getElementById('approveModal').classList.remove('hidden'); } function closeApproveModal() { document.getElementById('approveModal').classList.add('hidden'); actionRequestId = null; } async function confirmApprove() { if (!actionRequestId) return; try { await api('/visit-requests/requests/' + actionRequestId + '/approve', { method: 'PUT', body: JSON.stringify({}) }); showToast('승인되었습니다'); closeApproveModal(); await loadRequests(); } catch (e) { showToast(e.message, 'error'); } } /* ===== Reject Modal ===== */ function openRejectModal(id) { const r = allRequests.find(x => x.request_id === id); if (!r) return; actionRequestId = id; document.getElementById('rejectDetail').innerHTML = `

업체/이름: ${escapeHtml(r.visitor_company || r.visitor_name || '-')}

방문일: ${formatDate(r.visit_date)} ${r.visit_time ? String(r.visit_time).substring(0, 5) : ''}

작업장: ${escapeHtml(r.workplace_name || '-')}

`; document.getElementById('rejectionReason').value = ''; document.getElementById('rejectModal').classList.remove('hidden'); } function closeRejectModal() { document.getElementById('rejectModal').classList.add('hidden'); actionRequestId = null; } async function confirmReject() { if (!actionRequestId) return; const reason = document.getElementById('rejectionReason').value.trim(); if (!reason) { showToast('반려 사유를 입력해주세요', 'error'); return; } try { await api('/visit-requests/requests/' + actionRequestId + '/reject', { method: 'PUT', body: JSON.stringify({ rejection_reason: reason }) }); showToast('반려되었습니다'); closeRejectModal(); await loadRequests(); } catch (e) { showToast(e.message, 'error'); } } /* ===== Detail Modal ===== */ function openDetailModal(id) { const r = allRequests.find(x => x.request_id === id); if (!r) return; document.getElementById('detailContent').innerHTML = `
유형: ${r.request_type === 'internal' ? '내부 출입' : '외부 방문'}
신청자: ${escapeHtml(r.requester_full_name || r.requester_name || '-')}
업체: ${escapeHtml(r.visitor_company || '-')}
방문자: ${escapeHtml(r.visitor_name || '-')}
인원: ${r.visitor_count}명
분류: ${escapeHtml(r.category_name || '-')}
작업장: ${escapeHtml(r.workplace_name || '-')}
방문일: ${formatDate(r.visit_date)}
방문시간: ${r.visit_time ? String(r.visit_time).substring(0, 5) : '-'}
목적: ${escapeHtml(r.purpose_name || '-')}
상태: ${vrStatusBadge(r.status)}
신청일: ${formatDateTime(r.created_at)}
${r.check_in_time ? `
체크인: ${formatDateTime(r.check_in_time)}
` : ''} ${r.check_out_time ? `
체크아웃: ${formatDateTime(r.check_out_time)}
` : ''} ${r.department_name ? `
부서: ${escapeHtml(r.department_name)}
` : ''} ${r.approver_name ? `
처리자: ${escapeHtml(r.approver_name)}
` : ''} ${r.approved_at ? `
처리일: ${formatDateTime(r.approved_at)}
` : ''} ${r.rejection_reason ? `
반려사유: ${escapeHtml(r.rejection_reason)}
` : ''} ${r.notes ? `
비고: ${escapeHtml(r.notes)}
` : ''}
`; document.getElementById('detailModal').classList.remove('hidden'); } function closeDetailModal() { document.getElementById('detailModal').classList.add('hidden'); } /* ===== Delete request ===== */ async function doDeleteRequest(id) { if (!confirm('이 신청을 삭제하시겠습니까?')) return; try { await api('/visit-requests/requests/' + id, { method: 'DELETE' }); showToast('삭제되었습니다'); await loadRequests(); } catch (e) { showToast(e.message, 'error'); } } /* ===== Init ===== */ function initVisitManagementPage() { if (!initAuth()) return; // Check admin const isAdmin = currentUser && ['admin', 'system'].includes(currentUser.role); if (!isAdmin) { document.querySelector('.flex-1.min-w-0').innerHTML = `

관리자 권한이 필요합니다

`; return; } document.getElementById('filterStatus').addEventListener('change', loadRequests); document.getElementById('filterType').addEventListener('change', loadRequests); document.getElementById('filterDateFrom').addEventListener('change', loadRequests); document.getElementById('filterDateTo').addEventListener('change', loadRequests); loadRequests(); }