feat: TBM 모바일 시스템 + 작업 분할/이동 + 권한 통합
TBM 시스템: - 4단계 워크플로우 (draft→세부편집→완료→작업보고) - 모바일 전용 TBM 페이지 (tbm-mobile.html) + 3단계 생성 위자드 - 작업자 작업 분할 (work_hours + split_seq) - 작업자 이동 보내기/빼오기 (tbm_transfers 테이블) - 생성 시 중복 배정 방지 (당일 배정 현황 조회) - 데스크탑 TBM 페이지 세부편집 기능 추가 작업보고서: - 모바일 전용 작업보고서 페이지 (report-create-mobile.html) - TBM에서 사전 등록된 work_hours 자동 반영 권한 시스템: - tkuser user_page_permissions 테이블과 system1 페이지 접근 연동 - pageAccessRoutes를 userRoutes보다 먼저 등록 (라우트 우선순위 수정) - TKUSER_DEFAULT_ACCESS 폴백 추가 (개인→부서→기본값 3단계) - 권한 캐시키 갱신 (userPageAccess_v2) 기타: - app-init.js 캐시 버스팅 (v=5) - iOS Safari touch-action: manipulation 적용 - KST 타임존 날짜 버그 수정 (toISOString UTC 이슈) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
194
system1-factory/web/pages/work/report-create-mobile.html
Normal file
194
system1-factory/web/pages/work/report-create-mobile.html
Normal file
@@ -0,0 +1,194 @@
|
||||
<!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="stylesheet" href="/css/design-system.css">
|
||||
<link rel="stylesheet" href="/css/daily-work-report-mobile.css?v=1">
|
||||
<link rel="stylesheet" href="/css/mobile.css?v=1">
|
||||
<link rel="icon" type="image/png" href="/img/favicon.png">
|
||||
<script src="/js/api-base.js"></script>
|
||||
<script src="/js/app-init.js?v=5" defer></script>
|
||||
<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>
|
||||
<!-- 네비게이션 바 (app-init에서 로드) -->
|
||||
<div id="navbar-container"></div>
|
||||
|
||||
<div class="m-container">
|
||||
<!-- 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>
|
||||
|
||||
<!-- 시간 선택 오버레이 -->
|
||||
<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>
|
||||
|
||||
<!-- 작업보고서 모듈 (재사용) -->
|
||||
<script src="/js/daily-work-report/state.js?v=1"></script>
|
||||
<script src="/js/daily-work-report/utils.js?v=1"></script>
|
||||
<script src="/js/daily-work-report/api.js?v=1"></script>
|
||||
|
||||
<!-- 모바일 전용 UI 로직 -->
|
||||
<script src="/js/daily-work-report-mobile.js?v=3"></script>
|
||||
|
||||
<!-- 모바일 하단 네비게이션 -->
|
||||
<div id="mobile-nav-container"></div>
|
||||
<script>
|
||||
if (window.innerWidth <= 768) {
|
||||
fetch('/components/mobile-nav.html')
|
||||
.then(r => r.text())
|
||||
.then(html => {
|
||||
document.getElementById('mobile-nav-container').innerHTML = html;
|
||||
const scripts = document.getElementById('mobile-nav-container').querySelectorAll('script');
|
||||
scripts.forEach(s => eval(s.textContent));
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user