// 출입 신청 페이지 JavaScript let categories = []; let workplaces = []; let mapRegions = []; let visitPurposes = []; let selectedWorkplace = null; let selectedCategory = null; let canvas = null; let ctx = null; let layoutImage = null; // ==================== Toast 알림 ==================== /** * Toast 메시지 표시 */ function showToast(message, type = 'info', duration = 3000) { const toastContainer = document.getElementById('toastContainer') || createToastContainer(); const toast = document.createElement('div'); toast.className = `toast toast-${type}`; const iconMap = { success: '✅', error: '❌', warning: '⚠️', info: 'ℹ️' }; toast.innerHTML = ` ${iconMap[type] || 'ℹ️'} ${message} `; toastContainer.appendChild(toast); // 애니메이션 setTimeout(() => toast.classList.add('show'), 10); // 자동 제거 setTimeout(() => { toast.classList.remove('show'); setTimeout(() => toast.remove(), 300); }, duration); } /** * Toast 컨테이너 생성 */ function createToastContainer() { const container = document.createElement('div'); container.id = 'toastContainer'; container.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 9999; display: flex; flex-direction: column; gap: 10px; `; document.body.appendChild(container); // Toast 스타일 추가 if (!document.getElementById('toastStyles')) { const style = document.createElement('style'); style.id = 'toastStyles'; style.textContent = ` .toast { display: flex; align-items: center; gap: 12px; padding: 12px 20px; background: white; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); opacity: 0; transform: translateX(100px); transition: all 0.3s ease; min-width: 250px; max-width: 400px; } .toast.show { opacity: 1; transform: translateX(0); } .toast-success { border-left: 4px solid #10b981; } .toast-error { border-left: 4px solid #ef4444; } .toast-warning { border-left: 4px solid #f59e0b; } .toast-info { border-left: 4px solid #3b82f6; } .toast-icon { font-size: 20px; } .toast-message { font-size: 14px; color: #374151; } `; document.head.appendChild(style); } return container; } // ==================== 초기화 ==================== document.addEventListener('DOMContentLoaded', async () => { // 오늘 날짜 기본값 설정 const today = new Date().toISOString().split('T')[0]; document.getElementById('visitDate').value = today; document.getElementById('visitDate').min = today; // 현재 시간 + 1시간 기본값 설정 const now = new Date(); now.setHours(now.getHours() + 1); const timeString = now.toTimeString().slice(0, 5); document.getElementById('visitTime').value = timeString; // 데이터 로드 await loadCategories(); await loadVisitPurposes(); await loadMyRequests(); // 폼 제출 이벤트 document.getElementById('visitRequestForm').addEventListener('submit', handleSubmit); // 캔버스 초기화 canvas = document.getElementById('workplaceMapCanvas'); ctx = canvas.getContext('2d'); }); // ==================== 데이터 로드 ==================== /** * 카테고리(공장) 목록 로드 */ async function loadCategories() { try { const response = await window.apiCall('/workplaces/categories', 'GET'); if (response && response.success) { categories = response.data || []; const categorySelect = document.getElementById('categorySelect'); categorySelect.innerHTML = ''; categories.forEach(cat => { if (cat.is_active) { const option = document.createElement('option'); option.value = cat.category_id; option.textContent = cat.category_name; categorySelect.appendChild(option); } }); } } catch (error) { console.error('카테고리 로드 오류:', error); window.showToast('카테고리 로드 중 오류가 발생했습니다.', 'error'); } } /** * 방문 목적 목록 로드 */ async function loadVisitPurposes() { try { const response = await window.apiCall('/workplace-visits/purposes/active', 'GET'); if (response && response.success) { visitPurposes = response.data || []; const purposeSelect = document.getElementById('visitPurpose'); purposeSelect.innerHTML = ''; visitPurposes.forEach(purpose => { const option = document.createElement('option'); option.value = purpose.purpose_id; option.textContent = purpose.purpose_name; purposeSelect.appendChild(option); }); } } catch (error) { console.error('방문 목적 로드 오류:', error); window.showToast('방문 목적 로드 중 오류가 발생했습니다.', 'error'); } } /** * 내 출입 신청 목록 로드 */ async function loadMyRequests() { try { // localStorage에서 사용자 정보 가져오기 const userData = localStorage.getItem('user'); const currentUser = userData ? JSON.parse(userData) : null; if (!currentUser || !currentUser.user_id) { console.log('사용자 정보 없음'); return; } const response = await window.apiCall(`/workplace-visits/requests?requester_id=${currentUser.user_id}`, 'GET'); if (response && response.success) { const requests = response.data || []; renderMyRequests(requests); } } catch (error) { console.error('내 신청 목록 로드 오류:', error); } } /** * 내 신청 목록 렌더링 */ function renderMyRequests(requests) { const listDiv = document.getElementById('myRequestsList'); if (requests.length === 0) { listDiv.innerHTML = '

