/** * m-common.js — TKQC 모바일 공통 JS * 바텀 네비게이션, 바텀시트 엔진, 인증, 뷰포트 가드, 토스트 */ /* ===== Viewport Guard: 데스크탑이면 리다이렉트 ===== */ (function () { if (window.innerWidth > 768) { var page = location.pathname.replace('/m/', '').replace('.html', ''); var map = { dashboard: '/issues-dashboard.html', inbox: '/issues-inbox.html', management: '/issues-management.html' }; window.location.replace(map[page] || '/issues-dashboard.html'); } })(); /* ===== KST Date Utilities ===== */ // DB에 KST로 저장된 naive datetime을 그대로 표시 (이중 변환 방지) function getKSTDate(date) { return new Date(date); } function formatKSTDate(date) { return new Date(date).toLocaleDateString('ko-KR', { timeZone: 'Asia/Seoul' }); } function formatKSTTime(date) { return new Date(date).toLocaleTimeString('ko-KR', { timeZone: 'Asia/Seoul', hour: '2-digit', minute: '2-digit' }); } function formatKSTDateTime(date) { return new Date(date).toLocaleString('ko-KR', { timeZone: 'Asia/Seoul', year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } function getKSTToday() { var kst = new Date(new Date().toLocaleString('en-US', { timeZone: 'Asia/Seoul' })); return new Date(kst.getFullYear(), kst.getMonth(), kst.getDate()); } function getTimeAgo(date) { var now = new Date(); var d = new Date(date); var diff = now - d; var mins = Math.floor(diff / 60000); var hours = Math.floor(diff / 3600000); var days = Math.floor(diff / 86400000); if (mins < 1) return '방금 전'; if (mins < 60) return mins + '분 전'; if (hours < 24) return hours + '시간 전'; if (days < 7) return days + '일 전'; return formatKSTDate(date); } /* ===== Bottom Navigation ===== */ function renderBottomNav(activePage) { var nav = document.createElement('nav'); nav.className = 'm-bottom-nav'; var items = [ { icon: 'fa-chart-line', label: '현황판', href: '/m/dashboard.html', page: 'dashboard' }, { icon: 'fa-inbox', label: '수신함', href: '/m/inbox.html', page: 'inbox' }, { icon: 'fa-tasks', label: '관리함', href: '/m/management.html', page: 'management' }, { icon: 'fa-bullhorn', label: '신고', href: (location.hostname.includes('technicalkorea.net') ? 'https://tkreport.technicalkorea.net' : location.protocol + '//' + location.hostname + ':30180'), page: 'report', external: true, highlight: true } ]; items.forEach(function (item) { var a = document.createElement('a'); a.href = item.href; a.className = 'm-nav-item'; if (item.page === activePage) a.classList.add('active'); if (item.highlight) a.classList.add('highlight'); if (item.external) { a.target = '_blank'; a.rel = 'noopener'; } a.innerHTML = '' + item.label + ''; nav.appendChild(a); }); document.body.appendChild(nav); } /* ===== Bottom Sheet Engine ===== */ var _activeSheets = []; function openSheet(sheetId) { var overlay = document.getElementById(sheetId + 'Overlay'); var sheet = document.getElementById(sheetId + 'Sheet'); if (!overlay || !sheet) return; overlay.classList.add('open'); sheet.classList.add('open'); document.body.style.overflow = 'hidden'; _activeSheets.push(sheetId); } function closeSheet(sheetId) { var overlay = document.getElementById(sheetId + 'Overlay'); var sheet = document.getElementById(sheetId + 'Sheet'); if (!overlay || !sheet) return; overlay.classList.remove('open'); sheet.classList.remove('open'); _activeSheets = _activeSheets.filter(function (id) { return id !== sheetId; }); if (_activeSheets.length === 0) document.body.style.overflow = ''; } function closeAllSheets() { _activeSheets.slice().forEach(function (id) { closeSheet(id); }); } // ESC key closes topmost sheet document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && _activeSheets.length) { closeSheet(_activeSheets[_activeSheets.length - 1]); } }); /* ===== Toast ===== */ var _toastTimer = null; function showToast(message, type, duration) { type = type || 'info'; duration = duration || 3000; var existing = document.querySelector('.m-toast'); if (existing) existing.remove(); clearTimeout(_toastTimer); var toast = document.createElement('div'); toast.className = 'm-toast'; if (type !== 'info') toast.classList.add(type); toast.textContent = message; document.body.appendChild(toast); requestAnimationFrame(function () { toast.classList.add('show'); }); _toastTimer = setTimeout(function () { toast.classList.remove('show'); setTimeout(function () { toast.remove(); }, 300); }, duration); } /* ===== Photo Modal ===== */ function openPhotoModal(src) { var modal = document.getElementById('photoModal'); if (!modal) { modal = document.createElement('div'); modal.id = 'photoModal'; modal.className = 'm-photo-modal'; modal.innerHTML = ''; modal.addEventListener('click', function (e) { if (e.target === modal) closePhotoModal(); }); document.body.appendChild(modal); } modal.querySelector('img').src = src; modal.classList.add('open'); document.body.style.overflow = 'hidden'; } function closePhotoModal() { var modal = document.getElementById('photoModal'); if (modal) { modal.classList.remove('open'); if (!_activeSheets.length) document.body.style.overflow = ''; } } /* ===== Auth Helper (authManager 위임 + 루프 방지) ===== */ var _mRedirectKey = '_sso_redirect_ts'; var _mRedirectCooldown = 5000; // 5초 내 재리다이렉트 방지 function _mSafeRedirectToLogin() { var last = parseInt(sessionStorage.getItem(_mRedirectKey) || '0', 10); if (Date.now() - last < _mRedirectCooldown) { console.warn('[TKQC-M] 리다이렉트 루프 감지 — 로그인 페이지로 이동하지 않음'); return; } sessionStorage.setItem(_mRedirectKey, String(Date.now())); window.location.href = _getLoginUrl(); } async function mCheckAuth() { // authManager가 있으면 위임 (SW 정리 + 캐시 관리 포함) if (window.authManager && typeof window.authManager.checkAuth === 'function') { var user = await window.authManager.checkAuth(); if (user) { sessionStorage.removeItem(_mRedirectKey); return user; } _mSafeRedirectToLogin(); return null; } // 폴백: authManager 없는 경우 var token = TokenManager.getToken(); if (!token) { _mSafeRedirectToLogin(); return null; } try { var user = await AuthAPI.getCurrentUser(); sessionStorage.removeItem(_mRedirectKey); return user; } catch (e) { TokenManager.removeToken(); TokenManager.removeUser(); _mSafeRedirectToLogin(); return null; } } /* ===== Loading Overlay ===== */ function hideLoading() { var el = document.getElementById('loadingOverlay'); if (el) { el.classList.add('hide'); setTimeout(function () { el.remove(); }, 300); } } /* ===== Helpers ===== */ function escapeHtml(text) { if (!text) return ''; var div = document.createElement('div'); div.textContent = text; return div.innerHTML; } function getPhotoPaths(issue) { return [issue.photo_path, issue.photo_path2, issue.photo_path3, issue.photo_path4, issue.photo_path5].filter(Boolean); } function getCompletionPhotoPaths(issue) { return [issue.completion_photo_path, issue.completion_photo_path2, issue.completion_photo_path3, issue.completion_photo_path4, issue.completion_photo_path5].filter(Boolean); } function renderPhotoThumbs(photos) { if (!photos || !photos.length) return ''; return '
' + photos.map(function (p, i) { return '사진 ' + (i + 1) + ''; }).join('') + '
'; }