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:
Hyungi Ahn
2025-10-25 09:01:32 +09:00
parent 25123be806
commit 610a171b25
5 changed files with 615 additions and 363 deletions

View File

@@ -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') {