- 실시간 작업장 현황을 지도로 시각화 - 작업장 관리 페이지에서 정의한 구역 정보 활용 - 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>
372 lines
12 KiB
HTML
372 lines
12 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/common.css?v=2">
|
|
<link rel="stylesheet" href="/css/project-management.css?v=3">
|
|
<link rel="icon" type="image/png" href="/img/favicon.png">
|
|
<script src="/js/auth-check.js?v=1" defer></script>
|
|
<script type="module" src="/js/api-config.js?v=3"></script>
|
|
<style>
|
|
.visit-form-container {
|
|
max-width: 800px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.form-group {
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.form-group label {
|
|
display: block;
|
|
font-weight: 600;
|
|
margin-bottom: 8px;
|
|
color: var(--gray-700);
|
|
}
|
|
|
|
.form-group input,
|
|
.form-group select,
|
|
.form-group textarea {
|
|
width: 100%;
|
|
padding: 12px;
|
|
border: 1px solid var(--gray-300);
|
|
border-radius: var(--radius-md);
|
|
font-size: var(--text-base);
|
|
}
|
|
|
|
.form-group input:focus,
|
|
.form-group select:focus,
|
|
.form-group textarea:focus {
|
|
outline: none;
|
|
border-color: var(--primary-500);
|
|
box-shadow: 0 0 0 3px rgba(14, 165, 233, 0.1);
|
|
}
|
|
|
|
.form-group textarea {
|
|
min-height: 100px;
|
|
resize: vertical;
|
|
}
|
|
|
|
.form-row {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 16px;
|
|
}
|
|
|
|
.workplace-selection {
|
|
padding: 20px;
|
|
background: var(--gray-50);
|
|
border-radius: var(--radius-md);
|
|
border: 2px dashed var(--gray-300);
|
|
min-height: 150px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: pointer;
|
|
transition: all var(--transition-fast);
|
|
}
|
|
|
|
.workplace-selection:hover {
|
|
border-color: var(--primary-500);
|
|
background: var(--primary-50);
|
|
}
|
|
|
|
.workplace-selection.selected {
|
|
border-color: var(--primary-500);
|
|
border-style: solid;
|
|
background: white;
|
|
}
|
|
|
|
.workplace-selection .icon {
|
|
font-size: 48px;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.workplace-selection .text {
|
|
font-size: var(--text-base);
|
|
color: var(--gray-600);
|
|
text-align: center;
|
|
}
|
|
|
|
.workplace-selection.selected .text {
|
|
color: var(--primary-600);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.form-actions {
|
|
display: flex;
|
|
gap: 12px;
|
|
justify-content: flex-end;
|
|
margin-top: 32px;
|
|
}
|
|
|
|
/* 지도 모달 스타일 */
|
|
.map-modal {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
background: rgba(0, 0, 0, 0.7);
|
|
z-index: 1000;
|
|
align-items: center;
|
|
justify-content: center;
|
|
}
|
|
|
|
.map-modal-content {
|
|
background: white;
|
|
border-radius: var(--radius-lg);
|
|
max-width: 90vw;
|
|
max-height: 90vh;
|
|
overflow: auto;
|
|
padding: 32px;
|
|
box-shadow: var(--shadow-2xl);
|
|
}
|
|
|
|
.map-canvas-container {
|
|
position: relative;
|
|
margin-top: 20px;
|
|
border: 2px solid var(--gray-300);
|
|
border-radius: var(--radius-md);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.map-canvas {
|
|
cursor: pointer;
|
|
display: block;
|
|
max-width: 100%;
|
|
}
|
|
|
|
.workplace-info-card {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 16px;
|
|
background: var(--primary-50);
|
|
border: 2px solid var(--primary-200);
|
|
border-radius: var(--radius-md);
|
|
margin-top: 12px;
|
|
}
|
|
|
|
.workplace-info-card .icon {
|
|
font-size: 32px;
|
|
}
|
|
|
|
.workplace-info-card .details {
|
|
flex: 1;
|
|
}
|
|
|
|
.workplace-info-card .name {
|
|
font-size: var(--text-lg);
|
|
font-weight: 700;
|
|
color: var(--primary-700);
|
|
}
|
|
|
|
.workplace-info-card .category {
|
|
font-size: var(--text-sm);
|
|
color: var(--gray-600);
|
|
margin-top: 4px;
|
|
}
|
|
|
|
.my-requests-section {
|
|
margin-top: 48px;
|
|
}
|
|
|
|
.request-card {
|
|
padding: 20px;
|
|
background: white;
|
|
border: 1px solid var(--gray-200);
|
|
border-radius: var(--radius-md);
|
|
margin-bottom: 16px;
|
|
box-shadow: var(--shadow-sm);
|
|
}
|
|
|
|
.request-card-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.request-status {
|
|
padding: 6px 12px;
|
|
border-radius: var(--radius-full);
|
|
font-size: var(--text-sm);
|
|
font-weight: 600;
|
|
}
|
|
|
|
.request-status.pending {
|
|
background: var(--yellow-100);
|
|
color: var(--yellow-700);
|
|
}
|
|
|
|
.request-status.approved {
|
|
background: var(--green-100);
|
|
color: var(--green-700);
|
|
}
|
|
|
|
.request-status.rejected {
|
|
background: var(--red-100);
|
|
color: var(--red-700);
|
|
}
|
|
|
|
.request-status.training_completed {
|
|
background: var(--blue-100);
|
|
color: var(--blue-700);
|
|
}
|
|
|
|
.request-info {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 12px;
|
|
color: var(--gray-700);
|
|
}
|
|
|
|
.info-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.info-label {
|
|
font-size: var(--text-sm);
|
|
color: var(--gray-500);
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.info-value {
|
|
font-weight: 600;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="work-report-container">
|
|
<!-- 네비게이션 바 -->
|
|
<div id="navbar-container"></div>
|
|
|
|
<!-- 메인 콘텐츠 -->
|
|
<main class="work-report-main">
|
|
<div class="dashboard-main">
|
|
<div class="page-header">
|
|
<div class="page-title-section">
|
|
<h1 class="page-title">출입 신청</h1>
|
|
<p class="page-description">작업장 출입 및 안전교육 신청</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 출입 신청 폼 -->
|
|
<div class="visit-form-container">
|
|
<div class="code-section">
|
|
<h2 class="section-title">출입 정보 입력</h2>
|
|
|
|
<form id="visitRequestForm">
|
|
<!-- 방문자 정보 -->
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label for="visitorCompany">방문자 소속 *</label>
|
|
<input type="text" id="visitorCompany" placeholder="예: (주)협력업체, 일용직 등" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="visitorCount">방문 인원 *</label>
|
|
<input type="number" id="visitorCount" value="1" min="1" required>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 작업장 선택 -->
|
|
<div class="form-group">
|
|
<label>방문 작업장 *</label>
|
|
<div id="workplaceSelection" class="workplace-selection" onclick="openMapModal()">
|
|
<div class="icon">📍</div>
|
|
<div class="text">지도에서 작업장을 선택하세요</div>
|
|
</div>
|
|
<div id="selectedWorkplaceInfo" style="display: none;"></div>
|
|
</div>
|
|
|
|
<!-- 방문 일시 -->
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label for="visitDate">방문 날짜 *</label>
|
|
<input type="date" id="visitDate" required>
|
|
</div>
|
|
<div class="form-group">
|
|
<label for="visitTime">방문 시간 *</label>
|
|
<input type="time" id="visitTime" required>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 방문 목적 -->
|
|
<div class="form-group">
|
|
<label for="visitPurpose">방문 목적 *</label>
|
|
<select id="visitPurpose" required>
|
|
<option value="">선택하세요</option>
|
|
<!-- 동적으로 로드됨 -->
|
|
</select>
|
|
</div>
|
|
|
|
<!-- 비고 -->
|
|
<div class="form-group">
|
|
<label for="notes">비고 (선택)</label>
|
|
<textarea id="notes" placeholder="추가 전달 사항이 있다면 입력하세요"></textarea>
|
|
</div>
|
|
|
|
<!-- 버튼 -->
|
|
<div class="form-actions">
|
|
<button type="button" class="btn btn-secondary" onclick="resetForm()">초기화</button>
|
|
<button type="submit" class="btn btn-primary">출입 신청 및 안전교육 신청</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- 내 신청 목록 -->
|
|
<div class="my-requests-section">
|
|
<div class="code-section">
|
|
<h2 class="section-title">내 출입 신청 현황</h2>
|
|
<div id="myRequestsList">
|
|
<!-- 동적으로 로드됨 -->
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- 작업장 지도 모달 -->
|
|
<div id="mapModal" class="map-modal">
|
|
<div class="map-modal-content">
|
|
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px;">
|
|
<h2 style="margin: 0;">작업장 선택</h2>
|
|
<button class="btn btn-secondary" onclick="closeMapModal()">닫기</button>
|
|
</div>
|
|
|
|
<!-- 구역(공장) 선택 -->
|
|
<div class="form-group">
|
|
<label for="categorySelect">구역(공장) 선택</label>
|
|
<select id="categorySelect" onchange="loadWorkplaceMap()">
|
|
<option value="">구역을 선택하세요</option>
|
|
<!-- 동적으로 로드됨 -->
|
|
</select>
|
|
</div>
|
|
|
|
<!-- 지도 캔버스 -->
|
|
<div id="mapCanvasContainer" style="display: none;">
|
|
<div class="map-canvas-container">
|
|
<canvas id="workplaceMapCanvas" class="map-canvas"></canvas>
|
|
</div>
|
|
<p style="margin-top: 12px; color: var(--gray-600); font-size: var(--text-sm);">
|
|
지도에서 방문할 작업장을 클릭하세요
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Scripts -->
|
|
<script type="module" src="/js/load-navbar.js?v=5"></script>
|
|
<script src="/js/visit-request.js"></script>
|
|
</body>
|
|
</html>
|