feat: localStorage 문제 해결 및 시스템 개선
- localStorage와 DB ID 불일치 문제 해결 - 프로젝트별 보고서 시간 필터링 수정 - 일반 사용자에게 일일공수 메뉴 숨김 - 공통 헤더 및 인증 시스템 구현 - 프로젝트별 일일공수 분리 기능 추가 (ProjectDailyWork 모델) - IssuesAPI에서 project_id 누락 문제 수정 - 사용자 인증 통합 (TokenManager 기반)
This commit is contained in:
@@ -196,7 +196,7 @@
|
||||
<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">
|
||||
<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')">
|
||||
@@ -361,10 +361,10 @@
|
||||
<option value="pending">검토 필요</option>
|
||||
<option value="completed">검토 완료</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 날짜 필터 -->
|
||||
<div>
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">날짜</label>
|
||||
<select id="dateFilter" class="w-full px-3 py-2 border border-gray-300 rounded text-sm" onchange="displayIssueList()">
|
||||
<option value="">전체</option>
|
||||
@@ -372,8 +372,8 @@
|
||||
<option value="week">이번 주</option>
|
||||
<option value="month">이번 달</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 사용자 정의 날짜 -->
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 mb-1">특정 날짜</label>
|
||||
@@ -403,9 +403,9 @@
|
||||
<!-- 프로젝트 옵션들이 여기에 로드됩니다 -->
|
||||
</select>
|
||||
</div>
|
||||
<button onclick="printReport()" class="btn-primary px-4 py-2 rounded-lg text-sm">
|
||||
<i class="fas fa-print mr-2"></i>인쇄
|
||||
</button>
|
||||
<button onclick="printReport()" class="btn-primary px-4 py-2 rounded-lg text-sm">
|
||||
<i class="fas fa-print mr-2"></i>인쇄
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="reportContent">
|
||||
@@ -415,7 +415,7 @@
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script src="/static/js/api.js?v=20250917"></script>
|
||||
<script src="/static/js/api.js?v=20251024m"></script>
|
||||
<script src="/static/js/image-utils.js?v=20250917"></script>
|
||||
<script src="/static/js/date-utils.js?v=20250917"></script>
|
||||
<script>
|
||||
@@ -480,14 +480,15 @@
|
||||
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 {
|
||||
@@ -495,6 +496,7 @@
|
||||
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>비밀번호 변경';
|
||||
}
|
||||
@@ -811,6 +813,10 @@
|
||||
project_id: parseInt(projectId)
|
||||
};
|
||||
|
||||
console.log('DEBUG: 전송할 issueData:', issueData);
|
||||
console.log('DEBUG: projectId 원본:', projectId);
|
||||
console.log('DEBUG: parseInt(projectId):', parseInt(projectId));
|
||||
|
||||
const startTime = Date.now();
|
||||
await IssuesAPI.create(issueData);
|
||||
const uploadTime = Date.now() - startTime;
|
||||
@@ -873,59 +879,92 @@
|
||||
}
|
||||
|
||||
// 프로젝트 로드
|
||||
function loadProjects() {
|
||||
async function loadProjects() {
|
||||
// 1. 즉시 localStorage에서 로드 (빠른 응답)
|
||||
const saved = localStorage.getItem('work-report-projects');
|
||||
let projects = [];
|
||||
|
||||
if (saved) {
|
||||
const projects = JSON.parse(saved);
|
||||
const activeProjects = projects.filter(p => p.isActive);
|
||||
|
||||
// 부적합 등록 폼의 프로젝트 선택 (활성 프로젝트만)
|
||||
const projectSelect = document.getElementById('projectSelect');
|
||||
if (projectSelect) {
|
||||
projectSelect.innerHTML = '<option value="">프로젝트를 선택하세요</option>';
|
||||
|
||||
activeProjects.forEach(project => {
|
||||
const option = document.createElement('option');
|
||||
option.value = project.id;
|
||||
option.textContent = `${project.jobNo} - ${project.projectName}`;
|
||||
projectSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// 목록 관리의 프로젝트 필터 (모든 프로젝트)
|
||||
const listProjectFilter = document.getElementById('listProjectFilter');
|
||||
if (listProjectFilter) {
|
||||
listProjectFilter.innerHTML = '<option value="">전체 프로젝트</option>';
|
||||
|
||||
projects.forEach(project => {
|
||||
const option = document.createElement('option');
|
||||
option.value = project.id;
|
||||
option.textContent = `${project.jobNo} - ${project.projectName}${!project.isActive ? ' (비활성)' : ''}`;
|
||||
listProjectFilter.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// 보고서의 프로젝트 필터 (모든 프로젝트)
|
||||
const reportProjectFilter = document.getElementById('reportProjectFilter');
|
||||
if (reportProjectFilter) {
|
||||
reportProjectFilter.innerHTML = '<option value="">전체 프로젝트</option>';
|
||||
|
||||
projects.forEach(project => {
|
||||
const option = document.createElement('option');
|
||||
option.value = project.id;
|
||||
option.textContent = `${project.jobNo} - ${project.projectName}${!project.isActive ? ' (비활성)' : ''}`;
|
||||
reportProjectFilter.appendChild(option);
|
||||
});
|
||||
}
|
||||
projects = JSON.parse(saved);
|
||||
displayProjectsInUI(projects);
|
||||
}
|
||||
|
||||
// 2. 백그라운드에서 API 동기화
|
||||
try {
|
||||
const apiProjects = await ProjectsAPI.getAll(false);
|
||||
|
||||
// API 데이터를 localStorage 형식으로 변환
|
||||
const syncedProjects = apiProjects.map(p => ({
|
||||
id: p.id,
|
||||
jobNo: p.job_no,
|
||||
projectName: p.project_name,
|
||||
isActive: p.is_active,
|
||||
createdAt: p.created_at || new Date().toISOString(),
|
||||
createdByName: '관리자'
|
||||
}));
|
||||
|
||||
// localStorage 업데이트
|
||||
localStorage.setItem('work-report-projects', JSON.stringify(syncedProjects));
|
||||
|
||||
// UI 다시 업데이트 (동기화된 데이터로)
|
||||
displayProjectsInUI(syncedProjects);
|
||||
|
||||
} catch (error) {
|
||||
// API 실패해도 localStorage 데이터로 계속 동작
|
||||
console.log('API 동기화 실패, localStorage 데이터 사용:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function displayProjectsInUI(projects) {
|
||||
const activeProjects = projects.filter(p => p.isActive);
|
||||
|
||||
// 부적합 등록 폼의 프로젝트 선택 (활성 프로젝트만)
|
||||
const projectSelect = document.getElementById('projectSelect');
|
||||
if (projectSelect) {
|
||||
projectSelect.innerHTML = '<option value="">프로젝트를 선택하세요</option>';
|
||||
|
||||
activeProjects.forEach(project => {
|
||||
const option = document.createElement('option');
|
||||
option.value = project.id;
|
||||
option.textContent = `${project.jobNo} - ${project.projectName}`;
|
||||
projectSelect.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// 목록 관리의 프로젝트 필터 (모든 프로젝트)
|
||||
const listProjectFilter = document.getElementById('listProjectFilter');
|
||||
if (listProjectFilter) {
|
||||
listProjectFilter.innerHTML = '<option value="">전체 프로젝트</option>';
|
||||
|
||||
projects.forEach(project => {
|
||||
const option = document.createElement('option');
|
||||
option.value = project.id;
|
||||
option.textContent = `${project.jobNo} - ${project.projectName}${!project.isActive ? ' (비활성)' : ''}`;
|
||||
listProjectFilter.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// 보고서의 프로젝트 필터 (모든 프로젝트)
|
||||
const reportProjectFilter = document.getElementById('reportProjectFilter');
|
||||
if (reportProjectFilter) {
|
||||
reportProjectFilter.innerHTML = '<option value="">전체 프로젝트</option>';
|
||||
|
||||
projects.forEach(project => {
|
||||
const option = document.createElement('option');
|
||||
option.value = project.id;
|
||||
option.textContent = `${project.jobNo} - ${project.projectName}${!project.isActive ? ' (비활성)' : ''}`;
|
||||
reportProjectFilter.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// 전역 캐시에 저장
|
||||
window.projectsCache = projects;
|
||||
}
|
||||
|
||||
// 선택된 프로젝트 정보 가져오기
|
||||
function getSelectedProject(projectId) {
|
||||
const saved = localStorage.getItem('work-report-projects');
|
||||
if (saved) {
|
||||
const projects = JSON.parse(saved);
|
||||
return projects.find(p => p.id == projectId);
|
||||
if (window.projectsCache) {
|
||||
return window.projectsCache.find(p => p.id == projectId);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1090,7 +1129,7 @@
|
||||
div.innerHTML = `
|
||||
<div class="space-y-4">
|
||||
<!-- 프로젝트 정보 및 상태 (오른쪽 상단) -->
|
||||
<div class="flex justify-between items-start mb-2">
|
||||
<div class="flex justify-between items-start mb-2">
|
||||
<div class="flex items-center gap-2">
|
||||
${isCompleted ?
|
||||
'<div class="px-2 py-1 bg-green-100 text-green-800 rounded-full text-xs font-medium"><i class="fas fa-check-circle mr-1"></i>검토완료</div>' :
|
||||
@@ -1475,35 +1514,38 @@
|
||||
|
||||
// 프로젝트별 일일 공수 데이터 계산
|
||||
let dailyWorkTotal = 0;
|
||||
const dailyWorkData = JSON.parse(localStorage.getItem('daily-work-data') || '[]');
|
||||
|
||||
console.log('일일공수 데이터:', dailyWorkData);
|
||||
console.log('선택된 프로젝트 ID:', selectedProjectId);
|
||||
// localStorage의 프로젝트별 데이터 우선 사용 (프로젝트별 분리 지원)
|
||||
const dailyWorkData = JSON.parse(localStorage.getItem('daily-work-data') || '[]');
|
||||
|
||||
if (selectedProjectId) {
|
||||
// 선택된 프로젝트의 일일 공수만 합계
|
||||
dailyWorkData.forEach(dayWork => {
|
||||
console.log('일일공수 항목:', dayWork);
|
||||
if (dayWork.projects) {
|
||||
dayWork.projects.forEach(project => {
|
||||
console.log('프로젝트:', project, '매칭 확인:', project.projectId == selectedProjectId);
|
||||
if (project.projectId == selectedProjectId || project.projectId.toString() === selectedProjectId.toString()) {
|
||||
dailyWorkTotal += project.hours || 0;
|
||||
console.log('시간 추가:', project.hours, '누적:', dailyWorkTotal);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
console.log(`프로젝트 ID ${selectedProjectId}의 총 일일공수:`, dailyWorkTotal);
|
||||
} else {
|
||||
// 전체 프로젝트의 일일 공수 합계
|
||||
dailyWorkData.forEach(dayWork => {
|
||||
console.log('전체 일일공수 항목:', dayWork);
|
||||
dailyWorkTotal += dayWork.totalHours || 0;
|
||||
});
|
||||
try {
|
||||
// 백엔드 API에서 전체 일일공수 데이터 가져오기
|
||||
const apiDailyWork = await DailyWorkAPI.getAll();
|
||||
dailyWorkTotal = apiDailyWork.reduce((sum, work) => sum + (work.total_hours || 0), 0);
|
||||
console.log('API에서 가져온 전체 총 일일공수:', dailyWorkTotal);
|
||||
} catch (error) {
|
||||
// API 실패 시 localStorage 사용
|
||||
dailyWorkData.forEach(dayWork => {
|
||||
dailyWorkTotal += dayWork.totalHours || 0;
|
||||
});
|
||||
console.log('localStorage에서 가져온 전체 총 일일공수:', dailyWorkTotal);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('최종 일일공수 합계:', dailyWorkTotal);
|
||||
|
||||
// 부적합 사항 해결 시간 계산 (필터링된 이슈만)
|
||||
const issueHours = filteredIssues.reduce((sum, issue) => sum + (issue.work_hours || 0), 0);
|
||||
const categoryCount = {};
|
||||
|
||||
Reference in New Issue
Block a user