신청 내역이 없습니다

'; return; } let html = ''; requests.forEach(req => { const statusText = { 'pending': '승인 대기', 'approved': '승인됨', 'rejected': '반려됨', 'training_completed': '교육 완료' }[req.status] || req.status; html += `

${req.visitor_company} (${req.visitor_count}명)

${statusText}
방문 작업장 ${req.category_name} - ${req.workplace_name}
방문 일시 ${req.visit_date} ${req.visit_time}
방문 목적 ${req.purpose_name}
신청일 ${new Date(req.created_at).toLocaleDateString()}
${req.rejection_reason ? `

반려 사유: ${req.rejection_reason}

` : ''} ${req.notes ? `

비고: ${req.notes}

` : ''}
`; }); listDiv.innerHTML = html; } // ==================== 작업장 지도 모달 ==================== /** * 지도 모달 열기 */ function openMapModal() { document.getElementById('mapModal').style.display = 'flex'; document.body.style.overflow = 'hidden'; } /** * 지도 모달 닫기 */ function closeMapModal() { document.getElementById('mapModal').style.display = 'none'; document.body.style.overflow = ''; } /** * 작업장 지도 로드 */ async function loadWorkplaceMap() { const categoryId = document.getElementById('categorySelect').value; if (!categoryId) { document.getElementById('mapCanvasContainer').style.display = 'none'; return; } selectedCategory = categories.find(c => c.category_id == categoryId); try { // 작업장 목록 로드 const workplacesResponse = await window.apiCall(`/workplaces/categories/${categoryId}`, 'GET'); if (workplacesResponse && workplacesResponse.success) { workplaces = workplacesResponse.data || []; } // 지도 영역 로드 const regionsResponse = await window.apiCall(`/workplaces/categories/${categoryId}/map-regions`, 'GET'); if (regionsResponse && regionsResponse.success) { mapRegions = regionsResponse.data || []; } // 레이아웃 이미지가 있으면 표시 if (selectedCategory && selectedCategory.layout_image) { // API_BASE_URL에서 /api 제거하고 이미지 경로 생성 const baseUrl = (window.API_BASE_URL || 'http://localhost:20005').replace('/api', ''); const fullImageUrl = selectedCategory.layout_image.startsWith('http') ? selectedCategory.layout_image : `${baseUrl}${selectedCategory.layout_image}`; console.log('이미지 URL:', fullImageUrl); loadImageToCanvas(fullImageUrl); document.getElementById('mapCanvasContainer').style.display = 'block'; } else { window.showToast('선택한 구역에 레이아웃 지도가 없습니다.', 'warning'); document.getElementById('mapCanvasContainer').style.display = 'none'; } } catch (error) { console.error('작업장 지도 로드 오류:', error); window.showToast('작업장 지도 로드 중 오류가 발생했습니다.', 'error'); } } /** * 이미지를 캔버스에 로드 */ function loadImageToCanvas(imagePath) { const img = new Image(); // crossOrigin 제거 - 같은 도메인이므로 불필요 img.onload = function() { // 캔버스 크기 설정 const maxWidth = 800; const scale = img.width > maxWidth ? maxWidth / img.width : 1; canvas.width = img.width * scale; canvas.height = img.height * scale; // 이미지 그리기 ctx.drawImage(img, 0, 0, canvas.width, canvas.height); layoutImage = img; // 영역 표시 drawRegions(); // 클릭 이벤트 등록 canvas.onclick = handleCanvasClick; }; img.onerror = function() { window.showToast('지도 이미지를 불러올 수 없습니다.', 'error'); }; img.src = imagePath; } /** * 지도 영역 그리기 */ function drawRegions() { mapRegions.forEach(region => { const x1 = (region.x_start / 100) * canvas.width; const y1 = (region.y_start / 100) * canvas.height; const x2 = (region.x_end / 100) * canvas.width; const y2 = (region.y_end / 100) * canvas.height; // 영역 박스 ctx.strokeStyle = '#10b981'; ctx.lineWidth = 2; ctx.strokeRect(x1, y1, x2 - x1, y2 - y1); ctx.fillStyle = 'rgba(16, 185, 129, 0.1)'; ctx.fillRect(x1, y1, x2 - x1, y2 - y1); // 작업장 이름 ctx.fillStyle = '#10b981'; ctx.font = 'bold 14px sans-serif'; ctx.fillText(region.workplace_name || '', x1 + 5, y1 + 20); }); } /** * 캔버스 클릭 핸들러 */ function handleCanvasClick(event) { const rect = canvas.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; // 클릭한 위치의 영역 찾기 for (const region of mapRegions) { const x1 = (region.x_start / 100) * canvas.width; const y1 = (region.y_start / 100) * canvas.height; const x2 = (region.x_end / 100) * canvas.width; const y2 = (region.y_end / 100) * canvas.height; if (x >= x1 && x <= x2 && y >= y1 && y <= y2) { // 작업장 선택 selectWorkplace(region); return; } } window.showToast('작업장 영역을 클릭해주세요.', 'warning'); } /** * 작업장 선택 */ function selectWorkplace(region) { selectedWorkplace = { workplace_id: region.workplace_id, workplace_name: region.workplace_name, category_id: selectedCategory.category_id, category_name: selectedCategory.category_name }; // 선택 표시 const selectionDiv = document.getElementById('workplaceSelection'); selectionDiv.classList.add('selected'); selectionDiv.innerHTML = `
${selectedCategory.category_name} - ${region.workplace_name}
`; // 상세 정보 카드 표시 const infoDiv = document.getElementById('selectedWorkplaceInfo'); infoDiv.style.display = 'block'; infoDiv.innerHTML = `
📍
${region.workplace_name}
${selectedCategory.category_name}
`; // 모달 닫기 closeMapModal(); window.showToast(`${region.workplace_name} 작업장이 선택되었습니다.`, 'success'); } /** * 작업장 선택 초기화 */ function clearWorkplaceSelection() { selectedWorkplace = null; const selectionDiv = document.getElementById('workplaceSelection'); selectionDiv.classList.remove('selected'); selectionDiv.innerHTML = `
📍
지도에서 작업장을 선택하세요
`; document.getElementById('selectedWorkplaceInfo').style.display = 'none'; } // ==================== 폼 제출 ==================== /** * 출입 신청 제출 */ async function handleSubmit(event) { event.preventDefault(); if (!selectedWorkplace) { window.showToast('작업장을 선택해주세요.', 'warning'); openMapModal(); return; } const formData = { visitor_company: document.getElementById('visitorCompany').value.trim(), visitor_count: parseInt(document.getElementById('visitorCount').value), category_id: selectedWorkplace.category_id, workplace_id: selectedWorkplace.workplace_id, visit_date: document.getElementById('visitDate').value, visit_time: document.getElementById('visitTime').value, purpose_id: parseInt(document.getElementById('visitPurpose').value), notes: document.getElementById('notes').value.trim() || null }; try { const response = await window.apiCall('/workplace-visits/requests', 'POST', formData); if (response && response.success) { window.showToast('출입 신청 및 안전교육 신청이 완료되었습니다. 안전관리자의 승인을 기다려주세요.', 'success'); // 폼 초기화 resetForm(); // 내 신청 목록 새로고침 await loadMyRequests(); } else { throw new Error(response?.message || '신청 실패'); } } catch (error) { console.error('출입 신청 오류:', error); window.showToast(error.message || '출입 신청 중 오류가 발생했습니다.', 'error'); } } /** * 폼 초기화 */ function resetForm() { document.getElementById('visitRequestForm').reset(); clearWorkplaceSelection(); // 오늘 날짜와 시간 다시 설정 const today = new Date().toISOString().split('T')[0]; document.getElementById('visitDate').value = today; const now = new Date(); now.setHours(now.getHours() + 1); const timeString = now.toTimeString().slice(0, 5); document.getElementById('visitTime').value = timeString; document.getElementById('visitorCount').value = 1; } // 전역 함수로 노출 window.showToast = showToast; window.openMapModal = openMapModal; window.closeMapModal = closeMapModal; window.loadWorkplaceMap = loadWorkplaceMap; window.clearWorkplaceSelection = clearWorkplaceSelection; window.resetForm = resetForm;