/** * TBM - State Manager * TBM 페이지의 전역 상태 관리 */ class TbmState { constructor() { // 세션 데이터 this.allSessions = []; this.todaySessions = []; this.dateGroupedSessions = {}; this.allLoadedSessions = []; this.loadedDaysCount = 7; // 마스터 데이터 this.allWorkers = []; this.allProjects = []; this.allWorkTypes = []; this.allTasks = []; this.allSafetyChecks = []; this.allWorkplaces = []; this.allWorkplaceCategories = []; // 현재 상태 this.currentUser = null; this.currentSessionId = null; this.currentTab = 'tbm-input'; // 작업자 관련 this.selectedWorkers = new Set(); this.workerTaskList = []; this.selectedWorkersInModal = new Set(); this.currentEditingTaskLine = null; // 작업장 선택 관련 this.selectedCategory = null; this.selectedWorkplace = null; this.selectedCategoryName = ''; this.selectedWorkplaceName = ''; // 일괄 설정 관련 this.isBulkMode = false; this.bulkSelectedWorkers = new Set(); // 지도 관련 this.mapCanvas = null; this.mapCtx = null; this.mapImage = null; this.mapRegions = []; // 리스너 this.listeners = new Map(); console.log('[TbmState] 초기화 완료'); } /** * 상태 업데이트 */ update(key, value) { const prevValue = this[key]; this[key] = value; this.notifyListeners(key, value, prevValue); } /** * 리스너 등록 */ subscribe(key, callback) { if (!this.listeners.has(key)) { this.listeners.set(key, []); } this.listeners.get(key).push(callback); } /** * 리스너 알림 */ notifyListeners(key, newValue, prevValue) { const keyListeners = this.listeners.get(key) || []; keyListeners.forEach(callback => { try { callback(newValue, prevValue); } catch (error) { console.error(`[TbmState] 리스너 오류 (${key}):`, error); } }); } /** * 현재 사용자 정보 가져오기 */ getUser() { if (!this.currentUser) { const userInfo = localStorage.getItem('user'); this.currentUser = userInfo ? JSON.parse(userInfo) : null; } return this.currentUser; } /** * Admin 여부 확인 */ isAdminUser() { const user = this.getUser(); if (!user) return false; return user.role === 'Admin' || user.role === 'System Admin'; } /** * 탭 변경 */ setCurrentTab(tab) { const prevTab = this.currentTab; this.currentTab = tab; this.notifyListeners('currentTab', tab, prevTab); } /** * 작업자 목록에 추가 */ addWorkerToList(worker) { this.workerTaskList.push({ worker_id: worker.worker_id, worker_name: worker.worker_name, job_type: worker.job_type, tasks: [this.createEmptyTaskLine()] }); this.notifyListeners('workerTaskList', this.workerTaskList, null); } /** * 빈 작업 라인 생성 */ createEmptyTaskLine() { return { task_line_id: this.generateUUID(), project_id: null, work_type_id: null, task_id: null, workplace_category_id: null, workplace_id: null, workplace_category_name: '', workplace_name: '', work_detail: null, is_present: true }; } /** * 작업자에 작업 라인 추가 */ addTaskLineToWorker(workerIndex) { if (this.workerTaskList[workerIndex]) { this.workerTaskList[workerIndex].tasks.push(this.createEmptyTaskLine()); this.notifyListeners('workerTaskList', this.workerTaskList, null); } } /** * 작업 라인 제거 */ removeTaskLine(workerIndex, taskIndex) { if (this.workerTaskList[workerIndex]?.tasks) { this.workerTaskList[workerIndex].tasks.splice(taskIndex, 1); this.notifyListeners('workerTaskList', this.workerTaskList, null); } } /** * 작업자 제거 */ removeWorkerFromList(workerIndex) { const removed = this.workerTaskList.splice(workerIndex, 1); this.notifyListeners('workerTaskList', this.workerTaskList, null); return removed[0]; } /** * 작업장 선택 초기화 */ resetWorkplaceSelection() { this.selectedCategory = null; this.selectedWorkplace = null; this.selectedCategoryName = ''; this.selectedWorkplaceName = ''; this.mapCanvas = null; this.mapCtx = null; this.mapImage = null; this.mapRegions = []; } /** * 일괄 설정 초기화 */ resetBulkSettings() { this.isBulkMode = false; this.bulkSelectedWorkers.clear(); } /** * 날짜별 세션 그룹화 */ groupSessionsByDate(sessions) { this.dateGroupedSessions = {}; this.allLoadedSessions = []; sessions.forEach(session => { const date = this.formatDate(session.session_date); if (!this.dateGroupedSessions[date]) { this.dateGroupedSessions[date] = []; } this.dateGroupedSessions[date].push(session); this.allLoadedSessions.push(session); }); } /** * 날짜 포맷팅 */ formatDate(dateString) { if (!dateString) return ''; if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) { return dateString; } const date = new Date(dateString); const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}-${month}-${day}`; } /** * UUID 생성 */ generateUUID() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); } /** * 상태 초기화 */ reset() { this.workerTaskList = []; this.selectedWorkers.clear(); this.selectedWorkersInModal.clear(); this.currentEditingTaskLine = null; this.resetWorkplaceSelection(); this.resetBulkSettings(); } /** * 디버그 출력 */ debug() { console.log('[TbmState] 현재 상태:', { allSessions: this.allSessions.length, todaySessions: this.todaySessions.length, allWorkers: this.allWorkers.length, allProjects: this.allProjects.length, workerTaskList: this.workerTaskList.length, currentTab: this.currentTab }); } } // 전역 인스턴스 생성 window.TbmState = new TbmState(); // 하위 호환성을 위한 전역 변수 프록시 const tbmStateProxy = window.TbmState; Object.defineProperties(window, { allSessions: { get: () => tbmStateProxy.allSessions, set: (v) => { tbmStateProxy.allSessions = v; } }, todaySessions: { get: () => tbmStateProxy.todaySessions, set: (v) => { tbmStateProxy.todaySessions = v; } }, allWorkers: { get: () => tbmStateProxy.allWorkers, set: (v) => { tbmStateProxy.allWorkers = v; } }, allProjects: { get: () => tbmStateProxy.allProjects, set: (v) => { tbmStateProxy.allProjects = v; } }, allWorkTypes: { get: () => tbmStateProxy.allWorkTypes, set: (v) => { tbmStateProxy.allWorkTypes = v; } }, allTasks: { get: () => tbmStateProxy.allTasks, set: (v) => { tbmStateProxy.allTasks = v; } }, allSafetyChecks: { get: () => tbmStateProxy.allSafetyChecks, set: (v) => { tbmStateProxy.allSafetyChecks = v; } }, allWorkplaces: { get: () => tbmStateProxy.allWorkplaces, set: (v) => { tbmStateProxy.allWorkplaces = v; } }, allWorkplaceCategories: { get: () => tbmStateProxy.allWorkplaceCategories, set: (v) => { tbmStateProxy.allWorkplaceCategories = v; } }, currentUser: { get: () => tbmStateProxy.currentUser, set: (v) => { tbmStateProxy.currentUser = v; } }, currentSessionId: { get: () => tbmStateProxy.currentSessionId, set: (v) => { tbmStateProxy.currentSessionId = v; } }, selectedWorkers: { get: () => tbmStateProxy.selectedWorkers, set: (v) => { tbmStateProxy.selectedWorkers = v; } }, workerTaskList: { get: () => tbmStateProxy.workerTaskList, set: (v) => { tbmStateProxy.workerTaskList = v; } }, selectedWorkersInModal: { get: () => tbmStateProxy.selectedWorkersInModal, set: (v) => { tbmStateProxy.selectedWorkersInModal = v; } }, currentEditingTaskLine: { get: () => tbmStateProxy.currentEditingTaskLine, set: (v) => { tbmStateProxy.currentEditingTaskLine = v; } }, selectedCategory: { get: () => tbmStateProxy.selectedCategory, set: (v) => { tbmStateProxy.selectedCategory = v; } }, selectedWorkplace: { get: () => tbmStateProxy.selectedWorkplace, set: (v) => { tbmStateProxy.selectedWorkplace = v; } }, selectedCategoryName: { get: () => tbmStateProxy.selectedCategoryName, set: (v) => { tbmStateProxy.selectedCategoryName = v; } }, selectedWorkplaceName: { get: () => tbmStateProxy.selectedWorkplaceName, set: (v) => { tbmStateProxy.selectedWorkplaceName = v; } }, isBulkMode: { get: () => tbmStateProxy.isBulkMode, set: (v) => { tbmStateProxy.isBulkMode = v; } }, bulkSelectedWorkers: { get: () => tbmStateProxy.bulkSelectedWorkers, set: (v) => { tbmStateProxy.bulkSelectedWorkers = v; } }, dateGroupedSessions: { get: () => tbmStateProxy.dateGroupedSessions, set: (v) => { tbmStateProxy.dateGroupedSessions = v; } }, allLoadedSessions: { get: () => tbmStateProxy.allLoadedSessions, set: (v) => { tbmStateProxy.allLoadedSessions = v; } }, loadedDaysCount: { get: () => tbmStateProxy.loadedDaysCount, set: (v) => { tbmStateProxy.loadedDaysCount = v; } }, mapRegions: { get: () => tbmStateProxy.mapRegions, set: (v) => { tbmStateProxy.mapRegions = v; } }, mapCanvas: { get: () => tbmStateProxy.mapCanvas, set: (v) => { tbmStateProxy.mapCanvas = v; } }, mapCtx: { get: () => tbmStateProxy.mapCtx, set: (v) => { tbmStateProxy.mapCtx = v; } }, mapImage: { get: () => tbmStateProxy.mapImage, set: (v) => { tbmStateProxy.mapImage = v; } } }); console.log('[Module] tbm/state.js 로드 완료');