// 출입 신청 페이지 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.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;