Files
TK-FB-Project/web-ui/js/equipment-management.js
Hyungi Ahn b6485e3140 feat: 대시보드 작업장 현황 지도 구현
- 실시간 작업장 현황을 지도로 시각화
- 작업장 관리 페이지에서 정의한 구역 정보 활용
- TBM 작업자 및 방문자 현황 표시

주요 변경사항:
- dashboard.html: 작업장 현황 섹션 추가 (기존 작업 현황 테이블 제거)
- workplace-status.js: 지도 렌더링 및 데이터 통합 로직 구현
- modern-dashboard.js: 삭제된 DOM 요소 조건부 체크 추가

시각화 방식:
- 인원 없음: 회색 테두리 + 작업장 이름
- 내부 작업자: 파란색 영역 + 인원 수
- 외부 방문자: 보라색 영역 + 인원 수
- 둘 다: 초록색 영역 + 총 인원 수

기술 구현:
- Canvas API 기반 사각형 영역 렌더링
- map-regions API를 통한 데이터 일관성 보장
- 클릭 이벤트로 상세 정보 모달 표시

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-29 15:46:47 +09:00

367 lines
11 KiB
JavaScript

// equipment-management.js
// 설비 관리 페이지 JavaScript
let equipments = [];
let workplaces = [];
let equipmentTypes = [];
let currentEquipment = null;
// 페이지 로드 시 초기화
document.addEventListener('DOMContentLoaded', async () => {
// axios 설정이 완료될 때까지 대기
await waitForAxiosConfig();
await loadInitialData();
});
// axios 설정 대기 함수
function waitForAxiosConfig() {
return new Promise((resolve) => {
const check = setInterval(() => {
if (axios.defaults.baseURL) {
clearInterval(check);
resolve();
}
}, 50);
// 최대 5초 대기
setTimeout(() => {
clearInterval(check);
if (!axios.defaults.baseURL) {
console.error('⚠️ Axios 설정 시간 초과');
}
resolve();
}, 5000);
});
}
// 초기 데이터 로드
async function loadInitialData() {
try {
await Promise.all([
loadEquipments(),
loadWorkplaces(),
loadEquipmentTypes()
]);
} catch (error) {
console.error('초기 데이터 로드 실패:', error);
alert('데이터를 불러오는데 실패했습니다.');
}
}
// 설비 목록 로드
async function loadEquipments() {
try {
const response = await axios.get('/equipments');
if (response.data.success) {
equipments = response.data.data;
renderEquipmentList();
}
} catch (error) {
console.error('설비 목록 로드 실패:', error);
throw error;
}
}
// 작업장 목록 로드
async function loadWorkplaces() {
try {
const response = await axios.get('/workplaces');
if (response.data.success) {
workplaces = response.data.data;
populateWorkplaceFilters();
}
} catch (error) {
console.error('작업장 목록 로드 실패:', error);
throw error;
}
}
// 설비 유형 목록 로드
async function loadEquipmentTypes() {
try {
const response = await axios.get('/equipments/types');
if (response.data.success) {
equipmentTypes = response.data.data;
populateTypeFilter();
}
} catch (error) {
console.error('설비 유형 로드 실패:', error);
// 실패해도 계속 진행 (유형이 없을 수 있음)
}
}
// 작업장 필터 채우기
function populateWorkplaceFilters() {
const filterWorkplace = document.getElementById('filterWorkplace');
const modalWorkplace = document.getElementById('workplaceId');
const workplaceOptions = workplaces.map(w =>
`<option value="${w.workplace_id}">${w.category_name} - ${w.workplace_name}</option>`
).join('');
filterWorkplace.innerHTML = '<option value="">전체</option>' + workplaceOptions;
modalWorkplace.innerHTML = '<option value="">선택 안함</option>' + workplaceOptions;
}
// 설비 유형 필터 채우기
function populateTypeFilter() {
const filterType = document.getElementById('filterType');
const typeOptions = equipmentTypes.map(type =>
`<option value="${type}">${type}</option>`
).join('');
filterType.innerHTML = '<option value="">전체</option>' + typeOptions;
}
// 설비 목록 렌더링
function renderEquipmentList() {
const container = document.getElementById('equipmentList');
if (equipments.length === 0) {
container.innerHTML = `
<div class="empty-state">
<p>등록된 설비가 없습니다.</p>
<button class="btn btn-primary" onclick="openEquipmentModal()">설비 추가하기</button>
</div>
`;
return;
}
const tableHTML = `
<table class="data-table">
<thead>
<tr>
<th>설비 코드</th>
<th>설비명</th>
<th>유형</th>
<th>작업장</th>
<th>제조사</th>
<th>모델명</th>
<th>상태</th>
<th>관리</th>
</tr>
</thead>
<tbody>
${equipments.map(equipment => `
<tr>
<td><strong>${equipment.equipment_code}</strong></td>
<td>${equipment.equipment_name}</td>
<td>${equipment.equipment_type || '-'}</td>
<td>${equipment.workplace_name || '-'}</td>
<td>${equipment.manufacturer || '-'}</td>
<td>${equipment.model_name || '-'}</td>
<td>
<span class="status-badge status-${equipment.status}">
${getStatusText(equipment.status)}
</span>
</td>
<td>
<div class="action-buttons">
<button class="btn-small btn-primary" onclick="editEquipment(${equipment.equipment_id})" title="수정">
✏️
</button>
<button class="btn-small btn-danger" onclick="deleteEquipment(${equipment.equipment_id})" title="삭제">
🗑️
</button>
</div>
</td>
</tr>
`).join('')}
</tbody>
</table>
`;
container.innerHTML = tableHTML;
}
// 상태 텍스트 변환
function getStatusText(status) {
const statusMap = {
'active': '활성',
'maintenance': '정비중',
'inactive': '비활성'
};
return statusMap[status] || status;
}
// 필터링
function filterEquipments() {
const workplaceFilter = document.getElementById('filterWorkplace').value;
const typeFilter = document.getElementById('filterType').value;
const statusFilter = document.getElementById('filterStatus').value;
const searchTerm = document.getElementById('searchInput').value.toLowerCase();
// API에서 필터링된 데이터를 가져오는 것이 더 효율적이지만,
// 클라이언트 측에서도 필터링을 적용합니다.
let filtered = [...equipments];
if (workplaceFilter) {
filtered = filtered.filter(e => e.workplace_id == workplaceFilter);
}
if (typeFilter) {
filtered = filtered.filter(e => e.equipment_type === typeFilter);
}
if (statusFilter) {
filtered = filtered.filter(e => e.status === statusFilter);
}
if (searchTerm) {
filtered = filtered.filter(e =>
e.equipment_name.toLowerCase().includes(searchTerm) ||
e.equipment_code.toLowerCase().includes(searchTerm)
);
}
// 임시로 equipments를 필터링된 것으로 교체하고 렌더링
const originalEquipments = equipments;
equipments = filtered;
renderEquipmentList();
equipments = originalEquipments;
}
// 설비 추가 모달 열기
function openEquipmentModal(equipmentId = null) {
currentEquipment = equipmentId;
const modal = document.getElementById('equipmentModal');
const modalTitle = document.getElementById('modalTitle');
const form = document.getElementById('equipmentForm');
form.reset();
document.getElementById('equipmentId').value = '';
if (equipmentId) {
modalTitle.textContent = '설비 수정';
loadEquipmentData(equipmentId);
} else {
modalTitle.textContent = '설비 추가';
}
modal.style.display = 'flex';
}
// 설비 데이터 로드 (수정용)
async function loadEquipmentData(equipmentId) {
try {
const response = await axios.get(`/equipments/${equipmentId}`);
if (response.data.success) {
const equipment = response.data.data;
document.getElementById('equipmentId').value = equipment.equipment_id;
document.getElementById('equipmentCode').value = equipment.equipment_code;
document.getElementById('equipmentName').value = equipment.equipment_name;
document.getElementById('equipmentType').value = equipment.equipment_type || '';
document.getElementById('workplaceId').value = equipment.workplace_id || '';
document.getElementById('manufacturer').value = equipment.manufacturer || '';
document.getElementById('modelName').value = equipment.model_name || '';
document.getElementById('serialNumber').value = equipment.serial_number || '';
document.getElementById('installationDate').value = equipment.installation_date ? equipment.installation_date.split('T')[0] : '';
document.getElementById('equipmentStatus').value = equipment.status || 'active';
document.getElementById('specifications').value = equipment.specifications || '';
document.getElementById('notes').value = equipment.notes || '';
}
} catch (error) {
console.error('설비 데이터 로드 실패:', error);
alert('설비 정보를 불러오는데 실패했습니다.');
}
}
// 설비 모달 닫기
function closeEquipmentModal() {
document.getElementById('equipmentModal').style.display = 'none';
currentEquipment = null;
}
// 설비 저장
async function saveEquipment() {
const equipmentId = document.getElementById('equipmentId').value;
const equipmentData = {
equipment_code: document.getElementById('equipmentCode').value.trim(),
equipment_name: document.getElementById('equipmentName').value.trim(),
equipment_type: document.getElementById('equipmentType').value.trim() || null,
workplace_id: document.getElementById('workplaceId').value || null,
manufacturer: document.getElementById('manufacturer').value.trim() || null,
model_name: document.getElementById('modelName').value.trim() || null,
serial_number: document.getElementById('serialNumber').value.trim() || null,
installation_date: document.getElementById('installationDate').value || null,
status: document.getElementById('equipmentStatus').value,
specifications: document.getElementById('specifications').value.trim() || null,
notes: document.getElementById('notes').value.trim() || null
};
// 유효성 검사
if (!equipmentData.equipment_code) {
alert('설비 코드를 입력해주세요.');
return;
}
if (!equipmentData.equipment_name) {
alert('설비명을 입력해주세요.');
return;
}
try {
let response;
if (equipmentId) {
// 수정
response = await axios.put(`/equipments/${equipmentId}`, equipmentData);
} else {
// 추가
response = await axios.post('/equipments', equipmentData);
}
if (response.data.success) {
alert(equipmentId ? '설비가 수정되었습니다.' : '설비가 추가되었습니다.');
closeEquipmentModal();
await loadEquipments();
await loadEquipmentTypes(); // 새로운 유형이 추가될 수 있으므로
}
} catch (error) {
console.error('설비 저장 실패:', error);
if (error.response && error.response.data && error.response.data.message) {
alert(error.response.data.message);
} else {
alert('설비 저장 중 오류가 발생했습니다.');
}
}
}
// 설비 수정
function editEquipment(equipmentId) {
openEquipmentModal(equipmentId);
}
// 설비 삭제
async function deleteEquipment(equipmentId) {
const equipment = equipments.find(e => e.equipment_id === equipmentId);
if (!equipment) return;
if (!confirm(`'${equipment.equipment_name}' 설비를 삭제하시겠습니까?`)) {
return;
}
try {
const response = await axios.delete(`/equipments/${equipmentId}`);
if (response.data.success) {
alert('설비가 삭제되었습니다.');
await loadEquipments();
}
} catch (error) {
console.error('설비 삭제 실패:', error);
alert('설비 삭제 중 오류가 발생했습니다.');
}
}
// ESC 키로 모달 닫기
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
closeEquipmentModal();
}
});
// 모달 외부 클릭 시 닫기
document.getElementById('equipmentModal').addEventListener('click', (e) => {
if (e.target.id === 'equipmentModal') {
closeEquipmentModal();
}
});