// 작업장 레이아웃 지도 관리 // 전역 변수 let layoutMapImage = null; let mapRegions = []; let canvas = null; let ctx = null; let isDrawing = false; let startX = 0; let startY = 0; let currentRect = null; // ==================== 레이아웃 지도 모달 ==================== /** * 레이아웃 지도 모달 열기 */ async function openLayoutMapModal() { // window 객체에서 currentCategoryId 가져오기 const currentCategoryId = window.currentCategoryId; if (!currentCategoryId) { window.window.showToast('공장을 먼저 선택해주세요.', 'warning'); return; } const modal = document.getElementById('layoutMapModal'); if (!modal) return; // 캔버스 초기화 canvas = document.getElementById('regionCanvas'); ctx = canvas.getContext('2d'); // 현재 카테고리의 레이아웃 이미지 및 영역 로드 await loadLayoutMapData(); // 작업장 선택 옵션 업데이트 updateWorkplaceSelect(); modal.style.display = 'flex'; document.body.style.overflow = 'hidden'; } /** * 레이아웃 지도 모달 닫기 */ function closeLayoutMapModal() { const modal = document.getElementById('layoutMapModal'); if (modal) { modal.style.display = 'none'; document.body.style.overflow = ''; } // 캔버스 이벤트 리스너 제거 if (canvas) { canvas.removeEventListener('mousedown', startDrawing); canvas.removeEventListener('mousemove', draw); canvas.removeEventListener('mouseup', stopDrawing); } // 메인 페이지의 레이아웃 미리보기 업데이트 const currentCategoryId = window.currentCategoryId; const categories = window.categories; if (currentCategoryId && categories) { const category = categories.find(c => c.category_id == currentCategoryId); if (category && window.updateLayoutPreview) { window.updateLayoutPreview(category); } } } /** * 레이아웃 지도 데이터 로드 */ async function loadLayoutMapData() { try { const currentCategoryId = window.currentCategoryId; const categories = window.categories; // 현재 카테고리 정보 가져오기 const category = categories.find(c => c.category_id == currentCategoryId); if (!category) return; // 레이아웃 이미지 표시 const currentImageDiv = document.getElementById('currentLayoutImage'); if (category.layout_image) { // 이미지 경로를 전체 URL로 변환 const fullImageUrl = category.layout_image.startsWith('http') ? category.layout_image : `${window.API_BASE_URL || 'http://localhost:20005/api'}${category.layout_image}`.replace('/api/', '/'); currentImageDiv.innerHTML = ` 현재 레이아웃 이미지 `; // 캔버스에도 이미지 로드 loadImageToCanvas(fullImageUrl); } else { currentImageDiv.innerHTML = '업로드된 이미지가 없습니다'; } // 영역 데이터 로드 const regionsResponse = await window.apiCall(`/workplaces/categories/${currentCategoryId}/map-regions`, 'GET'); if (regionsResponse && regionsResponse.success) { mapRegions = regionsResponse.data || []; } else { mapRegions = []; } renderRegionList(); } catch (error) { console.error('레이아웃 지도 데이터 로딩 오류:', error); window.window.showToast('레이아웃 지도 데이터를 불러오는데 실패했습니다.', 'error'); } } /** * 이미지를 캔버스에 로드 */ function loadImageToCanvas(imagePath) { const img = new Image(); img.onload = function() { // 캔버스 크기를 이미지 크기에 맞춤 (최대 800px) 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); layoutMapImage = img; // 기존 영역들 그리기 drawExistingRegions(); // 캔버스 이벤트 리스너 등록 setupCanvasEvents(); }; img.src = imagePath; } /** * 작업장 선택 옵션 업데이트 */ function updateWorkplaceSelect() { const select = document.getElementById('regionWorkplaceSelect'); if (!select) return; const currentCategoryId = window.currentCategoryId; const workplaces = window.workplaces; // 현재 카테고리의 작업장만 필터링 const categoryWorkplaces = workplaces.filter(w => w.category_id == currentCategoryId); let options = ''; categoryWorkplaces.forEach(wp => { // 이미 영역이 정의된 작업장은 표시 const hasRegion = mapRegions.some(r => r.workplace_id === wp.workplace_id); options += ``; }); select.innerHTML = options; } // ==================== 이미지 업로드 ==================== /** * 이미지 미리보기 */ function previewLayoutImage(event) { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(e) { const currentImageDiv = document.getElementById('currentLayoutImage'); currentImageDiv.innerHTML = ` 미리보기

