/** * Workplace Management - Module Loader * 작업장 관리 모듈을 초기화하고 연결하는 메인 진입점 * * 로드 순서: * 1. state.js - 전역 상태 관리 * 2. utils.js - 유틸리티 함수 * 3. api.js - API 클라이언트 * 4. index.js - 이 파일 (메인 컨트롤러) */ class WorkplaceController { constructor() { this.state = window.WorkplaceState; this.api = window.WorkplaceAPI; this.utils = window.WorkplaceUtils; this.initialized = false; console.log('[WorkplaceController] 생성'); } /** * 초기화 */ async init() { if (this.initialized) { console.log('[WorkplaceController] 이미 초기화됨'); return; } console.log('🏗️ 작업장 관리 페이지 초기화 시작'); // API 함수가 로드될 때까지 대기 let retryCount = 0; while (!window.apiCall && retryCount < 50) { await new Promise(resolve => setTimeout(resolve, 100)); retryCount++; } if (!window.apiCall) { window.showToast?.('시스템을 초기화할 수 없습니다. 페이지를 새로고침해주세요.', 'error'); return; } // 모든 데이터 로드 await this.loadAllData(); this.initialized = true; console.log('[WorkplaceController] 초기화 완료'); } /** * 모든 데이터 로드 */ async loadAllData() { try { await this.api.loadAllData(); this.renderCategoryTabs(); this.renderWorkplaces(); this.updateStatistics(); } catch (error) { console.error('데이터 로딩 오류:', error); window.showToast?.('데이터를 불러오는데 실패했습니다.', 'error'); } } /** * 카테고리 탭 렌더링 */ renderCategoryTabs() { const tabsContainer = document.getElementById('categoryTabs'); if (!tabsContainer) return; const categories = this.state.categories; const workplaces = this.state.workplaces; const currentCategoryId = this.state.currentCategoryId; 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; } /** * 카테고리 전환 */ async switchCategory(categoryId) { this.state.setCurrentCategory(categoryId); this.renderCategoryTabs(); this.renderWorkplaces(); const layoutMapSection = document.getElementById('layoutMapSection'); const selectedCategoryName = document.getElementById('selectedCategoryName'); if (categoryId && layoutMapSection) { const category = this.state.getCurrentCategory(); if (category) { layoutMapSection.style.display = 'block'; if (selectedCategoryName) { selectedCategoryName.textContent = category.category_name; } await this.updateLayoutPreview(category); } } else if (layoutMapSection) { layoutMapSection.style.display = 'none'; } } /** * 레이아웃 미리보기 업데이트 */ async updateLayoutPreview(category) { const previewDiv = document.getElementById('layoutMapPreview'); if (!previewDiv) return; if (category.layout_image) { const fullImageUrl = this.utils.getFullImageUrl(category.layout_image); previewDiv.innerHTML = `

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

`; await this.loadImageWithRegions(fullImageUrl, category.category_id); } else { previewDiv.innerHTML = `
🗺️

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

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

`; } } /** * 이미지와 영역을 캔버스에 로드 */ async loadImageWithRegions(imageUrl, categoryId) { const canvas = document.getElementById('previewCanvas'); if (!canvas) return; const ctx = canvas.getContext('2d'); const img = new Image(); const self = this; 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 regions = await self.api.loadMapRegions(categoryId); 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.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; } /** * 작업장 렌더링 */ renderWorkplaces() { const grid = document.getElementById('workplaceGrid'); if (!grid) return; const filtered = this.state.getFilteredWorkplaces(); 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 purposeIcon = this.utils.getPurposeIcon(workplace.workplace_purpose); gridHtml += `
${purposeIcon}

${workplace.workplace_name}

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

${workplace.description}

` : ''}
등록: ${this.utils.formatDate(workplace.created_at)} ${workplace.updated_at !== workplace.created_at ? `수정: ${this.utils.formatDate(workplace.updated_at)}` : ''}
`; }); grid.innerHTML = gridHtml; filtered.forEach(workplace => { if (workplace.category_id) { this.loadWorkplaceMapThumbnail(workplace); } }); } /** * 작업장 카드에 지도 썸네일 로드 */ async loadWorkplaceMapThumbnail(workplace) { const thumbnailDiv = document.getElementById(`workplace-map-${workplace.workplace_id}`); if (!thumbnailDiv) return; if (workplace.layout_image) { const fullImageUrl = this.utils.getFullImageUrl(workplace.layout_image); let equipmentCount = 0; try { const eqResponse = await window.apiCall(`/equipments/workplace/${workplace.workplace_id}`, 'GET'); if (eqResponse && eqResponse.success && Array.isArray(eqResponse.data)) { equipmentCount = eqResponse.data.filter(eq => eq.map_x_percent != null).length; } } catch (e) { console.debug('설비 정보 로드 실패'); } const canvasId = `layout-canvas-${workplace.workplace_id}`; thumbnailDiv.innerHTML = `
📍 작업장 지도 ${equipmentCount > 0 ? `설비 ${equipmentCount}개` : ''}
클릭하여 지도 관리
`; await this.loadWorkplaceCanvasWithEquipments(workplace.workplace_id, fullImageUrl, canvasId); return; } try { const response = await this.api.loadWorkplaceMapRegion(workplace.workplace_id); if (!response || (!response.success && !response.region_id)) { thumbnailDiv.innerHTML = `
🗺️
클릭하여 지도 설정
`; return; } const region = response.success ? response.data : response; if (!region || region.x_start === undefined || region.y_start === undefined || region.x_end === undefined || region.y_end === undefined) { return; } const category = this.state.categories.find(c => c.category_id === workplace.category_id); if (!category || !category.layout_image) return; const fullImageUrl = this.utils.getFullImageUrl(category.layout_image); 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; const maxThumbWidth = 350; 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}의 지도 영역 없음`); } } /** * 작업장 캔버스에 설비 영역 함께 그리기 */ async loadWorkplaceCanvasWithEquipments(workplaceId, imageUrl, canvasId) { const img = new Image(); img.onload = async function() { const canvas = document.getElementById(canvasId); if (!canvas) return; const ctx = canvas.getContext('2d'); const maxThumbWidth = 400; const scale = img.width > maxThumbWidth ? maxThumbWidth / 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(`/equipments/workplace/${workplaceId}`, 'GET'); let equipments = []; if (response && response.success && Array.isArray(response.data)) { equipments = response.data.filter(eq => eq.map_x_percent != null); } equipments.forEach(eq => { const x = (parseFloat(eq.map_x_percent) / 100) * canvas.width; const y = (parseFloat(eq.map_y_percent) / 100) * canvas.height; const width = (parseFloat(eq.map_width_percent || 10) / 100) * canvas.width; const height = (parseFloat(eq.map_height_percent || 10) / 100) * canvas.height; ctx.fillStyle = 'rgba(16, 185, 129, 0.2)'; ctx.fillRect(x, y, width, height); ctx.strokeStyle = '#10b981'; ctx.lineWidth = 2; ctx.strokeRect(x, y, width, height); if (eq.equipment_code) { ctx.font = 'bold 10px sans-serif'; const textMetrics = ctx.measureText(eq.equipment_code); ctx.fillStyle = 'rgba(255, 255, 255, 0.9)'; ctx.fillRect(x + 2, y + 2, textMetrics.width + 6, 14); ctx.fillStyle = '#047857'; ctx.fillText(eq.equipment_code, x + 5, y + 12); } }); } catch (error) { console.debug('설비 영역 로드 실패'); } }; img.src = imageUrl; } /** * 통계 업데이트 */ async updateStatistics() { const stats = this.state.getStatistics(); const factoryCountEl = document.getElementById('factoryCount'); const totalCountEl = document.getElementById('totalCount'); const activeCountEl = document.getElementById('activeCount'); const equipmentCountEl = document.getElementById('equipmentCount'); if (factoryCountEl) factoryCountEl.textContent = stats.factoryTotal; if (totalCountEl) totalCountEl.textContent = stats.total; if (activeCountEl) activeCountEl.textContent = stats.active; if (equipmentCountEl) { try { const equipments = await this.api.loadAllEquipments(); equipmentCountEl.textContent = equipments.length; } catch (e) { equipmentCountEl.textContent = '-'; } } const sectionTotalEl = document.getElementById('sectionTotalCount'); const sectionActiveEl = document.getElementById('sectionActiveCount'); if (sectionTotalEl) sectionTotalEl.textContent = stats.filteredTotal; if (sectionActiveEl) sectionActiveEl.textContent = stats.filteredActive; } /** * 전체 새로고침 */ async refreshWorkplaces() { const refreshBtn = document.querySelector('.btn-secondary'); if (refreshBtn) { const originalText = refreshBtn.innerHTML; refreshBtn.innerHTML = '새로고침 중...'; refreshBtn.disabled = true; await this.loadAllData(); refreshBtn.innerHTML = originalText; refreshBtn.disabled = false; } else { await this.loadAllData(); } window.showToast?.('데이터가 새로고침되었습니다.', 'success'); } /** * 디버그 */ debug() { console.log('[WorkplaceController] 상태 디버그:'); this.state.debug(); } } // 전역 인스턴스 생성 window.WorkplaceController = new WorkplaceController(); // 하위 호환성: 기존 전역 함수들 window.switchCategory = (categoryId) => window.WorkplaceController.switchCategory(categoryId); window.renderCategoryTabs = () => window.WorkplaceController.renderCategoryTabs(); window.renderWorkplaces = () => window.WorkplaceController.renderWorkplaces(); window.updateStatistics = () => window.WorkplaceController.updateStatistics(); window.refreshWorkplaces = () => window.WorkplaceController.refreshWorkplaces(); window.loadAllData = () => window.WorkplaceController.loadAllData(); window.updateLayoutPreview = (category) => window.WorkplaceController.updateLayoutPreview(category); // DOMContentLoaded 이벤트에서 초기화 document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { window.WorkplaceController.init(); }, 100); }); console.log('[Module] workplace-management/index.js 로드 완료');