Files
tk-factory-services/system2-report/web/pages/safety/issue-report.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

789 lines
27 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, maximum-scale=1.0, user-scalable=no">
<title>신고 등록 | (주)테크니컬코리아</title>
<link rel="icon" type="image/png" href="/img/favicon.png">
<script src="/js/sso-relay.js?v=20260401"></script>
<script src="/js/api-base.js?v=2026040101"></script>
<script src="/js/app-init.js?v=2026031401" defer></script>
<style>
* { box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Noto Sans KR', sans-serif;
background: #f3f4f6;
margin: 0;
padding: 0;
padding-bottom: env(safe-area-inset-bottom);
-webkit-font-smoothing: antialiased;
}
/* Step indicator */
.step-indicator {
display: flex;
justify-content: center;
align-items: center;
padding: 0.75rem 0.5rem;
gap: 0;
background: white;
border-bottom: 1px solid #e5e7eb;
}
.step {
display: flex;
align-items: center;
gap: 0.125rem;
font-size: 0.625rem;
color: #9ca3af;
}
.step .step-dot {
width: 20px;
height: 20px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.625rem;
font-weight: 700;
background: #e5e7eb;
color: #9ca3af;
flex-shrink: 0;
}
.step.active .step-dot { background: #ef4444; color: white; }
.step.active { color: #ef4444; font-weight: 600; }
.step.completed .step-dot { background: #10b981; color: white; }
.step.completed { color: #10b981; }
.step-line {
width: 16px;
height: 2px;
background: #e5e7eb;
margin: 0 0.125rem;
flex-shrink: 0;
}
/* Sections */
.report-section {
margin: 0.75rem;
background: white;
border-radius: 0.75rem;
padding: 1.25rem;
box-shadow: 0 1px 3px rgba(0,0,0,0.06);
}
.section-title {
font-size: 0.9375rem;
font-weight: 700;
color: #1f2937;
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.section-title .sn {
width: 22px;
height: 22px;
border-radius: 50%;
background: #ef4444;
color: white;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 0.75rem;
font-weight: 700;
flex-shrink: 0;
}
/* Type buttons */
.type-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.625rem;
}
.type-btn {
padding: 1.125rem 0.5rem;
border: 2px solid #e5e7eb;
border-radius: 0.75rem;
background: white;
cursor: pointer;
text-align: center;
transition: all 0.15s;
-webkit-tap-highlight-color: transparent;
}
.type-btn:active { transform: scale(0.97); }
.type-btn .type-icon { font-size: 1.75rem; margin-bottom: 0.5rem; }
.type-btn .type-label { font-size: 0.8125rem; font-weight: 600; color: #374151; }
.type-btn.selected { border-color: #ef4444; background: #fef2f2; }
.type-btn.selected .type-label { color: #dc2626; }
/* Map */
.factory-select {
width: 100%;
padding: 0.625rem 0.875rem;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
font-size: 0.875rem;
margin-bottom: 0.75rem;
background: white;
-webkit-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%236b7280' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: right 0.75rem center;
padding-right: 2rem;
}
.map-container {
border: 1px solid #e5e7eb;
border-radius: 0.5rem;
overflow: hidden;
margin-bottom: 0.75rem;
}
.map-container canvas { width: 100%; display: block; }
.location-info {
padding: 0.75rem;
background: #f0fdf4;
border: 1px solid #bbf7d0;
border-radius: 0.5rem;
font-size: 0.8125rem;
color: #166534;
margin-bottom: 0.75rem;
line-height: 1.5;
}
.location-info.empty {
background: #f9fafb;
border-color: #e5e7eb;
color: #9ca3af;
}
.custom-location-toggle {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.8125rem;
color: #6b7280;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.custom-location-toggle input[type="checkbox"] {
width: 18px;
height: 18px;
accent-color: #ef4444;
}
#customLocationInput {
display: none;
margin-top: 0.5rem;
}
#customLocationInput.visible { display: block; }
#customLocationInput input {
width: 100%;
padding: 0.625rem 0.875rem;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
font-size: 0.875rem;
}
#customLocationInput input:focus {
outline: none;
border-color: #ef4444;
box-shadow: 0 0 0 3px rgba(239,68,68,0.1);
}
/* Project/Work selection - Accordion */
.project-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.project-group {
border: 1.5px solid #e5e7eb;
border-radius: 0.75rem;
overflow: hidden;
}
.project-group-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.875rem 1rem;
background: #f9fafb;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
user-select: none;
}
.project-group-header:active { background: #f3f4f6; }
.project-group-header .group-left {
display: flex;
align-items: center;
gap: 0.5rem;
}
.project-group-header .group-icon {
font-size: 1.125rem;
flex-shrink: 0;
}
.project-group-header .group-title {
font-size: 0.875rem;
font-weight: 600;
color: #1f2937;
}
.project-group-header .group-count {
font-size: 0.6875rem;
background: #e5e7eb;
color: #6b7280;
padding: 0.125rem 0.5rem;
border-radius: 9999px;
font-weight: 600;
}
.project-group-header .group-arrow {
font-size: 0.75rem;
color: #9ca3af;
transition: transform 0.2s;
}
.project-group.open .group-arrow { transform: rotate(180deg); }
.project-group.tbm-group { border-color: #93c5fd; }
.project-group.tbm-group .project-group-header { background: #eff6ff; }
.project-group.tbm-group .group-count { background: #dbeafe; color: #1d4ed8; }
.project-group-body {
display: none;
border-top: 1px solid #e5e7eb;
}
.project-group.open .project-group-body { display: block; }
.project-card {
padding: 0.875rem 1rem;
cursor: pointer;
transition: background 0.12s;
-webkit-tap-highlight-color: transparent;
border-bottom: 1px solid #f3f4f6;
}
.project-card:last-child { border-bottom: none; }
.project-card:active { background: #f9fafb; }
.project-card.selected { background: #f5f3ff; }
.project-card-title {
font-size: 0.8125rem;
font-weight: 600;
color: #1f2937;
margin-bottom: 0.125rem;
}
.project-card-desc {
font-size: 0.75rem;
color: #6b7280;
}
.project-card .tbm-info {
font-size: 0.6875rem;
color: #2563eb;
margin-top: 0.25rem;
}
/* 프로젝트 모름 */
.project-skip {
padding: 0.875rem 1rem;
border: 1.5px dashed #d1d5db;
border-radius: 0.75rem;
text-align: center;
font-size: 0.8125rem;
color: #6b7280;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.project-skip:active { background: #f9fafb; }
.project-skip.selected {
border-color: #8b5cf6;
border-style: solid;
background: #f5f3ff;
color: #7c3aed;
font-weight: 600;
}
.project-empty {
text-align: center;
padding: 1.5rem 1rem;
color: #9ca3af;
font-size: 0.8125rem;
}
/* 선택된 항목 표시 */
.project-group-header .group-selected {
font-size: 0.6875rem;
color: #7c3aed;
font-weight: 600;
margin-left: 0.25rem;
}
/* Category & Item */
#categoryContainer { display: none; }
.subsection-title {
font-size: 0.8125rem;
font-weight: 600;
color: #6b7280;
margin-bottom: 0.5rem;
}
.category-grid, #itemGrid {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.category-btn, .item-btn {
padding: 0.5rem 0.875rem;
border: 1.5px solid #d1d5db;
border-radius: 2rem;
background: white;
font-size: 0.8125rem;
cursor: pointer;
transition: all 0.12s;
-webkit-tap-highlight-color: transparent;
white-space: nowrap;
}
.category-btn:active, .item-btn:active { transform: scale(0.97); }
.category-btn.selected {
border-color: #ef4444;
background: #fef2f2;
color: #dc2626;
font-weight: 600;
}
.item-btn.selected {
border-color: #2563eb;
background: #eff6ff;
color: #1d4ed8;
font-weight: 600;
}
.item-btn.custom-input-btn {
border-style: dashed;
color: #6b7280;
}
.item-section {
margin-top: 1rem;
padding-top: 0.875rem;
border-top: 1px solid #f3f4f6;
}
#customItemInput {
display: none;
margin-top: 0.75rem;
gap: 0.5rem;
align-items: center;
}
#customItemInput input {
flex: 1;
padding: 0.5rem 0.75rem;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
font-size: 0.875rem;
}
#customItemInput input:focus {
outline: none;
border-color: #ef4444;
box-shadow: 0 0 0 3px rgba(239,68,68,0.1);
}
#customItemInput button {
padding: 0.5rem 0.75rem;
border: none;
border-radius: 0.5rem;
font-size: 0.8125rem;
font-weight: 600;
cursor: pointer;
white-space: nowrap;
}
.custom-confirm { background: #2563eb; color: white; }
.custom-cancel { background: #f3f4f6; color: #374151; }
/* Photo & Details */
.photo-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 0.5rem;
margin-bottom: 1rem;
}
.photo-slot {
aspect-ratio: 1;
border: 2px dashed #d1d5db;
border-radius: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
position: relative;
overflow: hidden;
background: #f9fafb;
-webkit-tap-highlight-color: transparent;
}
.photo-slot .add-icon { font-size: 1.25rem; color: #9ca3af; }
.photo-slot.has-photo { border-style: solid; border-color: #10b981; }
.photo-slot.has-photo .add-icon { display: none; }
.photo-slot img { width: 100%; height: 100%; object-fit: cover; }
.photo-slot .remove-btn {
display: none;
position: absolute;
top: 2px;
right: 2px;
width: 20px;
height: 20px;
border-radius: 50%;
background: rgba(0,0,0,0.6);
color: white;
border: none;
font-size: 0.625rem;
cursor: pointer;
align-items: center;
justify-content: center;
z-index: 2;
line-height: 1;
}
.photo-slot.has-photo .remove-btn { display: flex; }
.photo-hint {
font-size: 0.75rem;
color: #9ca3af;
margin-bottom: 0.75rem;
}
.description-textarea {
width: 100%;
padding: 0.75rem;
border: 1px solid #d1d5db;
border-radius: 0.5rem;
font-size: 0.875rem;
resize: vertical;
min-height: 80px;
font-family: inherit;
}
.description-textarea:focus {
outline: none;
border-color: #ef4444;
box-shadow: 0 0 0 3px rgba(239,68,68,0.1);
}
/* Submit */
.submit-section {
padding: 0.75rem;
padding-bottom: calc(1rem + env(safe-area-inset-bottom));
}
#submitBtn {
width: 100%;
padding: 0.9375rem;
background: #ef4444;
color: white;
border: none;
border-radius: 0.75rem;
font-size: 1rem;
font-weight: 700;
cursor: pointer;
transition: background 0.2s;
-webkit-tap-highlight-color: transparent;
}
#submitBtn:disabled { background: #d1d5db; cursor: not-allowed; }
#submitBtn:not(:disabled):active { background: #dc2626; }
/* 가로모드 전체화면 지도 오버레이 */
.landscape-overlay {
position: fixed;
inset: 0;
z-index: 10000;
background: rgba(0, 0, 0, 0.95);
display: flex;
align-items: center;
justify-content: center;
}
.landscape-inner {
display: flex;
flex-direction: column;
background: #fff;
overflow: hidden;
}
.landscape-inner.rotated {
width: 100vh;
height: 100vw;
transform: translate(-50%, -50%) rotate(90deg);
position: absolute;
top: 50%;
left: 50%;
}
.landscape-inner.no-rotate {
width: 100vw;
height: 100vh;
}
.landscape-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.5rem 1rem;
background: linear-gradient(135deg, #ef4444, #dc2626);
color: white;
flex-shrink: 0;
}
.landscape-header h3 {
margin: 0;
font-size: 1rem;
font-weight: 600;
}
.landscape-close-btn {
width: 36px;
height: 36px;
border-radius: 50%;
border: none;
background: rgba(255, 255, 255, 0.2);
color: white;
font-size: 1.5rem;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
-webkit-tap-highlight-color: transparent;
}
.landscape-canvas-wrap {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
background: #f1f5f9;
padding: 0.5rem;
}
.landscape-canvas-wrap canvas {
max-width: 100%;
max-height: 100%;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
}
.landscape-trigger-btn {
display: none;
}
@media (max-width: 768px) {
.landscape-trigger-btn {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.5rem 0.875rem;
background: linear-gradient(135deg, #8b5cf6, #7c3aed);
color: white;
border: none;
border-radius: 8px;
font-size: 0.8125rem;
font-weight: 600;
cursor: pointer;
margin-top: 0.5rem;
-webkit-tap-highlight-color: transparent;
}
.landscape-trigger-btn:active {
transform: scale(0.97);
opacity: 0.85;
}
}
/* 성공 모달 */
.success-modal-overlay {
display: none;
position: fixed;
inset: 0;
z-index: 10001;
background: rgba(0,0,0,0.5);
align-items: center;
justify-content: center;
padding: 1rem;
}
.success-modal {
background: white;
border-radius: 1rem;
padding: 2rem 1.5rem;
text-align: center;
max-width: 320px;
width: 100%;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
}
.success-modal .success-icon {
width: 64px;
height: 64px;
border-radius: 50%;
background: #d1fae5;
color: #059669;
font-size: 2rem;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 1rem;
}
.success-modal h3 {
font-size: 1.125rem;
font-weight: 700;
color: #1f2937;
margin: 0 0 0.5rem;
}
.success-modal p {
font-size: 0.875rem;
color: #6b7280;
margin: 0 0 1.5rem;
}
.success-modal .modal-buttons {
display: flex;
flex-direction: column;
gap: 0.625rem;
}
.success-modal .btn-primary {
padding: 0.75rem;
background: #ef4444;
color: white;
border: none;
border-radius: 0.625rem;
font-size: 0.9375rem;
font-weight: 600;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.success-modal .btn-primary:active { background: #dc2626; }
.success-modal .btn-secondary {
padding: 0.75rem;
background: #f3f4f6;
color: #374151;
border: none;
border-radius: 0.625rem;
font-size: 0.9375rem;
font-weight: 600;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.success-modal .btn-secondary:active { background: #e5e7eb; }
/* Responsive */
@media (min-width: 480px) {
body { max-width: 480px; margin: 0 auto; min-height: 100vh; }
}
@media (max-width: 480px) {
.photo-grid { grid-template-columns: repeat(4, 1fr); }
.type-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 360px) {
.type-grid { gap: 0.5rem; }
.type-btn { padding: 0.875rem 0.375rem; }
.type-btn .type-icon { font-size: 1.5rem; }
.photo-grid { gap: 0.375rem; }
}
</style>
</head>
<body>
<!-- AI 챗봇 신고 배너 -->
<a href="/pages/safety/chat-report.html" style="display:flex;align-items:center;gap:0.625rem;margin:0.75rem;padding:0.875rem 1rem;background:linear-gradient(135deg,#0ea5e9,#0284c7);color:white;border-radius:0.75rem;text-decoration:none;box-shadow:0 2px 8px rgba(14,165,233,0.3);-webkit-tap-highlight-color:transparent;">
<span style="font-size:1.5rem;">🤖</span>
<span style="flex:1;"><strong style="font-size:0.875rem;">AI 신고 도우미</strong><br><span style="font-size:0.75rem;opacity:0.9;">사진+설명만으로 간편하게 신고하기</span></span>
<span style="font-size:1.25rem;opacity:0.8;"></span>
</a>
<!-- Step Indicator (5 steps) -->
<div class="step-indicator">
<div class="step active"><span class="step-dot">1</span><span>유형</span></div>
<div class="step-line"></div>
<div class="step"><span class="step-dot">2</span><span>위치</span></div>
<div class="step-line"></div>
<div class="step"><span class="step-dot">3</span><span>작업</span></div>
<div class="step-line"></div>
<div class="step"><span class="step-dot">4</span><span>항목</span></div>
<div class="step-line"></div>
<div class="step"><span class="step-dot">5</span><span>사진</span></div>
</div>
<!-- Step 1: Type Selection -->
<div class="report-section">
<div class="section-title"><span class="sn">1</span>신고 유형</div>
<div class="type-grid">
<button type="button" class="type-btn" data-type="nonconformity">
<div class="type-icon">&#128270;</div>
<div class="type-label">부적합</div>
</button>
<button type="button" class="type-btn" data-type="facility">
<div class="type-icon">&#128295;</div>
<div class="type-label">시설설비</div>
</button>
<button type="button" class="type-btn" data-type="safety">
<div class="type-icon">&#9888;&#65039;</div>
<div class="type-label">안전</div>
</button>
</div>
</div>
<!-- Step 2: Location -->
<div class="report-section">
<div class="section-title"><span class="sn">2</span>위치 선택</div>
<select id="factorySelect" class="factory-select">
<option value="">공장 선택</option>
</select>
<div class="map-container">
<canvas id="issueMapCanvas"></canvas>
</div>
<button type="button" class="landscape-trigger-btn" id="landscapeTriggerBtn" onclick="openLandscapeMap()" style="display:none;">
&#128250; 전체화면 지도로 선택
</button>
<div id="selectedLocationInfo" class="location-info empty">
지도에서 작업장을 클릭하여 위치를 선택하세요
</div>
<label class="custom-location-toggle">
<input type="checkbox" id="useCustomLocation">
기타 위치 직접 입력
</label>
<div id="customLocationInput">
<input type="text" id="customLocation" placeholder="위치를 입력하세요">
</div>
</div>
<!-- Step 3: Project/Work Selection -->
<div class="report-section" id="projectContainer">
<div class="section-title"><span class="sn">3</span>프로젝트/작업 선택</div>
<div id="projectList" class="project-list">
<div class="project-empty">위치를 먼저 선택하세요</div>
</div>
</div>
<!-- Step 4: Category & Item -->
<div class="report-section" id="categoryContainer">
<div class="section-title"><span class="sn">4</span>세부 항목</div>
<div class="subsection-title">카테고리</div>
<div class="category-grid" id="categoryGrid"></div>
<div class="item-section">
<div class="subsection-title">항목</div>
<div id="itemGrid"></div>
<div id="customItemInput">
<input type="text" id="customItemName" placeholder="항목명을 입력하세요">
<button type="button" class="custom-confirm" onclick="confirmCustomItem()">확인</button>
<button type="button" class="custom-cancel" onclick="cancelCustomItem()">취소</button>
</div>
</div>
</div>
<!-- Step 5: Photos + Description -->
<div class="report-section">
<div class="section-title"><span class="sn">5</span>사진 및 상세</div>
<div class="photo-grid">
<div class="photo-slot" data-index="0"><span class="add-icon">+</span><button class="remove-btn">&times;</button></div>
<div class="photo-slot" data-index="1"><span class="add-icon">+</span><button class="remove-btn">&times;</button></div>
<div class="photo-slot" data-index="2"><span class="add-icon">+</span><button class="remove-btn">&times;</button></div>
<div class="photo-slot" data-index="3"><span class="add-icon">+</span><button class="remove-btn">&times;</button></div>
<div class="photo-slot" data-index="4"><span class="add-icon">+</span><button class="remove-btn">&times;</button></div>
</div>
<div class="photo-hint">* 사진 1장 이상 필수 (최대 5장, 카메라 촬영 또는 앨범에서 선택)</div>
<textarea id="additionalDescription" class="description-textarea" placeholder="추가 설명을 입력하세요 (선택사항)"></textarea>
</div>
<!-- Submit -->
<div class="submit-section">
<button type="button" id="submitBtn" disabled onclick="submitReport()">신고 제출</button>
</div>
<!-- 가로모드 전체화면 지도 오버레이 -->
<div id="landscapeOverlay" class="landscape-overlay" style="display:none;">
<div id="landscapeInner" class="landscape-inner">
<div class="landscape-header">
<h3>&#127981; 작업장 선택</h3>
<button type="button" class="landscape-close-btn" onclick="closeLandscapeMap()">×</button>
</div>
<div class="landscape-canvas-wrap">
<canvas id="landscapeCanvas"></canvas>
</div>
</div>
</div>
<!-- 성공 모달 -->
<div id="successModal" class="success-modal-overlay">
<div class="success-modal">
<div class="success-icon">&#10003;</div>
<h3>신고가 성공적으로 등록되었습니다!</h3>
<p>등록된 신고는 담당자가 확인 후 처리합니다.</p>
<div class="modal-buttons">
<button class="btn-primary" onclick="window.location.href='/pages/safety/report-status.html'">신고 현황 보기</button>
<button class="btn-secondary" onclick="window.location.href='/pages/safety/issue-report.html'">새 신고하기</button>
</div>
</div>
</div>
<!-- Hidden file input for camera/gallery -->
<input type="file" id="photoInput" accept="image/*" style="display:none">
<script src="/js/cross-nav.js?v=2026031401"></script>
<script src="/js/issue-report.js?v=2026031401"></script>
</body>
</html>