Files
tk-factory-services/system1-factory/web/pages/inspection/zone-detail.html
Hyungi Ahn 0de9d5bb48 feat(sso): 인앱 브라우저 SSO 토큰 릴레이 — 카톡 WebView 쿠키 미공유 해결
카카오톡 인앱 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>
2026-04-01 15:44:02 +09:00

314 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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()">&times;</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>