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:
Hyungi Ahn
2026-01-29 15:46:47 +09:00
parent e1227a69fe
commit b6485e3140
87 changed files with 17509 additions and 698 deletions

View File

@@ -17,6 +17,7 @@
<script type="module" src="/js/load-navbar.js"></script>
<script type="module" src="/js/modern-dashboard.js?v=10" defer></script>
<script type="module" src="/js/group-leader-dashboard.js?v=1" defer></script>
<script src="/js/workplace-status.js" defer></script>
</head>
<body>
@@ -46,6 +47,22 @@
<div class="action-arrow" style="color: white;"></div>
</a>
<a href="/pages/work/visit-request.html" class="quick-action-card" style="background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%); color: white;">
<div class="action-content">
<h3 style="color: white;">🚪 출입 신청</h3>
<p style="color: rgba(255, 255, 255, 0.9);">작업장 출입 및 안전교육을 신청합니다</p>
</div>
<div class="action-arrow" style="color: white;"></div>
</a>
<a href="/pages/admin/safety-management.html" class="quick-action-card admin-only" style="background: linear-gradient(135deg, #ec4899 0%, #db2777 100%); color: white;">
<div class="action-content">
<h3 style="color: white;">🛡️ 안전관리</h3>
<p style="color: rgba(255, 255, 255, 0.9);">출입 신청 승인 및 안전교육 관리</p>
</div>
<div class="action-arrow" style="color: white;"></div>
</a>
<a href="/pages/work/report-create.html" class="quick-action-card">
<div class="action-content">
<h3>작업 보고서 작성</h3>
@@ -77,46 +94,111 @@
</div>
<div class="action-arrow"></div>
</a>
<a href="/pages/common/daily-attendance.html" class="quick-action-card" style="background: linear-gradient(135deg, #06b6d4 0%, #0891b2 100%); color: white;">
<div class="action-content">
<h3 style="color: white;">📅 일일 출퇴근 입력</h3>
<p style="color: rgba(255, 255, 255, 0.9);">오늘의 출퇴근 기록을 입력합니다</p>
</div>
<div class="action-arrow" style="color: white;"></div>
</a>
<a href="/pages/common/monthly-attendance.html" class="quick-action-card">
<div class="action-content">
<h3>📆 월별 출퇴근 현황</h3>
<p>이번 달 출퇴근 현황을 조회합니다</p>
</div>
<div class="action-arrow"></div>
</a>
<a href="/pages/common/vacation-request.html" class="quick-action-card">
<div class="action-content">
<h3>📝 휴가 신청</h3>
<p>휴가를 신청하고 신청 내역을 확인합니다</p>
</div>
<div class="action-arrow"></div>
</a>
<a href="/pages/common/vacation-management.html" class="quick-action-card admin-only">
<div class="action-content">
<h3>🏖️ 휴가 관리</h3>
<p>휴가 승인, 직접 입력, 전체 내역을 관리합니다</p>
</div>
<div class="action-arrow"></div>
</a>
<a href="/pages/common/annual-vacation-overview.html" class="quick-action-card admin-only">
<div class="action-content">
<h3>📊 연간 연차 현황</h3>
<p>모든 작업자의 연간 휴가 현황을 차트로 확인합니다</p>
</div>
<div class="action-arrow"></div>
</a>
<a href="/pages/common/vacation-allocation.html" class="quick-action-card admin-only">
<div class="action-content">
<h3> 휴가 발생 입력</h3>
<p>작업자별 휴가를 입력하고 특별 휴가를 관리합니다</p>
</div>
<div class="action-arrow"></div>
</a>
<a href="/pages/admin/attendance-report-comparison.html" class="quick-action-card admin-only">
<div class="action-content">
<h3>🔍 출퇴근-작업보고서 대조</h3>
<p>출퇴근 기록과 작업보고서를 비교 분석합니다</p>
</div>
<div class="action-arrow"></div>
</a>
</div>
</div>
</div>
</section>
<!-- 오늘의 작업 현황 -->
<section class="work-status-section">
<!-- 작업 현황 -->
<section class="workplace-status-section">
<div class="card">
<div class="card-header">
<div class="flex justify-between items-center">
<h2 class="card-title">오늘의 작업 현황</h2>
<div class="date-selector">
<input type="date" id="selectedDate" class="date-input">
<button class="btn btn-primary btn-sm" id="refreshBtn">
<h2 class="card-title">작업 현황</h2>
<div class="flex items-center" style="gap: 12px;">
<select id="categorySelect" class="form-select" style="width: 200px;">
<option value="">공장을 선택하세요</option>
</select>
<button class="btn btn-primary btn-sm" id="refreshMapBtn">
새로고침
</button>
</div>
</div>
</div>
<div class="card-body">
<div class="work-status-table-container">
<table class="work-status-table">
<thead>
<tr>
<th>작업자</th>
<th>상태</th>
<th>작업시간</th>
<th>작업건수</th>
<th>액션</th>
</tr>
</thead>
<tbody id="workStatusTableBody">
<tr>
<td colspan="5" class="loading-state">
<div class="spinner"></div>
<p>작업 현황을 불러오는 중...</p>
</td>
</tr>
</tbody>
</table>
<!-- 지도 영역 -->
<div id="workplaceMapContainer" style="position: relative; min-height: 500px; display: none;">
<canvas id="workplaceMapCanvas" style="width: 100%; max-width: 100%; cursor: pointer; border: 2px solid var(--gray-300); border-radius: var(--radius-md);"></canvas>
<div id="mapLegend" style="position: absolute; top: 16px; right: 16px; background: white; padding: 16px; border-radius: var(--radius-md); box-shadow: var(--shadow-md);">
<h4 style="font-size: var(--text-sm); font-weight: 700; margin-bottom: 12px;">범례</h4>
<div style="display: flex; flex-direction: column; gap: 8px;">
<div style="display: flex; align-items: center; gap: 8px;">
<div style="width: 16px; height: 16px; background: rgba(59, 130, 246, 0.3); border: 2px solid rgb(59, 130, 246); border-radius: 4px;"></div>
<span style="font-size: var(--text-sm);">작업 중 (내부 작업자)</span>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<div style="width: 16px; height: 16px; background: rgba(168, 85, 247, 0.3); border: 2px solid rgb(168, 85, 247); border-radius: 4px;"></div>
<span style="font-size: var(--text-sm);">방문 예정 (외부 인원)</span>
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<div style="width: 16px; height: 16px; background: rgba(34, 197, 94, 0.3); border: 2px solid rgb(34, 197, 94); border-radius: 4px;"></div>
<span style="font-size: var(--text-sm);">작업 + 방문</span>
</div>
</div>
</div>
</div>
<!-- 안내 메시지 -->
<div id="mapPlaceholder" style="display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 400px; color: var(--gray-500);">
<div style="font-size: 48px; margin-bottom: 16px;">🏭</div>
<h3 style="margin-bottom: 8px;">공장을 선택하세요</h3>
<p style="font-size: var(--text-sm);">위에서 공장을 선택하면 작업장 현황을 확인할 수 있습니다.</p>
</div>
</div>
</div>
@@ -144,6 +226,28 @@
<!-- 알림 토스트 -->
<div class="toast-container" id="toastContainer"></div>
<!-- 작업장 상세 정보 모달 -->
<div id="workplaceDetailModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 1000; align-items: center; justify-content: center;">
<div style="background: white; border-radius: var(--radius-lg); padding: 32px; max-width: 800px; width: 90%; max-height: 80vh; overflow-y: auto; box-shadow: var(--shadow-2xl);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px;">
<h2 id="modalWorkplaceName" style="margin: 0; font-size: var(--text-2xl); font-weight: 700;"></h2>
<button class="btn btn-secondary btn-sm" onclick="closeWorkplaceModal()">닫기</button>
</div>
<!-- 내부 작업자 -->
<div id="internalWorkersSection" style="margin-bottom: 24px;">
<h3 style="font-size: var(--text-lg); font-weight: 600; margin-bottom: 16px; color: var(--primary-600);">👷 내부 작업자</h3>
<div id="internalWorkersList"></div>
</div>
<!-- 외부 방문자 -->
<div id="externalVisitorsSection">
<h3 style="font-size: var(--text-lg); font-weight: 600; margin-bottom: 16px; color: var(--purple-600);">🚪 외부 방문자</h3>
<div id="externalVisitorsList"></div>
</div>
</div>
</div>
</body>
</html>