feat: 페이지 구조 재구성 및 사이드바 네비게이션 구현

- 페이지 폴더 재구성: safety/, attendance/ 폴더 신규 생성
  - work/ → safety/: 이슈 신고, 출입 신청 관련 페이지 이동
  - common/ → attendance/: 근태/휴가 관련 페이지 이동
  - admin/ 정리: safety-* 파일들을 safety/로 이동

- 사이드바 네비게이션 메뉴 구현
  - 카테고리별 메뉴: 작업관리, 안전관리, 근태관리, 시스템관리
  - 접기/펼치기 기능 및 상태 저장
  - 관리자 전용 메뉴 자동 표시/숨김

- 날씨 API 연동 (기상청 단기예보)
  - TBM 및 navbar에 현재 날씨 표시
  - weatherService.js 추가

- 안전 체크리스트 확장
  - 기본/날씨별/작업별 체크 유형 추가
  - checklist-manage.html 페이지 추가

- 이슈 신고 시스템 구현
  - workIssueController, workIssueModel, workIssueRoutes 추가

- DB 마이그레이션 파일 추가 (실행 대기)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-02-02 14:27:22 +09:00
parent b6485e3140
commit 74d3a78aa3
116 changed files with 23117 additions and 294 deletions

View File

@@ -0,0 +1,650 @@
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>TBM 관리 | (주)테크니컬코리아</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>
.date-group {
margin-bottom: 2rem;
}
.date-group-header {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 0.75rem 1rem;
background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
border-radius: 0.5rem;
margin-bottom: 1rem;
border-left: 4px solid var(--primary-500);
}
.date-group-header.today {
background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
border-left-color: #3b82f6;
}
.date-group-date {
font-size: 1.1rem;
font-weight: 700;
color: #1f2937;
}
.date-group-day {
font-size: 0.875rem;
color: #6b7280;
padding: 0.25rem 0.5rem;
background: white;
border-radius: 0.25rem;
}
.date-group-count {
margin-left: auto;
font-size: 0.875rem;
color: #6b7280;
}
.date-group-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 1rem;
}
@media (max-width: 768px) {
.date-group-grid {
grid-template-columns: 1fr;
}
}
</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">
<span class="title-icon">🛠️</span>
TBM (Tool Box Meeting)
</h1>
<p class="page-description">아침 안전 회의 및 팀 구성 관리</p>
</div>
<div class="page-actions" id="headerActions">
<!-- 탭에 따라 동적으로 변경됩니다 -->
</div>
</div>
<!-- TBM 탭 -->
<div class="code-tabs">
<button class="tab-btn active" data-tab="tbm-input" onclick="switchTbmTab('tbm-input')">
<span class="tab-icon"></span>
TBM 입력
</button>
<button class="tab-btn" data-tab="tbm-manage" onclick="switchTbmTab('tbm-manage')">
<span class="tab-icon">📋</span>
TBM 관리
</button>
</div>
<!-- TBM 입력 탭 -->
<div id="tbm-input-tab" class="code-tab-content active">
<div class="code-section">
<div class="section-header">
<h2 class="section-title">
<span class="section-icon">🌅</span>
오늘의 TBM
</h2>
<div class="section-actions">
<button class="btn btn-primary" onclick="openNewTbmModal()">
<span class="btn-icon"></span>
새 TBM 시작
</button>
</div>
</div>
<div class="code-stats">
<span class="stat-item">
<span class="stat-icon">📋</span>
오늘 등록 <span id="todayTotalSessions">0</span>
</span>
<span class="stat-item">
<span class="stat-icon"></span>
완료 <span id="todayCompletedSessions">0</span>
</span>
<span class="stat-item">
<span class="stat-icon"></span>
진행중 <span id="todayActiveSessions">0</span>
</span>
</div>
<div class="code-grid" id="todayTbmGrid">
<!-- 오늘의 TBM 세션 카드들이 여기에 동적으로 생성됩니다 -->
</div>
<!-- Empty State -->
<div class="empty-state" id="todayEmptyState" style="display: none;">
<div class="empty-icon">🛠️</div>
<h3>오늘 등록된 TBM이 없습니다</h3>
<p>"새 TBM 시작" 버튼을 눌러 오늘의 TBM을 시작해보세요.</p>
<button class="btn btn-primary" onclick="openNewTbmModal()">
첫 TBM 시작하기
</button>
</div>
</div>
</div>
<!-- TBM 관리 탭 -->
<div id="tbm-manage-tab" class="code-tab-content">
<div class="code-section">
<div class="section-header">
<h2 class="section-title">
<span class="section-icon">📚</span>
TBM 기록
</h2>
<div class="section-actions">
<button class="btn btn-secondary" onclick="loadMoreTbmDays()" id="loadMoreBtn">
<span class="btn-icon">📅</span>
더 보기
</button>
</div>
</div>
<div class="code-stats">
<span class="stat-item">
<span class="stat-icon">📋</span>
<span id="totalSessions">0</span>
</span>
<span class="stat-item">
<span class="stat-icon"></span>
완료 <span id="completedSessions">0</span>
</span>
<span class="stat-item" id="viewModeIndicator" style="display: none;">
<span class="stat-icon">👤</span>
<span id="viewModeText">내 TBM만</span>
</span>
</div>
<!-- 날짜별 그룹 컨테이너 -->
<div id="tbmDateGroupsContainer">
<!-- 날짜별 TBM 그룹이 여기에 동적으로 생성됩니다 -->
</div>
<!-- Empty State -->
<div class="empty-state" id="emptyState" style="display: none;">
<div class="empty-icon">🛠️</div>
<h3>등록된 TBM 세션이 없습니다</h3>
<p>TBM 입력 탭에서 새로운 TBM을 시작해보세요.</p>
</div>
</div>
</div>
</div>
</main>
<!-- TBM 생성/수정 모달 -->
<div id="tbmModal" class="modal-overlay" style="display: none;">
<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>
</div>
<div class="modal-body">
<form id="tbmForm" onsubmit="event.preventDefault(); saveTbmSession();">
<input type="hidden" id="sessionId">
<!-- 고정 정보 섹션 -->
<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-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 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>
</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>
</div>
<!-- 팀 구성 모달 -->
<div id="teamModal" class="modal-overlay" style="display: none;">
<div class="modal-container" style="max-width: 900px;">
<div class="modal-header">
<h2>팀 구성</h2>
<button class="modal-close-btn" onclick="closeTeamModal()">×</button>
</div>
<div class="modal-body">
<div class="section-header" style="margin-bottom: 1rem;">
<h3 style="font-size: 1rem; font-weight: 600;">작업자 선택</h3>
<div>
<button class="btn btn-sm btn-secondary" onclick="selectAllWorkers()">전체 선택</button>
<button class="btn btn-sm btn-secondary" onclick="deselectAllWorkers()">전체 해제</button>
</div>
</div>
<div id="workerSelectionGrid" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 0.75rem; max-height: 400px; overflow-y: auto; padding: 0.5rem; border: 1px solid #e5e7eb; border-radius: 0.5rem;">
<!-- 작업자 체크박스 목록이 여기에 생성됩니다 -->
</div>
<div style="margin-top: 1.5rem;">
<h3 style="font-size: 1rem; font-weight: 600; margin-bottom: 0.75rem;">선택된 팀원 <span id="selectedCount">0</span></h3>
<div id="selectedWorkersList" style="display: flex; flex-wrap: wrap; gap: 0.5rem; min-height: 50px; padding: 0.75rem; background: #f9fafb; border-radius: 0.5rem;">
<p style="margin: 0; color: #9ca3af; font-size: 0.875rem;">작업자를 선택해주세요</p>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeTeamModal()">취소</button>
<button type="button" class="btn btn-primary" onclick="saveTeamComposition()">
👥 팀 구성 완료
</button>
</div>
</div>
</div>
<!-- 안전 체크리스트 모달 -->
<div id="safetyModal" class="modal-overlay" style="display: none;">
<div class="modal-container" style="max-width: 700px;">
<div class="modal-header">
<h2>안전 체크리스트</h2>
<button class="modal-close-btn" onclick="closeSafetyModal()">×</button>
</div>
<div class="modal-body">
<div id="safetyChecklistContainer" style="max-height: 500px; overflow-y: auto;">
<!-- 안전 체크리스트가 여기에 생성됩니다 -->
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeSafetyModal()">취소</button>
<button type="button" class="btn btn-primary" onclick="saveSafetyChecklist()">
✅ 안전 체크 완료
</button>
</div>
</div>
</div>
<!-- TBM 완료 모달 -->
<div id="completeModal" class="modal-overlay" style="display: none;">
<div class="modal-container" style="max-width: 500px;">
<div class="modal-header">
<h2>TBM 완료</h2>
<button class="modal-close-btn" onclick="closeCompleteModal()">×</button>
</div>
<div class="modal-body">
<p style="margin-bottom: 1rem;">이 TBM 세션을 완료 처리하시겠습니까?</p>
<p style="color: #6b7280; font-size: 0.875rem;">완료 후에는 수정할 수 없습니다.</p>
<div class="form-group" style="margin-top: 1.5rem;">
<label class="form-label">종료 시간</label>
<input type="time" id="endTime" class="form-control">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeCompleteModal()">취소</button>
<button type="button" class="btn btn-primary" onclick="completeTbmSession()">
✅ 완료
</button>
</div>
</div>
</div>
<!-- 작업 인계 모달 -->
<div id="handoverModal" class="modal-overlay" style="display: none;">
<div class="modal-container" style="max-width: 600px;">
<div class="modal-header">
<h2>작업 인계</h2>
<button class="modal-close-btn" onclick="closeHandoverModal()">×</button>
</div>
<div class="modal-body">
<form id="handoverForm">
<input type="hidden" id="handoverSessionId">
<div class="form-group">
<label class="form-label">인계 사유 *</label>
<select id="handoverReason" class="form-control" required>
<option value="">사유 선택...</option>
<option value="half_day">반차</option>
<option value="early_leave">조퇴</option>
<option value="emergency">긴급 상황</option>
<option value="other">기타</option>
</select>
</div>
<div class="form-group">
<label class="form-label">인수자 (다음 팀장) *</label>
<select id="toLeaderId" class="form-control" required>
<option value="">인수자 선택...</option>
</select>
</div>
<div class="form-row">
<div class="form-group">
<label class="form-label">인계 날짜 *</label>
<input type="date" id="handoverDate" class="form-control" required>
</div>
<div class="form-group">
<label class="form-label">인계 시간</label>
<input type="time" id="handoverTime" class="form-control">
</div>
</div>
<div class="form-group">
<label class="form-label">인계 내용</label>
<textarea id="handoverNotes" class="form-control" rows="4" placeholder="인수자에게 전달할 내용을 입력하세요"></textarea>
</div>
<div class="form-group">
<label class="form-label" style="margin-bottom: 0.75rem; display: block;">인계할 팀원 선택</label>
<div id="handoverTeamList" style="max-height: 200px; overflow-y: auto; border: 1px solid #e5e7eb; border-radius: 0.5rem; padding: 0.5rem;">
<!-- 팀원 체크박스 목록 -->
</div>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeHandoverModal()">취소</button>
<button type="button" class="btn btn-primary" onclick="saveHandover()">
📤 인계 요청
</button>
</div>
</div>
</div>
<!-- TBM 상세보기 모달 -->
<div id="detailModal" class="modal-overlay" style="display: none;">
<div class="modal-container" style="max-width: 900px;">
<div class="modal-header">
<h2>TBM 상세 정보</h2>
<button class="modal-close-btn" onclick="closeDetailModal()">×</button>
</div>
<div class="modal-body" style="max-height: 70vh; overflow-y: auto;">
<!-- 세션 기본 정보 -->
<div class="section" style="margin-bottom: 1.5rem;">
<h3 style="font-size: 1rem; font-weight: 600; margin-bottom: 1rem; color: #374151;">기본 정보</h3>
<div id="detailBasicInfo" style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 0.75rem;">
<!-- 동적 생성 -->
</div>
</div>
<!-- 팀 구성 -->
<div class="section" style="margin-bottom: 1.5rem;">
<h3 style="font-size: 1rem; font-weight: 600; margin-bottom: 1rem; color: #374151;">팀 구성</h3>
<div id="detailTeamMembers" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 0.75rem;">
<!-- 동적 생성 -->
</div>
</div>
<!-- 안전 체크 -->
<div class="section">
<h3 style="font-size: 1rem; font-weight: 600; margin-bottom: 1rem; color: #374151;">안전 체크리스트</h3>
<div id="detailSafetyChecks">
<!-- 동적 생성 -->
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeDetailModal()">닫기</button>
</div>
</div>
</div>
<!-- 토스트 알림 -->
<div class="toast-container" id="toastContainer"></div>
</div>
<script type="module" src="/js/load-navbar.js?v=5"></script>
<script type="module" src="/js/tbm.js?v=3"></script>
</body>
</html>