- 페이지 폴더 재구성: 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>
741 lines
21 KiB
JavaScript
741 lines
21 KiB
JavaScript
/**
|
|
* 문제 신고 등록 페이지 JavaScript
|
|
*/
|
|
|
|
// API 설정
|
|
const API_BASE = window.API_BASE_URL || 'http://localhost:20005/api';
|
|
|
|
// 상태 변수
|
|
let selectedFactoryId = null;
|
|
let selectedWorkplaceId = null;
|
|
let selectedWorkplaceName = null;
|
|
let selectedType = null; // 'nonconformity' | 'safety'
|
|
let selectedCategoryId = null;
|
|
let selectedCategoryName = null;
|
|
let selectedItemId = null;
|
|
let selectedTbmSessionId = null;
|
|
let selectedVisitRequestId = null;
|
|
let photos = [null, null, null, null, null];
|
|
|
|
// 지도 관련 변수
|
|
let canvas, ctx, canvasImage;
|
|
let mapRegions = [];
|
|
let todayWorkers = [];
|
|
let todayVisitors = [];
|
|
|
|
// DOM 요소
|
|
let factorySelect, issueMapCanvas;
|
|
let photoInput, currentPhotoIndex;
|
|
|
|
// 초기화
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
factorySelect = document.getElementById('factorySelect');
|
|
issueMapCanvas = document.getElementById('issueMapCanvas');
|
|
photoInput = document.getElementById('photoInput');
|
|
|
|
canvas = issueMapCanvas;
|
|
ctx = canvas.getContext('2d');
|
|
|
|
// 이벤트 리스너 설정
|
|
setupEventListeners();
|
|
|
|
// 공장 목록 로드
|
|
await loadFactories();
|
|
});
|
|
|
|
/**
|
|
* 이벤트 리스너 설정
|
|
*/
|
|
function setupEventListeners() {
|
|
// 공장 선택
|
|
factorySelect.addEventListener('change', onFactoryChange);
|
|
|
|
// 지도 클릭
|
|
canvas.addEventListener('click', onMapClick);
|
|
|
|
// 기타 위치 토글
|
|
document.getElementById('useCustomLocation').addEventListener('change', (e) => {
|
|
const customInput = document.getElementById('customLocationInput');
|
|
customInput.classList.toggle('visible', e.target.checked);
|
|
|
|
if (e.target.checked) {
|
|
// 지도 선택 초기화
|
|
selectedWorkplaceId = null;
|
|
selectedWorkplaceName = null;
|
|
selectedTbmSessionId = null;
|
|
selectedVisitRequestId = null;
|
|
updateLocationInfo();
|
|
}
|
|
});
|
|
|
|
// 유형 버튼 클릭
|
|
document.querySelectorAll('.type-btn').forEach(btn => {
|
|
btn.addEventListener('click', () => onTypeSelect(btn.dataset.type));
|
|
});
|
|
|
|
// 사진 슬롯 클릭
|
|
document.querySelectorAll('.photo-slot').forEach(slot => {
|
|
slot.addEventListener('click', (e) => {
|
|
if (e.target.classList.contains('remove-btn')) return;
|
|
currentPhotoIndex = parseInt(slot.dataset.index);
|
|
photoInput.click();
|
|
});
|
|
});
|
|
|
|
// 사진 삭제 버튼
|
|
document.querySelectorAll('.photo-slot .remove-btn').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
e.stopPropagation();
|
|
const slot = btn.closest('.photo-slot');
|
|
const index = parseInt(slot.dataset.index);
|
|
removePhoto(index);
|
|
});
|
|
});
|
|
|
|
// 사진 선택
|
|
photoInput.addEventListener('change', onPhotoSelect);
|
|
}
|
|
|
|
/**
|
|
* 공장 목록 로드
|
|
*/
|
|
async function loadFactories() {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/workplaces/categories/active/list`, {
|
|
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
|
});
|
|
|
|
if (!response.ok) throw new Error('공장 목록 조회 실패');
|
|
|
|
const data = await response.json();
|
|
if (data.success && data.data) {
|
|
data.data.forEach(factory => {
|
|
const option = document.createElement('option');
|
|
option.value = factory.category_id;
|
|
option.textContent = factory.category_name;
|
|
factorySelect.appendChild(option);
|
|
});
|
|
|
|
// 첫 번째 공장 자동 선택
|
|
if (data.data.length > 0) {
|
|
factorySelect.value = data.data[0].category_id;
|
|
onFactoryChange();
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('공장 목록 로드 실패:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 공장 변경 시
|
|
*/
|
|
async function onFactoryChange() {
|
|
selectedFactoryId = factorySelect.value;
|
|
if (!selectedFactoryId) return;
|
|
|
|
// 위치 선택 초기화
|
|
selectedWorkplaceId = null;
|
|
selectedWorkplaceName = null;
|
|
selectedTbmSessionId = null;
|
|
selectedVisitRequestId = null;
|
|
updateLocationInfo();
|
|
|
|
// 지도 데이터 로드
|
|
await Promise.all([
|
|
loadMapImage(),
|
|
loadMapRegions(),
|
|
loadTodayData()
|
|
]);
|
|
|
|
renderMap();
|
|
}
|
|
|
|
/**
|
|
* 배치도 이미지 로드
|
|
*/
|
|
async function loadMapImage() {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/workplaces/categories`, {
|
|
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
|
});
|
|
|
|
if (!response.ok) return;
|
|
|
|
const data = await response.json();
|
|
if (data.success && data.data) {
|
|
const selectedCategory = data.data.find(c => c.category_id == selectedFactoryId);
|
|
if (selectedCategory && selectedCategory.layout_image) {
|
|
const baseUrl = (window.API_BASE_URL || 'http://localhost:20005').replace('/api', '');
|
|
const fullImageUrl = selectedCategory.layout_image.startsWith('http')
|
|
? selectedCategory.layout_image
|
|
: `${baseUrl}${selectedCategory.layout_image}`;
|
|
|
|
canvasImage = new Image();
|
|
canvasImage.onload = () => renderMap();
|
|
canvasImage.src = fullImageUrl;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('배치도 이미지 로드 실패:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 지도 영역 로드
|
|
*/
|
|
async function loadMapRegions() {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/workplaces/categories/${selectedFactoryId}/map-regions`, {
|
|
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
|
});
|
|
|
|
if (!response.ok) return;
|
|
|
|
const data = await response.json();
|
|
if (data.success) {
|
|
mapRegions = data.data || [];
|
|
}
|
|
} catch (error) {
|
|
console.error('지도 영역 로드 실패:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 오늘 TBM/출입신청 데이터 로드
|
|
*/
|
|
async function loadTodayData() {
|
|
const today = new Date().toISOString().split('T')[0];
|
|
|
|
try {
|
|
// TBM 세션 로드
|
|
const tbmResponse = await fetch(`${API_BASE}/tbm/sessions/date/${today}`, {
|
|
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
|
});
|
|
|
|
if (tbmResponse.ok) {
|
|
const tbmData = await tbmResponse.json();
|
|
todayWorkers = tbmData.data || [];
|
|
}
|
|
|
|
// 출입 신청 로드
|
|
const visitResponse = await fetch(`${API_BASE}/workplace-visits/requests`, {
|
|
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
|
});
|
|
|
|
if (visitResponse.ok) {
|
|
const visitData = await visitResponse.json();
|
|
todayVisitors = (visitData.data || []).filter(v =>
|
|
v.visit_date === today &&
|
|
(v.status === 'approved' || v.status === 'training_completed')
|
|
);
|
|
}
|
|
} catch (error) {
|
|
console.error('오늘 데이터 로드 실패:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 지도 렌더링
|
|
*/
|
|
function renderMap() {
|
|
if (!canvas || !ctx) return;
|
|
|
|
// 캔버스 크기 설정
|
|
const container = canvas.parentElement;
|
|
canvas.width = container.clientWidth;
|
|
canvas.height = 400;
|
|
|
|
// 배경 그리기
|
|
ctx.fillStyle = '#f3f4f6';
|
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
|
|
// 배치도 이미지
|
|
if (canvasImage && canvasImage.complete) {
|
|
const scale = Math.min(canvas.width / canvasImage.width, canvas.height / canvasImage.height);
|
|
const x = (canvas.width - canvasImage.width * scale) / 2;
|
|
const y = (canvas.height - canvasImage.height * scale) / 2;
|
|
ctx.drawImage(canvasImage, x, y, canvasImage.width * scale, canvasImage.height * scale);
|
|
}
|
|
|
|
// 작업장 영역 그리기
|
|
mapRegions.forEach(region => {
|
|
const workers = todayWorkers.filter(w => w.workplace_id === region.workplace_id);
|
|
const visitors = todayVisitors.filter(v => v.workplace_id === region.workplace_id);
|
|
|
|
const workerCount = workers.reduce((sum, w) => sum + (w.member_count || 0), 0);
|
|
const visitorCount = visitors.reduce((sum, v) => sum + (v.visitor_count || 0), 0);
|
|
|
|
drawWorkplaceRegion(region, workerCount, visitorCount);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 작업장 영역 그리기
|
|
*/
|
|
function drawWorkplaceRegion(region, workerCount, visitorCount) {
|
|
const x1 = (region.x_start / 100) * canvas.width;
|
|
const y1 = (region.y_start / 100) * canvas.height;
|
|
const x2 = (region.x_end / 100) * canvas.width;
|
|
const y2 = (region.y_end / 100) * canvas.height;
|
|
const width = x2 - x1;
|
|
const height = y2 - y1;
|
|
|
|
// 선택된 작업장 하이라이트
|
|
const isSelected = region.workplace_id === selectedWorkplaceId;
|
|
|
|
// 색상 결정
|
|
let fillColor, strokeColor;
|
|
if (isSelected) {
|
|
fillColor = 'rgba(34, 197, 94, 0.3)'; // 초록색
|
|
strokeColor = 'rgb(34, 197, 94)';
|
|
} else if (workerCount > 0 && visitorCount > 0) {
|
|
fillColor = 'rgba(34, 197, 94, 0.2)'; // 초록색 (작업+방문)
|
|
strokeColor = 'rgb(34, 197, 94)';
|
|
} else if (workerCount > 0) {
|
|
fillColor = 'rgba(59, 130, 246, 0.2)'; // 파란색 (작업만)
|
|
strokeColor = 'rgb(59, 130, 246)';
|
|
} else if (visitorCount > 0) {
|
|
fillColor = 'rgba(168, 85, 247, 0.2)'; // 보라색 (방문만)
|
|
strokeColor = 'rgb(168, 85, 247)';
|
|
} else {
|
|
fillColor = 'rgba(156, 163, 175, 0.2)'; // 회색 (없음)
|
|
strokeColor = 'rgb(156, 163, 175)';
|
|
}
|
|
|
|
ctx.fillStyle = fillColor;
|
|
ctx.strokeStyle = strokeColor;
|
|
ctx.lineWidth = isSelected ? 3 : 2;
|
|
|
|
ctx.beginPath();
|
|
ctx.rect(x1, y1, width, height);
|
|
ctx.fill();
|
|
ctx.stroke();
|
|
|
|
// 작업장명 표시
|
|
const centerX = x1 + width / 2;
|
|
const centerY = y1 + height / 2;
|
|
|
|
ctx.fillStyle = '#374151';
|
|
ctx.font = '12px sans-serif';
|
|
ctx.textAlign = 'center';
|
|
ctx.textBaseline = 'middle';
|
|
ctx.fillText(region.workplace_name, centerX, centerY);
|
|
|
|
// 인원수 표시
|
|
const total = workerCount + visitorCount;
|
|
if (total > 0) {
|
|
ctx.fillStyle = strokeColor;
|
|
ctx.font = 'bold 14px sans-serif';
|
|
ctx.fillText(`(${total}명)`, centerX, centerY + 16);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 지도 클릭 처리
|
|
*/
|
|
function onMapClick(e) {
|
|
const rect = canvas.getBoundingClientRect();
|
|
const x = e.clientX - rect.left;
|
|
const y = e.clientY - rect.top;
|
|
|
|
// 클릭된 영역 찾기
|
|
for (const region of mapRegions) {
|
|
const x1 = (region.x_start / 100) * canvas.width;
|
|
const y1 = (region.y_start / 100) * canvas.height;
|
|
const x2 = (region.x_end / 100) * canvas.width;
|
|
const y2 = (region.y_end / 100) * canvas.height;
|
|
|
|
if (x >= x1 && x <= x2 && y >= y1 && y <= y2) {
|
|
selectWorkplace(region);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 작업장 선택
|
|
*/
|
|
function selectWorkplace(region) {
|
|
// 기타 위치 체크박스 해제
|
|
document.getElementById('useCustomLocation').checked = false;
|
|
document.getElementById('customLocationInput').classList.remove('visible');
|
|
|
|
selectedWorkplaceId = region.workplace_id;
|
|
selectedWorkplaceName = region.workplace_name;
|
|
|
|
// 해당 작업장의 TBM/출입신청 확인
|
|
const workers = todayWorkers.filter(w => w.workplace_id === region.workplace_id);
|
|
const visitors = todayVisitors.filter(v => v.workplace_id === region.workplace_id);
|
|
|
|
if (workers.length > 0 || visitors.length > 0) {
|
|
// 작업 선택 모달 표시
|
|
showWorkSelectionModal(workers, visitors);
|
|
} else {
|
|
selectedTbmSessionId = null;
|
|
selectedVisitRequestId = null;
|
|
}
|
|
|
|
updateLocationInfo();
|
|
renderMap();
|
|
updateStepStatus();
|
|
}
|
|
|
|
/**
|
|
* 작업 선택 모달 표시
|
|
*/
|
|
function showWorkSelectionModal(workers, visitors) {
|
|
const modal = document.getElementById('workSelectionModal');
|
|
const optionsList = document.getElementById('workOptionsList');
|
|
|
|
optionsList.innerHTML = '';
|
|
|
|
// TBM 작업 옵션
|
|
workers.forEach(w => {
|
|
const option = document.createElement('div');
|
|
option.className = 'work-option';
|
|
option.innerHTML = `
|
|
<div class="work-option-title">TBM: ${w.task_name || '작업'}</div>
|
|
<div class="work-option-desc">${w.project_name || ''} - ${w.member_count || 0}명</div>
|
|
`;
|
|
option.onclick = () => {
|
|
selectedTbmSessionId = w.session_id;
|
|
selectedVisitRequestId = null;
|
|
closeWorkModal();
|
|
updateLocationInfo();
|
|
};
|
|
optionsList.appendChild(option);
|
|
});
|
|
|
|
// 출입신청 옵션
|
|
visitors.forEach(v => {
|
|
const option = document.createElement('div');
|
|
option.className = 'work-option';
|
|
option.innerHTML = `
|
|
<div class="work-option-title">출입: ${v.visitor_company}</div>
|
|
<div class="work-option-desc">${v.purpose_name || '방문'} - ${v.visitor_count || 0}명</div>
|
|
`;
|
|
option.onclick = () => {
|
|
selectedVisitRequestId = v.request_id;
|
|
selectedTbmSessionId = null;
|
|
closeWorkModal();
|
|
updateLocationInfo();
|
|
};
|
|
optionsList.appendChild(option);
|
|
});
|
|
|
|
modal.classList.add('visible');
|
|
}
|
|
|
|
/**
|
|
* 작업 선택 모달 닫기
|
|
*/
|
|
function closeWorkModal() {
|
|
document.getElementById('workSelectionModal').classList.remove('visible');
|
|
}
|
|
|
|
/**
|
|
* 선택된 위치 정보 업데이트
|
|
*/
|
|
function updateLocationInfo() {
|
|
const infoBox = document.getElementById('selectedLocationInfo');
|
|
const customLocation = document.getElementById('customLocation').value;
|
|
const useCustom = document.getElementById('useCustomLocation').checked;
|
|
|
|
if (useCustom && customLocation) {
|
|
infoBox.classList.remove('empty');
|
|
infoBox.innerHTML = `<strong>선택된 위치:</strong> ${customLocation}`;
|
|
} else if (selectedWorkplaceName) {
|
|
infoBox.classList.remove('empty');
|
|
let html = `<strong>선택된 위치:</strong> ${selectedWorkplaceName}`;
|
|
|
|
if (selectedTbmSessionId) {
|
|
const worker = todayWorkers.find(w => w.session_id === selectedTbmSessionId);
|
|
if (worker) {
|
|
html += `<br><span style="color: var(--primary-600);">연결 작업: ${worker.task_name} (TBM)</span>`;
|
|
}
|
|
} else if (selectedVisitRequestId) {
|
|
const visitor = todayVisitors.find(v => v.request_id === selectedVisitRequestId);
|
|
if (visitor) {
|
|
html += `<br><span style="color: var(--primary-600);">연결 작업: ${visitor.visitor_company} (출입)</span>`;
|
|
}
|
|
}
|
|
|
|
infoBox.innerHTML = html;
|
|
} else {
|
|
infoBox.classList.add('empty');
|
|
infoBox.textContent = '지도에서 작업장을 클릭하여 위치를 선택하세요';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 유형 선택
|
|
*/
|
|
function onTypeSelect(type) {
|
|
selectedType = type;
|
|
selectedCategoryId = null;
|
|
selectedCategoryName = null;
|
|
selectedItemId = null;
|
|
|
|
// 버튼 상태 업데이트
|
|
document.querySelectorAll('.type-btn').forEach(btn => {
|
|
btn.classList.toggle('selected', btn.dataset.type === type);
|
|
});
|
|
|
|
// 카테고리 로드
|
|
loadCategories(type);
|
|
updateStepStatus();
|
|
}
|
|
|
|
/**
|
|
* 카테고리 로드
|
|
*/
|
|
async function loadCategories(type) {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/work-issues/categories/type/${type}`, {
|
|
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
|
});
|
|
|
|
if (!response.ok) throw new Error('카테고리 조회 실패');
|
|
|
|
const data = await response.json();
|
|
if (data.success && data.data) {
|
|
renderCategories(data.data);
|
|
}
|
|
} catch (error) {
|
|
console.error('카테고리 로드 실패:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 카테고리 렌더링
|
|
*/
|
|
function renderCategories(categories) {
|
|
const container = document.getElementById('categoryContainer');
|
|
const grid = document.getElementById('categoryGrid');
|
|
|
|
grid.innerHTML = '';
|
|
|
|
categories.forEach(cat => {
|
|
const btn = document.createElement('button');
|
|
btn.type = 'button';
|
|
btn.className = 'category-btn';
|
|
btn.textContent = cat.category_name;
|
|
btn.onclick = () => onCategorySelect(cat);
|
|
grid.appendChild(btn);
|
|
});
|
|
|
|
container.style.display = 'block';
|
|
}
|
|
|
|
/**
|
|
* 카테고리 선택
|
|
*/
|
|
function onCategorySelect(category) {
|
|
selectedCategoryId = category.category_id;
|
|
selectedCategoryName = category.category_name;
|
|
selectedItemId = null;
|
|
|
|
// 버튼 상태 업데이트
|
|
document.querySelectorAll('.category-btn').forEach(btn => {
|
|
btn.classList.toggle('selected', btn.textContent === category.category_name);
|
|
});
|
|
|
|
// 항목 로드
|
|
loadItems(category.category_id);
|
|
updateStepStatus();
|
|
}
|
|
|
|
/**
|
|
* 항목 로드
|
|
*/
|
|
async function loadItems(categoryId) {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/work-issues/items/category/${categoryId}`, {
|
|
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
|
});
|
|
|
|
if (!response.ok) throw new Error('항목 조회 실패');
|
|
|
|
const data = await response.json();
|
|
if (data.success && data.data) {
|
|
renderItems(data.data);
|
|
}
|
|
} catch (error) {
|
|
console.error('항목 로드 실패:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 항목 렌더링
|
|
*/
|
|
function renderItems(items) {
|
|
const grid = document.getElementById('itemGrid');
|
|
grid.innerHTML = '';
|
|
|
|
if (items.length === 0) {
|
|
grid.innerHTML = '<p style="color: var(--gray-400);">등록된 항목이 없습니다</p>';
|
|
return;
|
|
}
|
|
|
|
items.forEach(item => {
|
|
const btn = document.createElement('button');
|
|
btn.type = 'button';
|
|
btn.className = 'item-btn';
|
|
btn.textContent = item.item_name;
|
|
btn.dataset.severity = item.severity;
|
|
btn.onclick = () => onItemSelect(item, btn);
|
|
grid.appendChild(btn);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* 항목 선택
|
|
*/
|
|
function onItemSelect(item, btn) {
|
|
// 단일 선택 (기존 선택 해제)
|
|
document.querySelectorAll('.item-btn').forEach(b => b.classList.remove('selected'));
|
|
btn.classList.add('selected');
|
|
|
|
selectedItemId = item.item_id;
|
|
updateStepStatus();
|
|
}
|
|
|
|
/**
|
|
* 사진 선택
|
|
*/
|
|
function onPhotoSelect(e) {
|
|
const file = e.target.files[0];
|
|
if (!file) return;
|
|
|
|
const reader = new FileReader();
|
|
reader.onload = (event) => {
|
|
photos[currentPhotoIndex] = event.target.result;
|
|
updatePhotoSlot(currentPhotoIndex);
|
|
};
|
|
reader.readAsDataURL(file);
|
|
|
|
// 입력 초기화
|
|
e.target.value = '';
|
|
}
|
|
|
|
/**
|
|
* 사진 슬롯 업데이트
|
|
*/
|
|
function updatePhotoSlot(index) {
|
|
const slot = document.querySelector(`.photo-slot[data-index="${index}"]`);
|
|
|
|
if (photos[index]) {
|
|
slot.classList.add('has-photo');
|
|
let img = slot.querySelector('img');
|
|
if (!img) {
|
|
img = document.createElement('img');
|
|
slot.insertBefore(img, slot.firstChild);
|
|
}
|
|
img.src = photos[index];
|
|
} else {
|
|
slot.classList.remove('has-photo');
|
|
const img = slot.querySelector('img');
|
|
if (img) img.remove();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 사진 삭제
|
|
*/
|
|
function removePhoto(index) {
|
|
photos[index] = null;
|
|
updatePhotoSlot(index);
|
|
}
|
|
|
|
/**
|
|
* 단계 상태 업데이트
|
|
*/
|
|
function updateStepStatus() {
|
|
const steps = document.querySelectorAll('.step');
|
|
const customLocation = document.getElementById('customLocation').value;
|
|
const useCustom = document.getElementById('useCustomLocation').checked;
|
|
|
|
// Step 1: 위치
|
|
const step1Complete = (useCustom && customLocation) || selectedWorkplaceId;
|
|
steps[0].classList.toggle('completed', step1Complete);
|
|
steps[1].classList.toggle('active', step1Complete);
|
|
|
|
// Step 2: 유형
|
|
const step2Complete = selectedType && selectedCategoryId;
|
|
steps[1].classList.toggle('completed', step2Complete);
|
|
steps[2].classList.toggle('active', step2Complete);
|
|
|
|
// Step 3: 항목
|
|
const step3Complete = selectedItemId;
|
|
steps[2].classList.toggle('completed', step3Complete);
|
|
steps[3].classList.toggle('active', step3Complete);
|
|
|
|
// 제출 버튼 활성화
|
|
const submitBtn = document.getElementById('submitBtn');
|
|
const hasPhoto = photos.some(p => p !== null);
|
|
submitBtn.disabled = !(step1Complete && step2Complete && hasPhoto);
|
|
}
|
|
|
|
/**
|
|
* 신고 제출
|
|
*/
|
|
async function submitReport() {
|
|
const submitBtn = document.getElementById('submitBtn');
|
|
submitBtn.disabled = true;
|
|
submitBtn.textContent = '제출 중...';
|
|
|
|
try {
|
|
const useCustom = document.getElementById('useCustomLocation').checked;
|
|
const customLocation = document.getElementById('customLocation').value;
|
|
const additionalDescription = document.getElementById('additionalDescription').value;
|
|
|
|
const requestBody = {
|
|
factory_category_id: useCustom ? null : selectedFactoryId,
|
|
workplace_id: useCustom ? null : selectedWorkplaceId,
|
|
custom_location: useCustom ? customLocation : null,
|
|
tbm_session_id: selectedTbmSessionId,
|
|
visit_request_id: selectedVisitRequestId,
|
|
issue_category_id: selectedCategoryId,
|
|
issue_item_id: selectedItemId,
|
|
additional_description: additionalDescription || null,
|
|
photos: photos.filter(p => p !== null)
|
|
};
|
|
|
|
const response = await fetch(`${API_BASE}/work-issues`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
|
},
|
|
body: JSON.stringify(requestBody)
|
|
});
|
|
|
|
const data = await response.json();
|
|
|
|
if (data.success) {
|
|
alert('문제 신고가 등록되었습니다.');
|
|
window.location.href = '/pages/safety/issue-list.html';
|
|
} else {
|
|
throw new Error(data.error || '신고 등록 실패');
|
|
}
|
|
} catch (error) {
|
|
console.error('신고 제출 실패:', error);
|
|
alert('신고 등록에 실패했습니다: ' + error.message);
|
|
} finally {
|
|
submitBtn.disabled = false;
|
|
submitBtn.textContent = '신고 제출';
|
|
}
|
|
}
|
|
|
|
// 기타 위치 입력 시 위치 정보 업데이트
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const customLocationInput = document.getElementById('customLocation');
|
|
if (customLocationInput) {
|
|
customLocationInput.addEventListener('input', () => {
|
|
updateLocationInfo();
|
|
updateStepStatus();
|
|
});
|
|
}
|
|
});
|