/** * 안전 체크리스트 관리 페이지 스크립트 * * 3가지 유형의 체크리스트 항목을 관리: * 1. 기본 사항 - 항상 표시 * 2. 날씨별 - 날씨 조건에 따라 표시 * 3. 작업별 - 선택한 작업에 따라 표시 * * @since 2026-02-02 */ import { apiCall } from './api-config.js'; // 전역 상태 let allChecks = []; let weatherConditions = []; let workTypes = []; let tasks = []; let currentTab = 'basic'; let editingCheckId = null; // 카테고리 정보 const CATEGORIES = { PPE: { name: 'PPE (개인보호장비)', icon: '🦺' }, EQUIPMENT: { name: 'EQUIPMENT (장비점검)', icon: '🔧' }, ENVIRONMENT: { name: 'ENVIRONMENT (작업환경)', icon: '🏗️' }, EMERGENCY: { name: 'EMERGENCY (비상대응)', icon: '🚨' }, WEATHER: { name: 'WEATHER (날씨)', icon: '🌤️' }, TASK: { name: 'TASK (작업)', icon: '📋' } }; // 날씨 아이콘 매핑 const WEATHER_ICONS = { clear: '☀️', rain: '🌧️', snow: '❄️', heat: '🔥', cold: '🥶', wind: '💨', fog: '🌫️', dust: '😷' }; /** * 페이지 초기화 */ async function initPage() { try { console.log('📋 안전 체크리스트 관리 페이지 초기화...'); await Promise.all([ loadAllChecks(), loadWeatherConditions(), loadWorkTypes() ]); renderCurrentTab(); console.log('✅ 초기화 완료. 체크항목:', allChecks.length, '개'); } catch (error) { console.error('초기화 실패:', error); showToast('데이터를 불러오는데 실패했습니다.', 'error'); } } // DOMContentLoaded 이벤트 document.addEventListener('DOMContentLoaded', initPage); /** * 모든 안전 체크 항목 로드 */ async function loadAllChecks() { try { const response = await apiCall('/tbm/safety-checks'); if (response && response.success) { allChecks = response.data || []; console.log('✅ 체크 항목 로드:', allChecks.length, '개'); } else { console.warn('체크 항목 응답 실패:', response); allChecks = []; } } catch (error) { console.error('체크 항목 로드 실패:', error); allChecks = []; } } /** * 날씨 조건 목록 로드 */ async function loadWeatherConditions() { try { const response = await apiCall('/tbm/weather/conditions'); if (response && response.success) { weatherConditions = response.data || []; populateWeatherSelects(); console.log('✅ 날씨 조건 로드:', weatherConditions.length, '개'); } } catch (error) { console.error('날씨 조건 로드 실패:', error); weatherConditions = []; } } /** * 공정(작업 유형) 목록 로드 */ async function loadWorkTypes() { try { const response = await apiCall('/daily-work-reports/work-types'); if (response && response.success) { workTypes = response.data || []; populateWorkTypeSelects(); console.log('✅ 공정 목록 로드:', workTypes.length, '개'); } } catch (error) { console.error('공정 목록 로드 실패:', error); workTypes = []; } } /** * 날씨 조건 셀렉트 박스 채우기 */ function populateWeatherSelects() { const filterSelect = document.getElementById('weatherFilter'); const modalSelect = document.getElementById('weatherCondition'); const options = weatherConditions.map(wc => `` ).join(''); if (filterSelect) { filterSelect.innerHTML = `${options}`; } if (modalSelect) { modalSelect.innerHTML = options || ''; } } /** * 공정 셀렉트 박스 채우기 */ function populateWorkTypeSelects() { const filterSelect = document.getElementById('workTypeFilter'); const modalSelect = document.getElementById('modalWorkType'); const options = workTypes.map(wt => `` ).join(''); if (filterSelect) { filterSelect.innerHTML = `${options}`; } if (modalSelect) { modalSelect.innerHTML = `${options}`; } } /** * 탭 전환 */ function switchTab(tabName) { currentTab = tabName; // 탭 버튼 상태 업데이트 document.querySelectorAll('.tab-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.tab === tabName); }); // 탭 콘텐츠 표시/숨김 document.querySelectorAll('.tab-content').forEach(content => { content.classList.toggle('active', content.id === `${tabName}Tab`); }); renderCurrentTab(); } /** * 현재 탭 렌더링 */ function renderCurrentTab() { switch (currentTab) { case 'basic': renderBasicChecks(); break; case 'weather': renderWeatherChecks(); break; case 'task': renderTaskChecks(); break; } } /** * 기본 체크 항목 렌더링 */ function renderBasicChecks() { const container = document.getElementById('basicChecklistContainer'); const basicChecks = allChecks.filter(c => c.check_type === 'basic'); console.log('기본 체크항목:', basicChecks.length, '개'); if (basicChecks.length === 0) { container.innerHTML = renderEmptyState('기본 체크 항목이 없습니다.'); return; } // 카테고리별로 그룹화 const grouped = groupByCategory(basicChecks); container.innerHTML = Object.entries(grouped).map(([category, items]) => renderChecklistGroup(category, items) ).join(''); } /** * 날씨별 체크 항목 렌더링 */ function renderWeatherChecks() { const container = document.getElementById('weatherChecklistContainer'); const filterValue = document.getElementById('weatherFilter')?.value; let weatherChecks = allChecks.filter(c => c.check_type === 'weather'); if (filterValue) { weatherChecks = weatherChecks.filter(c => c.weather_condition === filterValue); } if (weatherChecks.length === 0) { container.innerHTML = renderEmptyState('날씨별 체크 항목이 없습니다.'); return; } // 날씨 조건별로 그룹화 const grouped = groupByWeather(weatherChecks); container.innerHTML = Object.entries(grouped).map(([condition, items]) => { const conditionInfo = weatherConditions.find(wc => wc.condition_code === condition); const icon = WEATHER_ICONS[condition] || '🌤️'; const name = conditionInfo?.condition_name || condition; return renderChecklistGroup(`${icon} ${name}`, items, condition); }).join(''); } /** * 작업별 체크 항목 렌더링 */ function renderTaskChecks() { const container = document.getElementById('taskChecklistContainer'); const workTypeId = document.getElementById('workTypeFilter')?.value; const taskId = document.getElementById('taskFilter')?.value; let taskChecks = allChecks.filter(c => c.check_type === 'task'); if (taskId) { taskChecks = taskChecks.filter(c => c.task_id == taskId); } else if (workTypeId && tasks.length > 0) { const workTypeTasks = tasks.filter(t => t.work_type_id == workTypeId); const taskIds = workTypeTasks.map(t => t.task_id); taskChecks = taskChecks.filter(c => taskIds.includes(c.task_id)); } if (taskChecks.length === 0) { container.innerHTML = renderEmptyState('작업별 체크 항목이 없습니다.'); return; } // 작업별로 그룹화 const grouped = groupByTask(taskChecks); container.innerHTML = Object.entries(grouped).map(([taskId, items]) => { const task = tasks.find(t => t.task_id == taskId); const taskName = task?.task_name || `작업 ${taskId}`; return renderChecklistGroup(`📋 ${taskName}`, items, null, taskId); }).join(''); } /** * 카테고리별 그룹화 */ function groupByCategory(checks) { return checks.reduce((acc, check) => { const category = check.check_category || 'OTHER'; if (!acc[category]) acc[category] = []; acc[category].push(check); return acc; }, {}); } /** * 날씨 조건별 그룹화 */ function groupByWeather(checks) { return checks.reduce((acc, check) => { const condition = check.weather_condition || 'other'; if (!acc[condition]) acc[condition] = []; acc[condition].push(check); return acc; }, {}); } /** * 작업별 그룹화 */ function groupByTask(checks) { return checks.reduce((acc, check) => { const taskId = check.task_id || 0; if (!acc[taskId]) acc[taskId] = []; acc[taskId].push(check); return acc; }, {}); } /** * 체크리스트 그룹 렌더링 */ function renderChecklistGroup(title, items, weatherCondition = null, taskId = null) { const categoryInfo = CATEGORIES[title] || { name: title, icon: '' }; const displayTitle = categoryInfo.name !== title ? categoryInfo.name : title; const icon = categoryInfo.icon || ''; // 표시 순서로 정렬 items.sort((a, b) => (a.display_order || 0) - (b.display_order || 0)); return `
${icon} ${displayTitle}
${items.length}개
${items.map(item => renderChecklistItem(item)).join('')}
`; } /** * 체크리스트 항목 렌더링 */ function renderChecklistItem(item) { const requiredBadge = item.is_required ? '필수' : '선택'; return `
${item.check_item}
${requiredBadge} ${item.description ? `${item.description}` : ''}
`; } /** * 빈 상태 렌더링 */ function renderEmptyState(message) { return `
📋