미리보기 (저장하려면 "이미지 업로드" 버튼을 클릭하세요)

`; }; reader.readAsDataURL(file); } /** * 레이아웃 이미지 업로드 */ async function uploadLayoutImage() { const fileInput = document.getElementById('layoutImageFile'); const file = fileInput.files[0]; if (!file) { window.showToast('이미지 파일을 선택해주세요.', 'warning'); return; } const currentCategoryId = window.currentCategoryId; if (!currentCategoryId) { window.showToast('공장을 먼저 선택해주세요.', 'error'); return; } try { // FormData 생성 const formData = new FormData(); formData.append('image', file); // 업로드 요청 const response = await fetch(`${window.API_BASE_URL || 'http://localhost:20005/api'}/workplaces/categories/${currentCategoryId}/layout-image`, { method: 'POST', headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }, body: formData }); const result = await response.json(); if (result.success) { window.showToast('이미지가 성공적으로 업로드되었습니다.', 'success'); // 이미지 경로를 전체 URL로 변환 const fullImageUrl = `${window.API_BASE_URL || 'http://localhost:20005/api'}${result.data.image_path}`.replace('/api/', '/'); // 이미지를 캔버스에 로드 loadImageToCanvas(fullImageUrl); // 현재 이미지 미리보기도 업데이트 const currentImageDiv = document.getElementById('currentLayoutImage'); if (currentImageDiv) { currentImageDiv.innerHTML = ` 현재 레이아웃 이미지 `; } // 카테고리 데이터 새로고침 (workplace-management.js의 loadCategories 함수 호출) if (window.loadCategories) { await window.loadCategories(); // 메인 페이지 미리보기도 업데이트 const currentCategoryId = window.currentCategoryId; const categories = window.categories; if (currentCategoryId && categories && window.updateLayoutPreview) { const category = categories.find(c => c.category_id == currentCategoryId); if (category) { window.updateLayoutPreview(category); } } } } else { throw new Error(result.message || '업로드 실패'); } } catch (error) { console.error('이미지 업로드 오류:', error); window.showToast(error.message || '이미지 업로드 중 오류가 발생했습니다.', 'error'); } } // ==================== 영역 그리기 ==================== /** * 캔버스 이벤트 설정 */ function setupCanvasEvents() { canvas.addEventListener('mousedown', startDrawing); canvas.addEventListener('mousemove', draw); canvas.addEventListener('mouseup', stopDrawing); canvas.addEventListener('mouseleave', stopDrawing); } /** * 그리기 시작 */ function startDrawing(e) { const rect = canvas.getBoundingClientRect(); startX = e.clientX - rect.left; startY = e.clientY - rect.top; isDrawing = true; } /** * 그리기 */ function draw(e) { if (!isDrawing) return; const rect = canvas.getBoundingClientRect(); const currentX = e.clientX - rect.left; const currentY = e.clientY - rect.top; // 캔버스 다시 그리기 ctx.clearRect(0, 0, canvas.width, canvas.height); if (layoutMapImage) { ctx.drawImage(layoutMapImage, 0, 0, canvas.width, canvas.height); } // 기존 영역들 그리기 drawExistingRegions(); // 현재 그리는 사각형 const width = currentX - startX; const height = currentY - startY; ctx.strokeStyle = '#3b82f6'; ctx.lineWidth = 3; ctx.strokeRect(startX, startY, width, height); ctx.fillStyle = 'rgba(59, 130, 246, 0.2)'; ctx.fillRect(startX, startY, width, height); currentRect = { startX, startY, endX: currentX, endY: currentY }; } /** * 그리기 종료 */ function stopDrawing() { isDrawing = false; } /** * 기존 영역들 그리기 */ function drawExistingRegions() { 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.15)'; ctx.fillRect(x1, y1, x2 - x1, y2 - y1); // 작업장 이름 표시 ctx.fillStyle = '#10b981'; ctx.font = '14px sans-serif'; ctx.fillText(region.workplace_name || '', x1 + 5, y1 + 20); }); } /** * 현재 영역 지우기 */ function clearCurrentRegion() { currentRect = null; // 캔버스 다시 그리기 ctx.clearRect(0, 0, canvas.width, canvas.height); if (layoutMapImage) { ctx.drawImage(layoutMapImage, 0, 0, canvas.width, canvas.height); } drawExistingRegions(); } /** * 영역 저장 */ async function saveRegion() { const workplaceId = document.getElementById('regionWorkplaceSelect').value; if (!workplaceId) { window.showToast('작업장을 선택해주세요.', 'warning'); return; } if (!currentRect) { window.showToast('영역을 그려주세요.', 'warning'); return; } const currentCategoryId = window.currentCategoryId; try { // 비율로 변환 (0~100%) const xStart = Math.min(currentRect.startX, currentRect.endX) / canvas.width * 100; const yStart = Math.min(currentRect.startY, currentRect.endY) / canvas.height * 100; const xEnd = Math.max(currentRect.startX, currentRect.endX) / canvas.width * 100; const yEnd = Math.max(currentRect.startY, currentRect.endY) / canvas.height * 100; // 기존 영역이 있는지 확인 const existingRegion = mapRegions.find(r => r.workplace_id == workplaceId); const regionData = { workplace_id: parseInt(workplaceId), category_id: parseInt(currentCategoryId), x_start: xStart.toFixed(2), y_start: yStart.toFixed(2), x_end: xEnd.toFixed(2), y_end: yEnd.toFixed(2), shape: 'rect' }; let response; if (existingRegion) { // 수정 response = await window.apiCall(`/workplaces/map-regions/${existingRegion.region_id}`, 'PUT', regionData); } else { // 신규 등록 response = await window.apiCall('/workplaces/map-regions', 'POST', regionData); } if (response && response.success) { window.showToast('영역이 성공적으로 저장되었습니다.', 'success'); // 데이터 새로고침 await loadLayoutMapData(); // 현재 그림 초기화 clearCurrentRegion(); // 작업장 선택 초기화 document.getElementById('regionWorkplaceSelect').value = ''; } else { throw new Error(response?.message || '저장 실패'); } } catch (error) { console.error('영역 저장 오류:', error); window.showToast(error.message || '영역 저장 중 오류가 발생했습니다.', 'error'); } } /** * 영역 목록 렌더링 */ function renderRegionList() { const listDiv = document.getElementById('regionList'); if (!listDiv) return; if (mapRegions.length === 0) { listDiv.innerHTML = '

정의된 영역이 없습니다

'; return; } let listHtml = '
'; mapRegions.forEach(region => { listHtml += `
${region.workplace_name} (${region.x_start}%, ${region.y_start}%) ~ (${region.x_end}%, ${region.y_end}%)
`; }); listHtml += '
'; listDiv.innerHTML = listHtml; } /** * 영역 삭제 */ async function deleteRegion(regionId) { if (!confirm('이 영역을 삭제하시겠습니까?')) { return; } try { const response = await window.apiCall(`/workplaces/map-regions/${regionId}`, 'DELETE'); if (response && response.success) { window.showToast('영역이 삭제되었습니다.', 'success'); await loadLayoutMapData(); } else { throw new Error(response?.message || '삭제 실패'); } } catch (error) { console.error('영역 삭제 오류:', error); window.showToast(error.message || '영역 삭제 중 오류가 발생했습니다.', 'error'); } } // 전역 함수로 노출 window.openLayoutMapModal = openLayoutMapModal; window.closeLayoutMapModal = closeLayoutMapModal; window.previewLayoutImage = previewLayoutImage; window.uploadLayoutImage = uploadLayoutImage; window.clearCurrentRegion = clearCurrentRegion; window.saveRegion = saveRegion; window.deleteRegion = deleteRegion;