카카오톡 인앱 WebView는 서브도메인 간 쿠키를 공유하지 않아 tkds에서 로그인 후 tkfb로 리다이렉트 시 인증이 풀리는 문제. - sso-relay.js: URL hash의 _sso= 토큰을 로컬 쿠키+localStorage로 설정 - gateway dashboard: 로그인 후 redirect URL에 #_sso=<token> 추가 - 전 서비스 HTML: core JS 직전에 sso-relay.js 로드 (81개 파일) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
314 lines
15 KiB
HTML
314 lines
15 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ko">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>구역 상세 - TK 공장관리</title>
|
||
<link rel="stylesheet" href="/css/zone-detail.css?v=2026031401">
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||
<link rel="stylesheet" href="/static/css/tkfb.css?v=2026040103">
|
||
</head>
|
||
<body class="bg-gray-50">
|
||
<header class="bg-orange-700 text-white sticky top-0 z-50">
|
||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||
<div class="flex justify-between items-center h-14">
|
||
<div class="flex items-center gap-3">
|
||
<button id="mobileMenuBtn" class="lg:hidden text-orange-200 hover:text-white"><i class="fas fa-bars text-xl"></i></button>
|
||
<i class="fas fa-industry text-xl text-orange-200"></i>
|
||
<h1 class="text-lg font-semibold">TK 공장관리</h1>
|
||
</div>
|
||
<div class="flex items-center gap-4">
|
||
<span id="headerUserName" class="text-sm hidden sm:block">-</span>
|
||
<div id="headerUserAvatar" class="w-8 h-8 bg-orange-600 rounded-full flex items-center justify-center text-sm font-bold">-</div>
|
||
<button onclick="doLogout()" class="text-orange-200 hover:text-white" title="로그아웃"><i class="fas fa-sign-out-alt"></i></button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</header>
|
||
<div id="mobileOverlay" class="hidden fixed inset-0 bg-black/50 z-30 lg:hidden"></div>
|
||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4 fade-in">
|
||
<div class="flex gap-6">
|
||
<nav id="sideNav" class="hidden lg:flex flex-col gap-1 w-52 flex-shrink-0 pt-2 fixed lg:static z-40 bg-white lg:bg-transparent p-4 lg:p-0 rounded-lg lg:rounded-none shadow-lg lg:shadow-none top-14 left-0 bottom-0 overflow-y-auto"></nav>
|
||
<div class="flex-1 min-w-0">
|
||
<!-- 페이지 헤더 -->
|
||
<div class="zone-header">
|
||
<div class="zone-header-left">
|
||
<button class="btn btn-back" onclick="goBack()">
|
||
<span>←</span> 돌아가기
|
||
</button>
|
||
</div>
|
||
<div class="zone-header-center">
|
||
<h1 id="zoneName" class="zone-title">작업장</h1>
|
||
<p id="zoneCategory" class="zone-subtitle">공장</p>
|
||
</div>
|
||
<div class="zone-header-right">
|
||
<span id="currentDate" class="current-date"></span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 요약 카드 -->
|
||
<div id="summaryCards" class="summary-cards">
|
||
<!-- JS에서 렌더링 -->
|
||
</div>
|
||
|
||
<!-- 탭 네비게이션 -->
|
||
<div class="tab-navigation">
|
||
<button class="tab-btn active" data-tab="map" onclick="switchTab('map')">
|
||
🗺️ 구역 현황
|
||
</button>
|
||
<button class="tab-btn" data-tab="issues" onclick="switchTab('issues')">
|
||
🚨 안전신고/부적합
|
||
</button>
|
||
<button class="tab-btn" data-tab="equipment" onclick="switchTab('equipment')">
|
||
⚙️ 설비/수리
|
||
</button>
|
||
<button class="tab-btn" data-tab="visits" onclick="switchTab('visits')">
|
||
🚶 출입현황
|
||
</button>
|
||
<button class="tab-btn" data-tab="tbm" onclick="switchTab('tbm')">
|
||
📋 TBM
|
||
</button>
|
||
<button class="tab-btn" data-tab="patrol" onclick="switchTab('patrol')">
|
||
🔍 순회점검
|
||
</button>
|
||
</div>
|
||
|
||
<!-- 탭 콘텐츠 -->
|
||
<div class="tab-contents">
|
||
<!-- 구역 현황 탭 -->
|
||
<div id="tab-map" class="tab-content active">
|
||
<div class="map-editor-section">
|
||
<div class="map-editor-header">
|
||
<h3>구역 현황</h3>
|
||
<div class="map-editor-actions">
|
||
<button class="btn btn-primary btn-sm" id="addItemBtn" onclick="startAddItem()">
|
||
➕ 현황 등록
|
||
</button>
|
||
</div>
|
||
</div>
|
||
<div class="map-editor-container">
|
||
<div id="zoneMapContainer" class="zone-map-container">
|
||
<div class="map-placeholder">지도를 로딩 중...</div>
|
||
</div>
|
||
<div class="map-legend">
|
||
<div class="legend-title">주의 수준</div>
|
||
<div class="legend-items">
|
||
<div class="legend-item"><span class="legend-color" style="background: #10b981;"></span> 양호</div>
|
||
<div class="legend-item"><span class="legend-color" style="background: #f59e0b;"></span> 주의</div>
|
||
<div class="legend-item"><span class="legend-color" style="background: #ef4444;"></span> 관리필요</div>
|
||
</div>
|
||
<div class="legend-title" style="margin-top: 1rem;">설비 상태</div>
|
||
<div class="legend-items">
|
||
<div class="legend-item"><span style="margin-right: 4px;">⚙️</span> 정상 가동</div>
|
||
<div class="legend-item"><span style="margin-right: 4px;">🔧</span> 수리 필요</div>
|
||
<div class="legend-item"><span style="margin-right: 4px;">⚠️</span> 점검중</div>
|
||
<div class="legend-item"><span style="margin-right: 4px;">📤</span> 타 작업장 이동</div>
|
||
<div class="legend-item"><span style="margin-right: 4px;">📥</span> 임시 배치</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="zoneItemsList" class="zone-items-list">
|
||
<!-- JS에서 렌더링 -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 안전신고/부적합 탭 -->
|
||
<div id="tab-issues" class="tab-content">
|
||
<div id="issuesContent" class="content-loading">
|
||
<div class="loading-spinner"></div>
|
||
<p>로딩 중...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 설비/수리 탭 -->
|
||
<div id="tab-equipment" class="tab-content">
|
||
<div id="equipmentContent" class="content-loading">
|
||
<div class="loading-spinner"></div>
|
||
<p>로딩 중...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 출입현황 탭 -->
|
||
<div id="tab-visits" class="tab-content">
|
||
<div id="visitsContent" class="content-loading">
|
||
<div class="loading-spinner"></div>
|
||
<p>로딩 중...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TBM 탭 -->
|
||
<div id="tab-tbm" class="tab-content">
|
||
<div id="tbmContent" class="content-loading">
|
||
<div class="loading-spinner"></div>
|
||
<p>로딩 중...</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 순회점검 탭 -->
|
||
<div id="tab-patrol" class="tab-content">
|
||
<div id="patrolContent" class="content-loading">
|
||
<div class="loading-spinner"></div>
|
||
<p>로딩 중...</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 현황 등록/수정 모달 -->
|
||
<div id="zoneItemModal" class="modal-overlay" style="display: none;">
|
||
<div class="modal-container" style="max-width: 520px;">
|
||
<div class="modal-header">
|
||
<h2 id="zoneItemModalTitle">현황 등록</h2>
|
||
<button class="btn-close" onclick="closeZoneItemModal()">×</button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="zoneItemForm">
|
||
<input type="hidden" id="zoneItemId">
|
||
<input type="hidden" id="zoneItemX">
|
||
<input type="hidden" id="zoneItemY">
|
||
<input type="hidden" id="zoneItemWidth">
|
||
<input type="hidden" id="zoneItemHeight">
|
||
|
||
<!-- 프로젝트 여부 -->
|
||
<div class="form-group">
|
||
<label>프로젝트 여부 *</label>
|
||
<div class="radio-group">
|
||
<label class="radio-label">
|
||
<input type="radio" name="zoneItemProjectType" value="project" onchange="onProjectTypeChange(this.value)">
|
||
<span class="radio-text">프로젝트</span>
|
||
</label>
|
||
<label class="radio-label">
|
||
<input type="radio" name="zoneItemProjectType" value="non_project" onchange="onProjectTypeChange(this.value)" checked>
|
||
<span class="radio-text">프로젝트 아님</span>
|
||
</label>
|
||
<label class="radio-label">
|
||
<input type="radio" name="zoneItemProjectType" value="unknown" onchange="onProjectTypeChange(this.value)">
|
||
<span class="radio-text">판단 못함</span>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 프로젝트 선택 (프로젝트일 경우만 표시) -->
|
||
<div class="form-group" id="projectSelectGroup" style="display: none;">
|
||
<label for="zoneItemProject">프로젝트 선택</label>
|
||
<select id="zoneItemProject" class="form-control">
|
||
<option value="">프로젝트를 선택하세요</option>
|
||
<!-- JS에서 동적 로드 -->
|
||
</select>
|
||
</div>
|
||
|
||
<!-- 명칭 -->
|
||
<div class="form-group">
|
||
<label for="zoneItemName">명칭 *</label>
|
||
<input type="text" id="zoneItemName" class="form-control" placeholder="예: A사 제품, 작업 자재, 이동 설비" required>
|
||
</div>
|
||
|
||
<!-- 상태/유형 + 주의수준 -->
|
||
<div class="form-row">
|
||
<div class="form-group" style="flex: 1.5;">
|
||
<label for="zoneItemType">상태/유형</label>
|
||
<div class="select-with-add">
|
||
<select id="zoneItemType" class="form-control">
|
||
<option value="working">작업중</option>
|
||
<option value="temp_storage">임시적치</option>
|
||
<option value="moved_equipment">이동설비</option>
|
||
<option value="unreported">미신고품</option>
|
||
</select>
|
||
<button type="button" class="btn-add-option" onclick="addCustomType()" title="유형 추가">+</button>
|
||
</div>
|
||
</div>
|
||
<div class="form-group" style="flex: 1;">
|
||
<label for="zoneItemWarning">주의 수준</label>
|
||
<select id="zoneItemWarning" class="form-control">
|
||
<option value="good">양호</option>
|
||
<option value="caution">주의</option>
|
||
<option value="needs_management">관리필요</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 상세 설명 -->
|
||
<div class="form-group">
|
||
<label for="zoneItemDesc">상세 설명</label>
|
||
<textarea id="zoneItemDesc" class="form-control" rows="2" placeholder="현황에 대한 상세 설명, 주의사항, 담당자 등"></textarea>
|
||
</div>
|
||
|
||
<!-- 사진 등록 -->
|
||
<div class="form-group">
|
||
<label>사진</label>
|
||
<div class="photo-upload-area">
|
||
<input type="file" id="zoneItemPhoto" accept="image/*" multiple onchange="onPhotoSelected(event)" style="display: none;">
|
||
<div id="photoPreviewList" class="photo-preview-list">
|
||
<!-- 미리보기 이미지들 -->
|
||
</div>
|
||
<button type="button" class="btn-add-photo" onclick="document.getElementById('zoneItemPhoto').click()">
|
||
<span class="photo-icon">📷</span>
|
||
<span>사진 추가</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 표시 색상 -->
|
||
<div class="form-group">
|
||
<label>표시 색상</label>
|
||
<div class="color-picker-row">
|
||
<input type="color" id="zoneItemColor" class="form-control color-input" value="#3b82f6">
|
||
<div class="color-presets">
|
||
<button type="button" class="color-preset" style="background: #10b981;" onclick="setItemColor('#10b981')" title="양호"></button>
|
||
<button type="button" class="color-preset" style="background: #f59e0b;" onclick="setItemColor('#f59e0b')" title="주의"></button>
|
||
<button type="button" class="color-preset" style="background: #ef4444;" onclick="setItemColor('#ef4444')" title="관리필요"></button>
|
||
<button type="button" class="color-preset" style="background: #3b82f6;" onclick="setItemColor('#3b82f6')" title="기본"></button>
|
||
<button type="button" class="color-preset" style="background: #8b5cf6;" onclick="setItemColor('#8b5cf6')" title="기타"></button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button type="button" class="btn btn-secondary" onclick="closeZoneItemModal()">취소</button>
|
||
<button type="button" class="btn btn-danger" id="deleteZoneItemBtn" onclick="deleteZoneItem()" style="display: none;">삭제</button>
|
||
<button type="button" class="btn btn-primary" onclick="saveZoneItem()">저장</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
|
||
<script type="module">
|
||
import '/js/api-config.js?v=2026031401
|
||
</script>
|
||
<script>
|
||
(function() {
|
||
const checkApiConfig = setInterval(() => {
|
||
if (window.API_BASE_URL) {
|
||
clearInterval(checkApiConfig);
|
||
axios.defaults.baseURL = window.API_BASE_URL;
|
||
const token = localStorage.getItem('sso_token');
|
||
if (token) {
|
||
axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
|
||
}
|
||
axios.interceptors.response.use(
|
||
response => response,
|
||
error => {
|
||
if (error.response?.status === 401) {
|
||
alert('세션이 만료되었습니다. 다시 로그인해주세요.');
|
||
window.location.href = '/pages/login.html';
|
||
}
|
||
return Promise.reject(error);
|
||
}
|
||
);
|
||
}
|
||
}, 50);
|
||
})();
|
||
</script>
|
||
<script src="/js/sso-relay.js?v=20260401"></script>
|
||
<script src="/static/js/tkfb-core.js?v=2026040105"></script>
|
||
<script src="/js/api-base.js?v=2026031401"></script>
|
||
<script src="/js/zone-detail.js?v=2026031401"></script>
|
||
<script>initAuth();</script>
|
||
</body>
|
||
</html>
|