// 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.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);
}