// 안전교육 진행 페이지 JavaScript let requestId = null; let requestData = null; let canvas = null; let ctx = null; let isDrawing = false; let hasSignature = false; let savedSignatures = []; // 저장된 서명 목록 // ==================== 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); } 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); 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 () => { // URL 파라미터에서 request_id 가져오기 const urlParams = new URLSearchParams(window.location.search); requestId = urlParams.get('request_id'); if (!requestId) { showToast('출입 신청 ID가 없습니다.', 'error'); setTimeout(() => { window.location.href = '/pages/safety/management.html'; }, 2000); return; } // 서명 캔버스 초기화 initSignatureCanvas(); // 현재 날짜 표시 const today = new Date().toLocaleDateString('ko-KR'); document.getElementById('signatureDate').textContent = today; // 출입 신청 정보 로드 await loadRequestInfo(); }); // ==================== 출입 신청 정보 로드 ==================== /** * 출입 신청 정보 로드 */ async function loadRequestInfo() { try { const response = await window.apiCall(`/workplace-visits/requests/${requestId}`, 'GET'); if (response && response.success) { requestData = response.data; // 상태 확인 - 승인됨 상태만 진행 가능 if (requestData.status !== 'approved') { showToast('이미 처리되었거나 승인되지 않은 신청입니다.', 'error'); setTimeout(() => { window.location.href = '/pages/safety/management.html'; }, 2000); return; } renderRequestInfo(); } else { throw new Error(response?.message || '정보를 불러올 수 없습니다.'); } } catch (error) { console.error('출입 신청 정보 로드 오류:', error); showToast('출입 신청 정보를 불러오는데 실패했습니다.', 'error'); } } /** * 출입 신청 정보 렌더링 */ function renderRequestInfo() { const container = document.getElementById('requestInfo'); // 날짜 포맷 변환 const visitDate = new Date(requestData.visit_date); const formattedDate = visitDate.toLocaleDateString('ko-KR', { year: 'numeric', month: 'long', day: 'numeric', weekday: 'short' }); const html = `
신청 번호
#${requestData.request_id}
신청자
${requestData.requester_full_name || requestData.requester_name}
방문자 소속
${requestData.visitor_company}
방문 인원
${requestData.visitor_count}명
방문 작업장
${requestData.category_name} - ${requestData.workplace_name}
방문 일시
${formattedDate} ${requestData.visit_time}
방문 목적
${requestData.purpose_name}
`; container.innerHTML = html; } // ==================== 서명 캔버스 ==================== /** * 서명 캔버스 초기화 */ function initSignatureCanvas() { canvas = document.getElementById('signatureCanvas'); ctx = canvas.getContext('2d'); // 캔버스 크기 설정 const container = canvas.parentElement; canvas.width = container.clientWidth - 4; // border 제외 canvas.height = 300; // 그리기 설정 ctx.strokeStyle = '#000'; ctx.lineWidth = 2; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; // 마우스 이벤트 canvas.addEventListener('mousedown', startDrawing); canvas.addEventListener('mousemove', draw); canvas.addEventListener('mouseup', stopDrawing); canvas.addEventListener('mouseout', stopDrawing); // 터치 이벤트 (모바일, Apple Pencil) canvas.addEventListener('touchstart', handleTouchStart, { passive: false }); canvas.addEventListener('touchmove', handleTouchMove, { passive: false }); canvas.addEventListener('touchend', stopDrawing); canvas.addEventListener('touchcancel', stopDrawing); // Pointer Events (Apple Pencil 최적화) if (window.PointerEvent) { canvas.addEventListener('pointerdown', handlePointerDown); canvas.addEventListener('pointermove', handlePointerMove); canvas.addEventListener('pointerup', stopDrawing); canvas.addEventListener('pointercancel', stopDrawing); } } /** * 그리기 시작 (마우스) */ function startDrawing(e) { isDrawing = true; hasSignature = true; document.getElementById('signaturePlaceholder').style.display = 'none'; const rect = canvas.getBoundingClientRect(); ctx.beginPath(); ctx.moveTo(e.clientX - rect.left, e.clientY - rect.top); } /** * 그리기 (마우스) */ function draw(e) { if (!isDrawing) return; const rect = canvas.getBoundingClientRect(); ctx.lineTo(e.clientX - rect.left, e.clientY - rect.top); ctx.stroke(); } /** * 그리기 중지 */ function stopDrawing() { isDrawing = false; ctx.beginPath(); } /** * 터치 시작 처리 */ function handleTouchStart(e) { e.preventDefault(); isDrawing = true; hasSignature = true; document.getElementById('signaturePlaceholder').style.display = 'none'; const touch = e.touches[0]; const rect = canvas.getBoundingClientRect(); ctx.beginPath(); ctx.moveTo(touch.clientX - rect.left, touch.clientY - rect.top); } /** * 터치 이동 처리 */ function handleTouchMove(e) { if (!isDrawing) return; e.preventDefault(); const touch = e.touches[0]; const rect = canvas.getBoundingClientRect(); ctx.lineTo(touch.clientX - rect.left, touch.clientY - rect.top); ctx.stroke(); } /** * Pointer 시작 처리 (Apple Pencil) */ function handlePointerDown(e) { isDrawing = true; hasSignature = true; document.getElementById('signaturePlaceholder').style.display = 'none'; const rect = canvas.getBoundingClientRect(); ctx.beginPath(); ctx.moveTo(e.clientX - rect.left, e.clientY - rect.top); } /** * Pointer 이동 처리 (Apple Pencil) */ function handlePointerMove(e) { if (!isDrawing) return; const rect = canvas.getBoundingClientRect(); ctx.lineTo(e.clientX - rect.left, e.clientY - rect.top); ctx.stroke(); } /** * 서명 지우기 */ function clearSignature() { ctx.clearRect(0, 0, canvas.width, canvas.height); hasSignature = false; document.getElementById('signaturePlaceholder').style.display = 'block'; } /** * 서명을 Base64로 변환 */ function getSignatureBase64() { if (!hasSignature) { return null; } return canvas.toDataURL('image/png'); } /** * 현재 서명 저장 */ function saveSignature() { if (!hasSignature) { showToast('서명이 없습니다. 이름과 서명을 작성해주세요.', 'warning'); return; } const signatureImage = getSignatureBase64(); const now = new Date(); savedSignatures.push({ id: Date.now(), image: signatureImage, timestamp: now.toLocaleString('ko-KR') }); // 서명 카운트 업데이트 document.getElementById('signatureCount').textContent = savedSignatures.length; // 캔버스 초기화 clearSignature(); // 저장된 서명 목록 렌더링 renderSavedSignatures(); // 교육 완료 버튼 활성화 updateCompleteButton(); showToast('서명이 저장되었습니다.', 'success'); } /** * 저장된 서명 목록 렌더링 */ function renderSavedSignatures() { const container = document.getElementById('savedSignatures'); if (savedSignatures.length === 0) { container.innerHTML = ''; return; } let html = '

저장된 서명 목록

'; savedSignatures.forEach((sig, index) => { html += `
서명 ${index + 1}
방문자 ${index + 1}
저장 시간: ${sig.timestamp}
`; }); container.innerHTML = html; } /** * 서명 삭제 */ function deleteSignature(signatureId) { if (!confirm('이 서명을 삭제하시겠습니까?')) { return; } savedSignatures = savedSignatures.filter(sig => sig.id !== signatureId); // 서명 카운트 업데이트 document.getElementById('signatureCount').textContent = savedSignatures.length; // 목록 다시 렌더링 renderSavedSignatures(); // 교육 완료 버튼 상태 업데이트 updateCompleteButton(); showToast('서명이 삭제되었습니다.', 'success'); } /** * 교육 완료 버튼 활성화/비활성화 */ function updateCompleteButton() { const completeBtn = document.getElementById('completeBtn'); // 체크리스트와 서명이 모두 있어야 활성화 const checkboxes = document.querySelectorAll('input[name="safety-check"]'); const checkedItems = Array.from(checkboxes).filter(cb => cb.checked); const allChecked = checkedItems.length === checkboxes.length; const hasSignatures = savedSignatures.length > 0; completeBtn.disabled = !(allChecked && hasSignatures); } // ==================== 교육 완료 처리 ==================== /** * 교육 완료 처리 */ async function completeTraining() { // 체크리스트 검증 const checkboxes = document.querySelectorAll('input[name="safety-check"]'); const checkedItems = Array.from(checkboxes).filter(cb => cb.checked); if (checkedItems.length !== checkboxes.length) { showToast('모든 안전교육 항목을 체크해주세요.', 'warning'); return; } // 서명 검증 if (savedSignatures.length === 0) { showToast('최소 1명 이상의 서명이 필요합니다.', 'warning'); return; } // 확인 if (!confirm(`${savedSignatures.length}명의 방문자 안전교육을 완료하시겠습니까?\n완료 후에는 수정할 수 없습니다.`)) { return; } try { // 교육 항목 수집 const trainingItems = checkedItems.map(cb => cb.value).join(', '); // API 호출 const userData = localStorage.getItem('user'); const currentUser = userData ? JSON.parse(userData) : null; if (!currentUser) { showToast('로그인 정보를 찾을 수 없습니다.', 'error'); return; } // 현재 시간 const now = new Date(); const currentTime = now.toTimeString().split(' ')[0]; // HH:MM:SS const trainingDate = now.toISOString().split('T')[0]; // YYYY-MM-DD // 각 서명에 대해 개별적으로 API 호출 let successCount = 0; for (let i = 0; i < savedSignatures.length; i++) { const sig = savedSignatures[i]; const payload = { request_id: requestId, conducted_by: currentUser.user_id, training_date: trainingDate, training_start_time: currentTime, training_end_time: currentTime, training_items: trainingItems, visitor_name: `방문자 ${i + 1}`, // 순번으로 구분 signature_image: sig.image, notes: `교육 완료 - ${checkedItems.length}개 항목 (${i + 1}/${savedSignatures.length})` }; const response = await window.apiCall( '/workplace-visits/training', 'POST', payload ); if (response && response.success) { successCount++; } else { console.error(`서명 ${i + 1} 저장 실패:`, response); } } if (successCount === savedSignatures.length) { showToast(`${successCount}명의 안전교육이 완료되었습니다.`, 'success'); setTimeout(() => { window.location.href = '/pages/safety/management.html'; }, 1500); } else if (successCount > 0) { showToast(`${successCount}/${savedSignatures.length}명의 교육만 저장되었습니다.`, 'warning'); } else { throw new Error('교육 완료 처리 실패'); } } catch (error) { console.error('교육 완료 처리 오류:', error); showToast(error.message || '교육 완료 처리 중 오류가 발생했습니다.', 'error'); } } /** * 뒤로 가기 */ function goBack() { if (hasSignature || document.querySelector('input[name="safety-check"]:checked')) { if (!confirm('작성 중인 내용이 있습니다. 정말 나가시겠습니까?')) { return; } } window.location.href = '/pages/safety/management.html'; } // 전역 함수로 노출 window.showToast = showToast; window.clearSignature = clearSignature; window.saveSignature = saveSignature; window.deleteSignature = deleteSignature; window.updateCompleteButton = updateCompleteButton; window.completeTraining = completeTraining; window.goBack = goBack;