Files
M-Project/frontend/mobile-fix.html
hyungi 41b557a709 Fix: 페이지 간 이동 시 로그아웃 문제 해결 및 기능 개선
- 토큰 저장 키 통일 (access_token으로 일관성 확보)
- 일일공수 페이지 API 스크립트 로딩 순서 수정
- 프로젝트 관리 페이지 비활성 프로젝트 표시 문제 해결
- 업로드 카테고리에 '기타' 항목 추가 (백엔드 schemas.py 포함)
- 비밀번호 변경 기능 API 연동으로 수정
- 프로젝트 드롭다운 z-index 문제 해결
- CORS 설정 및 Nginx 구성 개선
- 비밀번호 해싱 방식 pbkdf2_sha256으로 변경 (bcrypt 72바이트 제한 해결)
2025-10-25 07:22:20 +09:00

324 lines
12 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>모바일 프로젝트 문제 해결</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
padding: 20px;
margin: 0;
background: #f5f5f5;
}
.container {
max-width: 100%;
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
font-size: 24px;
margin-bottom: 20px;
}
.section {
margin-bottom: 30px;
padding: 15px;
background: #f9f9f9;
border-radius: 8px;
border: 1px solid #e0e0e0;
}
.status {
padding: 10px;
margin: 10px 0;
border-radius: 5px;
font-size: 14px;
}
.success { background: #d4edda; color: #155724; }
.error { background: #f8d7da; color: #721c24; }
.info { background: #d1ecf1; color: #0c5460; }
.warning { background: #fff3cd; color: #856404; }
button {
background: #3b82f6;
color: white;
border: none;
padding: 12px 24px;
border-radius: 6px;
font-size: 16px;
margin: 5px;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
button:active {
background: #2563eb;
}
select {
width: 100%;
padding: 12px;
font-size: 16px;
border: 2px solid #3b82f6;
border-radius: 8px;
background: white;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3e%3cpolyline points='6 9 12 15 18 9'%3e%3c/polyline%3e%3c/svg%3e");
background-repeat: no-repeat;
background-position: right 10px center;
background-size: 20px;
}
pre {
background: #f4f4f4;
padding: 10px;
border-radius: 5px;
overflow-x: auto;
font-size: 12px;
}
.test-select {
margin: 20px 0;
}
</style>
</head>
<body>
<div class="container">
<h1>🔧 모바일 프로젝트 문제 해결</h1>
<div class="section">
<h2>📱 디바이스 정보</h2>
<div id="deviceInfo"></div>
</div>
<div class="section">
<h2>💾 localStorage 상태</h2>
<div id="storageStatus"></div>
<button onclick="checkStorage()">localStorage 확인</button>
<button onclick="fixProjects()">프로젝트 복구</button>
</div>
<div class="section">
<h2>🧪 드롭다운 테스트</h2>
<div class="test-select">
<label>테스트 드롭다운:</label>
<select id="testSelect">
<option value="">선택하세요</option>
</select>
</div>
<button onclick="testDropdown()">드롭다운 테스트</button>
</div>
<div class="section">
<h2>📊 실제 프로젝트 드롭다운</h2>
<div class="test-select">
<label>프로젝트 선택:</label>
<select id="projectSelect">
<option value="">프로젝트를 선택하세요</option>
</select>
</div>
<button onclick="loadProjects()">프로젝트 로드</button>
</div>
<div class="section">
<h2>🔍 디버그 로그</h2>
<pre id="debugLog"></pre>
<button onclick="clearLog()">로그 지우기</button>
</div>
<div style="margin-top: 30px;">
<button onclick="location.href='index.html'">메인으로</button>
<button onclick="location.reload()">새로고침</button>
</div>
</div>
<script>
let logContent = '';
function log(message) {
const time = new Date().toLocaleTimeString('ko-KR');
logContent += `[${time}] ${message}\n`;
document.getElementById('debugLog').textContent = logContent;
}
function clearLog() {
logContent = '';
document.getElementById('debugLog').textContent = '';
}
// 디바이스 정보
function showDeviceInfo() {
const info = `
<div class="status info">
<strong>화면 크기:</strong> ${window.innerWidth} x ${window.innerHeight}<br>
<strong>User Agent:</strong> ${navigator.userAgent}<br>
<strong>플랫폼:</strong> ${navigator.platform}<br>
<strong>모바일 여부:</strong> ${window.innerWidth <= 768 ? '예' : '아니오'}
</div>
`;
document.getElementById('deviceInfo').innerHTML = info;
log('디바이스 정보 표시 완료');
}
// localStorage 확인
function checkStorage() {
log('localStorage 확인 시작');
const statusDiv = document.getElementById('storageStatus');
try {
// 프로젝트 데이터 확인
const projectData = localStorage.getItem('work-report-projects');
if (projectData) {
const projects = JSON.parse(projectData);
statusDiv.innerHTML = `
<div class="status success">
✅ 프로젝트 데이터 있음: ${projects.length}
</div>
<pre>${JSON.stringify(projects, null, 2)}</pre>
`;
log(`프로젝트 ${projects.length}개 발견`);
} else {
statusDiv.innerHTML = `
<div class="status warning">
⚠️ 프로젝트 데이터 없음
</div>
`;
log('프로젝트 데이터 없음');
}
// 사용자 데이터 확인
const userData = localStorage.getItem('currentUser');
if (userData) {
const user = JSON.parse(userData);
statusDiv.innerHTML += `
<div class="status success">
✅ 사용자: ${user.username} (${user.role})
</div>
`;
log(`사용자: ${user.username}`);
}
} catch (error) {
statusDiv.innerHTML = `
<div class="status error">
❌ 에러: ${error.message}
</div>
`;
log(`에러: ${error.message}`);
}
}
// 프로젝트 복구
function fixProjects() {
log('프로젝트 복구 시작');
const projects = [
{
id: 1,
jobNo: 'TKR-25009R',
projectName: 'M Project',
isActive: true,
createdAt: new Date().toISOString(),
createdByName: '관리자'
},
{
id: 2,
jobNo: 'TKG-24011P',
projectName: 'TKG Project',
isActive: true,
createdAt: new Date().toISOString(),
createdByName: '관리자'
}
];
try {
localStorage.setItem('work-report-projects', JSON.stringify(projects));
log('프로젝트 데이터 저장 완료');
alert('프로젝트가 복구되었습니다!');
checkStorage();
loadProjects();
} catch (error) {
log(`복구 실패: ${error.message}`);
alert('복구 실패: ' + error.message);
}
}
// 드롭다운 테스트
function testDropdown() {
log('드롭다운 테스트 시작');
const select = document.getElementById('testSelect');
// 옵션 추가
select.innerHTML = '<option value="">선택하세요</option>';
for (let i = 1; i <= 5; i++) {
const option = document.createElement('option');
option.value = i;
option.textContent = `테스트 옵션 ${i}`;
select.appendChild(option);
}
log(`테스트 옵션 ${select.options.length - 1}개 추가됨`);
// 이벤트 리스너
select.onchange = function() {
log(`선택됨: ${this.value} - ${this.options[this.selectedIndex].text}`);
};
}
// 프로젝트 로드
function loadProjects() {
log('프로젝트 로드 시작');
const select = document.getElementById('projectSelect');
try {
const saved = localStorage.getItem('work-report-projects');
if (!saved) {
log('localStorage에 프로젝트 없음');
alert('프로젝트 데이터가 없습니다. "프로젝트 복구" 버튼을 눌러주세요.');
return;
}
const projects = JSON.parse(saved);
const activeProjects = projects.filter(p => p.isActive);
log(`활성 프로젝트 ${activeProjects.length}개 발견`);
// 드롭다운 초기화
select.innerHTML = '<option value="">프로젝트를 선택하세요</option>';
// 프로젝트 옵션 추가
activeProjects.forEach(project => {
const option = document.createElement('option');
option.value = project.id;
option.textContent = `${project.jobNo} - ${project.projectName}`;
select.appendChild(option);
log(`옵션 추가: ${project.jobNo} - ${project.projectName}`);
});
log(`드롭다운에 ${select.options.length - 1}개 프로젝트 표시됨`);
// 이벤트 리스너
select.onchange = function() {
log(`프로젝트 선택됨: ${this.value}`);
};
} catch (error) {
log(`프로젝트 로드 에러: ${error.message}`);
alert('프로젝트 로드 실패: ' + error.message);
}
}
// 페이지 로드 시 실행
window.onload = function() {
log('페이지 로드 완료');
showDeviceInfo();
checkStorage();
testDropdown();
loadProjects();
};
</script>
</body>
</html>