- Gateway 로그인/포탈 페이지 SSO 연동 - System1 web/fastapi-bridge API base URL 동적 설정 - SSO 토큰 기반 인증 흐름 통일 - deprecated JS 파일 삭제 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
851 lines
24 KiB
JavaScript
851 lines
24 KiB
JavaScript
/**
|
|
* 안전 체크리스트 관리 페이지 스크립트
|
|
*
|
|
* 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 {
|
|
|
|
await Promise.all([
|
|
loadAllChecks(),
|
|
loadWeatherConditions(),
|
|
loadWorkTypes()
|
|
]);
|
|
|
|
renderCurrentTab();
|
|
} 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 || [];
|
|
} 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();
|
|
}
|
|
} 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();
|
|
}
|
|
} catch (error) {
|
|
console.error('공정 목록 로드 실패:', error);
|
|
workTypes = [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 날씨 조건 셀렉트 박스 채우기
|
|
*/
|
|
function populateWeatherSelects() {
|
|
const filterSelect = document.getElementById('weatherFilter');
|
|
const modalSelect = document.getElementById('weatherCondition');
|
|
|
|
const options = weatherConditions.map(wc =>
|
|
`<option value="${wc.condition_code}">${WEATHER_ICONS[wc.condition_code] || ''} ${wc.condition_name}</option>`
|
|
).join('');
|
|
|
|
if (filterSelect) {
|
|
filterSelect.innerHTML = `<option value="">모든 날씨 조건</option>${options}`;
|
|
}
|
|
|
|
if (modalSelect) {
|
|
modalSelect.innerHTML = options || '<option value="">날씨 조건 없음</option>';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 공정 셀렉트 박스 채우기
|
|
*/
|
|
function populateWorkTypeSelects() {
|
|
const filterSelect = document.getElementById('workTypeFilter');
|
|
const modalSelect = document.getElementById('modalWorkType');
|
|
|
|
const options = workTypes.map(wt =>
|
|
`<option value="${wt.id}">${wt.name}</option>`
|
|
).join('');
|
|
|
|
if (filterSelect) {
|
|
filterSelect.innerHTML = `<option value="">공정 선택</option>${options}`;
|
|
}
|
|
|
|
if (modalSelect) {
|
|
modalSelect.innerHTML = `<option value="">공정 선택</option>${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('기본 체크 항목이 없습니다.') + renderInlineAddStandalone('basic');
|
|
return;
|
|
}
|
|
|
|
// 카테고리별로 그룹화
|
|
const grouped = groupByCategory(basicChecks);
|
|
|
|
container.innerHTML = Object.entries(grouped).map(([category, items]) =>
|
|
renderChecklistGroup(category, items)
|
|
).join('') + renderInlineAddStandalone('basic');
|
|
}
|
|
|
|
/**
|
|
* 날씨별 체크 항목 렌더링
|
|
*/
|
|
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);
|
|
}
|
|
|
|
const inlineRow = filterValue ? renderInlineAddStandalone('weather') : '';
|
|
|
|
if (weatherChecks.length === 0) {
|
|
container.innerHTML = renderEmptyState('날씨별 체크 항목이 없습니다.') + inlineRow;
|
|
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('') + inlineRow;
|
|
}
|
|
|
|
/**
|
|
* 작업별 체크 항목 렌더링
|
|
*/
|
|
function renderTaskChecks() {
|
|
const container = document.getElementById('taskChecklistContainer');
|
|
const workTypeId = document.getElementById('workTypeFilter')?.value;
|
|
const taskId = document.getElementById('taskFilter')?.value;
|
|
|
|
// 공정 미선택 시 안내
|
|
if (!workTypeId) {
|
|
container.innerHTML = renderGuideState('공정을 먼저 선택해주세요.');
|
|
return;
|
|
}
|
|
|
|
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));
|
|
}
|
|
|
|
const inlineRow = taskId ? renderInlineAddStandalone('task') : '';
|
|
|
|
if (taskChecks.length === 0) {
|
|
container.innerHTML = renderEmptyState('작업별 체크 항목이 없습니다.') + inlineRow;
|
|
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('') + inlineRow;
|
|
}
|
|
|
|
/**
|
|
* 카테고리별 그룹화
|
|
*/
|
|
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 `
|
|
<div class="checklist-group">
|
|
<div class="group-header">
|
|
<div class="group-title">
|
|
<span class="group-icon">${icon}</span>
|
|
<span>${displayTitle}</span>
|
|
</div>
|
|
<span class="group-count">${items.length}개</span>
|
|
</div>
|
|
<div class="checklist-items">
|
|
${items.map(item => renderChecklistItem(item)).join('')}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* 체크리스트 항목 렌더링
|
|
*/
|
|
function renderChecklistItem(item) {
|
|
const requiredBadge = item.is_required
|
|
? '<span class="item-badge badge-required">필수</span>'
|
|
: '<span class="item-badge badge-optional">선택</span>';
|
|
|
|
return `
|
|
<div class="checklist-item" data-check-id="${item.check_id}">
|
|
<div class="item-info">
|
|
<div class="item-name">${item.check_item}</div>
|
|
<div class="item-meta">
|
|
${requiredBadge}
|
|
${item.description ? `<span>${item.description}</span>` : ''}
|
|
</div>
|
|
</div>
|
|
<div class="item-actions">
|
|
<button class="btn-icon btn-edit" onclick="openEditModal(${item.check_id})" title="수정">
|
|
✏️
|
|
</button>
|
|
<button class="btn-icon btn-delete" onclick="confirmDelete(${item.check_id})" title="삭제">
|
|
🗑️
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* 빈 상태 렌더링
|
|
*/
|
|
function renderEmptyState(message) {
|
|
return `
|
|
<div class="empty-state">
|
|
<div class="empty-state-icon">📋</div>
|
|
<p>${message}</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* 안내 상태 렌더링 (필터 미선택 시)
|
|
*/
|
|
function renderGuideState(message) {
|
|
return `
|
|
<div class="empty-state">
|
|
<div class="empty-state-icon">👆</div>
|
|
<p>${message}</p>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* 날씨 필터 변경
|
|
*/
|
|
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 = '<option value="">작업 선택</option>';
|
|
}
|
|
tasks = [];
|
|
renderTaskChecks();
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await apiCall(`/tasks/by-work-type/${workTypeId}`);
|
|
if (response && response.success) {
|
|
tasks = response.data || [];
|
|
taskSelect.innerHTML = '<option value="">작업 선택</option>' +
|
|
tasks.map(t => `<option value="${t.task_id}">${t.task_name}</option>`).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 = '<option value="">작업 선택</option>';
|
|
}
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await apiCall(`/tasks/by-work-type/${workTypeId}`);
|
|
if (response && response.success) {
|
|
const modalTasks = response.data || [];
|
|
taskSelect.innerHTML = '<option value="">작업 선택</option>' +
|
|
modalTasks.map(t => `<option value="${t.task_id}">${t.task_name}</option>`).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();
|
|
|
|
// 날씨별 탭: 현재 필터의 날씨 조건 반영
|
|
if (currentTab === 'weather') {
|
|
const weatherFilter = document.getElementById('weatherFilter')?.value;
|
|
if (weatherFilter) {
|
|
document.getElementById('weatherCondition').value = weatherFilter;
|
|
}
|
|
}
|
|
|
|
// 작업별 탭: 현재 필터의 공정/작업 반영
|
|
if (currentTab === 'task') {
|
|
const workTypeId = document.getElementById('workTypeFilter')?.value;
|
|
if (workTypeId) {
|
|
document.getElementById('modalWorkType').value = workTypeId;
|
|
loadModalTasks().then(() => {
|
|
const taskId = document.getElementById('taskFilter')?.value;
|
|
if (taskId) {
|
|
document.getElementById('modalTask').value = taskId;
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
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 renderInlineAddRow(tabType) {
|
|
if (tabType === 'basic') {
|
|
const categoryOptions = Object.entries(CATEGORIES)
|
|
.filter(([key]) => !['WEATHER', 'TASK'].includes(key))
|
|
.map(([key, val]) => `<option value="${key}">${val.name}</option>`)
|
|
.join('');
|
|
|
|
return `
|
|
<div class="inline-add-row">
|
|
<select class="inline-add-select" id="inlineCategory">${categoryOptions}</select>
|
|
<input type="text" class="inline-add-input" id="inlineBasicInput"
|
|
placeholder="새 체크 항목 입력..." onkeydown="if(event.key==='Enter'){event.preventDefault();addInlineCheck('basic');}">
|
|
<button class="inline-add-btn" onclick="addInlineCheck('basic')">추가</button>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
if (tabType === 'weather') {
|
|
return `
|
|
<div class="inline-add-row">
|
|
<input type="text" class="inline-add-input" id="inlineWeatherInput"
|
|
placeholder="새 날씨별 체크 항목 입력..." onkeydown="if(event.key==='Enter'){event.preventDefault();addInlineCheck('weather');}">
|
|
<button class="inline-add-btn" onclick="addInlineCheck('weather')">추가</button>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
if (tabType === 'task') {
|
|
return `
|
|
<div class="inline-add-row">
|
|
<input type="text" class="inline-add-input" id="inlineTaskInput"
|
|
placeholder="새 작업별 체크 항목 입력..." onkeydown="if(event.key==='Enter'){event.preventDefault();addInlineCheck('task');}">
|
|
<button class="inline-add-btn" onclick="addInlineCheck('task')">추가</button>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
/**
|
|
* 인라인 추가 행을 standalone 컨테이너로 감싸기 (빈 상태용)
|
|
*/
|
|
function renderInlineAddStandalone(tabType) {
|
|
return `<div class="inline-add-standalone">${renderInlineAddRow(tabType)}</div>`;
|
|
}
|
|
|
|
/**
|
|
* 인라인으로 체크 항목 추가
|
|
*/
|
|
async function addInlineCheck(tabType) {
|
|
let checkItem, data;
|
|
|
|
if (tabType === 'basic') {
|
|
const input = document.getElementById('inlineBasicInput');
|
|
const categorySelect = document.getElementById('inlineCategory');
|
|
checkItem = input?.value.trim();
|
|
if (!checkItem) { input?.focus(); return; }
|
|
|
|
data = {
|
|
check_type: 'basic',
|
|
check_item: checkItem,
|
|
check_category: categorySelect?.value || 'PPE',
|
|
is_required: true,
|
|
display_order: 0
|
|
};
|
|
} else if (tabType === 'weather') {
|
|
const input = document.getElementById('inlineWeatherInput');
|
|
checkItem = input?.value.trim();
|
|
if (!checkItem) { input?.focus(); return; }
|
|
|
|
const weatherFilter = document.getElementById('weatherFilter')?.value;
|
|
if (!weatherFilter) {
|
|
showToast('날씨 조건을 먼저 선택해주세요.', 'error');
|
|
return;
|
|
}
|
|
|
|
data = {
|
|
check_type: 'weather',
|
|
check_item: checkItem,
|
|
check_category: 'WEATHER',
|
|
weather_condition: weatherFilter,
|
|
is_required: true,
|
|
display_order: 0
|
|
};
|
|
} else if (tabType === 'task') {
|
|
const input = document.getElementById('inlineTaskInput');
|
|
checkItem = input?.value.trim();
|
|
if (!checkItem) { input?.focus(); return; }
|
|
|
|
const taskId = document.getElementById('taskFilter')?.value;
|
|
if (!taskId) {
|
|
showToast('작업을 먼저 선택해주세요.', 'error');
|
|
return;
|
|
}
|
|
|
|
data = {
|
|
check_type: 'task',
|
|
check_item: checkItem,
|
|
check_category: 'TASK',
|
|
task_id: parseInt(taskId),
|
|
is_required: true,
|
|
display_order: 0
|
|
};
|
|
} else {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await apiCall('/tbm/safety-checks', 'POST', data);
|
|
if (response && response.success) {
|
|
showToast('항목이 추가되었습니다.', 'success');
|
|
await loadAllChecks();
|
|
renderCurrentTab();
|
|
} else {
|
|
showToast(response?.message || '추가에 실패했습니다.', 'error');
|
|
}
|
|
} catch (error) {
|
|
console.error('인라인 추가 실패:', error);
|
|
showToast('추가 중 오류가 발생했습니다.', 'error');
|
|
}
|
|
}
|
|
|
|
// showToast → api-base.js 전역 사용
|
|
|
|
// 모달 외부 클릭 시 닫기
|
|
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;
|
|
window.addInlineCheck = addInlineCheck;
|