// 작업장 관리 페이지 JavaScript // 전역 변수 let categories = []; let workplaces = []; let currentCategoryId = ''; let currentEditingCategory = null; let currentEditingWorkplace = null; // 페이지 초기화 document.addEventListener('DOMContentLoaded', function() { console.log('🏗️ 작업장 관리 페이지 초기화 시작'); loadAllData(); }); // 모든 데이터 로드 async function loadAllData() { try { await Promise.all([ loadCategories(), loadWorkplaces() ]); renderCategoryTabs(); renderWorkplaces(); updateStatistics(); } catch (error) { console.error('데이터 로딩 오류:', error); showToast('데이터를 불러오는데 실패했습니다.', 'error'); } } // ==================== 카테고리(공장) 관련 ==================== // 카테고리 목록 로드 async function loadCategories() { try { const response = await window.apiCall('/workplaces/categories', 'GET'); let categoryData = []; if (response && response.success && Array.isArray(response.data)) { categoryData = response.data; } else if (Array.isArray(response)) { categoryData = response; } categories = categoryData; console.log(`✅ 카테고리 ${categories.length}개 로드 완료`); } catch (error) { console.error('카테고리 로딩 오류:', error); categories = []; } } // 카테고리 탭 렌더링 function renderCategoryTabs() { const tabsContainer = document.getElementById('categoryTabs'); if (!tabsContainer) return; // 전체 탭은 항상 표시 let tabsHtml = ` `; // 각 카테고리 탭 추가 categories.forEach(category => { const count = workplaces.filter(w => w.category_id === category.category_id).length; const isActive = currentCategoryId === category.category_id; tabsHtml += ` `; }); tabsContainer.innerHTML = tabsHtml; } // 카테고리 전환 function switchCategory(categoryId) { currentCategoryId = categoryId === '' ? '' : categoryId; renderCategoryTabs(); renderWorkplaces(); // 레이아웃 지도 섹션 표시/숨김 const layoutMapSection = document.getElementById('layoutMapSection'); const selectedCategoryName = document.getElementById('selectedCategoryName'); if (currentCategoryId && layoutMapSection) { const category = categories.find(c => c.category_id == currentCategoryId); if (category) { layoutMapSection.style.display = 'block'; if (selectedCategoryName) { selectedCategoryName.textContent = category.category_name; } // 레이아웃 미리보기 업데이트 updateLayoutPreview(category); } } else if (layoutMapSection) { layoutMapSection.style.display = 'none'; } } // 레이아웃 미리보기 업데이트 async function updateLayoutPreview(category) { const previewDiv = document.getElementById('layoutMapPreview'); if (!previewDiv) return; 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/', '/'); // Canvas 컨테이너 생성 previewDiv.innerHTML = `

클릭하여 작업장 영역을 수정하려면 "지도 설정" 버튼을 누르세요

`; // 이미지와 영역 로드 await loadImageWithRegions(fullImageUrl, category.category_id); } else { previewDiv.innerHTML = `
🗺️

이 공장의 레이아웃 이미지가 아직 등록되지 않았습니다

"지도 설정" 버튼을 눌러 레이아웃 이미지를 업로드하고 작업장 위치를 지정하세요

