- 공통 유틸리티 추출 (common/utils.js, common/base-state.js) - TBM 모바일 인라인 JS/CSS 외부 파일로 분리 (tbm-mobile.js, tbm-mobile.css) - 미사용 코드 삭제 (index.js, work-report-*.js 등 5개 파일) - TBM/작업보고 state.js, utils.js를 공통 모듈 기반으로 전환 - 작업보고서 SSO 인증 호환 수정 (token/user 함수) - tbmModel.js: incomplete-reports 쿼리에서 users→sso_users 조인 수정, leader_name 조인 추가 - docker-compose.yml: system1-web 볼륨 마운트 추가 - 모바일 인계(handover) 기능 추가 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
298 lines
13 KiB
HTML
298 lines
13 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ko">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>구역 상세 | (주)테크니컬코리아</title>
|
||
<link rel="stylesheet" href="/css/design-system.css">
|
||
<link rel="stylesheet" href="/css/admin-pages.css?v=8">
|
||
<link rel="stylesheet" href="/css/zone-detail.css?v=3">
|
||
<link rel="icon" type="image/png" href="/img/favicon.png">
|
||
<script src="/js/api-base.js?v=2"></script>
|
||
<script src="/js/app-init.js?v=9" defer></script>
|
||
</head>
|
||
<body>
|
||
<!-- 네비게이션 바 -->
|
||
<div id="navbar-container"></div>
|
||
|
||
<!-- 메인 레이아웃 -->
|
||
<div class="page-container">
|
||
<main class="main-content">
|
||
<div class="dashboard-main">
|
||
<!-- 페이지 헤더 -->
|
||
<div class="zone-header">
|
||
<div class="zone-header-left">
|
||
<button class="btn btn-back" onclick="goBack()">
|
||
<span>←</span> 돌아가기
|
||
</button>
|
||
</div>
|
||
<div class="zone-header-center">
|
||
<h1 id="zoneName" class="zone-title">작업장</h1>
|
||
<p id="zoneCategory" class="zone-subtitle">공장</p>
|
||
</div>
|
||
<div class="zone-header-right">
|
||
<span id="currentDate" class="current-date"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 요약 카드 -->
|
||
<div id="summaryCards" class="summary-cards">
|
||
<!-- JS에서 렌더링 -->
|
||
</div>
|
||
|
||
<!-- 탭 네비게이션 -->
|
||
<div class="tab-navigation">
|
||
<button class="tab-btn active" data-tab="map" onclick="switchTab('map')">
|
||
🗺️ 구역 현황
|
||
</button>
|
||
<button class="tab-btn" data-tab="issues" onclick="switchTab('issues')">
|
||
🚨 안전신고/부적합
|
||
</button>
|
||
<button class="tab-btn" data-tab="equipment" onclick="switchTab('equipment')">
|
||
⚙️ 설비/수리
|
||
</button>
|
||
<button class="tab-btn" data-tab="visits" onclick="switchTab('visits')">
|
||
🚶 출입현황
|
||
</button>
|
||
<button class="tab-btn" data-tab="tbm" onclick="switchTab('tbm')">
|
||
📋 TBM
|
||
</button>
|
||
<button class="tab-btn" data-tab="patrol" onclick="switchTab('patrol')">
|
||
🔍 순회점검
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 탭 콘텐츠 -->
|
||
<div class="tab-contents">
|
||
<!-- 구역 현황 탭 -->
|
||
<div id="tab-map" class="tab-content active">
|
||
<div class="map-editor-section">
|
||
<div class="map-editor-header">
|
||
<h3>구역 현황</h3>
|
||
<div class="map-editor-actions">
|
||
<button class="btn btn-primary btn-sm" id="addItemBtn" onclick="startAddItem()">
|
||
➕ 현황 등록
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="map-editor-container">
|
||
<div id="zoneMapContainer" class="zone-map-container">
|
||
<div class="map-placeholder">지도를 로딩 중...</div>
|
||
</div>
|
||
<div class="map-legend">
|
||
<div class="legend-title">주의 수준</div>
|
||
<div class="legend-items">
|
||
<div class="legend-item"><span class="legend-color" style="background: #10b981;"></span> 양호</div>
|
||
<div class="legend-item"><span class="legend-color" style="background: #f59e0b;"></span> 주의</div>
|
||
<div class="legend-item"><span class="legend-color" style="background: #ef4444;"></span> 관리필요</div>
|
||
</div>
|
||
<div class="legend-title" style="margin-top: 1rem;">설비 상태</div>
|
||
<div class="legend-items">
|
||
<div class="legend-item"><span style="margin-right: 4px;">⚙️</span> 정상 가동</div>
|
||
<div class="legend-item"><span style="margin-right: 4px;">🔧</span> 수리 필요</div>
|
||
<div class="legend-item"><span style="margin-right: 4px;">⚠️</span> 점검중</div>
|
||
<div class="legend-item"><span style="margin-right: 4px;">📤</span> 타 작업장 이동</div>
|
||
<div class="legend-item"><span style="margin-right: 4px;">📥</span> 임시 배치</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="zoneItemsList" class="zone-items-list">
|
||
<!-- JS에서 렌더링 -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 안전신고/부적합 탭 -->
|
||
<div id="tab-issues" class="tab-content">
|
||
<div id="issuesContent" class="content-loading">
|
||
<div class="loading-spinner"></div>
|
||
<p>로딩 중...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 설비/수리 탭 -->
|
||
<div id="tab-equipment" class="tab-content">
|
||
<div id="equipmentContent" class="content-loading">
|
||
<div class="loading-spinner"></div>
|
||
<p>로딩 중...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 출입현황 탭 -->
|
||
<div id="tab-visits" class="tab-content">
|
||
<div id="visitsContent" class="content-loading">
|
||
<div class="loading-spinner"></div>
|
||
<p>로딩 중...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TBM 탭 -->
|
||
<div id="tab-tbm" class="tab-content">
|
||
<div id="tbmContent" class="content-loading">
|
||
<div class="loading-spinner"></div>
|
||
<p>로딩 중...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 순회점검 탭 -->
|
||
<div id="tab-patrol" class="tab-content">
|
||
<div id="patrolContent" class="content-loading">
|
||
<div class="loading-spinner"></div>
|
||
<p>로딩 중...</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
|
||
<!-- 현황 등록/수정 모달 -->
|
||
<div id="zoneItemModal" class="modal-overlay" style="display: none;">
|
||
<div class="modal-container" style="max-width: 520px;">
|
||
<div class="modal-header">
|
||
<h2 id="zoneItemModalTitle">현황 등록</h2>
|
||
<button class="btn-close" onclick="closeZoneItemModal()">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="zoneItemForm">
|
||
<input type="hidden" id="zoneItemId">
|
||
<input type="hidden" id="zoneItemX">
|
||
<input type="hidden" id="zoneItemY">
|
||
<input type="hidden" id="zoneItemWidth">
|
||
<input type="hidden" id="zoneItemHeight">
|
||
|
||
<!-- 프로젝트 여부 -->
|
||
<div class="form-group">
|
||
<label>프로젝트 여부 *</label>
|
||
<div class="radio-group">
|
||
<label class="radio-label">
|
||
<input type="radio" name="zoneItemProjectType" value="project" onchange="onProjectTypeChange(this.value)">
|
||
<span class="radio-text">프로젝트</span>
|
||
</label>
|
||
<label class="radio-label">
|
||
<input type="radio" name="zoneItemProjectType" value="non_project" onchange="onProjectTypeChange(this.value)" checked>
|
||
<span class="radio-text">프로젝트 아님</span>
|
||
</label>
|
||
<label class="radio-label">
|
||
<input type="radio" name="zoneItemProjectType" value="unknown" onchange="onProjectTypeChange(this.value)">
|
||
<span class="radio-text">판단 못함</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 프로젝트 선택 (프로젝트일 경우만 표시) -->
|
||
<div class="form-group" id="projectSelectGroup" style="display: none;">
|
||
<label for="zoneItemProject">프로젝트 선택</label>
|
||
<select id="zoneItemProject" class="form-control">
|
||
<option value="">프로젝트를 선택하세요</option>
|
||
<!-- JS에서 동적 로드 -->
|
||
</select>
|
||
</div>
|
||
|
||
<!-- 명칭 -->
|
||
<div class="form-group">
|
||
<label for="zoneItemName">명칭 *</label>
|
||
<input type="text" id="zoneItemName" class="form-control" placeholder="예: A사 제품, 작업 자재, 이동 설비" required>
|
||
</div>
|
||
|
||
<!-- 상태/유형 + 주의수준 -->
|
||
<div class="form-row">
|
||
<div class="form-group" style="flex: 1.5;">
|
||
<label for="zoneItemType">상태/유형</label>
|
||
<div class="select-with-add">
|
||
<select id="zoneItemType" class="form-control">
|
||
<option value="working">작업중</option>
|
||
<option value="temp_storage">임시적치</option>
|
||
<option value="moved_equipment">이동설비</option>
|
||
<option value="unreported">미신고품</option>
|
||
</select>
|
||
<button type="button" class="btn-add-option" onclick="addCustomType()" title="유형 추가">+</button>
|
||
</div>
|
||
</div>
|
||
<div class="form-group" style="flex: 1;">
|
||
<label for="zoneItemWarning">주의 수준</label>
|
||
<select id="zoneItemWarning" class="form-control">
|
||
<option value="good">양호</option>
|
||
<option value="caution">주의</option>
|
||
<option value="needs_management">관리필요</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 상세 설명 -->
|
||
<div class="form-group">
|
||
<label for="zoneItemDesc">상세 설명</label>
|
||
<textarea id="zoneItemDesc" class="form-control" rows="2" placeholder="현황에 대한 상세 설명, 주의사항, 담당자 등"></textarea>
|
||
</div>
|
||
|
||
<!-- 사진 등록 -->
|
||
<div class="form-group">
|
||
<label>사진</label>
|
||
<div class="photo-upload-area">
|
||
<input type="file" id="zoneItemPhoto" accept="image/*" multiple onchange="onPhotoSelected(event)" style="display: none;">
|
||
<div id="photoPreviewList" class="photo-preview-list">
|
||
<!-- 미리보기 이미지들 -->
|
||
</div>
|
||
<button type="button" class="btn-add-photo" onclick="document.getElementById('zoneItemPhoto').click()">
|
||
<span class="photo-icon">📷</span>
|
||
<span>사진 추가</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 표시 색상 -->
|
||
<div class="form-group">
|
||
<label>표시 색상</label>
|
||
<div class="color-picker-row">
|
||
<input type="color" id="zoneItemColor" class="form-control color-input" value="#3b82f6">
|
||
<div class="color-presets">
|
||
<button type="button" class="color-preset" style="background: #10b981;" onclick="setItemColor('#10b981')" title="양호"></button>
|
||
<button type="button" class="color-preset" style="background: #f59e0b;" onclick="setItemColor('#f59e0b')" title="주의"></button>
|
||
<button type="button" class="color-preset" style="background: #ef4444;" onclick="setItemColor('#ef4444')" title="관리필요"></button>
|
||
<button type="button" class="color-preset" style="background: #3b82f6;" onclick="setItemColor('#3b82f6')" title="기본"></button>
|
||
<button type="button" class="color-preset" style="background: #8b5cf6;" onclick="setItemColor('#8b5cf6')" title="기타"></button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" onclick="closeZoneItemModal()">취소</button>
|
||
<button type="button" class="btn btn-danger" id="deleteZoneItemBtn" onclick="deleteZoneItem()" style="display: none;">삭제</button>
|
||
<button type="button" class="btn btn-primary" onclick="saveZoneItem()">저장</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||
<script type="module">
|
||
import '/js/api-config.js?v=3';
|
||
</script>
|
||
<script>
|
||
(function() {
|
||
const checkApiConfig = setInterval(() => {
|
||
if (window.API_BASE_URL) {
|
||
clearInterval(checkApiConfig);
|
||
axios.defaults.baseURL = window.API_BASE_URL;
|
||
const token = localStorage.getItem('sso_token');
|
||
if (token) {
|
||
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
|
||
}
|
||
axios.interceptors.response.use(
|
||
response => response,
|
||
error => {
|
||
if (error.response?.status === 401) {
|
||
alert('세션이 만료되었습니다. 다시 로그인해주세요.');
|
||
window.location.href = '/pages/login.html';
|
||
}
|
||
return Promise.reject(error);
|
||
}
|
||
);
|
||
}
|
||
}, 50);
|
||
})();
|
||
</script>
|
||
<script src="/js/zone-detail.js?v=6"></script>
|
||
</body>
|
||
</html>
|