feat: 페이지 구조 재구성 및 사이드바 네비게이션 구현
- 페이지 폴더 재구성: safety/, attendance/ 폴더 신규 생성 - work/ → safety/: 이슈 신고, 출입 신청 관련 페이지 이동 - common/ → attendance/: 근태/휴가 관련 페이지 이동 - admin/ 정리: safety-* 파일들을 safety/로 이동 - 사이드바 네비게이션 메뉴 구현 - 카테고리별 메뉴: 작업관리, 안전관리, 근태관리, 시스템관리 - 접기/펼치기 기능 및 상태 저장 - 관리자 전용 메뉴 자동 표시/숨김 - 날씨 API 연동 (기상청 단기예보) - TBM 및 navbar에 현재 날씨 표시 - weatherService.js 추가 - 안전 체크리스트 확장 - 기본/날씨별/작업별 체크 유형 추가 - checklist-manage.html 페이지 추가 - 이슈 신고 시스템 구현 - workIssueController, workIssueModel, workIssueRoutes 추가 - DB 마이그레이션 파일 추가 (실행 대기) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
718
web-ui/js/safety-checklist-manage.js
Normal file
718
web-ui/js/safety-checklist-manage.js
Normal file
@@ -0,0 +1,718 @@
|
||||
/**
|
||||
* 안전 체크리스트 관리 페이지 스크립트
|
||||
*
|
||||
* 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 =>
|
||||
`<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.work_type_id}">${wt.work_type_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('기본 체크 항목이 없습니다.');
|
||||
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 `
|
||||
<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 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/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/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();
|
||||
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;
|
||||
Reference in New Issue
Block a user