/** * equipment-detail.js - 설비 상세 페이지 스크립트 */ // 전역 변수 let currentEquipment = null; let equipmentId = null; let workplaces = []; let factories = []; let selectedMovePosition = null; let repairPhotoBases = []; // 상태 라벨 const STATUS_LABELS = { active: '정상 가동', maintenance: '점검 중', repair_needed: '수리 필요', inactive: '비활성', external: '외부 반출', repair_external: '수리 외주' }; // 페이지 초기화 document.addEventListener('DOMContentLoaded', () => { // URL에서 equipment_id 추출 const urlParams = new URLSearchParams(window.location.search); equipmentId = urlParams.get('id'); if (!equipmentId) { alert('설비 ID가 필요합니다.'); goBack(); return; } // API 설정 후 데이터 로드 waitForApiConfig().then(() => { loadEquipmentData(); loadFactories(); loadRepairCategories(); }); }); // API 설정 대기 function waitForApiConfig() { return new Promise(resolve => { const check = setInterval(() => { if (window.API_BASE_URL) { clearInterval(check); resolve(); } }, 50); }); } // 뒤로가기 function goBack() { if (document.referrer && document.referrer.includes(window.location.host)) { history.back(); } else { window.location.href = '/pages/admin/equipments.html'; } } // ========================================== // 설비 데이터 로드 // ========================================== async function loadEquipmentData() { try { const response = await axios.get(`/equipments/${equipmentId}`); if (response.data.success) { currentEquipment = response.data.data; renderEquipmentInfo(); loadPhotos(); loadRepairHistory(); loadExternalLogs(); loadMoveLogs(); } } catch (error) { console.error('설비 정보 로드 실패:', error); alert('설비 정보를 불러오는데 실패했습니다.'); } } function renderEquipmentInfo() { const eq = currentEquipment; // 헤더 document.getElementById('equipmentTitle').textContent = `[${eq.equipment_code}] ${eq.equipment_name}`; document.getElementById('equipmentMeta').textContent = `${eq.model_name || '-'} | ${eq.manufacturer || '-'}`; // 상태 배지 const statusBadge = document.getElementById('equipmentStatus'); statusBadge.textContent = STATUS_LABELS[eq.status] || eq.status; statusBadge.className = `eq-status-badge ${eq.status}`; // 기본 정보 카드 document.getElementById('equipmentInfoCard').innerHTML = `
관리번호 ${eq.equipment_code}
설비명 ${eq.equipment_name}
모델명 ${eq.model_name || '-'}
규격 ${eq.specifications || '-'}
제조사 ${eq.manufacturer || '-'}
구입처 ${eq.supplier || '-'}
구입일 ${eq.installation_date ? formatDate(eq.installation_date) : '-'}
구입가격 ${eq.purchase_price ? Number(eq.purchase_price).toLocaleString() + '원' : '-'}
시리얼번호 ${eq.serial_number || '-'}
설비유형 ${eq.equipment_type || '-'}
`; // 위치 정보 const originalLocation = eq.workplace_name ? `${eq.category_name || ''} > ${eq.workplace_name}` : '미배정'; document.getElementById('originalLocation').textContent = originalLocation; if (eq.is_temporarily_moved && eq.current_workplace_id) { document.getElementById('currentLocationRow').style.display = 'flex'; // 현재 위치 작업장 이름 로드 필요 loadCurrentWorkplaceName(eq.current_workplace_id); } // 지도 미리보기 (작업장 지도 표시) renderMapPreview(); } async function loadCurrentWorkplaceName(workplaceId) { try { const response = await axios.get(`/workplaces/${workplaceId}`); if (response.data.success) { const wp = response.data.data; document.getElementById('currentLocation').textContent = `${wp.category_name || ''} > ${wp.workplace_name}`; } } catch (error) { console.error('현재 위치 로드 실패:', error); } } function renderMapPreview() { const eq = currentEquipment; const mapPreview = document.getElementById('mapPreview'); if (!eq.workplace_id) { mapPreview.innerHTML = '
위치 미배정
'; return; } // 작업장 지도 정보 로드 axios.get(`/workplaces/${eq.workplace_id}`).then(response => { if (response.data.success && response.data.data.map_image_url) { const wp = response.data.data; const xPercent = eq.is_temporarily_moved ? eq.current_map_x_percent : eq.map_x_percent; const yPercent = eq.is_temporarily_moved ? eq.current_map_y_percent : eq.map_y_percent; mapPreview.innerHTML = ` 작업장 지도
`; } else { mapPreview.innerHTML = '
지도 없음
'; } }).catch(() => { mapPreview.innerHTML = '
지도 로드 실패
'; }); } // ========================================== // 사진 관리 // ========================================== async function loadPhotos() { try { const response = await axios.get(`/equipments/${equipmentId}/photos`); if (response.data.success) { renderPhotos(response.data.data); } } catch (error) { console.error('사진 로드 실패:', error); } } function renderPhotos(photos) { const grid = document.getElementById('photoGrid'); if (!photos || photos.length === 0) { grid.innerHTML = '
등록된 사진이 없습니다
'; return; } grid.innerHTML = photos.map(photo => `
${photo.description || '설비 사진'}
`).join(''); } function openPhotoModal() { document.getElementById('photoInput').value = ''; document.getElementById('photoDescription').value = ''; document.getElementById('photoPreviewContainer').style.display = 'none'; document.getElementById('photoModal').style.display = 'flex'; } function closePhotoModal() { document.getElementById('photoModal').style.display = 'none'; } function previewPhoto(event) { const file = event.target.files[0]; if (file) { const reader = new FileReader(); reader.onload = e => { document.getElementById('photoPreview').src = e.target.result; document.getElementById('photoPreviewContainer').style.display = 'block'; }; reader.readAsDataURL(file); } } async function uploadPhoto() { const fileInput = document.getElementById('photoInput'); const description = document.getElementById('photoDescription').value; if (!fileInput.files[0]) { alert('사진을 선택하세요.'); return; } const reader = new FileReader(); reader.onload = async e => { try { const response = await axios.post(`/equipments/${equipmentId}/photos`, { photo_base64: e.target.result, description: description }); if (response.data.success) { closePhotoModal(); loadPhotos(); alert('사진이 추가되었습니다.'); } } catch (error) { console.error('사진 업로드 실패:', error); alert('사진 업로드에 실패했습니다.'); } }; reader.readAsDataURL(fileInput.files[0]); } async function deletePhoto(photoId) { if (!confirm('이 사진을 삭제하시겠습니까?')) return; try { const response = await axios.delete(`/equipments/photos/${photoId}`); if (response.data.success) { loadPhotos(); } } catch (error) { console.error('사진 삭제 실패:', error); alert('사진 삭제에 실패했습니다.'); } } function viewPhoto(url) { document.getElementById('photoViewImage').src = url; document.getElementById('photoViewModal').style.display = 'flex'; } function closePhotoView() { document.getElementById('photoViewModal').style.display = 'none'; } // ========================================== // 임시 이동 // ========================================== async function loadFactories() { try { const response = await axios.get('/workplace-categories'); if (response.data.success) { factories = response.data.data; } } catch (error) { console.error('공장 목록 로드 실패:', error); } } function openMoveModal() { // 공장 선택 초기화 const factorySelect = document.getElementById('moveFactorySelect'); factorySelect.innerHTML = ''; factories.forEach(f => { factorySelect.innerHTML += ``; }); document.getElementById('moveWorkplaceSelect').innerHTML = ''; document.getElementById('moveStep2').style.display = 'none'; document.getElementById('moveStep1').style.display = 'block'; document.getElementById('moveConfirmBtn').disabled = true; document.getElementById('moveReason').value = ''; selectedMovePosition = null; document.getElementById('moveModal').style.display = 'flex'; } function closeMoveModal() { document.getElementById('moveModal').style.display = 'none'; } async function loadMoveWorkplaces() { const categoryId = document.getElementById('moveFactorySelect').value; const workplaceSelect = document.getElementById('moveWorkplaceSelect'); workplaceSelect.innerHTML = ''; if (!categoryId) return; try { const response = await axios.get(`/workplaces?category_id=${categoryId}`); if (response.data.success) { workplaces = response.data.data; workplaces.forEach(wp => { if (wp.map_image_url) { workplaceSelect.innerHTML += ``; } }); } } catch (error) { console.error('작업장 로드 실패:', error); } } function loadMoveMap() { const workplaceId = document.getElementById('moveWorkplaceSelect').value; if (!workplaceId) { document.getElementById('moveStep2').style.display = 'none'; return; } const workplace = workplaces.find(wp => wp.workplace_id == workplaceId); if (!workplace || !workplace.map_image_url) { alert('선택한 작업장에 지도가 없습니다.'); return; } const container = document.getElementById('moveMapContainer'); container.innerHTML = ``; document.getElementById('moveStep2').style.display = 'block'; } function onMoveMapClick(event) { const img = event.target; const rect = img.getBoundingClientRect(); const x = ((event.clientX - rect.left) / rect.width) * 100; const y = ((event.clientY - rect.top) / rect.height) * 100; selectedMovePosition = { x, y }; // 기존 마커 제거 const container = document.getElementById('moveMapContainer'); const existingMarker = container.querySelector('.move-marker'); if (existingMarker) existingMarker.remove(); // 새 마커 추가 const marker = document.createElement('div'); marker.className = 'move-marker'; marker.style.left = x + '%'; marker.style.top = y + '%'; container.appendChild(marker); document.getElementById('moveConfirmBtn').disabled = false; } async function confirmMove() { const targetWorkplaceId = document.getElementById('moveWorkplaceSelect').value; const reason = document.getElementById('moveReason').value; if (!targetWorkplaceId || !selectedMovePosition) { alert('이동할 위치를 선택하세요.'); return; } try { const response = await axios.post(`/equipments/${equipmentId}/move`, { target_workplace_id: targetWorkplaceId, target_x_percent: selectedMovePosition.x.toFixed(2), target_y_percent: selectedMovePosition.y.toFixed(2), from_workplace_id: currentEquipment.workplace_id, from_x_percent: currentEquipment.map_x_percent, from_y_percent: currentEquipment.map_y_percent, reason: reason }); if (response.data.success) { closeMoveModal(); loadEquipmentData(); loadMoveLogs(); alert('설비가 임시 이동되었습니다.'); } } catch (error) { console.error('이동 실패:', error); alert('설비 이동에 실패했습니다.'); } } async function returnToOriginal() { if (!confirm('설비를 원래 위치로 복귀시키겠습니까?')) return; try { const response = await axios.post(`/equipments/${equipmentId}/return`); if (response.data.success) { loadEquipmentData(); loadMoveLogs(); alert('설비가 원위치로 복귀되었습니다.'); } } catch (error) { console.error('복귀 실패:', error); alert('설비 복귀에 실패했습니다.'); } } // ========================================== // 수리 신청 // ========================================== let repairCategories = []; async function loadRepairCategories() { try { const response = await axios.get('/equipments/repair-categories'); if (response.data.success) { repairCategories = response.data.data; } } catch (error) { console.error('수리 항목 로드 실패:', error); } } function openRepairModal() { const select = document.getElementById('repairItemSelect'); select.innerHTML = ''; repairCategories.forEach(item => { select.innerHTML += ``; }); document.getElementById('repairDescription').value = ''; document.getElementById('repairPhotoInput').value = ''; document.getElementById('repairPhotoPreviews').innerHTML = ''; repairPhotoBases = []; document.getElementById('repairModal').style.display = 'flex'; } function closeRepairModal() { document.getElementById('repairModal').style.display = 'none'; } function previewRepairPhotos(event) { const files = event.target.files; const previewContainer = document.getElementById('repairPhotoPreviews'); previewContainer.innerHTML = ''; repairPhotoBases = []; Array.from(files).forEach(file => { const reader = new FileReader(); reader.onload = e => { repairPhotoBases.push(e.target.result); const img = document.createElement('img'); img.src = e.target.result; img.className = 'repair-photo-preview'; previewContainer.appendChild(img); }; reader.readAsDataURL(file); }); } async function submitRepairRequest() { const itemId = document.getElementById('repairItemSelect').value; const description = document.getElementById('repairDescription').value; if (!description) { alert('수리 내용을 입력하세요.'); return; } try { const response = await axios.post(`/equipments/${equipmentId}/repair-request`, { item_id: itemId || null, description: description, photo_base64_list: repairPhotoBases, workplace_id: currentEquipment.workplace_id }); if (response.data.success) { closeRepairModal(); loadEquipmentData(); loadRepairHistory(); alert('수리 신청이 접수되었습니다.'); } } catch (error) { console.error('수리 신청 실패:', error); alert('수리 신청에 실패했습니다.'); } } async function loadRepairHistory() { try { const response = await axios.get(`/equipments/${equipmentId}/repair-history`); if (response.data.success) { renderRepairHistory(response.data.data); } } catch (error) { console.error('수리 이력 로드 실패:', error); } } function renderRepairHistory(history) { const container = document.getElementById('repairHistory'); if (!history || history.length === 0) { container.innerHTML = '
수리 이력이 없습니다
'; return; } container.innerHTML = history.map(h => `
${formatDate(h.created_at)}
${h.item_name || '수리 요청'}
${h.description || '-'}
${getRepairStatusLabel(h.status)}
`).join(''); } function getRepairStatusLabel(status) { const labels = { pending: '대기중', in_progress: '처리중', completed: '완료', closed: '종료' }; return labels[status] || status; } // ========================================== // 외부 반출 // ========================================== function openExportModal() { document.getElementById('exportDate').value = new Date().toISOString().slice(0, 10); document.getElementById('expectedReturnDate').value = ''; document.getElementById('exportDestination').value = ''; document.getElementById('exportReason').value = ''; document.getElementById('exportNotes').value = ''; document.getElementById('isRepairExport').checked = false; document.getElementById('exportModal').style.display = 'flex'; } function closeExportModal() { document.getElementById('exportModal').style.display = 'none'; } function toggleRepairFields() { // 현재는 특별한 필드 차이 없음 } async function submitExport() { const exportDate = document.getElementById('exportDate').value; const expectedReturnDate = document.getElementById('expectedReturnDate').value; const destination = document.getElementById('exportDestination').value; const reason = document.getElementById('exportReason').value; const notes = document.getElementById('exportNotes').value; const isRepair = document.getElementById('isRepairExport').checked; if (!exportDate) { alert('반출일을 입력하세요.'); return; } try { const response = await axios.post(`/equipments/${equipmentId}/export`, { export_date: exportDate, expected_return_date: expectedReturnDate || null, destination: destination, reason: reason, notes: notes, is_repair: isRepair }); if (response.data.success) { closeExportModal(); loadEquipmentData(); loadExternalLogs(); alert('외부 반출이 등록되었습니다.'); } } catch (error) { console.error('반출 등록 실패:', error); alert('반출 등록에 실패했습니다.'); } } async function loadExternalLogs() { try { const response = await axios.get(`/equipments/${equipmentId}/external-logs`); if (response.data.success) { renderExternalLogs(response.data.data); } } catch (error) { console.error('외부반출 이력 로드 실패:', error); } } function renderExternalLogs(logs) { const container = document.getElementById('externalHistory'); if (!logs || logs.length === 0) { container.innerHTML = '
외부반출 이력이 없습니다
'; return; } container.innerHTML = logs.map(log => { const dateRange = log.actual_return_date ? `${formatDate(log.export_date)} ~ ${formatDate(log.actual_return_date)}` : `${formatDate(log.export_date)} ~ (미반입)`; const isReturned = !!log.actual_return_date; const statusClass = isReturned ? 'returned' : 'exported'; const statusLabel = isReturned ? '반입완료' : '반출중'; return `
${dateRange}
${log.destination || '외부'}
${log.reason || '-'}
${statusLabel} ${!isReturned ? `` : ''}
`; }).join(''); } function openReturnModal(logId) { document.getElementById('returnLogId').value = logId; document.getElementById('returnDate').value = new Date().toISOString().slice(0, 10); document.getElementById('returnStatus').value = 'active'; document.getElementById('returnNotes').value = ''; document.getElementById('returnModal').style.display = 'flex'; } function closeReturnModal() { document.getElementById('returnModal').style.display = 'none'; } async function submitReturn() { const logId = document.getElementById('returnLogId').value; const returnDate = document.getElementById('returnDate').value; const newStatus = document.getElementById('returnStatus').value; const notes = document.getElementById('returnNotes').value; if (!returnDate) { alert('반입일을 입력하세요.'); return; } try { const response = await axios.post(`/equipments/external-logs/${logId}/return`, { return_date: returnDate, new_status: newStatus, notes: notes }); if (response.data.success) { closeReturnModal(); loadEquipmentData(); loadExternalLogs(); alert('반입 처리가 완료되었습니다.'); } } catch (error) { console.error('반입 처리 실패:', error); alert('반입 처리에 실패했습니다.'); } } // ========================================== // 이동 이력 // ========================================== async function loadMoveLogs() { try { const response = await axios.get(`/equipments/${equipmentId}/move-logs`); if (response.data.success) { renderMoveLogs(response.data.data); } } catch (error) { console.error('이동 이력 로드 실패:', error); } } function renderMoveLogs(logs) { const container = document.getElementById('moveHistory'); if (!logs || logs.length === 0) { container.innerHTML = '
이동 이력이 없습니다
'; return; } container.innerHTML = logs.map(log => { const typeLabel = log.move_type === 'temporary' ? '임시이동' : '복귀'; const location = log.move_type === 'temporary' ? `${log.to_workplace_name || '-'}` : `원위치 복귀`; return `
${formatDateTime(log.moved_at)}
${typeLabel}: ${location}
${log.reason || '-'} (${log.moved_by_name || '시스템'})
`; }).join(''); } // ========================================== // 유틸리티 // ========================================== function formatDate(dateStr) { if (!dateStr) return '-'; const date = new Date(dateStr); return date.toLocaleDateString('ko-KR', { year: 'numeric', month: '2-digit', day: '2-digit' }).replace(/\. /g, '-').replace('.', ''); } function formatDateTime(dateStr) { if (!dateStr) return '-'; const date = new Date(dateStr); return date.toLocaleDateString('ko-KR', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); }