`; } } // 이미지와 영역을 캔버스에 로드 async function loadImageWithRegions(imageUrl, categoryId) { const canvas = document.getElementById('previewCanvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const img = new Image(); img.onload = async 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); // 영역 데이터 로드 및 그리기 try { const response = await window.apiCall(`/workplaces/categories/${categoryId}/map-regions`, 'GET'); let regions = []; if (response && response.success && Array.isArray(response.data)) { regions = response.data; } else if (Array.isArray(response)) { regions = response; } // 각 영역 그리기 regions.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; const width = x2 - x1; const height = y2 - y1; // 영역 테두리 그리기 ctx.strokeStyle = '#10b981'; ctx.lineWidth = 2; ctx.strokeRect(x1, y1, width, height); // 영역 배경 (반투명) ctx.fillStyle = 'rgba(16, 185, 129, 0.15)'; ctx.fillRect(x1, y1, width, height); // 작업장 이름 표시 if (region.workplace_name) { ctx.fillStyle = '#10b981'; ctx.font = 'bold 14px sans-serif'; // 텍스트 배경 const textMetrics = ctx.measureText(region.workplace_name); const textPadding = 4; ctx.fillStyle = 'rgba(255, 255, 255, 0.9)'; ctx.fillRect(x1 + 5, y1 + 5, textMetrics.width + textPadding * 2, 20); // 텍스트 ctx.fillStyle = '#10b981'; ctx.fillText(region.workplace_name, x1 + 5 + textPadding, y1 + 20); } }); if (regions.length > 0) { console.log(`✅ 레이아웃 미리보기에 ${regions.length}개 영역 표시 완료`); } } catch (error) { console.error('영역 로드 오류:', error); } }; img.onerror = function() { console.error('이미지 로드 실패:', imageUrl); }; img.src = imageUrl; } // 작업장 카드에 지도 썸네일 로드 async function loadWorkplaceMapThumbnail(workplace) { const thumbnailDiv = document.getElementById(`workplace-map-${workplace.workplace_id}`); if (!thumbnailDiv) return; // 작업장 자체에 레이아웃 이미지가 있는 경우 if (workplace.layout_image) { const fullImageUrl = workplace.layout_image.startsWith('http') ? workplace.layout_image : `${window.API_BASE_URL || 'http://localhost:20005/api'}${workplace.layout_image}`.replace('/api/', '/'); thumbnailDiv.innerHTML = `
📍 작업장 레이아웃
작업장 레이아웃
`; return; } // 작업장에 이미지가 없으면 카테고리 지도의 영역 표시 try { // 해당 작업장의 지도 영역 정보 가져오기 const response = await window.apiCall(`/workplaces/map-regions/workplace/${workplace.workplace_id}`, 'GET'); if (!response || (!response.success && !response.region_id)) { return; // 영역이 정의되지 않은 경우 아무것도 표시하지 않음 } const region = response.success ? response.data : response; // 카테고리 정보에서 레이아웃 이미지 가져오기 const category = categories.find(c => c.category_id === workplace.category_id); if (!category || !category.layout_image) return; const fullImageUrl = category.layout_image.startsWith('http') ? category.layout_image : `${window.API_BASE_URL || 'http://localhost:20005/api'}${category.layout_image}`.replace('/api/', '/'); // 캔버스 생성 const canvasId = `thumbnail-canvas-${workplace.workplace_id}`; thumbnailDiv.innerHTML = `
📍 지도 위치
`; // 이미지 로드 const img = new Image(); img.onload = function() { const canvas = document.getElementById(canvasId); if (!canvas) return; const ctx = canvas.getContext('2d'); // 원본 이미지에서 영역 좌표 계산 (퍼센트를 픽셀로) const x1 = (region.x_start / 100) * img.width; const y1 = (region.y_start / 100) * img.height; const x2 = (region.x_end / 100) * img.width; const y2 = (region.y_end / 100) * img.height; const regionWidth = x2 - x1; const regionHeight = y2 - y1; // 썸네일 크기 설정 (최대 너비 300px) const maxThumbWidth = 300; const scale = regionWidth > maxThumbWidth ? maxThumbWidth / regionWidth : 1; canvas.width = regionWidth * scale; canvas.height = regionHeight * scale; // 영역만 잘라서 그리기 ctx.drawImage( img, x1, y1, regionWidth, regionHeight, // 원본에서 잘라낼 영역 0, 0, canvas.width, canvas.height // 캔버스에 그릴 위치와 크기 ); // 테두리 그리기 ctx.strokeStyle = '#10b981'; ctx.lineWidth = 3; ctx.strokeRect(0, 0, canvas.width, canvas.height); }; img.onerror = function() { thumbnailDiv.innerHTML = ''; }; img.src = fullImageUrl; } catch (error) { // 오류 시 조용히 처리 (지도가 없는 작업장도 많을 수 있으므로) console.debug(`작업장 ${workplace.workplace_id}의 지도 영역 없음`); } } // 카테고리 모달 열기 function openCategoryModal(categoryData = null) { const modal = document.getElementById('categoryModal'); const modalTitle = document.getElementById('categoryModalTitle'); const deleteBtn = document.getElementById('deleteCategoryBtn'); if (!modal) return; currentEditingCategory = categoryData; if (categoryData) { // 수정 모드 modalTitle.textContent = '공장 수정'; deleteBtn.style.display = 'inline-flex'; document.getElementById('categoryId').value = categoryData.category_id; document.getElementById('categoryName').value = categoryData.category_name || ''; document.getElementById('categoryDescription').value = categoryData.description || ''; document.getElementById('categoryOrder').value = categoryData.display_order || 0; } else { // 신규 등록 모드 modalTitle.textContent = '공장 추가'; deleteBtn.style.display = 'none'; document.getElementById('categoryForm').reset(); document.getElementById('categoryId').value = ''; } modal.style.display = 'flex'; document.body.style.overflow = 'hidden'; setTimeout(() => { document.getElementById('categoryName').focus(); }, 100); } // 카테고리 모달 닫기 function closeCategoryModal() { const modal = document.getElementById('categoryModal'); if (modal) { modal.style.display = 'none'; document.body.style.overflow = ''; currentEditingCategory = null; } } // 카테고리 저장 async function saveCategory() { try { const categoryId = document.getElementById('categoryId').value; const categoryData = { category_name: document.getElementById('categoryName').value.trim(), description: document.getElementById('categoryDescription').value.trim() || null, display_order: parseInt(document.getElementById('categoryOrder').value) || 0, is_active: true }; if (!categoryData.category_name) { showToast('공장명은 필수 입력 항목입니다.', 'error'); return; } console.log('💾 저장할 카테고리 데이터:', categoryData); let response; if (categoryId) { // 수정 response = await window.apiCall(`/workplaces/categories/${categoryId}`, 'PUT', categoryData); } else { // 신규 등록 response = await window.apiCall('/workplaces/categories', 'POST', categoryData); } if (response && (response.success || response.category_id)) { const action = categoryId ? '수정' : '등록'; showToast(`공장이 성공적으로 ${action}되었습니다.`, 'success'); closeCategoryModal(); await loadAllData(); } else { throw new Error(response?.message || '저장에 실패했습니다.'); } } catch (error) { console.error('카테고리 저장 오류:', error); showToast(error.message || '카테고리 저장 중 오류가 발생했습니다.', 'error'); } } // 카테고리 삭제 async function deleteCategory() { if (!currentEditingCategory) return; if (!confirm(`"${currentEditingCategory.category_name}" 공장을 정말 삭제하시겠습니까?\n\n⚠️ 이 공장에 속한 모든 작업장의 공장 정보가 제거됩니다.`)) { return; } try { const response = await window.apiCall(`/workplaces/categories/${currentEditingCategory.category_id}`, 'DELETE'); if (response && response.success) { showToast('공장이 성공적으로 삭제되었습니다.', 'success'); closeCategoryModal(); await loadAllData(); } else { throw new Error(response?.message || '삭제에 실패했습니다.'); } } catch (error) { console.error('카테고리 삭제 오류:', error); showToast(error.message || '카테고리 삭제 중 오류가 발생했습니다.', 'error'); } } // ==================== 작업장 관련 ==================== // 작업장 목록 로드 async function loadWorkplaces() { try { const response = await window.apiCall('/workplaces', 'GET'); let workplaceData = []; if (response && response.success && Array.isArray(response.data)) { workplaceData = response.data; } else if (Array.isArray(response)) { workplaceData = response; } workplaces = workplaceData; console.log(`✅ 작업장 ${workplaces.length}개 로드 완료`); } catch (error) { console.error('작업장 로딩 오류:', error); workplaces = []; } } // 작업장 렌더링 function renderWorkplaces() { const grid = document.getElementById('workplaceGrid'); if (!grid) return; // 현재 카테고리별 필터링 const filtered = currentCategoryId === '' ? workplaces : workplaces.filter(w => w.category_id == currentCategoryId); if (filtered.length === 0) { grid.innerHTML = `
🏗️

