- 실시간 작업장 현황을 지도로 시각화 - 작업장 관리 페이지에서 정의한 구역 정보 활용 - 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>
367 lines
11 KiB
JavaScript
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();
|
|
}
|
|
});
|