diff --git a/web-ui/js/daily-work-report.js b/web-ui/js/daily-work-report.js index 5037e64..14e9977 100644 --- a/web-ui/js/daily-work-report.js +++ b/web-ui/js/daily-work-report.js @@ -294,11 +294,68 @@ async function loadErrorTypes() { } } +// TBM 팀 구성 자동 불러오기 +async function loadTbmTeamForDate(date) { + try { + console.log('🛠️ TBM 팀 구성 조회 중:', date); + const response = await window.apiCall(`/tbm/sessions/date/${date}`); + + if (response && response.success && response.data && response.data.length > 0) { + // 가장 최근 세션 선택 (진행중인 세션 우선) + const draftSessions = response.data.filter(s => s.status === 'draft'); + const targetSession = draftSessions.length > 0 ? draftSessions[0] : response.data[0]; + + if (targetSession) { + // 팀 구성 조회 + const teamRes = await window.apiCall(`/tbm/sessions/${targetSession.session_id}/team`); + if (teamRes && teamRes.success && teamRes.data) { + const teamWorkerIds = teamRes.data.map(m => m.worker_id); + console.log(`✅ TBM 팀 구성 로드 성공: ${teamWorkerIds.length}명`); + return teamWorkerIds; + } + } + } + + console.log('ℹ️ 해당 날짜의 TBM 팀 구성이 없습니다.'); + return []; + } catch (error) { + console.error('❌ TBM 팀 구성 조회 오류:', error); + return []; + } +} + // 작업자 그리드 생성 -function populateWorkerGrid() { +async function populateWorkerGrid() { const grid = document.getElementById('workerGrid'); grid.innerHTML = ''; + // 선택된 날짜의 TBM 팀 구성 불러오기 + const reportDate = document.getElementById('reportDate').value; + let tbmWorkerIds = []; + + if (reportDate) { + tbmWorkerIds = await loadTbmTeamForDate(reportDate); + } + + // TBM 팀 구성이 있으면 안내 메시지 표시 + if (tbmWorkerIds.length > 0) { + const infoDiv = document.createElement('div'); + infoDiv.style.cssText = ` + padding: 1rem; + background: #eff6ff; + border: 1px solid #3b82f6; + border-radius: 0.5rem; + margin-bottom: 1rem; + color: #1e40af; + font-size: 0.875rem; + `; + infoDiv.innerHTML = ` + 🛠️ TBM 팀 구성 자동 적용
+ 오늘 TBM에서 구성된 팀원 ${tbmWorkerIds.length}명이 자동으로 선택되었습니다. + `; + grid.appendChild(infoDiv); + } + workers.forEach(worker => { const btn = document.createElement('button'); btn.type = 'button'; @@ -306,12 +363,24 @@ function populateWorkerGrid() { btn.textContent = worker.worker_name; btn.dataset.id = worker.worker_id; + // TBM 팀 구성에 포함된 작업자는 자동 선택 + if (tbmWorkerIds.includes(worker.worker_id)) { + btn.classList.add('selected'); + selectedWorkers.add(worker.worker_id); + } + btn.addEventListener('click', () => { toggleWorkerSelection(worker.worker_id, btn); }); grid.appendChild(btn); }); + + // 자동 선택된 작업자가 있으면 다음 단계 버튼 활성화 + const nextBtn = document.getElementById('nextStep2'); + if (nextBtn) { + nextBtn.disabled = selectedWorkers.size === 0; + } } // 작업자 선택 토글 diff --git a/web-ui/js/tbm.js b/web-ui/js/tbm.js index 9c018ad..5f5ad92 100644 --- a/web-ui/js/tbm.js +++ b/web-ui/js/tbm.js @@ -176,14 +176,17 @@ function displayTbmSessions() { ` : ''} -
+
${session.status === 'draft' ? ` - - + @@ -587,12 +590,261 @@ async function completeTbmSession() { window.completeTbmSession = completeTbmSession; // TBM 세션 상세 보기 -function viewTbmSession(sessionId) { - // TODO: 상세 보기 페이지 또는 모달 구현 - console.log('TBM 세션 상세 보기:', sessionId); +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'); diff --git a/web-ui/pages/work/tbm.html b/web-ui/pages/work/tbm.html index 97ae32d..c458644 100644 --- a/web-ui/pages/work/tbm.html +++ b/web-ui/pages/work/tbm.html @@ -226,11 +226,115 @@
+ + + + + +
- +