${message}

`; } /** * 날씨 필터 변경 */ function filterByWeather() { renderWeatherChecks(); } /** * 공정 필터 변경 */ async function filterByWorkType() { const workTypeId = document.getElementById('workTypeFilter')?.value; const taskSelect = document.getElementById('taskFilter'); // workTypeId가 없거나 빈 문자열이면 early return if (!workTypeId || workTypeId === '' || workTypeId === 'undefined') { if (taskSelect) { taskSelect.innerHTML = ''; } tasks = []; renderTaskChecks(); return; } try { const response = await apiCall(`/tasks/work-type/${workTypeId}`); if (response && response.success) { tasks = response.data || []; taskSelect.innerHTML = '' + tasks.map(t => ``).join(''); } } catch (error) { console.error('작업 목록 로드 실패:', error); tasks = []; } renderTaskChecks(); } /** * 작업 필터 변경 */ function filterByTask() { renderTaskChecks(); } /** * 모달의 작업 목록 로드 */ async function loadModalTasks() { const workTypeId = document.getElementById('modalWorkType')?.value; const taskSelect = document.getElementById('modalTask'); // workTypeId가 없거나 빈 문자열이면 early return if (!workTypeId || workTypeId === '' || workTypeId === 'undefined') { if (taskSelect) { taskSelect.innerHTML = ''; } return; } try { const response = await apiCall(`/tasks/work-type/${workTypeId}`); if (response && response.success) { const modalTasks = response.data || []; taskSelect.innerHTML = '' + modalTasks.map(t => ``).join(''); } } catch (error) { console.error('작업 목록 로드 실패:', error); } } /** * 조건부 필드 토글 */ function toggleConditionalFields() { const checkType = document.querySelector('input[name="checkType"]:checked')?.value; document.getElementById('basicFields').classList.toggle('show', checkType === 'basic'); document.getElementById('weatherFields').classList.toggle('show', checkType === 'weather'); document.getElementById('taskFields').classList.toggle('show', checkType === 'task'); } /** * 추가 모달 열기 */ function openAddModal() { editingCheckId = null; document.getElementById('modalTitle').textContent = '체크 항목 추가'; // 폼 초기화 document.getElementById('checkForm').reset(); document.getElementById('checkId').value = ''; // 현재 탭에 맞는 유형 선택 const typeRadio = document.querySelector(`input[name="checkType"][value="${currentTab}"]`); if (typeRadio) { typeRadio.checked = true; } toggleConditionalFields(); showModal(); } /** * 수정 모달 열기 */ async function openEditModal(checkId) { editingCheckId = checkId; const check = allChecks.find(c => c.check_id === checkId); if (!check) { showToast('항목을 찾을 수 없습니다.', 'error'); return; } document.getElementById('modalTitle').textContent = '체크 항목 수정'; document.getElementById('checkId').value = checkId; // 유형 선택 const typeRadio = document.querySelector(`input[name="checkType"][value="${check.check_type}"]`); if (typeRadio) { typeRadio.checked = true; } toggleConditionalFields(); // 카테고리 if (check.check_type === 'basic') { document.getElementById('checkCategory').value = check.check_category || 'PPE'; } // 날씨 조건 if (check.check_type === 'weather') { document.getElementById('weatherCondition').value = check.weather_condition || ''; } // 작업 if (check.check_type === 'task' && check.task_id) { // 먼저 공정 찾기 (task를 통해) const task = tasks.find(t => t.task_id === check.task_id); if (task) { document.getElementById('modalWorkType').value = task.work_type_id; await loadModalTasks(); document.getElementById('modalTask').value = check.task_id; } } // 공통 필드 document.getElementById('checkItem').value = check.check_item || ''; document.getElementById('checkDescription').value = check.description || ''; document.getElementById('isRequired').checked = check.is_required === 1 || check.is_required === true; document.getElementById('displayOrder').value = check.display_order || 0; showModal(); } /** * 모달 표시 */ function showModal() { document.getElementById('checkModal').style.display = 'flex'; } /** * 모달 닫기 */ function closeModal() { document.getElementById('checkModal').style.display = 'none'; editingCheckId = null; } /** * 체크 항목 저장 */ async function saveCheck() { const checkType = document.querySelector('input[name="checkType"]:checked')?.value; const checkItem = document.getElementById('checkItem').value.trim(); if (!checkItem) { showToast('체크 항목을 입력해주세요.', 'error'); return; } const data = { check_type: checkType, check_item: checkItem, description: document.getElementById('checkDescription').value.trim() || null, is_required: document.getElementById('isRequired').checked, display_order: parseInt(document.getElementById('displayOrder').value) || 0 }; // 유형별 추가 데이터 switch (checkType) { case 'basic': data.check_category = document.getElementById('checkCategory').value; break; case 'weather': data.check_category = 'WEATHER'; data.weather_condition = document.getElementById('weatherCondition').value; if (!data.weather_condition) { showToast('날씨 조건을 선택해주세요.', 'error'); return; } break; case 'task': data.check_category = 'TASK'; data.task_id = document.getElementById('modalTask').value; if (!data.task_id) { showToast('작업을 선택해주세요.', 'error'); return; } break; } try { let response; if (editingCheckId) { // 수정 response = await apiCall(`/tbm/safety-checks/${editingCheckId}`, 'PUT', data); } else { // 추가 response = await apiCall('/tbm/safety-checks', 'POST', data); } if (response && response.success) { showToast(editingCheckId ? '항목이 수정되었습니다.' : '항목이 추가되었습니다.', 'success'); closeModal(); await loadAllChecks(); renderCurrentTab(); } else { showToast(response?.message || '저장에 실패했습니다.', 'error'); } } catch (error) { console.error('저장 실패:', error); showToast('저장 중 오류가 발생했습니다.', 'error'); } } /** * 삭제 확인 */ function confirmDelete(checkId) { const check = allChecks.find(c => c.check_id === checkId); if (!check) { showToast('항목을 찾을 수 없습니다.', 'error'); return; } if (confirm(`"${check.check_item}" 항목을 삭제하시겠습니까?`)) { deleteCheck(checkId); } } /** * 체크 항목 삭제 */ async function deleteCheck(checkId) { try { const response = await apiCall(`/tbm/safety-checks/${checkId}`, 'DELETE'); if (response && response.success) { showToast('항목이 삭제되었습니다.', 'success'); await loadAllChecks(); renderCurrentTab(); } else { showToast(response?.message || '삭제에 실패했습니다.', 'error'); } } catch (error) { console.error('삭제 실패:', error); showToast('삭제 중 오류가 발생했습니다.', 'error'); } } /** * 토스트 메시지 표시 */ function showToast(message, type = 'info') { // 기존 토스트 제거 const existingToast = document.querySelector('.toast-message'); if (existingToast) { existingToast.remove(); } const toast = document.createElement('div'); toast.className = `toast-message toast-${type}`; toast.textContent = message; toast.style.cssText = ` position: fixed; bottom: 20px; left: 50%; transform: translateX(-50%); padding: 12px 24px; border-radius: 8px; font-size: 14px; font-weight: 500; z-index: 9999; animation: fadeInUp 0.3s ease; ${type === 'success' ? 'background: #10b981; color: white;' : ''} ${type === 'error' ? 'background: #ef4444; color: white;' : ''} ${type === 'info' ? 'background: #3b82f6; color: white;' : ''} `; document.body.appendChild(toast); setTimeout(() => { toast.style.animation = 'fadeOut 0.3s ease'; setTimeout(() => toast.remove(), 300); }, 3000); } // 모달 외부 클릭 시 닫기 document.getElementById('checkModal')?.addEventListener('click', function(e) { if (e.target === this) { closeModal(); } }); // HTML onclick에서 호출할 수 있도록 전역에 노출 window.switchTab = switchTab; window.openAddModal = openAddModal; window.openEditModal = openEditModal; window.closeModal = closeModal; window.saveCheck = saveCheck; window.confirmDelete = confirmDelete; window.filterByWeather = filterByWeather; window.filterByWorkType = filterByWorkType; window.filterByTask = filterByTask; window.loadModalTasks = loadModalTasks; window.toggleConditionalFields = toggleConditionalFields;