// tbm.js - TBM 관리 페이지 JavaScript // 전역 변수 let allSessions = []; let allWorkers = []; let allProjects = []; let allSafetyChecks = []; let currentSessionId = null; let selectedWorkers = new Set(); // 페이지 초기화 document.addEventListener('DOMContentLoaded', async () => { console.log('🛠️ TBM 관리 페이지 초기화'); // API 함수가 로드될 때까지 대기 let retryCount = 0; while (!window.apiCall && retryCount < 50) { await new Promise(resolve => setTimeout(resolve, 100)); retryCount++; } if (!window.apiCall) { showToast('시스템을 초기화할 수 없습니다. 페이지를 새로고침해주세요.', 'error'); return; } // 오늘 날짜 설정 const today = new Date().toISOString().split('T')[0]; document.getElementById('tbmDate').value = today; document.getElementById('sessionDate').value = today; // 이벤트 리스너 설정 setupEventListeners(); // 초기 데이터 로드 await loadInitialData(); await loadTodayTbm(); }); // 이벤트 리스너 설정 function setupEventListeners() { const tbmDateInput = document.getElementById('tbmDate'); if (tbmDateInput) { tbmDateInput.addEventListener('change', () => { const date = tbmDateInput.value; loadTbmSessionsByDate(date); }); } } // 초기 데이터 로드 async function loadInitialData() { try { // 작업자 목록 로드 const workersResponse = await window.apiCall('/workers?limit=1000'); if (workersResponse) { allWorkers = Array.isArray(workersResponse) ? workersResponse : (workersResponse.data || []); // 활성 상태인 작업자만 필터링 allWorkers = allWorkers.filter(w => w.status === 'active' && w.employment_status === 'employed'); console.log('✅ 작업자 목록 로드:', allWorkers.length + '명'); } // 프로젝트 목록 로드 const projectsResponse = await window.apiCall('/projects?is_active=1'); if (projectsResponse) { allProjects = Array.isArray(projectsResponse) ? projectsResponse : (projectsResponse.data || []); console.log('✅ 프로젝트 목록 로드:', allProjects.length + '개'); populateProjectSelect(); } // 안전 체크리스트 로드 const safetyResponse = await window.apiCall('/tbm/safety-checks'); if (safetyResponse && safetyResponse.success) { allSafetyChecks = safetyResponse.data; console.log('✅ 안전 체크리스트 로드:', allSafetyChecks.length + '개'); } } catch (error) { console.error('❌ 초기 데이터 로드 오류:', error); showToast('데이터를 불러오는 중 오류가 발생했습니다.', 'error'); } } // 오늘 TBM 로드 async function loadTodayTbm() { const today = new Date().toISOString().split('T')[0]; document.getElementById('tbmDate').value = today; await loadTbmSessionsByDate(today); } window.loadTodayTbm = loadTodayTbm; // 특정 날짜의 TBM 세션 목록 로드 async function loadTbmSessionsByDate(date) { try { const response = await window.apiCall(`/tbm/sessions/date/${date}`); if (response && response.success) { allSessions = response.data || []; displayTbmSessions(); } else { allSessions = []; displayTbmSessions(); } } catch (error) { console.error('❌ TBM 세션 조회 오류:', error); showToast('TBM 세션을 불러오는 중 오류가 발생했습니다.', 'error'); allSessions = []; displayTbmSessions(); } } // TBM 세션 목록 표시 function displayTbmSessions() { const grid = document.getElementById('tbmSessionsGrid'); const emptyState = document.getElementById('emptyState'); const totalSessionsEl = document.getElementById('totalSessions'); const completedSessionsEl = document.getElementById('completedSessions'); if (allSessions.length === 0) { grid.innerHTML = ''; emptyState.style.display = 'flex'; totalSessionsEl.textContent = '0'; completedSessionsEl.textContent = '0'; return; } emptyState.style.display = 'none'; const completedCount = allSessions.filter(s => s.status === 'completed').length; totalSessionsEl.textContent = allSessions.length; completedSessionsEl.textContent = completedCount; grid.innerHTML = allSessions.map(session => { const statusBadge = { 'draft': '진행중', 'completed': '완료', 'cancelled': '취소' }[session.status] || ''; return `

