- 설비 마커 클릭 시 슬라이드 패널로 상세 정보 표시 - 설비 사진 업로드/삭제 기능 - 설비 임시 이동 기능 (3단계 지도 기반 선택) - Step 1: 공장 선택 - Step 2: 레이아웃 지도에서 작업장 선택 - Step 3: 상세 지도에서 위치 선택 - 설비 외부 반출/반입 기능 - 설비 수리 신청 기능 (기존 신고 시스템 연동) - DB 마이그레이션 추가 (사진, 임시이동, 외부반출 테이블) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
791 lines
24 KiB
JavaScript
791 lines
24 KiB
JavaScript
/**
|
|
* 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 = `
|
|
<div class="eq-info-grid">
|
|
<div class="eq-info-item">
|
|
<span class="eq-info-label">관리번호</span>
|
|
<span class="eq-info-value">${eq.equipment_code}</span>
|
|
</div>
|
|
<div class="eq-info-item">
|
|
<span class="eq-info-label">설비명</span>
|
|
<span class="eq-info-value">${eq.equipment_name}</span>
|
|
</div>
|
|
<div class="eq-info-item">
|
|
<span class="eq-info-label">모델명</span>
|
|
<span class="eq-info-value">${eq.model_name || '-'}</span>
|
|
</div>
|
|
<div class="eq-info-item">
|
|
<span class="eq-info-label">규격</span>
|
|
<span class="eq-info-value">${eq.specifications || '-'}</span>
|
|
</div>
|
|
<div class="eq-info-item">
|
|
<span class="eq-info-label">제조사</span>
|
|
<span class="eq-info-value">${eq.manufacturer || '-'}</span>
|
|
</div>
|
|
<div class="eq-info-item">
|
|
<span class="eq-info-label">구입처</span>
|
|
<span class="eq-info-value">${eq.supplier || '-'}</span>
|
|
</div>
|
|
<div class="eq-info-item">
|
|
<span class="eq-info-label">구입일</span>
|
|
<span class="eq-info-value">${eq.installation_date ? formatDate(eq.installation_date) : '-'}</span>
|
|
</div>
|
|
<div class="eq-info-item">
|
|
<span class="eq-info-label">구입가격</span>
|
|
<span class="eq-info-value">${eq.purchase_price ? Number(eq.purchase_price).toLocaleString() + '원' : '-'}</span>
|
|
</div>
|
|
<div class="eq-info-item">
|
|
<span class="eq-info-label">시리얼번호</span>
|
|
<span class="eq-info-value">${eq.serial_number || '-'}</span>
|
|
</div>
|
|
<div class="eq-info-item">
|
|
<span class="eq-info-label">설비유형</span>
|
|
<span class="eq-info-value">${eq.equipment_type || '-'}</span>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// 위치 정보
|
|
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 = '<div style="padding: 1rem; text-align: center; color: #9ca3af;">위치 미배정</div>';
|
|
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 = `
|
|
<img src="${window.API_BASE_URL}${wp.map_image_url}" alt="작업장 지도">
|
|
<div class="eq-map-marker" style="left: ${xPercent}%; top: ${yPercent}%;"></div>
|
|
`;
|
|
} else {
|
|
mapPreview.innerHTML = '<div style="padding: 1rem; text-align: center; color: #9ca3af;">지도 없음</div>';
|
|
}
|
|
}).catch(() => {
|
|
mapPreview.innerHTML = '<div style="padding: 1rem; text-align: center; color: #9ca3af;">지도 로드 실패</div>';
|
|
});
|
|
}
|
|
|
|
// ==========================================
|
|
// 사진 관리
|
|
// ==========================================
|
|
|
|
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 = '<div class="eq-photo-empty">등록된 사진이 없습니다</div>';
|
|
return;
|
|
}
|
|
|
|
grid.innerHTML = photos.map(photo => `
|
|
<div class="eq-photo-item" onclick="viewPhoto('${window.API_BASE_URL}${photo.photo_path}')">
|
|
<img src="${window.API_BASE_URL}${photo.photo_path}" alt="${photo.description || '설비 사진'}">
|
|
<button class="eq-photo-delete" onclick="event.stopPropagation(); deletePhoto(${photo.photo_id})">×</button>
|
|
</div>
|
|
`).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 = '<option value="">공장을 선택하세요</option>';
|
|
factories.forEach(f => {
|
|
factorySelect.innerHTML += `<option value="${f.category_id}">${f.category_name}</option>`;
|
|
});
|
|
|
|
document.getElementById('moveWorkplaceSelect').innerHTML = '<option value="">작업장을 선택하세요</option>';
|
|
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 = '<option value="">작업장을 선택하세요</option>';
|
|
|
|
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 += `<option value="${wp.workplace_id}">${wp.workplace_name}</option>`;
|
|
}
|
|
});
|
|
}
|
|
} 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 = `<img src="${window.API_BASE_URL}${workplace.map_image_url}" id="moveMapImage" onclick="onMoveMapClick(event)">`;
|
|
|
|
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 = '<option value="">선택하세요</option>';
|
|
repairCategories.forEach(item => {
|
|
select.innerHTML += `<option value="${item.item_id}">${item.item_name}</option>`;
|
|
});
|
|
|
|
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 = '<div class="eq-history-empty">수리 이력이 없습니다</div>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = history.map(h => `
|
|
<div class="eq-history-item">
|
|
<span class="eq-history-date">${formatDate(h.created_at)}</span>
|
|
<div class="eq-history-content">
|
|
<div class="eq-history-title">${h.item_name || '수리 요청'}</div>
|
|
<div class="eq-history-detail">${h.description || '-'}</div>
|
|
</div>
|
|
<span class="eq-history-status ${h.status}">${getRepairStatusLabel(h.status)}</span>
|
|
</div>
|
|
`).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 = '<div class="eq-history-empty">외부반출 이력이 없습니다</div>';
|
|
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 `
|
|
<div class="eq-history-item">
|
|
<span class="eq-history-date">${dateRange}</span>
|
|
<div class="eq-history-content">
|
|
<div class="eq-history-title">${log.destination || '외부'}</div>
|
|
<div class="eq-history-detail">${log.reason || '-'}</div>
|
|
</div>
|
|
<span class="eq-history-status ${statusClass}">${statusLabel}</span>
|
|
${!isReturned ? `<button class="eq-history-action" onclick="openReturnModal(${log.log_id})">반입처리</button>` : ''}
|
|
</div>
|
|
`;
|
|
}).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 = '<div class="eq-history-empty">이동 이력이 없습니다</div>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = logs.map(log => {
|
|
const typeLabel = log.move_type === 'temporary' ? '임시이동' : '복귀';
|
|
const location = log.move_type === 'temporary'
|
|
? `${log.to_workplace_name || '-'}`
|
|
: `원위치 복귀`;
|
|
|
|
return `
|
|
<div class="eq-history-item">
|
|
<span class="eq-history-date">${formatDateTime(log.moved_at)}</span>
|
|
<div class="eq-history-content">
|
|
<div class="eq-history-title">${typeLabel}: ${location}</div>
|
|
<div class="eq-history-detail">${log.reason || '-'} (${log.moved_by_name || '시스템'})</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).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'
|
|
});
|
|
}
|