- sw.js: Network-First 캐시 전략 (GET + same-origin + res.ok만 캐시) - tkfb-core.js: SW 등록 + 업데이트 감지 시 자동 새로고침 (최초 설치 시 토스트 방지: controller 체크) - manifest.json: start_url → dashboard-new.html - nginx: sw.js, manifest.json no-cache 헤더 - 배포 시 sw.js의 APP_VERSION만 변경하면 전 사용자 자동 갱신 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
209 lines
8.7 KiB
HTML
209 lines
8.7 KiB
HTML
<!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>작업보고서 - TK 공장관리</title>
|
|
<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">
|
|
<link rel="stylesheet" href="/css/daily-work-report-mobile.css?v=2026031401">
|
|
<link rel="stylesheet" href="/css/tbm-mobile.css?v=2026033108">
|
|
<style>
|
|
/* 데스크탑이면 리다이렉트 */
|
|
@media (min-width: 769px) {
|
|
body::before {
|
|
content: 'redirect';
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|
|
<script>
|
|
// 데스크탑 접속 시 리다이렉트
|
|
if (window.innerWidth > 768) {
|
|
window.location.replace('/pages/work/report-create.html');
|
|
}
|
|
</script>
|
|
</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">
|
|
|
|
<!-- Sticky 헤더 -->
|
|
<div class="m-header">
|
|
<h1 class="m-header-title">작업보고서</h1>
|
|
<div class="m-header-action">
|
|
<button class="m-btn-add" id="btnAddManual" onclick="MobileReport.addManualCard()" style="display:none;">+ 수동추가</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 탭바 -->
|
|
<div class="m-tab-bar">
|
|
<button class="m-tab-btn active" data-tab="tbm" onclick="MobileReport.switchTab('tbm')">
|
|
TBM 작업 <span class="m-tab-count" id="tbmCount">0</span>
|
|
</button>
|
|
<button class="m-tab-btn" data-tab="manual" onclick="MobileReport.switchTab('manual')">
|
|
수동 입력
|
|
</button>
|
|
<button class="m-tab-btn" data-tab="completed" onclick="MobileReport.switchTab('completed')">
|
|
완료
|
|
</button>
|
|
</div>
|
|
|
|
<!-- 메시지 -->
|
|
<div class="m-message" id="mMessage"></div>
|
|
|
|
<!-- TBM 탭 -->
|
|
<div class="m-tab-content active" id="tabTbm">
|
|
<div id="tbmCardList">
|
|
<div class="m-loading"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 수동 입력 탭 -->
|
|
<div class="m-tab-content" id="tabManual">
|
|
<div id="manualCardList">
|
|
<div class="m-empty">
|
|
<div class="m-empty-icon">📝</div>
|
|
<div>수동으로 작업보고서를 추가하세요</div>
|
|
<button class="m-btn-add" style="margin-top:0.75rem;" onclick="MobileReport.addManualCard()">+ 수동추가</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 완료 탭 -->
|
|
<div class="m-tab-content" id="tabCompleted">
|
|
<div class="m-completed-header">
|
|
<input type="date" class="m-date-input" id="completedDate" onchange="MobileReport.loadCompletedReports()">
|
|
</div>
|
|
<div id="completedCardList">
|
|
<div class="m-empty">
|
|
<div class="m-empty-icon">📋</div>
|
|
<div>날짜를 선택하세요</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 시간 선택 오버레이 -->
|
|
<div class="m-time-overlay" id="mTimeOverlay" onclick="MobileReport.closeTimePicker()">
|
|
<div class="m-time-popup" onclick="event.stopPropagation()">
|
|
<div class="m-time-header">
|
|
<h3 class="m-time-title" id="mTimeTitle">작업시간 선택</h3>
|
|
<button class="m-time-close" onclick="MobileReport.closeTimePicker()">×</button>
|
|
</div>
|
|
<div class="m-quick-time-grid">
|
|
<button class="m-time-btn" onclick="MobileReport.setTime(0.5)">30분</button>
|
|
<button class="m-time-btn" onclick="MobileReport.setTime(1)">1시간</button>
|
|
<button class="m-time-btn" onclick="MobileReport.setTime(2)">2시간</button>
|
|
<button class="m-time-btn" onclick="MobileReport.setTime(4)">4시간</button>
|
|
<button class="m-time-btn" onclick="MobileReport.setTime(8)">8시간</button>
|
|
</div>
|
|
<div class="m-time-adjust">
|
|
<button class="m-time-adjust-btn" onclick="MobileReport.adjustTime(-0.5)">-</button>
|
|
<span class="m-time-current" id="mTimeCurrent">0시간</span>
|
|
<button class="m-time-adjust-btn" onclick="MobileReport.adjustTime(0.5)">+</button>
|
|
</div>
|
|
<button class="m-time-confirm" onclick="MobileReport.confirmTime()">확인</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 부적합 바텀시트 -->
|
|
<div class="m-overlay" id="defectOverlay" onclick="MobileReport.hideDefectSheet()"></div>
|
|
<div class="m-bottom-sheet" id="defectSheet">
|
|
<div class="m-sheet-handle"></div>
|
|
<div class="m-sheet-header">
|
|
<h3 class="m-sheet-title">부적합 입력</h3>
|
|
<button class="m-sheet-close" onclick="MobileReport.hideDefectSheet()">×</button>
|
|
</div>
|
|
<div class="m-sheet-body" id="defectSheetBody">
|
|
<!-- 동적 렌더링 -->
|
|
</div>
|
|
<div class="m-sheet-footer">
|
|
<button class="m-submit-btn primary" onclick="MobileReport.saveDefects()">저장</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 작업장소 바텀시트 -->
|
|
<div class="m-overlay" id="wpOverlay" onclick="MobileReport.hideWorkplaceSheet()"></div>
|
|
<div class="m-bottom-sheet" id="wpSheet">
|
|
<div class="m-sheet-handle"></div>
|
|
<div class="m-sheet-header">
|
|
<h3 class="m-sheet-title" id="wpSheetTitle">작업장소 선택</h3>
|
|
<button class="m-sheet-close" onclick="MobileReport.hideWorkplaceSheet()">×</button>
|
|
</div>
|
|
<div class="m-sheet-body" id="wpSheetBody">
|
|
<!-- 동적 렌더링 -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 수정 바텀시트 -->
|
|
<div class="m-overlay" id="editOverlay" onclick="MobileReport.hideEditSheet()"></div>
|
|
<div class="m-bottom-sheet" id="editSheet">
|
|
<div class="m-sheet-handle"></div>
|
|
<div class="m-sheet-header">
|
|
<h3 class="m-sheet-title">보고서 수정</h3>
|
|
<button class="m-sheet-close" onclick="MobileReport.hideEditSheet()">×</button>
|
|
</div>
|
|
<div class="m-sheet-body m-edit-form" id="editSheetBody">
|
|
<!-- 동적 렌더링 -->
|
|
</div>
|
|
<div class="m-sheet-footer">
|
|
<button class="m-submit-btn primary" onclick="MobileReport.saveEditedReport()">저장</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 결과 모달 -->
|
|
<div class="m-result-overlay" id="mResultOverlay">
|
|
<div class="m-result-box">
|
|
<div class="m-result-icon" id="mResultIcon"></div>
|
|
<div class="m-result-title" id="mResultTitle"></div>
|
|
<div class="m-result-message" id="mResultMessage"></div>
|
|
<div class="m-result-details" id="mResultDetails" style="display:none;"></div>
|
|
<button class="m-result-close" onclick="MobileReport.closeResult()">확인</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 토스트 -->
|
|
<div class="m-toast" id="mToast"></div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 공통 모듈 -->
|
|
<script src="/static/js/tkfb-core.js?v=2026040105"></script>
|
|
<script src="/js/api-base.js?v=2026031401"></script>
|
|
<script src="/js/common/utils.js?v=2026031401"></script>
|
|
<script src="/js/common/base-state.js?v=2026031401"></script>
|
|
|
|
<!-- 작업보고서 모듈 (재사용) -->
|
|
<script src="/js/daily-work-report/state.js?v=2026031401"></script>
|
|
<script src="/js/daily-work-report/utils.js?v=2026031401"></script>
|
|
<script src="/js/daily-work-report/api.js?v=2026031401"></script>
|
|
|
|
<!-- 모바일 전용 UI 로직 -->
|
|
<script src="/js/daily-work-report-mobile.js?v=2026031401"></script>
|
|
<script>initAuth();</script>
|
|
<script src="/static/js/shared-bottom-nav.js?v=2026040103"></script>
|
|
</body>
|
|
</html>
|