${session.leader_name || '팀장 미지정'}

${session.leader_job_type || ''}

${statusBadge}
프로젝트 ${session.project_name || '-'}
작업 장소 ${session.work_location || '-'}
팀원 수 ${session.team_member_count || 0}명
시작 시간 ${session.start_time || '-'}
${session.work_description ? `
${session.work_description}
` : ''}
${session.status === 'draft' ? ` ` : ''}
`; }).join(''); } // 새 TBM 모달 열기 function openNewTbmModal() { currentSessionId = null; document.getElementById('modalTitle').textContent = '새 TBM 시작'; document.getElementById('sessionId').value = ''; document.getElementById('tbmForm').reset(); const today = new Date().toISOString().split('T')[0]; document.getElementById('sessionDate').value = today; // 팀장 목록 로드 populateLeaderSelect(); populateProjectSelect(); document.getElementById('tbmModal').style.display = 'flex'; document.body.style.overflow = 'hidden'; } window.openNewTbmModal = openNewTbmModal; // 팀장 선택 드롭다운 채우기 function populateLeaderSelect() { const leaderSelect = document.getElementById('leaderId'); if (!leaderSelect) return; const leaders = allWorkers.filter(w => w.job_type === 'leader' || w.job_type === '그룹장' || w.job_type === 'admin' ); leaderSelect.innerHTML = '' + leaders.map(w => ` `).join(''); } // 프로젝트 선택 드롭다운 채우기 function populateProjectSelect() { const projectSelect = document.getElementById('projectId'); if (!projectSelect) return; projectSelect.innerHTML = '' + allProjects.map(p => ` `).join(''); } // TBM 모달 닫기 function closeTbmModal() { document.getElementById('tbmModal').style.display = 'none'; document.body.style.overflow = 'auto'; } window.closeTbmModal = closeTbmModal; // TBM 세션 저장 async function saveTbmSession() { const sessionData = { session_date: document.getElementById('sessionDate').value, leader_id: parseInt(document.getElementById('leaderId').value), project_id: document.getElementById('projectId').value || null, work_location: document.getElementById('workLocation').value || null, work_description: document.getElementById('workDescription').value || null, safety_notes: document.getElementById('safetyNotes').value || null, start_time: document.getElementById('startTime').value || null }; if (!sessionData.session_date || !sessionData.leader_id) { showToast('TBM 날짜와 팀장을 선택해주세요.', 'error'); return; } try { const response = await window.apiCall('/tbm/sessions', 'POST', sessionData); if (response && response.success) { showToast('TBM 세션이 생성되었습니다.', 'success'); closeTbmModal(); const createdSessionId = response.data.session_id; // 목록 새로고침 await loadTbmSessionsByDate(sessionData.session_date); // 팀 구성 모달 열기 setTimeout(() => { openTeamCompositionModal(createdSessionId); }, 500); } else { throw new Error(response.message || '저장에 실패했습니다.'); } } catch (error) { console.error('❌ TBM 세션 저장 오류:', error); showToast('TBM 세션 저장 중 오류가 발생했습니다.', 'error'); } } window.saveTbmSession = saveTbmSession; // 팀 구성 모달 열기 async function openTeamCompositionModal(sessionId) { currentSessionId = sessionId; selectedWorkers.clear(); // 기존 팀 구성 로드 try { const response = await window.apiCall(`/tbm/sessions/${sessionId}/team`); if (response && response.success) { response.data.forEach(member => { selectedWorkers.add(member.worker_id); }); } } catch (error) { console.error('팀 구성 조회 오류:', error); } // 작업자 선택 그리드 생성 const grid = document.getElementById('workerSelectionGrid'); grid.innerHTML = allWorkers.map(worker => ` `).join(''); updateSelectedWorkers(); document.getElementById('teamModal').style.display = 'flex'; document.body.style.overflow = 'hidden'; } window.openTeamCompositionModal = openTeamCompositionModal; // 선택된 작업자 업데이트 function updateSelectedWorkers() { selectedWorkers.clear(); document.querySelectorAll('.worker-checkbox:checked').forEach(cb => { selectedWorkers.add(parseInt(cb.dataset.workerId)); }); const selectedCount = document.getElementById('selectedCount'); const selectedList = document.getElementById('selectedWorkersList'); selectedCount.textContent = selectedWorkers.size; if (selectedWorkers.size === 0) { selectedList.innerHTML = '

작업자를 선택해주세요

'; } else { const selectedWorkersArray = Array.from(selectedWorkers).map(id => { const worker = allWorkers.find(w => w.worker_id === id); return worker ? ` ${worker.worker_name} ` : ''; }); selectedList.innerHTML = selectedWorkersArray.join(''); } } window.updateSelectedWorkers = updateSelectedWorkers; // 작업자 제거 function removeWorker(workerId) { const checkbox = document.querySelector(`.worker-checkbox[data-worker-id="${workerId}"]`); if (checkbox) { checkbox.checked = false; updateSelectedWorkers(); } } window.removeWorker = removeWorker; // 전체 선택 function selectAllWorkers() { document.querySelectorAll('.worker-checkbox').forEach(cb => { cb.checked = true; }); updateSelectedWorkers(); } window.selectAllWorkers = selectAllWorkers; // 전체 해제 function deselectAllWorkers() { document.querySelectorAll('.worker-checkbox').forEach(cb => { cb.checked = false; }); updateSelectedWorkers(); } window.deselectAllWorkers = deselectAllWorkers; // 팀 구성 모달 닫기 function closeTeamModal() { document.getElementById('teamModal').style.display = 'none'; document.body.style.overflow = 'auto'; } window.closeTeamModal = closeTeamModal; // 팀 구성 저장 async function saveTeamComposition() { if (selectedWorkers.size === 0) { showToast('최소 1명 이상의 작업자를 선택해주세요.', 'error'); return; } const members = Array.from(selectedWorkers).map(workerId => ({ worker_id: workerId })); try { const response = await window.apiCall( `/tbm/sessions/${currentSessionId}/team/batch`, 'POST', { members } ); if (response && response.success) { showToast(`${selectedWorkers.size}명의 팀원이 추가되었습니다.`, 'success'); closeTeamModal(); // 목록 새로고침 const date = document.getElementById('tbmDate').value; await loadTbmSessionsByDate(date); } else { throw new Error(response.message || '저장에 실패했습니다.'); } } catch (error) { console.error('❌ 팀 구성 저장 오류:', error); showToast('팀 구성 저장 중 오류가 발생했습니다.', 'error'); } } window.saveTeamComposition = saveTeamComposition; // 안전 체크 모달 열기 async function openSafetyCheckModal(sessionId) { currentSessionId = sessionId; // 기존 안전 체크 기록 로드 try { const response = await window.apiCall(`/tbm/sessions/${sessionId}/safety`); const existingRecords = response && response.success ? response.data : []; // 카테고리별로 그룹화 const grouped = {}; allSafetyChecks.forEach(check => { if (!grouped[check.check_category]) { grouped[check.check_category] = []; } const existingRecord = existingRecords.find(r => r.check_id === check.check_id); grouped[check.check_category].push({ ...check, is_checked: existingRecord ? existingRecord.is_checked : false, notes: existingRecord ? existingRecord.notes : '' }); }); const categoryNames = { 'PPE': '개인 보호 장비', 'EQUIPMENT': '장비 점검', 'ENVIRONMENT': '작업 환경', 'EMERGENCY': '비상 대응' }; const container = document.getElementById('safetyChecklistContainer'); container.innerHTML = Object.keys(grouped).map(category => `
${categoryNames[category] || category}
${grouped[category].map(check => `
`).join('')}
`).join(''); document.getElementById('safetyModal').style.display = 'flex'; document.body.style.overflow = 'hidden'; } catch (error) { console.error('❌ 안전 체크 조회 오류:', error); showToast('안전 체크 정보를 불러오는 중 오류가 발생했습니다.', 'error'); } } window.openSafetyCheckModal = openSafetyCheckModal; // 안전 체크 모달 닫기 function closeSafetyModal() { document.getElementById('safetyModal').style.display = 'none'; document.body.style.overflow = 'auto'; } window.closeSafetyModal = closeSafetyModal; // 안전 체크리스트 저장 async function saveSafetyChecklist() { const records = []; document.querySelectorAll('.safety-check').forEach(cb => { records.push({ check_id: parseInt(cb.dataset.checkId), is_checked: cb.checked }); }); try { const response = await window.apiCall( `/tbm/sessions/${currentSessionId}/safety`, 'POST', { records } ); if (response && response.success) { showToast('안전 체크가 완료되었습니다.', 'success'); closeSafetyModal(); } else { throw new Error(response.message || '저장에 실패했습니다.'); } } catch (error) { console.error('❌ 안전 체크 저장 오류:', error); showToast('안전 체크 저장 중 오류가 발생했습니다.', 'error'); } } window.saveSafetyChecklist = saveSafetyChecklist; // TBM 완료 모달 열기 function openCompleteTbmModal(sessionId) { currentSessionId = sessionId; const now = new Date(); const timeString = now.toTimeString().slice(0, 5); document.getElementById('endTime').value = timeString; document.getElementById('completeModal').style.display = 'flex'; document.body.style.overflow = 'hidden'; } window.openCompleteTbmModal = openCompleteTbmModal; // 완료 모달 닫기 function closeCompleteModal() { document.getElementById('completeModal').style.display = 'none'; document.body.style.overflow = 'auto'; } window.closeCompleteModal = closeCompleteModal; // TBM 세션 완료 async function completeTbmSession() { const endTime = document.getElementById('endTime').value; try { const response = await window.apiCall( `/tbm/sessions/${currentSessionId}/complete`, 'POST', { end_time: endTime } ); if (response && response.success) { showToast('TBM이 완료되었습니다.', 'success'); closeCompleteModal(); // 목록 새로고침 const date = document.getElementById('tbmDate').value; await loadTbmSessionsByDate(date); } else { throw new Error(response.message || '완료 처리에 실패했습니다.'); } } catch (error) { console.error('❌ TBM 완료 처리 오류:', error); showToast('TBM 완료 처리 중 오류가 발생했습니다.', 'error'); } } window.completeTbmSession = completeTbmSession; // TBM 세션 상세 보기 function viewTbmSession(sessionId) { // TODO: 상세 보기 페이지 또는 모달 구현 console.log('TBM 세션 상세 보기:', sessionId); } window.viewTbmSession = viewTbmSession; // 토스트 알림 function showToast(message, type = 'info', duration = 3000) { const container = document.getElementById('toastContainer'); if (!container) return; const toast = document.createElement('div'); toast.className = `toast ${type}`; const iconMap = { success: '✅', error: '❌', warning: '⚠️', info: 'ℹ️' }; toast.innerHTML = `
${iconMap[type] || 'ℹ️'}
${message}
`; toast.style.cssText = ` display: flex; align-items: center; gap: 0.75rem; padding: 1rem 1.25rem; background: white; border-radius: 0.5rem; box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1); margin-bottom: 0.75rem; min-width: 300px; animation: slideIn 0.3s ease-out; `; container.appendChild(toast); setTimeout(() => { if (toast.parentElement) { toast.style.animation = 'slideOut 0.3s ease-out'; setTimeout(() => toast.remove(), 300); } }, duration); }