feat: 모든 페이지에 공통 헤더 적용 및 모바일 최적화
- 모든 HTML 페이지에 권한 기반 공통 헤더 적용 - 부적합 등록 페이지 모바일 최적화 (사진 업로드 UI 개선) - 부적합 조회 페이지에 모바일 캘린더 날짜 필터 적용 - 사용자별 권한에 따른 동적 페이지 제목 및 메시지 표시 Page Updates: - index.html: 모바일 친화적 사진 업로드 UI, 공통 헤더 적용 - issue-view.html: 터치/스와이프 캘린더 필터, 권한별 조회 제한 - daily-work.html: 공통 헤더 적용, 프로젝트 로딩 로직 개선 - project-management.html: 공통 헤더 적용, 권한 체크 강화 - admin.html: 페이지 권한 관리 UI 추가, 공통 헤더 적용 Mobile Optimizations: - 터치 타겟 최소 44px 보장 - 스와이프 제스처 지원 - 반응형 레이아웃 - 모바일 전용 UI 컴포넌트
This commit is contained in:
@@ -208,108 +208,86 @@
|
||||
|
||||
<!-- 메인 화면 -->
|
||||
<div id="mainScreen" class="hidden min-h-screen bg-gray-50">
|
||||
<!-- 헤더 -->
|
||||
<header class="bg-white shadow-sm sticky top-0 z-50">
|
||||
<div class="container mx-auto px-4 py-3">
|
||||
<div class="flex justify-between items-center">
|
||||
<h1 class="text-xl font-bold text-gray-800">
|
||||
<i class="fas fa-clipboard-check text-blue-500 mr-2"></i>작업보고서
|
||||
</h1>
|
||||
<div class="flex items-center gap-4">
|
||||
<span class="text-sm text-gray-600" id="userDisplay"></span>
|
||||
<button onclick="logout()" class="text-gray-500 hover:text-gray-700">
|
||||
<i class="fas fa-sign-out-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
<!-- 네비게이션 -->
|
||||
<nav class="bg-white border-b">
|
||||
<div class="container mx-auto px-4">
|
||||
<div id="navContainer" class="flex gap-2 py-2 overflow-x-auto">
|
||||
<a href="daily-work.html" class="nav-link" id="dailyWorkBtn" style="display: none;">
|
||||
<i class="fas fa-calendar-check mr-2"></i>일일 공수
|
||||
</a>
|
||||
<button class="nav-link active" onclick="showSection('report')">
|
||||
<i class="fas fa-camera-retro mr-2"></i>부적합 등록
|
||||
</button>
|
||||
<a href="issue-view.html" class="nav-link">
|
||||
<i class="fas fa-search mr-2"></i>부적합 조회
|
||||
</a>
|
||||
<button class="nav-link" onclick="showSection('list')" style="display:none;" id="listBtn">
|
||||
<i class="fas fa-list mr-2"></i>목록 관리
|
||||
</button>
|
||||
<button class="nav-link" onclick="showSection('summary')" style="display:none;" id="summaryBtn">
|
||||
<i class="fas fa-chart-bar mr-2"></i>보고서
|
||||
</button>
|
||||
<a href="project-management.html" class="nav-link" style="display:none;" id="projectBtn">
|
||||
<i class="fas fa-folder-open mr-2"></i>프로젝트 관리
|
||||
</a>
|
||||
<button class="nav-link" style="display:none;" id="adminBtn" onclick="handleAdminClick()">
|
||||
<i class="fas fa-users-cog mr-2"></i>관리
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<!-- 공통 헤더가 여기에 자동으로 삽입됩니다 -->
|
||||
|
||||
<!-- 부적합 등록 섹션 (모바일 최적화) -->
|
||||
<section id="reportSection" class="container mx-auto px-4 py-6 max-w-lg">
|
||||
<div class="bg-white rounded-xl shadow-sm p-6">
|
||||
<h2 class="text-lg font-semibold text-gray-800 mb-4">
|
||||
<i class="fas fa-exclamation-triangle text-yellow-500 mr-2"></i>부적합 사항 등록
|
||||
</h2>
|
||||
<section id="reportSection" class="container mx-auto px-3 py-4 max-w-md">
|
||||
<!-- 페이지 헤더 -->
|
||||
<div class="mb-4">
|
||||
<h1 class="text-xl font-bold text-gray-900 flex items-center">
|
||||
<i class="fas fa-exclamation-triangle text-yellow-500 mr-3"></i>
|
||||
부적합 등록
|
||||
</h1>
|
||||
<p class="text-sm text-gray-600 mt-1">현장에서 발견한 부적합 사항을 등록해주세요</p>
|
||||
</div>
|
||||
|
||||
<div class="bg-white rounded-2xl shadow-sm border border-gray-100 p-4">
|
||||
<!-- 진행 상태 표시 -->
|
||||
<div class="mb-6">
|
||||
<div class="flex items-center justify-between text-xs text-gray-500 mb-2">
|
||||
<span>등록 진행률</span>
|
||||
<span id="progressText">0/6</span>
|
||||
</div>
|
||||
<div class="w-full bg-gray-200 rounded-full h-2">
|
||||
<div id="progressBar" class="bg-blue-500 h-2 rounded-full transition-all duration-300" style="width: 0%"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form id="reportForm" class="space-y-4">
|
||||
<!-- 사진 업로드 (선택사항, 최대 2장) -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-2">
|
||||
사진 <span class="text-gray-500 text-xs">(선택사항, 최대 2장)</span>
|
||||
</label>
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-sm font-medium text-gray-700">
|
||||
📸 사진 첨부
|
||||
</label>
|
||||
<span class="text-xs text-gray-500 bg-gray-100 px-2 py-1 rounded-full">
|
||||
선택사항 • 최대 2장
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 사진 미리보기 영역 -->
|
||||
<div id="photoPreviewContainer" class="grid grid-cols-2 gap-3 mb-3" style="display: none;">
|
||||
<div id="photoPreviewContainer" class="grid grid-cols-2 gap-2 mb-3" style="display: none;">
|
||||
<!-- 첫 번째 사진 -->
|
||||
<div id="photo1Container" class="relative hidden">
|
||||
<img id="previewImg1" class="w-full h-32 object-cover rounded-lg">
|
||||
<button type="button" onclick="removePhoto(0)" class="absolute top-1 right-1 p-1 bg-red-500 text-white rounded-full text-xs hover:bg-red-600">
|
||||
<img id="previewImg1" class="w-full h-24 object-cover rounded-xl border-2 border-gray-200">
|
||||
<button type="button" onclick="removePhoto(0)" class="absolute -top-1 -right-1 w-6 h-6 bg-red-500 text-white rounded-full text-xs hover:bg-red-600 flex items-center justify-center shadow-lg">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<!-- 두 번째 사진 -->
|
||||
<div id="photo2Container" class="relative hidden">
|
||||
<img id="previewImg2" class="w-full h-32 object-cover rounded-lg">
|
||||
<button type="button" onclick="removePhoto(1)" class="absolute top-1 right-1 p-1 bg-red-500 text-white rounded-full text-xs hover:bg-red-600">
|
||||
<img id="previewImg2" class="w-full h-24 object-cover rounded-xl border-2 border-gray-200">
|
||||
<button type="button" onclick="removePhoto(1)" class="absolute -top-1 -right-1 w-6 h-6 bg-red-500 text-white rounded-full text-xs hover:bg-red-600 flex items-center justify-center shadow-lg">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 업로드 버튼들 -->
|
||||
<div class="flex gap-3">
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
<!-- 카메라 촬영 버튼 -->
|
||||
<div
|
||||
<button
|
||||
type="button"
|
||||
id="cameraUpload"
|
||||
class="flex-1 border-2 border-dashed border-blue-300 rounded-lg p-5 text-center cursor-pointer hover:border-blue-500 hover:bg-blue-50 transition-all"
|
||||
class="flex flex-col items-center justify-center p-4 border-2 border-dashed border-blue-300 rounded-xl text-center cursor-pointer hover:border-blue-500 hover:bg-blue-50 transition-all active:scale-95"
|
||||
onclick="openCamera()"
|
||||
>
|
||||
<i class="fas fa-camera text-4xl text-blue-500 mb-2"></i>
|
||||
<p class="text-gray-700 font-medium text-sm">📷 카메라</p>
|
||||
<p class="text-gray-500 text-xs mt-1">즉시 촬영</p>
|
||||
</div>
|
||||
<i class="fas fa-camera text-2xl text-blue-500 mb-2"></i>
|
||||
<span class="text-sm font-medium text-gray-700">카메라</span>
|
||||
<span class="text-xs text-gray-500">즉시 촬영</span>
|
||||
</button>
|
||||
|
||||
<!-- 갤러리 선택 버튼 -->
|
||||
<div
|
||||
<button
|
||||
type="button"
|
||||
id="galleryUpload"
|
||||
class="flex-1 border-2 border-dashed border-green-300 rounded-lg p-5 text-center cursor-pointer hover:border-green-500 hover:bg-green-50 transition-all"
|
||||
class="flex flex-col items-center justify-center p-4 border-2 border-dashed border-green-300 rounded-xl text-center cursor-pointer hover:border-green-500 hover:bg-green-50 transition-all active:scale-95"
|
||||
onclick="openGallery()"
|
||||
>
|
||||
<i class="fas fa-images text-4xl text-green-500 mb-2"></i>
|
||||
<p class="text-gray-700 font-medium text-sm">🖼️ 갤러리</p>
|
||||
<p class="text-gray-500 text-xs mt-1">사진 선택</p>
|
||||
</div>
|
||||
<i class="fas fa-images text-2xl text-green-500 mb-2"></i>
|
||||
<span class="text-sm font-medium text-gray-700">갤러리</span>
|
||||
<span class="text-xs text-gray-500">사진 선택</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 현재 상태 표시 -->
|
||||
@@ -484,6 +462,11 @@
|
||||
</script>
|
||||
<script src="/static/js/image-utils.js?v=20250917"></script>
|
||||
<script src="/static/js/date-utils.js?v=20250917"></script>
|
||||
<script src="/static/js/core/permissions.js?v=20251025"></script>
|
||||
<script src="/static/js/components/common-header.js?v=20251025"></script>
|
||||
<script src="/static/js/core/page-manager.js?v=20251025"></script>
|
||||
<script src="/static/js/core/page-preloader.js?v=20251025"></script>
|
||||
<script src="/static/js/core/keyboard-shortcuts.js?v=20251025"></script>
|
||||
<script>
|
||||
let currentUser = null;
|
||||
let currentPhotos = [];
|
||||
@@ -504,13 +487,22 @@
|
||||
// localStorage에도 백업 저장
|
||||
localStorage.setItem('currentUser', JSON.stringify(user));
|
||||
|
||||
document.getElementById('userDisplay').textContent = user.full_name || user.username;
|
||||
// 공통 헤더 초기화
|
||||
await window.commonHeader.init(user, 'issues_create');
|
||||
|
||||
// 페이지 접근 권한 체크 (부적합 등록 페이지)
|
||||
setTimeout(() => {
|
||||
if (!canAccessPage('issues_create')) {
|
||||
alert('부적합 등록 페이지에 접근할 권한이 없습니다.');
|
||||
window.location.href = '/issue-view.html';
|
||||
return;
|
||||
}
|
||||
}, 500);
|
||||
|
||||
// 사용자 정보는 공통 헤더에서 표시됨
|
||||
document.getElementById('loginScreen').classList.add('hidden');
|
||||
document.getElementById('mainScreen').classList.remove('hidden');
|
||||
|
||||
// 권한에 따른 메뉴 표시/숨김
|
||||
updateNavigation();
|
||||
|
||||
// 프로젝트 로드
|
||||
await loadProjects();
|
||||
|
||||
@@ -547,12 +539,11 @@
|
||||
localStorage.setItem('access_token', data.access_token);
|
||||
localStorage.setItem('currentUser', JSON.stringify(currentUser));
|
||||
|
||||
document.getElementById('userDisplay').textContent = currentUser.full_name || currentUser.username;
|
||||
// 사용자 정보는 공통 헤더에서 표시됨
|
||||
document.getElementById('loginScreen').classList.add('hidden');
|
||||
document.getElementById('mainScreen').classList.remove('hidden');
|
||||
|
||||
// 권한에 따른 메뉴 표시/숨김
|
||||
updateNavigation();
|
||||
// 공통 헤더에서 권한 기반 메뉴 처리됨
|
||||
|
||||
// 프로젝트 로드
|
||||
await loadProjects();
|
||||
@@ -571,32 +562,7 @@
|
||||
AuthAPI.logout();
|
||||
}
|
||||
|
||||
// 네비게이션 권한 업데이트
|
||||
function updateNavigation() {
|
||||
const listBtn = document.getElementById('listBtn');
|
||||
const summaryBtn = document.getElementById('summaryBtn');
|
||||
const adminBtn = document.getElementById('adminBtn');
|
||||
const projectBtn = document.getElementById('projectBtn');
|
||||
const dailyWorkBtn = document.getElementById('dailyWorkBtn');
|
||||
|
||||
if (currentUser.role === 'admin') {
|
||||
// 관리자는 모든 메뉴 표시 (비밀번호 변경은 사용자 관리 페이지에서)
|
||||
listBtn.style.display = '';
|
||||
summaryBtn.style.display = '';
|
||||
projectBtn.style.display = '';
|
||||
dailyWorkBtn.style.display = '';
|
||||
adminBtn.style.display = '';
|
||||
adminBtn.innerHTML = '<i class="fas fa-users-cog mr-2"></i>사용자 관리';
|
||||
} else {
|
||||
// 일반 사용자는 제한된 메뉴만 표시 (비밀번호 변경 버튼 표시)
|
||||
listBtn.style.display = 'none';
|
||||
summaryBtn.style.display = 'none';
|
||||
projectBtn.style.display = 'none';
|
||||
dailyWorkBtn.style.display = 'none';
|
||||
adminBtn.style.display = '';
|
||||
adminBtn.innerHTML = '<i class="fas fa-key mr-2"></i>비밀번호 변경';
|
||||
}
|
||||
}
|
||||
// 네비게이션은 공통 헤더에서 처리됨
|
||||
|
||||
// 관리 버튼 클릭 처리
|
||||
function handleAdminClick() {
|
||||
@@ -627,19 +593,7 @@
|
||||
// 선택된 섹션 표시
|
||||
document.getElementById(section + 'Section').classList.remove('hidden');
|
||||
|
||||
// 네비게이션 활성화 상태 변경
|
||||
document.querySelectorAll('.nav-link').forEach(link => link.classList.remove('active'));
|
||||
|
||||
// event가 있는 경우에만 활성화 처리
|
||||
if (typeof event !== 'undefined' && event.target && event.target.closest) {
|
||||
event.target.closest('.nav-link').classList.add('active');
|
||||
} else {
|
||||
// URL 해시로 접근한 경우 해당 버튼 찾아서 활성화
|
||||
const targetButton = document.querySelector(`[onclick="showSection('${section}')"]`);
|
||||
if (targetButton) {
|
||||
targetButton.classList.add('active');
|
||||
}
|
||||
}
|
||||
// 네비게이션 활성화는 공통 헤더에서 처리됨
|
||||
|
||||
// 부적합 등록 섹션으로 전환 시 프로젝트 다시 로드 (모바일 대응)
|
||||
if (section === 'report') {
|
||||
|
||||
Reference in New Issue
Block a user