feat: 모바일 UX 대폭 개선 + PWA 구현 + 로그인 루프 수정

- 모바일 하단 네비: 메뉴 제거, 4개 핵심 기능(홈/TBM/작업보고/출근) SVG 아이콘
- 모바일 사이드바 스킵: 768px 이하에서 사이드바 미로드, 레이아웃 오프셋 해결
- 모바일 헤더: 햄버거 메뉴 숨김, 본문 margin/overflow 정리
- TBM 모바일: 풀스크린 모달, 저장 버튼 하단 고정, 터치 UX 개선
- PWA: manifest.json, sw.js(network-first), 앱 아이콘, iOS 메타태그, 킬스위치
- 로그인 무한루프 수정: 토큰 만료 검증, 쿠키 정리, loginPage 경로 수정
- 신고 메뉴 tkreport 리다이렉트: navbar + sidebar cross-system-link 적용
- TBM API: 작업장별 안전점검 체크리스트 조회 엔드포인트 추가
- 안전점검 체크리스트 관리 UI 개선
- tkuser: 이슈유형 관리 기능 추가

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Hyungi Ahn
2026-02-24 08:20:50 +09:00
parent 3cc29c03a8
commit d36303101e
60 changed files with 1418 additions and 270 deletions

73
system1-factory/web/sw.js Normal file
View File

@@ -0,0 +1,73 @@
// sw.js - TK공장관리 Service Worker (network-first)
// 주의: 이 파일을 수정할 때는 반드시 CACHE_VERSION을 올려주세요.
// 잘못된 수정은 사용자 브라우저에 최대 24시간 캐시됩니다.
// 자세한 내용: /docs/PWA-GUIDE.md
const CACHE_VERSION = 'tkfb-v3';
const CACHE_NAME = `tkfb-cache-${CACHE_VERSION}`;
// 캐시할 정적 리소스 (앱 셸)
const APP_SHELL = [
'/pages/dashboard.html',
'/css/design-system.css',
'/css/mobile.css',
'/img/icon-192x192.png'
];
// 설치: 앱 셸 프리캐시
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.addAll(APP_SHELL))
.then(() => self.skipWaiting())
);
});
// 활성화: 이전 버전 캐시 삭제
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys()
.then((keys) => Promise.all(
keys
.filter((key) => key.startsWith('tkfb-cache-') && key !== CACHE_NAME)
.map((key) => caches.delete(key))
))
.then(() => self.clients.claim())
);
});
// 요청 가로채기: network-first 전략
self.addEventListener('fetch', (event) => {
const request = event.request;
// API 요청은 캐시하지 않음 (항상 네트워크)
if (request.url.includes('/api/')) {
return;
}
// 로그인 관련 경로는 캐시하지 않음
if (request.url.includes('/login')) {
return;
}
// GET 요청만 캐시
if (request.method !== 'GET') {
return;
}
event.respondWith(
fetch(request)
.then((response) => {
// 정상 응답이면 캐시에 저장
if (response.ok) {
const clone = response.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(request, clone));
}
return response;
})
.catch(() => {
// 네트워크 실패 시 캐시에서 응답
return caches.match(request);
})
);
});