feat: TBM JavaScript 로직 구현 완료
## 주요 기능 ### 1. TBM 세션 관리 - 날짜별 TBM 세션 목록 조회 - 새 TBM 세션 생성 - TBM 세션 완료 처리 - 세션 상태별 표시 (진행중/완료/취소) ### 2. 팀 구성 관리 - 작업자 선택 그리드 UI - 전체 선택/해제 기능 - 선택된 작업자 실시간 표시 - 팀원 일괄 추가 ### 3. 안전 체크리스트 - 카테고리별 체크리스트 표시 - PPE (개인 보호 장비) - EQUIPMENT (장비 점검) - ENVIRONMENT (작업 환경) - EMERGENCY (비상 대응) - 필수/선택 항목 구분 - 체크 상태 저장 ### 4. UI/UX - 모달 기반 인터페이스 - 토스트 알림 - 실시간 통계 표시 (총 세션, 완료 세션) - 반응형 그리드 레이아웃 ## 구현 상세 ### 전역 상태 관리 - allSessions: TBM 세션 목록 - allWorkers: 작업자 목록 - allProjects: 프로젝트 목록 - allSafetyChecks: 안전 체크리스트 - selectedWorkers: 선택된 작업자 (Set) ### API 연동 - GET /api/tbm/sessions/date/:date - POST /api/tbm/sessions - POST /api/tbm/sessions/:id/team/batch - GET /api/tbm/sessions/:id/safety - POST /api/tbm/sessions/:id/safety - POST /api/tbm/sessions/:id/complete ### 주요 함수 - loadTbmSessionsByDate(): 날짜별 세션 조회 - saveTbmSession(): TBM 세션 생성 - saveTeamComposition(): 팀 구성 저장 - saveSafetyChecklist(): 안전 체크 저장 - completeTbmSession(): TBM 완료 처리 ## 파일 - web-ui/js/tbm.js (신규, 약 600줄) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
638
web-ui/js/tbm.js
Normal file
638
web-ui/js/tbm.js
Normal file
@@ -0,0 +1,638 @@
|
||||
// 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': '<span class="badge" style="background: #fef3c7; color: #92400e;">진행중</span>',
|
||||
'completed': '<span class="badge" style="background: #dcfce7; color: #166534;">완료</span>',
|
||||
'cancelled': '<span class="badge" style="background: #fee2e2; color: #991b1b;">취소</span>'
|
||||
}[session.status] || '';
|
||||
|
||||
return `
|
||||
<div class="project-card" style="cursor: pointer;" onclick="viewTbmSession(${session.session_id})">
|
||||
<div class="project-header">
|
||||
<div>
|
||||
<h3 class="project-name" style="font-size: 1rem; margin-bottom: 0.25rem;">
|
||||
${session.leader_name || '팀장 미지정'}
|
||||
</h3>
|
||||
<p style="font-size: 0.75rem; color: #6b7280; margin: 0;">
|
||||
${session.leader_job_type || ''}
|
||||
</p>
|
||||
</div>
|
||||
${statusBadge}
|
||||
</div>
|
||||
|
||||
<div class="project-info" style="margin-top: 1rem;">
|
||||
<div class="info-item">
|
||||
<span class="info-label">프로젝트</span>
|
||||
<span class="info-value">${session.project_name || '-'}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">작업 장소</span>
|
||||
<span class="info-value">${session.work_location || '-'}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">팀원 수</span>
|
||||
<span class="info-value">${session.team_member_count || 0}명</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="info-label">시작 시간</span>
|
||||
<span class="info-value">${session.start_time || '-'}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
${session.work_description ? `
|
||||
<div style="margin-top: 0.75rem; padding: 0.75rem; background: #f9fafb; border-radius: 0.375rem; font-size: 0.875rem; color: #374151;">
|
||||
${session.work_description}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div style="margin-top: 1rem; display: flex; gap: 0.5rem;">
|
||||
${session.status === 'draft' ? `
|
||||
<button class="btn btn-sm btn-primary" onclick="event.stopPropagation(); openTeamCompositionModal(${session.session_id})" style="flex: 1;">
|
||||
👥 팀 구성
|
||||
</button>
|
||||
<button class="btn btn-sm btn-secondary" onclick="event.stopPropagation(); openSafetyCheckModal(${session.session_id})" style="flex: 1;">
|
||||
✅ 안전 체크
|
||||
</button>
|
||||
<button class="btn btn-sm btn-success" onclick="event.stopPropagation(); openCompleteTbmModal(${session.session_id})">
|
||||
완료
|
||||
</button>
|
||||
` : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).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 = '<option value="">팀장 선택...</option>' +
|
||||
leaders.map(w => `
|
||||
<option value="${w.worker_id}">${w.worker_name} (${w.job_type || ''})</option>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
// 프로젝트 선택 드롭다운 채우기
|
||||
function populateProjectSelect() {
|
||||
const projectSelect = document.getElementById('projectId');
|
||||
if (!projectSelect) return;
|
||||
|
||||
projectSelect.innerHTML = '<option value="">프로젝트 선택...</option>' +
|
||||
allProjects.map(p => `
|
||||
<option value="${p.project_id}">${p.project_name} (${p.job_no})</option>
|
||||
`).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 => `
|
||||
<label style="display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem; border: 1px solid #e5e7eb; border-radius: 0.375rem; cursor: pointer; transition: all 0.2s;"
|
||||
onmouseover="this.style.background='#f9fafb'" onmouseout="this.style.background='white'">
|
||||
<input type="checkbox"
|
||||
class="worker-checkbox"
|
||||
data-worker-id="${worker.worker_id}"
|
||||
data-worker-name="${worker.worker_name}"
|
||||
${selectedWorkers.has(worker.worker_id) ? 'checked' : ''}
|
||||
onchange="updateSelectedWorkers()"
|
||||
style="width: 16px; height: 16px; cursor: pointer;">
|
||||
<div style="flex: 1;">
|
||||
<div style="font-weight: 500; font-size: 0.875rem;">${worker.worker_name}</div>
|
||||
<div style="font-size: 0.75rem; color: #6b7280;">${worker.job_type || ''}</div>
|
||||
</div>
|
||||
</label>
|
||||
`).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 = '<p style="margin: 0; color: #9ca3af; font-size: 0.875rem;">작업자를 선택해주세요</p>';
|
||||
} else {
|
||||
const selectedWorkersArray = Array.from(selectedWorkers).map(id => {
|
||||
const worker = allWorkers.find(w => w.worker_id === id);
|
||||
return worker ? `
|
||||
<span style="display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.25rem 0.75rem; background: #3b82f6; color: white; border-radius: 9999px; font-size: 0.875rem;">
|
||||
${worker.worker_name}
|
||||
<button onclick="removeWorker(${id})" style="background: none; border: none; color: white; cursor: pointer; padding: 0; margin-left: 0.25rem; font-size: 1rem; line-height: 1;">×</button>
|
||||
</span>
|
||||
` : '';
|
||||
});
|
||||
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 => `
|
||||
<div style="margin-bottom: 1.5rem;">
|
||||
<div style="font-weight: 600; font-size: 0.9375rem; color: #374151; padding: 0.75rem; background: #f9fafb; border-radius: 0.375rem; margin-bottom: 0.5rem;">
|
||||
${categoryNames[category] || category}
|
||||
</div>
|
||||
${grouped[category].map(check => `
|
||||
<div style="padding: 0.75rem; border-bottom: 1px solid #f3f4f6;">
|
||||
<label style="display: flex; align-items: start; gap: 0.75rem; cursor: pointer;">
|
||||
<input type="checkbox"
|
||||
class="safety-check"
|
||||
data-check-id="${check.check_id}"
|
||||
${check.is_checked ? 'checked' : ''}
|
||||
${check.is_required ? 'required' : ''}
|
||||
style="width: 18px; height: 18px; margin-top: 0.125rem; cursor: pointer;">
|
||||
<div style="flex: 1;">
|
||||
<div style="font-weight: 500; color: #111827;">
|
||||
${check.check_item}
|
||||
${check.is_required ? '<span style="color: #ef4444;">*</span>' : ''}
|
||||
</div>
|
||||
${check.description ? `<div style="font-size: 0.75rem; color: #6b7280; margin-top: 0.25rem;">${check.description}</div>` : ''}
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
`).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 = `
|
||||
<div class="toast-icon">${iconMap[type] || 'ℹ️'}</div>
|
||||
<div class="toast-message">${message}</div>
|
||||
<button class="toast-close" onclick="this.parentElement.remove()">×</button>
|
||||
`;
|
||||
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user