feat: 3-System 분리 프로젝트 초기 코드 작성

TK-FB(공장관리+신고)와 M-Project(부적합관리)를 3개 독립 시스템으로
분리하기 위한 전체 코드 구조 작성.
- SSO 인증 서비스 (bcrypt + pbkdf2 이중 해시 지원)
- System 1: 공장관리 (TK-FB 기반, 신고 코드 제거)
- System 2: 신고 (TK-FB에서 workIssue 코드 추출)
- System 3: 부적합관리 (M-Project 기반)
- Gateway 포털 (path-based 라우팅)
- 통합 docker-compose.yml 및 배포 스크립트

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-02-09 14:40:11 +09:00
commit 550633b89d
824 changed files with 1071683 additions and 0 deletions

View File

@@ -0,0 +1,687 @@
<!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/tbm.css?v=1">
<link rel="stylesheet" href="/css/mobile.css?v=1">
<link rel="icon" type="image/png" href="/img/favicon.png">
<!-- 최적화된 로딩: API 설정 → 앱 초기화 (병렬 컴포넌트 로딩) -->
<script src="/js/api-base.js"></script>
<script src="/js/app-init.js?v=2" defer></script>
<!-- instant.page: 링크 호버 시 페이지 프리로딩 -->
<script src="https://instant.page/5.2.0" type="module"></script>
</head>
<body>
<div class="work-report-container">
<!-- 네비게이션 바 -->
<div id="navbar-container"></div>
<!-- 메인 콘텐츠 -->
<main class="work-report-main">
<div class="tbm-container">
<!-- 페이지 헤더 -->
<div class="tbm-page-header">
<div class="tbm-title-section">
<h1 class="tbm-page-title">
<span class="tbm-page-title-icon">&#128736;</span>
TBM (Tool Box Meeting)
</h1>
<p class="tbm-page-description">아침 안전 회의 및 팀 구성 관리</p>
</div>
</div>
<!-- TBM 탭 메뉴 -->
<div class="tbm-tab-menu">
<button class="tbm-tab-btn active" data-tab="tbm-input" onclick="switchTbmTab('tbm-input')">
<span class="tbm-tab-icon">&#128221;</span>
TBM 입력
</button>
<button class="tbm-tab-btn" data-tab="tbm-manage" onclick="switchTbmTab('tbm-manage')">
<span class="tbm-tab-icon">&#128202;</span>
TBM 관리
</button>
</div>
<!-- TBM 입력 탭 -->
<div id="tbm-input-tab" class="tbm-tab-content active">
<div class="tbm-section">
<div class="tbm-section-header">
<h2 class="tbm-section-title">
<span>&#128197;</span>
오늘의 TBM
</h2>
<div class="tbm-section-actions">
<button class="tbm-btn tbm-btn-primary" onclick="openNewTbmModal()">
<span class="tbm-btn-icon">+</span>
새 TBM 시작
</button>
</div>
</div>
<div class="tbm-stats-bar">
<span class="tbm-stat-item">
<span class="tbm-stat-label">오늘 등록</span>
<span class="tbm-stat-value highlight" id="todayTotalSessions">0</span>
<span class="tbm-stat-label"></span>
</span>
<span class="tbm-stat-item">
<span class="tbm-stat-label">완료</span>
<span class="tbm-stat-value success" id="todayCompletedSessions">0</span>
<span class="tbm-stat-label"></span>
</span>
<span class="tbm-stat-item">
<span class="tbm-stat-label">진행중</span>
<span class="tbm-stat-value warning" id="todayActiveSessions">0</span>
<span class="tbm-stat-label"></span>
</span>
</div>
<div class="tbm-card-grid" id="todayTbmGrid">
<!-- 오늘의 TBM 세션 카드들이 여기에 동적으로 생성됩니다 -->
</div>
<!-- Empty State -->
<div class="tbm-empty-state" id="todayEmptyState" style="display: none;">
<div class="tbm-empty-icon">&#128203;</div>
<h3 class="tbm-empty-title">오늘 등록된 TBM이 없습니다</h3>
<p class="tbm-empty-description">"새 TBM 시작" 버튼을 눌러 오늘의 TBM을 시작해보세요.</p>
<button class="tbm-btn tbm-btn-primary" onclick="openNewTbmModal()">
<span class="tbm-btn-icon">+</span>
첫 TBM 시작하기
</button>
</div>
</div>
</div>
<!-- TBM 관리 탭 -->
<div id="tbm-manage-tab" class="tbm-tab-content">
<div class="tbm-section">
<div class="tbm-section-header">
<h2 class="tbm-section-title">
<span>&#128218;</span>
TBM 기록
</h2>
<div class="tbm-section-actions">
<button class="tbm-btn tbm-btn-secondary" onclick="loadMoreTbmDays()" id="loadMoreBtn">
더 보기
</button>
</div>
</div>
<div class="tbm-stats-bar">
<span class="tbm-stat-item">
<span class="tbm-stat-label"></span>
<span class="tbm-stat-value" id="totalSessions">0</span>
<span class="tbm-stat-label"></span>
</span>
<span class="tbm-stat-item">
<span class="tbm-stat-label">완료</span>
<span class="tbm-stat-value success" id="completedSessions">0</span>
<span class="tbm-stat-label"></span>
</span>
<span class="tbm-stat-item" id="viewModeIndicator" style="display: none;">
<span class="tbm-stat-value" id="viewModeText">내 TBM만</span>
</span>
</div>
<!-- 날짜별 그룹 컨테이너 -->
<div class="tbm-section-body" id="tbmDateGroupsContainer">
<!-- 날짜별 TBM 그룹이 여기에 동적으로 생성됩니다 -->
</div>
<!-- Empty State -->
<div class="tbm-empty-state" id="emptyState" style="display: none;">
<div class="tbm-empty-icon">&#128218;</div>
<h3 class="tbm-empty-title">등록된 TBM 세션이 없습니다</h3>
<p class="tbm-empty-description">TBM 입력 탭에서 새로운 TBM을 시작해보세요.</p>
</div>
</div>
</div>
</div>
</main>
<!-- TBM 생성/수정 모달 -->
<div id="tbmModal" class="tbm-modal-overlay" style="display: none;">
<div class="tbm-modal" style="max-width: 1000px;">
<div class="tbm-modal-header">
<h2 class="tbm-modal-title" id="modalTitle">
<span>&#128221;</span>
새 TBM 시작
</h2>
<button class="tbm-modal-close" onclick="closeTbmModal()">×</button>
</div>
<div class="tbm-modal-body">
<form id="tbmForm" onsubmit="event.preventDefault(); saveTbmSession();">
<input type="hidden" id="sessionId">
<!-- 고정 정보 섹션 -->
<div class="tbm-form-section">
<h3 class="tbm-form-section-title">
<span>&#128197;</span>
기본 정보
</h3>
<div class="tbm-form-row">
<div class="tbm-form-group">
<label class="tbm-form-label">TBM 날짜<span class="tbm-form-required">*</span></label>
<div class="tbm-form-input-readonly" id="sessionDateDisplay">-</div>
<input type="hidden" id="sessionDate">
</div>
<div class="tbm-form-group">
<label class="tbm-form-label">입력자<span class="tbm-form-required">*</span></label>
<div class="tbm-form-input-readonly" id="leaderName">-</div>
<input type="hidden" id="leaderId">
</div>
</div>
</div>
<!-- 작업자 및 작업 정보 섹션 -->
<div class="tbm-form-section">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<h3 class="tbm-form-section-title" style="margin: 0; border: 0; padding: 0;">
<span>&#128101;</span>
작업자 및 작업 정보
</h3>
<div style="display: flex; gap: 0.5rem;">
<button type="button" class="tbm-btn tbm-btn-secondary tbm-btn-sm" onclick="openBulkSettingModal()">
일괄 설정
</button>
<button type="button" class="tbm-btn tbm-btn-primary tbm-btn-sm" onclick="openWorkerSelectionModal()">
<span class="tbm-btn-icon">+</span>
작업자 선택
</button>
</div>
</div>
<!-- 작업자 카드 리스트 -->
<div id="workerTaskList" class="tbm-worker-list">
<!-- 작업자 카드들이 여기에 동적으로 추가됩니다 -->
<div class="tbm-empty-state" id="workerListEmpty" style="padding: 2rem; border: 2px dashed #d1d5db; border-radius: 10px;">
<div class="tbm-empty-icon">&#128101;</div>
<p class="tbm-empty-description" style="margin: 0;">작업자를 선택해주세요</p>
</div>
</div>
</div>
</form>
</div>
<div class="tbm-modal-footer">
<button type="button" class="tbm-btn tbm-btn-secondary" onclick="closeTbmModal()">취소</button>
<button type="button" class="tbm-btn tbm-btn-primary" onclick="saveTbmSession()">
<span class="tbm-btn-icon">&#10003;</span>
저장하기
</button>
</div>
</div>
</div>
<!-- 일괄 설정 모달 -->
<div id="bulkSettingModal" class="tbm-modal-overlay" style="display: none; z-index: 1001;">
<div class="tbm-modal" style="max-width: 700px;">
<div class="tbm-modal-header">
<h2 class="tbm-modal-title">
<span>&#9881;</span>
일괄 설정
</h2>
<button class="tbm-modal-close" onclick="closeBulkSettingModal()">×</button>
</div>
<div class="tbm-modal-body">
<div class="tbm-alert tbm-alert-info">
<span class="tbm-alert-icon">&#128161;</span>
<div class="tbm-alert-content">
<div class="tbm-alert-title">일괄 설정</div>
<div class="tbm-alert-text">선택한 작업자들의 <strong>첫 번째 작업 라인</strong>에 동일한 정보가 적용됩니다.</div>
</div>
</div>
<!-- 작업자 선택 -->
<div class="tbm-form-section">
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 0.75rem;">
<label class="tbm-form-label">적용할 작업자 선택<span class="tbm-form-required">*</span></label>
<div style="display: flex; gap: 0.25rem;">
<button type="button" class="tbm-btn tbm-btn-secondary tbm-btn-sm" onclick="selectAllForBulk()">전체</button>
<button type="button" class="tbm-btn tbm-btn-secondary tbm-btn-sm" onclick="deselectAllForBulk()">해제</button>
</div>
</div>
<div id="bulkWorkerSelection" class="tbm-worker-select-grid" style="max-height: 180px;">
<!-- 작업자 체크박스들이 여기에 생성됩니다 -->
</div>
</div>
<div class="tbm-form-section" style="border-top: 1px solid #e2e8f0; padding-top: 1.5rem;">
<h3 class="tbm-form-section-title" style="border: 0; padding: 0; margin-bottom: 1rem;">
<span>&#128736;</span>
적용할 작업 정보
</h3>
<div class="tbm-form-group">
<label class="tbm-form-label">프로젝트</label>
<button type="button" id="bulkProjectBtn" onclick="openBulkItemSelect('project')" class="tbm-select-btn">
프로젝트 선택
<span class="tbm-select-arrow">&#9660;</span>
</button>
<input type="hidden" id="bulkProjectId">
</div>
<div class="tbm-form-row">
<div class="tbm-form-group">
<label class="tbm-form-label">공정<span class="tbm-form-required">*</span></label>
<button type="button" id="bulkWorkTypeBtn" onclick="openBulkItemSelect('workType')" class="tbm-select-btn">
공정 선택
<span class="tbm-select-arrow">&#9660;</span>
</button>
<input type="hidden" id="bulkWorkTypeId">
</div>
<div class="tbm-form-group">
<label class="tbm-form-label">작업<span class="tbm-form-required">*</span></label>
<button type="button" id="bulkTaskBtn" onclick="openBulkItemSelect('task')" class="tbm-select-btn" disabled>
작업 선택
<span class="tbm-select-arrow">&#9660;</span>
</button>
<input type="hidden" id="bulkTaskId">
</div>
</div>
<div class="tbm-form-group">
<label class="tbm-form-label">작업장<span class="tbm-form-required">*</span></label>
<button type="button" id="bulkWorkplaceBtn" onclick="openBulkWorkplaceSelect()" class="tbm-select-btn">
작업장 선택
<span class="tbm-select-arrow">&#9660;</span>
</button>
<input type="hidden" id="bulkWorkplaceCategoryId">
<input type="hidden" id="bulkWorkplaceId">
</div>
</div>
</div>
<div class="tbm-modal-footer">
<button type="button" class="tbm-btn tbm-btn-secondary" onclick="closeBulkSettingModal()">취소</button>
<button type="button" class="tbm-btn tbm-btn-primary" onclick="applyBulkSettings()">
<span class="tbm-btn-icon">&#10003;</span>
선택한 작업자에 적용
</button>
</div>
</div>
</div>
<!-- 작업자 선택 모달 -->
<div id="workerSelectionModal" class="tbm-modal-overlay" style="display: none; z-index: 1001;">
<div class="tbm-modal" style="max-width: 800px;">
<div class="tbm-modal-header">
<h2 class="tbm-modal-title">
<span>&#128101;</span>
작업자 선택
</h2>
<button class="tbm-modal-close" onclick="closeWorkerSelectionModal()">×</button>
</div>
<div class="tbm-modal-body">
<div style="margin-bottom: 1rem; display: flex; gap: 0.5rem;">
<button type="button" class="tbm-btn tbm-btn-secondary tbm-btn-sm" onclick="selectAllWorkersInModal()">전체 선택</button>
<button type="button" class="tbm-btn tbm-btn-secondary tbm-btn-sm" onclick="deselectAllWorkersInModal()">전체 해제</button>
</div>
<div id="workerCardGrid" class="tbm-worker-select-grid">
<!-- 작업자 카드들이 여기에 생성됩니다 -->
</div>
</div>
<div class="tbm-modal-footer">
<button type="button" class="tbm-btn tbm-btn-secondary" onclick="closeWorkerSelectionModal()">취소</button>
<button type="button" class="tbm-btn tbm-btn-primary" onclick="confirmWorkerSelection()">
<span class="tbm-btn-icon">&#10003;</span>
선택 완료
</button>
</div>
</div>
</div>
<!-- 항목 선택 모달 (프로젝트/공정/작업 선택용) -->
<div id="itemSelectModal" class="tbm-modal-overlay" style="display: none; z-index: 1002;">
<div class="tbm-modal" style="max-width: 600px;">
<div class="tbm-modal-header">
<h2 class="tbm-modal-title" id="itemSelectModalTitle">항목 선택</h2>
<button class="tbm-modal-close" onclick="closeItemSelectModal()">×</button>
</div>
<div class="tbm-modal-body">
<div id="itemSelectList" class="tbm-item-list">
<!-- 선택 항목들이 여기에 생성됩니다 -->
</div>
</div>
<div class="tbm-modal-footer">
<button type="button" class="tbm-btn tbm-btn-secondary" onclick="closeItemSelectModal()">취소</button>
</div>
</div>
</div>
<!-- 작업장 선택 모달 (2단계: 공장 → 작업장) -->
<div id="workplaceSelectModal" class="tbm-modal-overlay" style="display: none; z-index: 1002;">
<div class="tbm-modal" style="max-width: 1000px;">
<div class="tbm-modal-header">
<h2 class="tbm-modal-title">
<span>&#127981;</span>
작업장 선택
</h2>
<button class="tbm-modal-close" onclick="closeWorkplaceSelectModal()">×</button>
</div>
<div class="tbm-modal-body">
<!-- 1단계: 공장 선택 -->
<div class="tbm-form-section">
<h3 class="tbm-form-section-title">
<span style="background: #3b82f6; color: white; width: 24px; height: 24px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 0.8rem;">1</span>
공장 선택
</h3>
<div id="categoryList" style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
<!-- 공장 카테고리 버튼들이 여기에 생성됩니다 -->
</div>
</div>
<!-- 2단계: 작업장 선택 (지도 + 리스트) -->
<div id="workplaceSelectionArea" style="display: none;">
<div class="tbm-form-section">
<h3 class="tbm-form-section-title">
<span style="background: #3b82f6; color: white; width: 24px; height: 24px; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-size: 0.8rem;">2</span>
작업장 선택
</h3>
<!-- 지도 기반 선택 영역 -->
<div id="layoutMapArea" style="display: none; margin-bottom: 1rem; padding: 1rem; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 10px;">
<div style="font-size: 0.875rem; color: #64748b; margin-bottom: 0.75rem;">
지도에서 작업장을 클릭하여 선택하세요
</div>
<div class="tbm-workplace-map-container">
<canvas id="workplaceMapCanvas"></canvas>
</div>
</div>
<!-- 리스트 기반 선택 (오류 대비용) -->
<div>
<div style="font-size: 0.875rem; color: #64748b; margin-bottom: 0.75rem; display: flex; align-items: center; justify-content: space-between;">
<span>리스트에서 선택 (지도 오류 시)</span>
<button type="button" class="tbm-btn tbm-btn-secondary tbm-btn-sm" onclick="toggleWorkplaceList()" id="toggleListBtn">
<span id="toggleListIcon">&#9660;</span>
리스트 보기
</button>
</div>
<div id="workplaceList" class="tbm-item-list" style="display: none; max-height: 250px;">
<div style="color: #94a3b8; text-align: center; padding: 2rem;">
공장을 먼저 선택해주세요
</div>
</div>
</div>
</div>
</div>
</div>
<div class="tbm-modal-footer">
<button type="button" class="tbm-btn tbm-btn-secondary" onclick="closeWorkplaceSelectModal()">취소</button>
<button type="button" class="tbm-btn tbm-btn-primary" onclick="confirmWorkplaceSelection()" id="confirmWorkplaceBtn" disabled>
<span class="tbm-btn-icon">&#10003;</span>
선택 완료
</button>
</div>
</div>
</div>
<!-- 팀 구성 모달 -->
<div id="teamModal" class="tbm-modal-overlay" style="display: none;">
<div class="tbm-modal" style="max-width: 900px;">
<div class="tbm-modal-header">
<h2 class="tbm-modal-title">
<span>&#128101;</span>
팀 구성
</h2>
<button class="tbm-modal-close" onclick="closeTeamModal()">×</button>
</div>
<div class="tbm-modal-body">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
<h3 class="tbm-form-section-title" style="margin: 0; border: 0; padding: 0;">작업자 선택</h3>
<div style="display: flex; gap: 0.5rem;">
<button class="tbm-btn tbm-btn-secondary tbm-btn-sm" onclick="selectAllWorkers()">전체 선택</button>
<button class="tbm-btn tbm-btn-secondary tbm-btn-sm" onclick="deselectAllWorkers()">전체 해제</button>
</div>
</div>
<div id="workerSelectionGrid" class="tbm-worker-select-grid">
<!-- 작업자 체크박스 목록이 여기에 생성됩니다 -->
</div>
<div style="margin-top: 1.5rem;">
<h3 class="tbm-form-section-title">
선택된 팀원 <span id="selectedCount" style="color: #3b82f6;">0</span>
</h3>
<div id="selectedWorkersList" style="display: flex; flex-wrap: wrap; gap: 0.5rem; min-height: 50px; padding: 0.75rem; background: #f8fafc; border-radius: 10px; border: 1px solid #e2e8f0;">
<p style="margin: 0; color: #94a3b8; font-size: 0.875rem;">작업자를 선택해주세요</p>
</div>
</div>
</div>
<div class="tbm-modal-footer">
<button type="button" class="tbm-btn tbm-btn-secondary" onclick="closeTeamModal()">취소</button>
<button type="button" class="tbm-btn tbm-btn-primary" onclick="saveTeamComposition()">
<span class="tbm-btn-icon">&#10003;</span>
팀 구성 완료
</button>
</div>
</div>
</div>
<!-- 안전 체크리스트 모달 -->
<div id="safetyModal" class="tbm-modal-overlay" style="display: none;">
<div class="tbm-modal" style="max-width: 700px;">
<div class="tbm-modal-header">
<h2 class="tbm-modal-title">
<span>&#128737;</span>
안전 체크리스트
</h2>
<button class="tbm-modal-close" onclick="closeSafetyModal()">×</button>
</div>
<div class="tbm-modal-body">
<div id="safetyChecklistContainer" class="tbm-safety-list">
<!-- 안전 체크리스트가 여기에 생성됩니다 -->
</div>
</div>
<div class="tbm-modal-footer">
<button type="button" class="tbm-btn tbm-btn-secondary" onclick="closeSafetyModal()">취소</button>
<button type="button" class="tbm-btn tbm-btn-success" onclick="saveSafetyChecklist()">
<span class="tbm-btn-icon">&#10003;</span>
안전 체크 완료
</button>
</div>
</div>
</div>
<!-- TBM 완료 모달 -->
<div id="completeModal" class="tbm-modal-overlay" style="display: none;">
<div class="tbm-modal" style="max-width: 500px;">
<div class="tbm-modal-header">
<h2 class="tbm-modal-title">
<span>&#10003;</span>
TBM 완료
</h2>
<button class="tbm-modal-close" onclick="closeCompleteModal()">×</button>
</div>
<div class="tbm-modal-body">
<div class="tbm-alert tbm-alert-warning">
<span class="tbm-alert-icon">&#9888;</span>
<div class="tbm-alert-content">
<div class="tbm-alert-title">TBM 완료 확인</div>
<div class="tbm-alert-text">완료 후에는 수정할 수 없습니다.</div>
</div>
</div>
<div class="tbm-form-group" style="margin-top: 1.5rem;">
<label class="tbm-form-label">종료 시간</label>
<input type="time" id="endTime" class="tbm-form-input">
</div>
</div>
<div class="tbm-modal-footer">
<button type="button" class="tbm-btn tbm-btn-secondary" onclick="closeCompleteModal()">취소</button>
<button type="button" class="tbm-btn tbm-btn-success" onclick="completeTbmSession()">
<span class="tbm-btn-icon">&#10003;</span>
완료
</button>
</div>
</div>
</div>
<!-- 작업 인계 모달 -->
<div id="handoverModal" class="tbm-modal-overlay" style="display: none;">
<div class="tbm-modal" style="max-width: 600px;">
<div class="tbm-modal-header">
<h2 class="tbm-modal-title">
<span>&#128073;</span>
작업 인계
</h2>
<button class="tbm-modal-close" onclick="closeHandoverModal()">×</button>
</div>
<div class="tbm-modal-body">
<form id="handoverForm">
<input type="hidden" id="handoverSessionId">
<div class="tbm-form-group">
<label class="tbm-form-label">인계 사유<span class="tbm-form-required">*</span></label>
<select id="handoverReason" class="tbm-form-input" 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="tbm-form-group">
<label class="tbm-form-label">인수자 (다음 팀장)<span class="tbm-form-required">*</span></label>
<select id="toLeaderId" class="tbm-form-input" required>
<option value="">인수자 선택...</option>
</select>
</div>
<div class="tbm-form-row">
<div class="tbm-form-group">
<label class="tbm-form-label">인계 날짜<span class="tbm-form-required">*</span></label>
<input type="date" id="handoverDate" class="tbm-form-input" required>
</div>
<div class="tbm-form-group">
<label class="tbm-form-label">인계 시간</label>
<input type="time" id="handoverTime" class="tbm-form-input">
</div>
</div>
<div class="tbm-form-group">
<label class="tbm-form-label">인계 내용</label>
<textarea id="handoverNotes" class="tbm-form-input" rows="4" placeholder="인수자에게 전달할 내용을 입력하세요" style="resize: vertical;"></textarea>
</div>
<div class="tbm-form-group">
<label class="tbm-form-label" style="margin-bottom: 0.75rem;">인계할 팀원 선택</label>
<div id="handoverTeamList" style="max-height: 200px; overflow-y: auto; border: 1px solid #e2e8f0; border-radius: 10px; padding: 0.75rem; background: #f8fafc;">
<!-- 팀원 체크박스 목록 -->
</div>
</div>
</form>
</div>
<div class="tbm-modal-footer">
<button type="button" class="tbm-btn tbm-btn-secondary" onclick="closeHandoverModal()">취소</button>
<button type="button" class="tbm-btn tbm-btn-primary" onclick="saveHandover()">
<span class="tbm-btn-icon">&#128073;</span>
인계 요청
</button>
</div>
</div>
</div>
<!-- TBM 상세보기 모달 -->
<div id="detailModal" class="tbm-modal-overlay" style="display: none;">
<div class="tbm-modal" style="max-width: 900px;">
<div class="tbm-modal-header">
<h2 class="tbm-modal-title">
<span>&#128203;</span>
TBM 상세 정보
</h2>
<button class="tbm-modal-close" onclick="closeDetailModal()">×</button>
</div>
<div class="tbm-modal-body">
<!-- 세션 기본 정보 -->
<div class="tbm-form-section">
<h3 class="tbm-form-section-title">
<span>&#128197;</span>
기본 정보
</h3>
<div id="detailBasicInfo" style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 1rem;">
<!-- 동적 생성 -->
</div>
</div>
<!-- 팀 구성 -->
<div class="tbm-form-section">
<h3 class="tbm-form-section-title">
<span>&#128101;</span>
팀 구성
</h3>
<div id="detailTeamMembers" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 0.75rem;">
<!-- 동적 생성 -->
</div>
</div>
<!-- 안전 체크 -->
<div class="tbm-form-section">
<h3 class="tbm-form-section-title">
<span>&#128737;</span>
안전 체크리스트
</h3>
<div id="detailSafetyChecks">
<!-- 동적 생성 -->
</div>
</div>
</div>
<div class="tbm-modal-footer">
<button type="button" class="tbm-btn tbm-btn-secondary" onclick="closeDetailModal()">닫기</button>
</div>
</div>
</div>
<!-- 토스트 알림 -->
<div class="toast-container" id="toastContainer"></div>
</div>
<!-- TBM 모듈 (리팩토링된 구조) -->
<script src="/js/tbm/state.js?v=1"></script>
<script src="/js/tbm/utils.js?v=1"></script>
<script src="/js/tbm/api.js?v=1"></script>
<!-- 기존 UI 로직 (점진적 마이그레이션) -->
<script type="module" src="/js/tbm.js?v=4"></script>
<!-- 모바일 하단 네비게이션 -->
<div id="mobile-nav-container"></div>
<script>
if (window.innerWidth <= 768) {
fetch('/components/mobile-nav.html')
.then(r => r.text())
.then(html => {
document.getElementById('mobile-nav-container').innerHTML = html;
const scripts = document.getElementById('mobile-nav-container').querySelectorAll('script');
scripts.forEach(s => eval(s.textContent));
});
}
</script>
</body>
</html>