// tbm.js - TBM 관리 페이지 JavaScript // 전역 변수 let allSessions = []; let todaySessions = []; let allWorkers = []; let allProjects = []; let allWorkTypes = []; let allTasks = []; let allSafetyChecks = []; let currentSessionId = null; let selectedWorkers = new Set(); let currentTab = 'tbm-input'; // 페이지 초기화 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 loadTodayOnlyTbm(); }); // 이벤트 리스너 설정 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 + '개'); } // 공정(Work Types) 목록 로드 const workTypesResponse = await window.apiCall('/tools/work-types'); if (workTypesResponse && workTypesResponse.success) { allWorkTypes = workTypesResponse.data || []; console.log('✅ 공정 목록 로드:', allWorkTypes.length + '개'); } // 작업(Tasks) 목록 로드 const tasksResponse = await window.apiCall('/tasks/active/list'); if (tasksResponse && tasksResponse.success) { allTasks = tasksResponse.data || []; console.log('✅ 작업 목록 로드:', allTasks.length + '개'); } } catch (error) { console.error('❌ 초기 데이터 로드 오류:', error); showToast('데이터를 불러오는 중 오류가 발생했습니다.', 'error'); } } // ==================== 탭 전환 ==================== // 탭 전환 function switchTbmTab(tabName) { currentTab = tabName; // 탭 버튼 활성화 상태 변경 document.querySelectorAll('.tab-btn').forEach(btn => { if (btn.dataset.tab === tabName) { btn.classList.add('active'); } else { btn.classList.remove('active'); } }); // 탭 컨텐츠 표시 변경 document.querySelectorAll('.code-tab-content').forEach(content => { content.classList.remove('active'); }); document.getElementById(`${tabName}-tab`).classList.add('active'); // 탭에 따라 데이터 로드 if (tabName === 'tbm-input') { loadTodayOnlyTbm(); } else if (tabName === 'tbm-manage') { const tbmDate = document.getElementById('tbmDate'); if (tbmDate && tbmDate.value) { loadTbmSessionsByDate(tbmDate.value); } else { loadTodayTbm(); } } } window.switchTbmTab = switchTbmTab; // ==================== TBM 입력 탭 ==================== // 오늘의 TBM만 로드 (TBM 입력 탭용) async function loadTodayOnlyTbm() { const today = new Date().toISOString().split('T')[0]; try { const response = await window.apiCall(`/tbm/sessions/date/${today}`); if (response && response.success) { todaySessions = response.data || []; displayTodayTbmSessions(); } else { todaySessions = []; displayTodayTbmSessions(); } } catch (error) { console.error('❌ 오늘 TBM 조회 오류:', error); showToast('오늘 TBM을 불러오는 중 오류가 발생했습니다.', 'error'); todaySessions = []; displayTodayTbmSessions(); } } window.loadTodayOnlyTbm = loadTodayOnlyTbm; // 오늘의 TBM 세션 표시 function displayTodayTbmSessions() { const grid = document.getElementById('todayTbmGrid'); const emptyState = document.getElementById('todayEmptyState'); const todayTotalEl = document.getElementById('todayTotalSessions'); const todayCompletedEl = document.getElementById('todayCompletedSessions'); const todayActiveEl = document.getElementById('todayActiveSessions'); if (todaySessions.length === 0) { grid.innerHTML = ''; emptyState.style.display = 'flex'; todayTotalEl.textContent = '0'; todayCompletedEl.textContent = '0'; todayActiveEl.textContent = '0'; return; } emptyState.style.display = 'none'; const completedCount = todaySessions.filter(s => s.status === 'completed').length; const activeCount = todaySessions.filter(s => s.status === 'draft').length; todayTotalEl.textContent = todaySessions.length; todayCompletedEl.textContent = completedCount; todayActiveEl.textContent = activeCount; grid.innerHTML = todaySessions.map(session => createSessionCard(session)).join(''); } // ==================== TBM 관리 탭 ==================== // 오늘 TBM 로드 (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 loadAllTbm() { try { const response = await window.apiCall('/tbm/sessions'); if (response && response.success) { allSessions = response.data || []; document.getElementById('tbmDate').value = ''; displayTbmSessions(); } else { allSessions = []; displayTbmSessions(); } } catch (error) { console.error('❌ 전체 TBM 조회 오류:', error); showToast('전체 TBM을 불러오는 중 오류가 발생했습니다.', 'error'); allSessions = []; displayTbmSessions(); } } window.loadAllTbm = loadAllTbm; // 특정 날짜의 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 => createSessionCard(session)).join(''); } // TBM 세션 카드 생성 (공통) function createSessionCard(session) { const statusBadge = { 'draft': '진행중', 'completed': '완료', 'cancelled': '취소' }[session.status] || ''; return `

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

