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>
This commit is contained in:
@@ -141,7 +141,7 @@
|
||||
|
||||
<!-- TBM 생성/수정 모달 -->
|
||||
<div id="tbmModal" class="modal-overlay" style="display: none;">
|
||||
<div class="modal-container" style="max-width: 800px;">
|
||||
<div class="modal-container" style="max-width: 1000px;">
|
||||
<div class="modal-header">
|
||||
<h2 id="modalTitle">새 TBM 시작</h2>
|
||||
<button class="modal-close-btn" onclick="closeTbmModal()">×</button>
|
||||
@@ -151,68 +151,252 @@
|
||||
<form id="tbmForm" onsubmit="event.preventDefault(); saveTbmSession();">
|
||||
<input type="hidden" id="sessionId">
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">TBM 날짜 *</label>
|
||||
<input type="date" id="sessionDate" class="form-control" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">팀장 *</label>
|
||||
<select id="leaderId" class="form-control" required>
|
||||
<option value="">팀장 선택...</option>
|
||||
</select>
|
||||
<!-- 고정 정보 섹션 -->
|
||||
<div class="form-section" style="background: #f9fafb; padding: 1rem; border-radius: 0.5rem; margin-bottom: 1.5rem;">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">TBM 날짜 *</label>
|
||||
<input type="date" id="sessionDate" class="form-control" required readonly style="background: #e5e7eb;">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">입력자 *</label>
|
||||
<input type="text" id="leaderName" class="form-control" readonly style="background: #e5e7eb;">
|
||||
<input type="hidden" id="leaderId">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">프로젝트</label>
|
||||
<select id="projectId" class="form-control">
|
||||
<option value="">프로젝트 선택...</option>
|
||||
</select>
|
||||
<!-- 작업자 및 작업 정보 섹션 -->
|
||||
<div class="form-section">
|
||||
<div class="section-header" style="margin-bottom: 1rem;">
|
||||
<h3 style="font-size: 1.1rem; font-weight: 600; color: #1f2937;">
|
||||
<span style="margin-right: 0.5rem;">👷</span>
|
||||
작업자 및 작업 정보
|
||||
</h3>
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="openBulkSettingModal()" style="display: flex; align-items: center; gap: 0.25rem;">
|
||||
<span>📋</span>
|
||||
일괄 설정
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-primary" onclick="openWorkerSelectionModal()">
|
||||
<span style="font-size: 1.2rem; margin-right: 0.25rem;">+</span>
|
||||
작업자 선택
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">작업 장소</label>
|
||||
<input type="text" id="workLocation" class="form-control" placeholder="작업 현장 위치">
|
||||
|
||||
<!-- 작업자 카드 리스트 -->
|
||||
<div id="workerTaskList" style="display: flex; flex-direction: column; gap: 0.75rem; min-height: 100px;">
|
||||
<!-- 작업자 카드들이 여기에 동적으로 추가됩니다 -->
|
||||
<div class="empty-state-small" id="workerListEmpty" style="display: flex; align-items: center; justify-content: center; padding: 2rem; border: 2px dashed #d1d5db; border-radius: 0.5rem; color: #6b7280;">
|
||||
<div style="text-align: center;">
|
||||
<div style="font-size: 2rem; margin-bottom: 0.5rem;">👷♂️</div>
|
||||
<p>작업자를 선택해주세요</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">공정 (Work Type) *</label>
|
||||
<select id="workTypeId" class="form-control" onchange="loadTasksByWorkType()" required>
|
||||
<option value="">공정 선택...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">작업 (Task) *</label>
|
||||
<select id="taskId" class="form-control" required>
|
||||
<option value="">작업 선택...</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">작업 내용</label>
|
||||
<textarea id="workDescription" class="form-control" rows="3" placeholder="오늘 진행할 작업 내용을 입력하세요"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">안전 관련 특이사항</label>
|
||||
<textarea id="safetyNotes" class="form-control" rows="2" placeholder="안전 주의사항이나 특이사항"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">시작 시간</label>
|
||||
<input type="time" id="startTime" class="form-control">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeTbmModal()">취소</button>
|
||||
<button type="button" class="btn btn-primary" onclick="saveTbmSession()">
|
||||
💾 저장 및 팀 구성하기
|
||||
💾 저장하기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 일괄 설정 모달 -->
|
||||
<div id="bulkSettingModal" class="modal-overlay" style="display: none; z-index: 1001;">
|
||||
<div class="modal-container" style="max-width: 700px;">
|
||||
<div class="modal-header">
|
||||
<h2>일괄 설정</h2>
|
||||
<button class="modal-close-btn" onclick="closeBulkSettingModal()">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div style="background: #dbeafe; border: 1px solid #3b82f6; padding: 1rem; border-radius: 0.5rem; margin-bottom: 1.5rem;">
|
||||
<div style="font-weight: 600; color: #1e40af; margin-bottom: 0.25rem;">💡 일괄 설정</div>
|
||||
<div style="color: #1e40af; font-size: 0.9rem;">
|
||||
선택한 작업자들의 <strong>첫 번째 작업 라인</strong>에 동일한 정보가 적용됩니다.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 작업자 선택 -->
|
||||
<div class="form-group">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.5rem;">
|
||||
<label class="form-label" style="margin-bottom: 0;">적용할 작업자 선택 *</label>
|
||||
<div style="display: flex; gap: 0.25rem;">
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="selectAllForBulk()" style="font-size: 0.75rem; padding: 0.25rem 0.5rem;">전체</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="deselectAllForBulk()" style="font-size: 0.75rem; padding: 0.25rem 0.5rem;">해제</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="bulkWorkerSelection" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 0.5rem; padding: 0.75rem; border: 1px solid #e5e7eb; border-radius: 0.5rem; max-height: 200px; overflow-y: auto; background: #f9fafb;">
|
||||
<!-- 작업자 체크박스들이 여기에 생성됩니다 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="border-top: 1px solid #e5e7eb; margin: 1.5rem 0; padding-top: 1.5rem;">
|
||||
<h4 style="font-size: 0.95rem; font-weight: 600; margin-bottom: 1rem; color: #374151;">적용할 작업 정보</h4>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">프로젝트</label>
|
||||
<button type="button" id="bulkProjectBtn" onclick="openBulkItemSelect('project')" class="btn btn-secondary" style="width: 100%; text-align: left; justify-content: flex-start;">
|
||||
📁 프로젝트 선택
|
||||
</button>
|
||||
<input type="hidden" id="bulkProjectId">
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">공정 *</label>
|
||||
<button type="button" id="bulkWorkTypeBtn" onclick="openBulkItemSelect('workType')" class="btn btn-secondary" style="width: 100%; text-align: left; justify-content: flex-start;">
|
||||
⚙️ 공정 선택
|
||||
</button>
|
||||
<input type="hidden" id="bulkWorkTypeId">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">작업 *</label>
|
||||
<button type="button" id="bulkTaskBtn" onclick="openBulkItemSelect('task')" class="btn btn-secondary" style="width: 100%; text-align: left; justify-content: flex-start;" disabled>
|
||||
🔧 작업 선택
|
||||
</button>
|
||||
<input type="hidden" id="bulkTaskId">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="form-label">작업장 *</label>
|
||||
<button type="button" id="bulkWorkplaceBtn" onclick="openBulkWorkplaceSelect()" class="btn btn-secondary" style="width: 100%; text-align: left; justify-content: flex-start;">
|
||||
📍 작업장 선택
|
||||
</button>
|
||||
<input type="hidden" id="bulkWorkplaceCategoryId">
|
||||
<input type="hidden" id="bulkWorkplaceId">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeBulkSettingModal()">취소</button>
|
||||
<button type="button" class="btn btn-primary" onclick="applyBulkSettings()">
|
||||
✓ 선택한 작업자에 적용
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 작업자 선택 모달 -->
|
||||
<div id="workerSelectionModal" class="modal-overlay" style="display: none; z-index: 1001;">
|
||||
<div class="modal-container" style="max-width: 800px;">
|
||||
<div class="modal-header">
|
||||
<h2>작업자 선택</h2>
|
||||
<button class="modal-close-btn" onclick="closeWorkerSelectionModal()">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div style="margin-bottom: 1rem; display: flex; gap: 0.5rem;">
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="selectAllWorkersInModal()">전체 선택</button>
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="deselectAllWorkersInModal()">전체 해제</button>
|
||||
</div>
|
||||
|
||||
<div id="workerCardGrid" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 0.75rem; max-height: 500px; overflow-y: auto; padding: 0.5rem;">
|
||||
<!-- 작업자 카드들이 여기에 생성됩니다 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeWorkerSelectionModal()">취소</button>
|
||||
<button type="button" class="btn btn-primary" onclick="confirmWorkerSelection()">
|
||||
✓ 선택 완료
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 항목 선택 모달 (프로젝트/공정/작업 선택용) -->
|
||||
<div id="itemSelectModal" class="modal-overlay" style="display: none; z-index: 1002;">
|
||||
<div class="modal-container" style="max-width: 600px;">
|
||||
<div class="modal-header">
|
||||
<h2 id="itemSelectModalTitle">항목 선택</h2>
|
||||
<button class="modal-close-btn" onclick="closeItemSelectModal()">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
<div id="itemSelectList" style="display: flex; flex-direction: column; gap: 0.5rem; max-height: 400px; overflow-y: auto; padding: 0.5rem;">
|
||||
<!-- 선택 항목들이 여기에 생성됩니다 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeItemSelectModal()">취소</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 작업장 선택 모달 (2단계: 공장 → 작업장) -->
|
||||
<div id="workplaceSelectModal" class="modal-overlay" style="display: none; z-index: 1002;">
|
||||
<div class="modal-container" style="max-width: 1000px; max-height: 90vh;">
|
||||
<div class="modal-header">
|
||||
<h2>작업장 선택</h2>
|
||||
<button class="modal-close-btn" onclick="closeWorkplaceSelectModal()">×</button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body" style="overflow-y: auto;">
|
||||
<!-- 1단계: 공장 선택 -->
|
||||
<div style="margin-bottom: 1.5rem;">
|
||||
<h3 style="font-size: 1rem; font-weight: 600; margin-bottom: 0.75rem; color: #374151;">
|
||||
<span style="margin-right: 0.5rem;">🏭</span>
|
||||
1. 공장 선택
|
||||
</h3>
|
||||
<div id="categoryList" style="display: flex; flex-wrap: wrap; gap: 0.5rem; padding: 0.75rem; border: 1px solid #e5e7eb; border-radius: 0.5rem; background: #f9fafb;">
|
||||
<!-- 공장 카테고리 버튼들이 여기에 생성됩니다 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 2단계: 작업장 선택 (지도 + 리스트) -->
|
||||
<div id="workplaceSelectionArea" style="display: none;">
|
||||
<h3 style="font-size: 1rem; font-weight: 600; margin-bottom: 0.75rem; color: #374151;">
|
||||
<span style="margin-right: 0.5rem;">📍</span>
|
||||
2. 작업장 선택
|
||||
</h3>
|
||||
|
||||
<!-- 지도 기반 선택 영역 -->
|
||||
<div id="layoutMapArea" style="display: none; margin-bottom: 1.5rem; padding: 1rem; background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 0.5rem;">
|
||||
<div style="font-size: 0.875rem; color: #6b7280; margin-bottom: 0.75rem;">
|
||||
<span style="margin-right: 0.25rem;">🗺️</span>
|
||||
지도에서 작업장을 클릭하여 선택하세요
|
||||
</div>
|
||||
<div style="text-align: center; position: relative; display: inline-block; max-width: 100%;">
|
||||
<canvas id="workplaceMapCanvas" style="max-width: 100%; border-radius: 0.5rem; cursor: pointer; box-shadow: 0 2px 4px rgba(0,0,0,0.1);"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 리스트 기반 선택 (오류 대비용) -->
|
||||
<div>
|
||||
<div style="font-size: 0.875rem; color: #6b7280; margin-bottom: 0.75rem; display: flex; align-items: center; justify-content: space-between;">
|
||||
<span>
|
||||
<span style="margin-right: 0.25rem;">📋</span>
|
||||
리스트에서 선택 (지도 오류 시)
|
||||
</span>
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="toggleWorkplaceList()" id="toggleListBtn">
|
||||
<span id="toggleListIcon">▼</span>
|
||||
리스트 보기
|
||||
</button>
|
||||
</div>
|
||||
<div id="workplaceList" style="display: none; flex-direction: column; gap: 0.5rem; max-height: 300px; overflow-y: auto; padding: 0.75rem; border: 1px solid #e5e7eb; border-radius: 0.5rem; background: white;">
|
||||
<div style="color: #9ca3af; text-align: center; padding: 2rem;">
|
||||
공장을 먼저 선택해주세요
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" onclick="closeWorkplaceSelectModal()">취소</button>
|
||||
<button type="button" class="btn btn-primary" onclick="confirmWorkplaceSelection()" id="confirmWorkplaceBtn" disabled>
|
||||
✓ 선택 완료
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -415,6 +599,6 @@
|
||||
</div>
|
||||
|
||||
<script type="module" src="/js/load-navbar.js?v=5"></script>
|
||||
<script type="module" src="/js/tbm.js?v=2"></script>
|
||||
<script type="module" src="/js/tbm.js?v=3"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user