등록된 작업장이 없습니다.

"작업장 추가" 버튼을 눌러 작업장을 등록해보세요.

`; return; } let gridHtml = ''; filtered.forEach(workplace => { const categoryName = workplace.category_name || '미분류'; const isActive = workplace.is_active === 1 || workplace.is_active === true; // 작업장 용도 아이콘 매핑 const purposeIcons = { '작업구역': '🔧', '설비': '⚙️', '휴게시설': '☕', '회의실': '💼', '창고': '📦', '기타': '📍' }; const purposeIcon = workplace.workplace_purpose ? purposeIcons[workplace.workplace_purpose] || '📍' : '🏗️'; gridHtml += `
${purposeIcon}

${workplace.workplace_name}

${workplace.category_id ? `🏭 ${categoryName}` : ''} ${workplace.workplace_purpose ? `${workplace.workplace_purpose}` : ''}
${workplace.description ? `

${workplace.description}

` : ''}
등록: ${formatDate(workplace.created_at)} ${workplace.updated_at !== workplace.created_at ? `수정: ${formatDate(workplace.updated_at)}` : ''}
`; }); grid.innerHTML = gridHtml; // 각 작업장의 지도 미리보기 로드 filtered.forEach(workplace => { if (workplace.category_id) { loadWorkplaceMapThumbnail(workplace); } }); } // 작업장 모달 열기 function openWorkplaceModal(workplaceData = null) { const modal = document.getElementById('workplaceModal'); const modalTitle = document.getElementById('workplaceModalTitle'); const deleteBtn = document.getElementById('deleteWorkplaceBtn'); const categorySelect = document.getElementById('workplaceCategoryId'); if (!modal) return; currentEditingWorkplace = workplaceData; // 카테고리 선택 옵션 업데이트 let categoryOptions = ''; categories.forEach(cat => { categoryOptions += ``; }); categorySelect.innerHTML = categoryOptions; if (workplaceData) { // 수정 모드 modalTitle.textContent = '작업장 수정'; deleteBtn.style.display = 'inline-flex'; document.getElementById('workplaceId').value = workplaceData.workplace_id; document.getElementById('workplaceCategoryId').value = workplaceData.category_id || ''; document.getElementById('workplaceName').value = workplaceData.workplace_name || ''; document.getElementById('workplacePurpose').value = workplaceData.workplace_purpose || ''; document.getElementById('displayPriority').value = workplaceData.display_priority || 0; document.getElementById('workplaceDescription').value = workplaceData.description || ''; } else { // 신규 등록 모드 modalTitle.textContent = '작업장 추가'; deleteBtn.style.display = 'none'; document.getElementById('workplaceForm').reset(); document.getElementById('workplaceId').value = ''; // 현재 선택된 카테고리가 있으면 자동 선택 if (currentCategoryId) { document.getElementById('workplaceCategoryId').value = currentCategoryId; } } modal.style.display = 'flex'; document.body.style.overflow = 'hidden'; setTimeout(() => { document.getElementById('workplaceName').focus(); }, 100); } // 작업장 모달 닫기 function closeWorkplaceModal() { const modal = document.getElementById('workplaceModal'); if (modal) { modal.style.display = 'none'; document.body.style.overflow = ''; currentEditingWorkplace = null; } } // 작업장 편집 function editWorkplace(workplaceId) { const workplace = workplaces.find(w => w.workplace_id === workplaceId); if (workplace) { openWorkplaceModal(workplace); } else { showToast('작업장을 찾을 수 없습니다.', 'error'); } } // 작업장 저장 async function saveWorkplace() { try { const workplaceId = document.getElementById('workplaceId').value; const workplaceData = { category_id: document.getElementById('workplaceCategoryId').value || null, workplace_name: document.getElementById('workplaceName').value.trim(), workplace_purpose: document.getElementById('workplacePurpose').value || null, display_priority: parseInt(document.getElementById('displayPriority').value) || 0, description: document.getElementById('workplaceDescription').value.trim() || null, is_active: true }; if (!workplaceData.workplace_name) { showToast('작업장명은 필수 입력 항목입니다.', 'error'); return; } console.log('💾 저장할 작업장 데이터:', workplaceData); let response; if (workplaceId) { // 수정 response = await window.apiCall(`/workplaces/${workplaceId}`, 'PUT', workplaceData); } else { // 신규 등록 response = await window.apiCall('/workplaces', 'POST', workplaceData); } if (response && (response.success || response.workplace_id)) { const action = workplaceId ? '수정' : '등록'; showToast(`작업장이 성공적으로 ${action}되었습니다.`, 'success'); closeWorkplaceModal(); await loadAllData(); } else { throw new Error(response?.message || '저장에 실패했습니다.'); } } catch (error) { console.error('작업장 저장 오류:', error); showToast(error.message || '작업장 저장 중 오류가 발생했습니다.', 'error'); } } // 작업장 삭제 확인 function confirmDeleteWorkplace(workplaceId) { const workplace = workplaces.find(w => w.workplace_id === workplaceId); if (!workplace) { showToast('작업장을 찾을 수 없습니다.', 'error'); return; } if (confirm(`"${workplace.workplace_name}" 작업장을 정말 삭제하시겠습니까?\n\n⚠️ 삭제된 작업장은 복구할 수 없습니다.`)) { deleteWorkplaceById(workplaceId); } } // 작업장 삭제 (수정 모드에서) function deleteWorkplace() { if (currentEditingWorkplace) { confirmDeleteWorkplace(currentEditingWorkplace.workplace_id); } } // 작업장 삭제 실행 async function deleteWorkplaceById(workplaceId) { try { const response = await window.apiCall(`/workplaces/${workplaceId}`, 'DELETE'); if (response && response.success) { showToast('작업장이 성공적으로 삭제되었습니다.', 'success'); closeWorkplaceModal(); await loadAllData(); } else { throw new Error(response?.message || '삭제에 실패했습니다.'); } } catch (error) { console.error('작업장 삭제 오류:', error); showToast(error.message || '작업장 삭제 중 오류가 발생했습니다.', 'error'); } } // ==================== 유틸리티 ==================== // 전체 새로고침 async function refreshWorkplaces() { const refreshBtn = document.querySelector('.btn-secondary'); if (refreshBtn) { const originalText = refreshBtn.innerHTML; refreshBtn.innerHTML = '새로고침 중...'; refreshBtn.disabled = true; await loadAllData(); refreshBtn.innerHTML = originalText; refreshBtn.disabled = false; } else { await loadAllData(); } showToast('데이터가 새로고침되었습니다.', 'success'); } // 통계 업데이트 function updateStatistics() { const total = workplaces.length; const active = workplaces.filter(w => w.is_active === 1 || w.is_active === true).length; document.getElementById('totalCount').textContent = total; document.getElementById('activeCount').textContent = active; } // 날짜 포맷팅 function formatDate(dateString) { if (!dateString) return ''; const date = new Date(dateString); return date.toLocaleDateString('ko-KR', { year: 'numeric', month: '2-digit', day: '2-digit' }); } // 토스트 메시지 표시 function showToast(message, type = 'info') { // 기존 토스트 제거 const existingToast = document.querySelector('.toast'); if (existingToast) { existingToast.remove(); } // 새 토스트 생성 const toast = document.createElement('div'); toast.className = `toast toast-${type}`; toast.textContent = message; // 스타일 적용 Object.assign(toast.style, { position: 'fixed', top: '20px', right: '20px', padding: '12px 24px', borderRadius: '8px', color: 'white', fontWeight: '500', zIndex: '1000', transform: 'translateX(100%)', transition: 'transform 0.3s ease' }); // 타입별 배경색 const colors = { success: '#10b981', error: '#ef4444', warning: '#f59e0b', info: '#3b82f6' }; toast.style.backgroundColor = colors[type] || colors.info; document.body.appendChild(toast); // 애니메이션 setTimeout(() => { toast.style.transform = 'translateX(0)'; }, 100); // 자동 제거 setTimeout(() => { toast.style.transform = 'translateX(100%)'; setTimeout(() => { if (toast.parentNode) { toast.remove(); } }, 300); }, 3000); } // 전역 함수 및 변수로 노출 (다른 모듈에서 접근 가능하도록) // getter/setter를 사용하여 항상 최신 값을 반환 Object.defineProperty(window, 'categories', { get: function() { return categories; } }); Object.defineProperty(window, 'workplaces', { get: function() { return workplaces; } }); Object.defineProperty(window, 'currentCategoryId', { get: function() { return currentCategoryId; }, set: function(value) { currentCategoryId = value; } }); // ==================== 작업장 지도 관리 ==================== // 작업장 지도 관련 전역 변수 let workplaceCanvas = null; let workplaceCtx = null; let workplaceImage = null; let workplaceIsDrawing = false; let workplaceStartX = 0; let workplaceStartY = 0; let workplaceCurrentRect = null; let workplaceEquipmentRegions = []; // 작업장 지도 모달 열기 async function openWorkplaceMapModal(workplaceId) { const workplace = workplaces.find(w => w.workplace_id === workplaceId); if (!workplace) { showToast('작업장 정보를 찾을 수 없습니다.', 'error'); return; } // 작업장 이름 설정 const modalTitle = document.getElementById('workplaceMapModalTitle'); if (modalTitle) { modalTitle.textContent = `${workplace.workplace_name} - 지도 관리`; } // 현재 작업장 ID 저장 window.currentWorkplaceMapId = workplaceId; // 레이아웃 이미지 미리보기 영역 초기화 const preview = document.getElementById('workplaceLayoutPreview'); if (preview && workplace.layout_image) { const fullImageUrl = workplace.layout_image.startsWith('http') ? workplace.layout_image : `${window.API_BASE_URL || 'http://localhost:20005/api'}${workplace.layout_image}`.replace('/api/', '/'); preview.innerHTML = `작업장 레이아웃`; // 캔버스 초기화 initWorkplaceCanvas(fullImageUrl); } else if (preview) { preview.innerHTML = '

레이아웃 이미지를 업로드해주세요

'; } // 설비 영역 목록 로드 (TODO: API 연동) workplaceEquipmentRegions = []; renderWorkplaceEquipmentList(); // 모달 표시 const modal = document.getElementById('workplaceMapModal'); if (modal) { modal.style.display = 'flex'; } } // 작업장 지도 모달 닫기 function closeWorkplaceMapModal() { const modal = document.getElementById('workplaceMapModal'); if (modal) { modal.style.display = 'none'; } window.currentWorkplaceMapId = null; } // 작업장 레이아웃 이미지 업로드 async function uploadWorkplaceLayout() { const fileInput = document.getElementById('workplaceLayoutFile'); if (!fileInput || !fileInput.files || !fileInput.files[0]) { showToast('파일을 선택해주세요.', 'warning'); return; } if (!window.currentWorkplaceMapId) { showToast('작업장 정보를 찾을 수 없습니다.', 'error'); return; } const formData = new FormData(); formData.append('image', fileInput.files[0]); try { const response = await fetch(`${window.API_BASE_URL || 'http://localhost:20005/api'}/workplaces/${window.currentWorkplaceMapId}/layout-image`, { method: 'POST', headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }, body: formData }); const result = await response.json(); if (result.success) { showToast('레이아웃 이미지가 업로드되었습니다.', 'success'); // 작업장 목록 새로고침 await loadWorkplaces(); renderWorkplaces(); // 미리보기 업데이트 const preview = document.getElementById('workplaceLayoutPreview'); if (preview && result.data.image_path) { const fullImageUrl = result.data.image_path.startsWith('http') ? result.data.image_path : `${window.API_BASE_URL || 'http://localhost:20005/api'}${result.data.image_path}`.replace('/api/', '/'); preview.innerHTML = `작업장 레이아웃`; } // 파일 입력 초기화 fileInput.value = ''; } else { showToast(result.message || '업로드 실패', 'error'); } } catch (error) { console.error('레이아웃 이미지 업로드 오류:', error); showToast('레이아웃 이미지 업로드 중 오류가 발생했습니다.', 'error'); } } // 작업장 캔버스 초기화 function initWorkplaceCanvas(imageUrl) { const img = new Image(); img.onload = function() { workplaceImage = img; workplaceCanvas = document.getElementById('workplaceRegionCanvas'); if (!workplaceCanvas) return; workplaceCtx = workplaceCanvas.getContext('2d'); // 캔버스 크기 설정 (최대 800px 너비) const maxWidth = 800; const scale = img.width > maxWidth ? maxWidth / img.width : 1; workplaceCanvas.width = img.width * scale; workplaceCanvas.height = img.height * scale; // 이미지 그리기 workplaceCtx.drawImage(img, 0, 0, workplaceCanvas.width, workplaceCanvas.height); // 기존 영역들 표시 drawWorkplaceRegions(); // 이벤트 리스너 등록 workplaceCanvas.onmousedown = startWorkplaceDraw; workplaceCanvas.onmousemove = drawWorkplace; workplaceCanvas.onmouseup = endWorkplaceDraw; }; img.src = imageUrl; } // 드래그 시작 function startWorkplaceDraw(e) { workplaceIsDrawing = true; const rect = workplaceCanvas.getBoundingClientRect(); workplaceStartX = e.clientX - rect.left; workplaceStartY = e.clientY - rect.top; } // 드래그 중 function drawWorkplace(e) { if (!workplaceIsDrawing) return; const rect = workplaceCanvas.getBoundingClientRect(); const currentX = e.clientX - rect.left; const currentY = e.clientY - rect.top; // 캔버스 초기화 및 이미지 다시 그리기 workplaceCtx.drawImage(workplaceImage, 0, 0, workplaceCanvas.width, workplaceCanvas.height); // 기존 영역들 표시 drawWorkplaceRegions(); // 현재 그리는 사각형 workplaceCtx.strokeStyle = '#3b82f6'; workplaceCtx.lineWidth = 3; workplaceCtx.strokeRect( workplaceStartX, workplaceStartY, currentX - workplaceStartX, currentY - workplaceStartY ); workplaceCurrentRect = { x: workplaceStartX, y: workplaceStartY, width: currentX - workplaceStartX, height: currentY - workplaceStartY }; } // 드래그 종료 function endWorkplaceDraw(e) { workplaceIsDrawing = false; } // 기존 영역들 그리기 function drawWorkplaceRegions() { workplaceEquipmentRegions.forEach((region, index) => { workplaceCtx.strokeStyle = '#10b981'; workplaceCtx.lineWidth = 2; workplaceCtx.strokeRect(region.x, region.y, region.width, region.height); // 영역 이름 표시 workplaceCtx.fillStyle = '#10b981'; workplaceCtx.font = '14px sans-serif'; workplaceCtx.fillText(region.equipment_name, region.x + 5, region.y + 20); }); } // 현재 영역 지우기 function clearWorkplaceCurrentRegion() { workplaceCurrentRect = null; if (workplaceCanvas && workplaceImage) { workplaceCtx.drawImage(workplaceImage, 0, 0, workplaceCanvas.width, workplaceCanvas.height); drawWorkplaceRegions(); } } // 설비 위치 저장 function saveWorkplaceEquipmentRegion() { const equipmentName = document.getElementById('equipmentNameInput'); if (!equipmentName || !equipmentName.value.trim()) { showToast('설비 이름을 입력해주세요.', 'warning'); return; } if (!workplaceCurrentRect) { showToast('영역을 드래그하여 선택해주세요.', 'warning'); return; } // 퍼센트로 변환 const xPercent = (workplaceCurrentRect.x / workplaceCanvas.width) * 100; const yPercent = (workplaceCurrentRect.y / workplaceCanvas.height) * 100; const widthPercent = (workplaceCurrentRect.width / workplaceCanvas.width) * 100; const heightPercent = (workplaceCurrentRect.height / workplaceCanvas.height) * 100; const newRegion = { equipment_name: equipmentName.value.trim(), x: workplaceCurrentRect.x, y: workplaceCurrentRect.y, width: workplaceCurrentRect.width, height: workplaceCurrentRect.height, x_percent: xPercent, y_percent: yPercent, width_percent: widthPercent, height_percent: heightPercent }; workplaceEquipmentRegions.push(newRegion); // UI 업데이트 renderWorkplaceEquipmentList(); clearWorkplaceCurrentRegion(); equipmentName.value = ''; showToast(`설비 "${newRegion.equipment_name}" 위치가 저장되었습니다.`, 'success'); } // 설비 목록 렌더링 function renderWorkplaceEquipmentList() { const listDiv = document.getElementById('workplaceEquipmentList'); if (!listDiv) return; if (workplaceEquipmentRegions.length === 0) { listDiv.innerHTML = '

아직 정의된 설비가 없습니다

'; return; } let html = ''; workplaceEquipmentRegions.forEach((region, index) => { html += `
${region.equipment_name} (${region.x_percent.toFixed(1)}%, ${region.y_percent.toFixed(1)}%)
`; }); listDiv.innerHTML = html; } // 설비 영역 삭제 function removeWorkplaceEquipmentRegion(index) { workplaceEquipmentRegions.splice(index, 1); renderWorkplaceEquipmentList(); // 캔버스 다시 그리기 if (workplaceCanvas && workplaceImage) { workplaceCtx.drawImage(workplaceImage, 0, 0, workplaceCanvas.width, workplaceCanvas.height); drawWorkplaceRegions(); } showToast('설비 위치가 삭제되었습니다.', 'success'); } // 작업장 레이아웃 이미지 미리보기 function previewWorkplaceLayoutImage(event) { const file = event.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = function(e) { const preview = document.getElementById('workplaceLayoutPreview'); if (preview) { preview.innerHTML = `미리보기`; } }; reader.readAsDataURL(file); } } window.switchCategory = switchCategory; window.openCategoryModal = openCategoryModal; window.closeCategoryModal = closeCategoryModal; window.saveCategory = saveCategory; window.deleteCategory = deleteCategory; window.openWorkplaceModal = openWorkplaceModal; window.closeWorkplaceModal = closeWorkplaceModal; window.editWorkplace = editWorkplace; window.saveWorkplace = saveWorkplace; window.deleteWorkplace = deleteWorkplace; window.confirmDeleteWorkplace = confirmDeleteWorkplace; window.refreshWorkplaces = refreshWorkplaces; window.showToast = showToast; window.loadCategories = loadCategories; window.updateLayoutPreview = updateLayoutPreview; window.openWorkplaceMapModal = openWorkplaceMapModal; window.closeWorkplaceMapModal = closeWorkplaceMapModal; window.uploadWorkplaceLayout = uploadWorkplaceLayout; window.clearWorkplaceCurrentRegion = clearWorkplaceCurrentRegion; window.saveWorkplaceEquipmentRegion = saveWorkplaceEquipmentRegion; window.removeWorkplaceEquipmentRegion = removeWorkplaceEquipmentRegion; window.previewWorkplaceLayoutImage = previewWorkplaceLayoutImage;