${session.session_date} | ${session.leader_job_type || ''}

${statusBadge}
프로젝트 ${session.project_name || '-'}
공정 ${session.work_type_name || '-'}
작업 ${session.task_name || '-'}
작업 장소 ${session.work_location || '-'}
팀원 수 ${session.team_member_count || 0}명
시작 시간 ${session.start_time || '-'}
${session.work_description ? `
${session.work_description}
` : ''}
${session.status === 'draft' ? ` ` : ''}
`; } // 새 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(); populateWorkTypeSelect(); // 작업 드롭다운 초기화 const taskSelect = document.getElementById('taskId'); if (taskSelect) { taskSelect.innerHTML = ''; taskSelect.disabled = true; } 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(''); } // 공정(Work Type) 선택 드롭다운 채우기 function populateWorkTypeSelect() { const workTypeSelect = document.getElementById('workTypeId'); if (!workTypeSelect) return; workTypeSelect.innerHTML = '' + allWorkTypes.map(wt => ` `).join(''); } // 작업(Task) 선택 드롭다운 채우기 (공정 선택 시 호출) function loadTasksByWorkType() { const workTypeId = document.getElementById('workTypeId').value; const taskSelect = document.getElementById('taskId'); if (!taskSelect) return; if (!workTypeId) { taskSelect.innerHTML = ''; taskSelect.disabled = true; return; } // 선택한 공정에 해당하는 작업만 필터링 const filteredTasks = allTasks.filter(task => task.work_type_id === parseInt(workTypeId) ); taskSelect.disabled = false; taskSelect.innerHTML = '' + filteredTasks.map(task => ` `).join(''); if (filteredTasks.length === 0) { taskSelect.innerHTML = ''; taskSelect.disabled = true; } } window.loadTasksByWorkType = loadTasksByWorkType; // TBM 모달 닫기 function closeTbmModal() { document.getElementById('tbmModal').style.display = 'none'; document.body.style.overflow = 'auto'; } window.closeTbmModal = closeTbmModal; // TBM 세션 저장 async function saveTbmSession() { const workTypeId = document.getElementById('workTypeId').value; const taskId = document.getElementById('taskId').value; const sessionData = { session_date: document.getElementById('sessionDate').value, leader_id: parseInt(document.getElementById('leaderId').value), project_id: document.getElementById('projectId').value || null, work_type_id: workTypeId ? parseInt(workTypeId) : null, task_id: taskId ? parseInt(taskId) : 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; } if (!sessionData.work_type_id || !sessionData.task_id) { showToast('공정과 작업을 선택해주세요.', '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; // 목록 새로고침 if (currentTab === 'tbm-input') { await loadTodayOnlyTbm(); } else { 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(); // 목록 새로고침 if (currentTab === 'tbm-input') { await loadTodayOnlyTbm(); } else { 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(); // 목록 새로고침 if (currentTab === 'tbm-input') { await loadTodayOnlyTbm(); } else { 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 세션 상세 보기 async function viewTbmSession(sessionId) { try { // 세션 정보, 팀 구성, 안전 체크 동시 조회 const [sessionRes, teamRes, safetyRes] = await Promise.all([ window.apiCall(`/tbm/sessions/${sessionId}`), window.apiCall(`/tbm/sessions/${sessionId}/team`), window.apiCall(`/tbm/sessions/${sessionId}/safety`) ]); const session = sessionRes?.data; const team = teamRes?.data || []; const safety = safetyRes?.data || []; if (!session) { showToast('세션 정보를 불러올 수 없습니다.', 'error'); return; } // 기본 정보 표시 const basicInfo = document.getElementById('detailBasicInfo'); basicInfo.innerHTML = `
팀장
${session.leader_name}
날짜
${session.session_date}
프로젝트
${session.project_name || '-'}
작업 장소
${session.work_location || '-'}
작업 내용
${session.work_description || '-'}
${session.safety_notes ? `
⚠️ 안전 특이사항
${session.safety_notes}
` : ''} `; // 팀 구성 표시 const teamMembers = document.getElementById('detailTeamMembers'); if (team.length === 0) { teamMembers.innerHTML = '

등록된 팀원이 없습니다.

'; } else { teamMembers.innerHTML = team.map(member => `
${member.worker_name}
${member.job_type || ''}
${member.is_present ? '' : '
결석
'}
`).join(''); } // 안전 체크 표시 const safetyChecks = document.getElementById('detailSafetyChecks'); if (safety.length === 0) { safetyChecks.innerHTML = '

안전 체크 기록이 없습니다.

'; } else { // 카테고리별 그룹화 const grouped = {}; safety.forEach(check => { if (!grouped[check.check_category]) { grouped[check.check_category] = []; } grouped[check.check_category].push(check); }); const categoryNames = { 'PPE': '개인 보호 장비', 'EQUIPMENT': '장비 점검', 'ENVIRONMENT': '작업 환경', 'EMERGENCY': '비상 대응' }; safetyChecks.innerHTML = Object.keys(grouped).map(category => `
${categoryNames[category] || category}
${grouped[category].map(check => `
${check.is_checked ? '✅' : '❌'} ${check.check_item}
`).join('')}
`).join(''); } document.getElementById('detailModal').style.display = 'flex'; document.body.style.overflow = 'hidden'; } catch (error) { console.error('❌ TBM 상세 조회 오류:', error); showToast('상세 정보를 불러오는 중 오류가 발생했습니다.', 'error'); } } window.viewTbmSession = viewTbmSession; // 상세보기 모달 닫기 function closeDetailModal() { document.getElementById('detailModal').style.display = 'none'; document.body.style.overflow = 'auto'; } window.closeDetailModal = closeDetailModal; // 작업 인계 모달 열기 async function openHandoverModal(sessionId) { currentSessionId = sessionId; // 세션 정보와 팀 구성 조회 try { const [sessionRes, teamRes] = await Promise.all([ window.apiCall(`/tbm/sessions/${sessionId}`), window.apiCall(`/tbm/sessions/${sessionId}/team`) ]); const session = sessionRes?.data; const team = teamRes?.data || []; if (!session) { showToast('세션 정보를 불러올 수 없습니다.', 'error'); return; } // 현재 세션의 팀장을 제외한 리더 목록 const toLeaderSelect = document.getElementById('toLeaderId'); const otherLeaders = allWorkers.filter(w => (w.job_type === 'leader' || w.job_type === '그룹장' || w.job_type === 'admin') && w.worker_id !== session.leader_id ); toLeaderSelect.innerHTML = '' + otherLeaders.map(w => ` `).join(''); // 인계할 팀원 목록 const handoverTeamList = document.getElementById('handoverTeamList'); if (team.length === 0) { handoverTeamList.innerHTML = '

팀 구성이 없습니다.

'; } else { handoverTeamList.innerHTML = team.map(member => ` `).join(''); } // 기본값 설정 document.getElementById('handoverSessionId').value = sessionId; const today = new Date().toISOString().split('T')[0]; const now = new Date().toTimeString().slice(0, 5); document.getElementById('handoverDate').value = today; document.getElementById('handoverTime').value = now; document.getElementById('handoverReason').value = ''; document.getElementById('handoverNotes').value = ''; document.getElementById('handoverModal').style.display = 'flex'; document.body.style.overflow = 'hidden'; } catch (error) { console.error('❌ 인계 모달 열기 오류:', error); showToast('인계 정보를 불러오는 중 오류가 발생했습니다.', 'error'); } } window.openHandoverModal = openHandoverModal; // 인계 모달 닫기 function closeHandoverModal() { document.getElementById('handoverModal').style.display = 'none'; document.body.style.overflow = 'auto'; } window.closeHandoverModal = closeHandoverModal; // 작업 인계 저장 async function saveHandover() { const sessionId = currentSessionId; const toLeaderId = parseInt(document.getElementById('toLeaderId').value); const reason = document.getElementById('handoverReason').value; const handoverDate = document.getElementById('handoverDate').value; const handoverTime = document.getElementById('handoverTime').value; const handoverNotes = document.getElementById('handoverNotes').value; if (!toLeaderId || !reason || !handoverDate) { showToast('필수 항목을 입력해주세요.', 'error'); return; } // 인계할 작업자 목록 const workerIds = []; document.querySelectorAll('.handover-worker-checkbox:checked').forEach(cb => { workerIds.push(parseInt(cb.value)); }); if (workerIds.length === 0) { showToast('인계할 팀원을 최소 1명 이상 선택해주세요.', 'error'); return; } try { // 세션 정보 조회 (from_leader_id 가져오기) const sessionRes = await window.apiCall(`/tbm/sessions/${sessionId}`); const fromLeaderId = sessionRes?.data?.leader_id; if (!fromLeaderId) { showToast('세션 정보를 찾을 수 없습니다.', 'error'); return; } const handoverData = { session_id: sessionId, from_leader_id: fromLeaderId, to_leader_id: toLeaderId, handover_date: handoverDate, handover_time: handoverTime, reason: reason, handover_notes: handoverNotes, worker_ids: workerIds }; const response = await window.apiCall('/tbm/handovers', 'POST', handoverData); if (response && response.success) { showToast('작업 인계가 요청되었습니다.', 'success'); closeHandoverModal(); } else { throw new Error(response.message || '인계 요청에 실패했습니다.'); } } catch (error) { console.error('❌ 작업 인계 저장 오류:', error); showToast('작업 인계 중 오류가 발생했습니다.', 'error'); } } window.saveHandover = saveHandover; // 토스트 알림 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); }