feat: 임시 이동 설비 현황 표시 기능 추가

- 대시보드 하단에 임시 이동된 설비 카드 섹션 추가
- 작업장 모달에 '이동 설비' 탭 추가
  - 이 작업장으로 이동해 온 설비 표시
  - 다른 곳으로 이동한 설비 표시
- 설비 마커에 이동 상태 색상 구분 (주황색 점선 + 깜빡임)
- 원위치 복귀 기능
- 사이드바 기본값을 접힌 상태로 변경

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-02-04 14:30:25 +09:00
parent 4d83f10b07
commit d1aec517a6
5 changed files with 415 additions and 9 deletions

View File

@@ -201,8 +201,8 @@
}
});
// 저장된 상태 복원
const isCollapsed = localStorage.getItem('sidebarCollapsed') === 'true';
// 저장된 상태 복원 (기본값: 접힌 상태)
const isCollapsed = localStorage.getItem('sidebarCollapsed') !== 'false';
const sidebar = doc.querySelector('.sidebar-nav');
if (isCollapsed && sidebar) {
sidebar.classList.add('collapsed');

View File

@@ -116,10 +116,10 @@ function highlightCurrentPage(doc) {
}
/**
* 사이드바 상태 복원
* 사이드바 상태 복원 (기본값: 접힌 상태)
*/
function restoreSidebarState(doc) {
const isCollapsed = localStorage.getItem('sidebarCollapsed') === 'true';
const isCollapsed = localStorage.getItem('sidebarCollapsed') !== 'false';
const sidebar = doc.querySelector('.sidebar-nav');
if (isCollapsed && sidebar) {

View File

@@ -24,6 +24,9 @@ document.addEventListener('DOMContentLoaded', async () => {
// 기본값으로 제1공장 선택
await selectFirstCategory();
// 임시 이동된 설비 로드
await loadMovedEquipments();
});
// ==================== 카테고리 (공장) 로드 ====================
@@ -641,9 +644,15 @@ async function loadEquipmentMarkers(workplaceId) {
equipments.forEach(eq => {
// 위치 정보가 있는 설비만 마커 표시
if (eq.map_x_percent != null && eq.map_y_percent != null) {
const statusClass = eq.status === 'active' ? 'active' :
eq.status === 'maintenance' ? 'maintenance' :
eq.status === 'repair_needed' ? 'repair' : 'inactive';
// 임시 이동된 설비는 별도 클래스 적용
let statusClass = '';
if (eq.is_temporarily_moved) {
statusClass = 'moved';
} else {
statusClass = eq.status === 'active' ? 'active' :
eq.status === 'maintenance' ? 'maintenance' :
eq.status === 'repair_needed' ? 'repair' : 'inactive';
}
// 마커 크기 (기본값 또는 설정된 값)
const width = eq.map_width_percent || 8;
@@ -651,14 +660,15 @@ async function loadEquipmentMarkers(workplaceId) {
// 표시 이름: [코드] 이름
const displayName = `[${eq.equipment_code}] ${eq.equipment_name}`;
const movedBadge = eq.is_temporarily_moved ? ' 🚚' : '';
markersHtml += `
<div class="equipment-marker ${statusClass}"
style="left: ${eq.map_x_percent}%; top: ${eq.map_y_percent}%;
width: ${width}%; height: ${height}%;"
title="${displayName}"
title="${displayName}${eq.is_temporarily_moved ? ' (임시이동)' : ''}"
onclick="openEquipmentPanel(${JSON.stringify(eq).replace(/"/g, '&quot;')})">
<span class="marker-label">${displayName}</span>
<span class="marker-label">${displayName}${movedBadge}</span>
</div>
`;
}
@@ -747,6 +757,11 @@ function switchWorkplaceTab(tabName) {
// 선택한 탭 활성화
document.querySelector(`.workplace-tab[data-tab="${tabName}"]`).classList.add('active');
document.getElementById(`tab-${tabName}`).classList.add('active');
// 이동 설비 탭 선택 시 데이터 로드
if (tabName === 'moved-eq' && currentModalWorkplace) {
loadWorkplaceMovedEquipments(currentModalWorkplace.workplace_id);
}
}
// 순회점검 페이지로 이동
@@ -1527,6 +1542,159 @@ async function submitPanelReturn() {
}
}
// ==================== 임시 이동 설비 목록 ====================
async function loadMovedEquipments() {
const listEl = document.getElementById('movedEquipmentList');
const emptyEl = document.getElementById('noMovedEquipment');
if (!listEl) return;
try {
const response = await window.apiCall('/equipments/moved/list', 'GET');
if (response && response.success && response.data && response.data.length > 0) {
const equipments = response.data;
emptyEl.style.display = 'none';
listEl.style.display = 'grid';
listEl.innerHTML = equipments.map(eq => `
<div class="moved-equipment-card" onclick="showMovedEquipmentDetail(${eq.equipment_id})">
<div class="moved-eq-header">
<span class="moved-eq-code">${eq.equipment_code}</span>
<span class="moved-eq-badge">임시이동</span>
</div>
<div class="moved-eq-name">${eq.equipment_name}</div>
<div class="moved-eq-location">
<div class="location-row">
<span class="location-label">원래 위치</span>
<span class="location-value">${eq.original_workplace_name || '-'}</span>
</div>
<div class="location-arrow">↓</div>
<div class="location-row">
<span class="location-label">현재 위치</span>
<span class="location-value current">${eq.current_workplace_name || '-'}</span>
</div>
</div>
<div class="moved-eq-date">
이동일: ${formatPanelDate(eq.moved_at)}
</div>
<button class="btn btn-sm btn-outline" onclick="event.stopPropagation(); returnEquipmentToOriginal(${eq.equipment_id})">
원위치 복귀
</button>
</div>
`).join('');
} else {
listEl.style.display = 'none';
emptyEl.style.display = 'block';
}
} catch (error) {
console.error('임시 이동 설비 로드 실패:', error);
listEl.innerHTML = '<div style="text-align: center; padding: 20px; color: var(--gray-500);">로드 실패</div>';
}
}
// 작업장별 이동 설비 로드
async function loadWorkplaceMovedEquipments(workplaceId) {
const movedInList = document.getElementById('movedInEquipmentList');
const movedOutList = document.getElementById('movedOutEquipmentList');
const badge = document.getElementById('movedEqCountBadge');
try {
const response = await window.apiCall('/equipments/moved/list', 'GET');
if (response && response.success && response.data) {
const allMoved = response.data;
// 이 작업장으로 이동해 온 설비 (current_workplace_id = workplaceId)
const movedIn = allMoved.filter(eq => eq.current_workplace_id === workplaceId);
// 이 작업장에서 다른 곳으로 이동한 설비 (workplace_id = workplaceId)
const movedOut = allMoved.filter(eq => eq.workplace_id === workplaceId);
// 배지 업데이트
const totalCount = movedIn.length + movedOut.length;
if (totalCount > 0) {
badge.textContent = totalCount;
badge.style.display = 'inline-flex';
} else {
badge.style.display = 'none';
}
// 이동해 온 설비 렌더링
if (movedIn.length > 0) {
movedInList.innerHTML = movedIn.map(eq => `
<div class="moved-eq-item in">
<div class="moved-eq-item-header">
<span class="eq-code">${eq.equipment_code}</span>
<span class="eq-name">${eq.equipment_name}</span>
</div>
<div class="moved-eq-item-info">
<span class="from-location">📤 ${eq.original_workplace_name || '알 수 없음'}</span>
<span class="arrow">→</span>
<span class="to-location">📥 여기</span>
</div>
<div class="moved-eq-item-date">이동일: ${formatPanelDate(eq.moved_at)}</div>
</div>
`).join('');
} else {
movedInList.innerHTML = '<p class="empty-message">없음</p>';
}
// 이동해 간 설비 렌더링
if (movedOut.length > 0) {
movedOutList.innerHTML = movedOut.map(eq => `
<div class="moved-eq-item out">
<div class="moved-eq-item-header">
<span class="eq-code">${eq.equipment_code}</span>
<span class="eq-name">${eq.equipment_name}</span>
</div>
<div class="moved-eq-item-info">
<span class="from-location">📤 여기</span>
<span class="arrow">→</span>
<span class="to-location">📥 ${eq.current_workplace_name || '알 수 없음'}</span>
</div>
<div class="moved-eq-item-date">이동일: ${formatPanelDate(eq.moved_at)}</div>
</div>
`).join('');
} else {
movedOutList.innerHTML = '<p class="empty-message">없음</p>';
}
}
} catch (error) {
console.error('작업장 이동 설비 로드 실패:', error);
movedInList.innerHTML = '<p class="empty-message">로드 실패</p>';
movedOutList.innerHTML = '<p class="empty-message">로드 실패</p>';
}
}
// 원위치 복귀
async function returnEquipmentToOriginal(equipmentId) {
if (!confirm('이 설비를 원래 위치로 복귀시키겠습니까?')) return;
try {
const response = await window.apiCall(`/equipments/${equipmentId}/return`, 'POST');
if (response && response.success) {
alert('설비가 원래 위치로 복귀되었습니다.');
loadMovedEquipments();
// 지도 새로고침
if (selectedCategory) {
renderMap();
}
}
} catch (error) {
console.error('복귀 실패:', error);
alert('복귀에 실패했습니다.');
}
}
// 이동된 설비 상세 보기
function showMovedEquipmentDetail(equipmentId) {
// TODO: 설비 상세 패널 열기
console.log('설비 상세:', equipmentId);
}
// ==================== 유틸리티 ====================
function formatPanelDate(dateStr) {
@@ -1566,3 +1734,7 @@ window.submitPanelExport = submitPanelExport;
window.openPanelReturnModal = openPanelReturnModal;
window.closePanelReturnModal = closePanelReturnModal;
window.submitPanelReturn = submitPanelReturn;
window.loadMovedEquipments = loadMovedEquipments;
window.returnEquipmentToOriginal = returnEquipmentToOriginal;
window.showMovedEquipmentDetail = showMovedEquipmentDetail;
window.loadWorkplaceMovedEquipments = loadWorkplaceMovedEquipments;