feat: localStorage 문제 해결 및 시스템 개선
- localStorage와 DB ID 불일치 문제 해결 - 프로젝트별 보고서 시간 필터링 수정 - 일반 사용자에게 일일공수 메뉴 숨김 - 공통 헤더 및 인증 시스템 구현 - 프로젝트별 일일공수 분리 기능 추가 (ProjectDailyWork 모델) - IssuesAPI에서 project_id 누락 문제 수정 - 사용자 인증 통합 (TokenManager 기반)
This commit is contained in:
@@ -93,3 +93,17 @@ class DailyWork(Base):
|
||||
|
||||
# Relationships
|
||||
created_by = relationship("User", back_populates="daily_works")
|
||||
|
||||
class ProjectDailyWork(Base):
|
||||
__tablename__ = "project_daily_works"
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
date = Column(DateTime, nullable=False, index=True)
|
||||
project_id = Column(BigInteger, ForeignKey("projects.id"), nullable=False)
|
||||
hours = Column(Float, nullable=False)
|
||||
created_by_id = Column(Integer, ForeignKey("users.id"))
|
||||
created_at = Column(DateTime, default=get_kst_now)
|
||||
|
||||
# Relationships
|
||||
project = relationship("Project")
|
||||
created_by = relationship("User")
|
||||
|
||||
@@ -62,7 +62,7 @@ class LoginRequest(BaseModel):
|
||||
class IssueBase(BaseModel):
|
||||
category: IssueCategory
|
||||
description: str
|
||||
project_id: Optional[int] = None
|
||||
project_id: int
|
||||
|
||||
class IssueCreate(IssueBase):
|
||||
photo: Optional[str] = None # Base64 encoded image
|
||||
@@ -162,3 +162,21 @@ class ReportSummary(BaseModel):
|
||||
category_stats: CategoryStats
|
||||
completed_issues: int
|
||||
average_resolution_time: float
|
||||
|
||||
# Project Daily Work schemas
|
||||
class ProjectDailyWorkBase(BaseModel):
|
||||
date: datetime
|
||||
project_id: int
|
||||
hours: float
|
||||
|
||||
class ProjectDailyWorkCreate(ProjectDailyWorkBase):
|
||||
pass
|
||||
|
||||
class ProjectDailyWork(ProjectDailyWorkBase):
|
||||
id: int
|
||||
created_by_id: int
|
||||
created_at: datetime
|
||||
project: Project
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
25
backend/migrations/009_add_project_daily_works_table.sql
Normal file
25
backend/migrations/009_add_project_daily_works_table.sql
Normal file
@@ -0,0 +1,25 @@
|
||||
-- 프로젝트별 일일공수 테이블 생성
|
||||
CREATE TABLE project_daily_works (
|
||||
id SERIAL PRIMARY KEY,
|
||||
date DATE NOT NULL,
|
||||
project_id BIGINT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
|
||||
hours FLOAT NOT NULL,
|
||||
created_by_id INTEGER NOT NULL REFERENCES users(id),
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 인덱스 생성
|
||||
CREATE INDEX idx_project_daily_works_date ON project_daily_works(date);
|
||||
CREATE INDEX idx_project_daily_works_project_id ON project_daily_works(project_id);
|
||||
CREATE INDEX idx_project_daily_works_date_project ON project_daily_works(date, project_id);
|
||||
|
||||
-- 기존 일일공수 데이터를 프로젝트별로 마이그레이션 (M Project로)
|
||||
INSERT INTO project_daily_works (date, project_id, hours, created_by_id, created_at)
|
||||
SELECT
|
||||
date::date,
|
||||
1, -- M Project ID
|
||||
total_hours,
|
||||
created_by_id,
|
||||
created_at
|
||||
FROM daily_works
|
||||
WHERE total_hours > 0;
|
||||
@@ -17,6 +17,8 @@ async def create_issue(
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
print(f"DEBUG: 받은 issue 데이터: {issue}")
|
||||
print(f"DEBUG: project_id: {issue.project_id}")
|
||||
# 이미지 저장
|
||||
photo_path = None
|
||||
photo_path2 = None
|
||||
@@ -34,6 +36,7 @@ async def create_issue(
|
||||
photo_path=photo_path,
|
||||
photo_path2=photo_path2,
|
||||
reporter_id=current_user.id,
|
||||
project_id=issue.project_id,
|
||||
status=IssueStatus.new
|
||||
)
|
||||
db.add(db_issue)
|
||||
|
||||
@@ -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 = {};
|
||||
|
||||
@@ -88,7 +88,7 @@
|
||||
<nav class="bg-white border-b">
|
||||
<div class="container mx-auto px-4">
|
||||
<div 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>
|
||||
<a href="index.html" class="nav-link">
|
||||
@@ -187,7 +187,7 @@
|
||||
</main>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script src="/static/js/api.js?v=20250917"></script>
|
||||
<script src="/static/js/api.js?v=20251024m"></script>
|
||||
<script src="/static/js/date-utils.js?v=20250917"></script>
|
||||
<script>
|
||||
let currentUser = null;
|
||||
@@ -607,28 +607,22 @@
|
||||
|
||||
// 페이지 로드 시 사용자 정보 확인 및 관리자 배너 표시
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const currentUserData = localStorage.getItem('currentUser');
|
||||
if (!currentUserData) {
|
||||
// 메인 페이지와 동일한 방식으로 토큰에서 사용자 정보 가져오기
|
||||
const user = TokenManager.getUser();
|
||||
if (!user) {
|
||||
window.location.href = 'index.html';
|
||||
return;
|
||||
}
|
||||
|
||||
let currentUser;
|
||||
try {
|
||||
// JSON 파싱 시도
|
||||
currentUser = JSON.parse(currentUserData);
|
||||
} catch (e) {
|
||||
// JSON이 아니면 문자열로 처리 (기존 방식 호환)
|
||||
currentUser = { username: currentUserData };
|
||||
}
|
||||
currentUser = user;
|
||||
|
||||
// 사용자 표시
|
||||
const username = currentUser.username || currentUser;
|
||||
const displayName = currentUser.full_name || (username === 'hyungi' ? '관리자' : username);
|
||||
document.getElementById('userDisplay').textContent = `${displayName} (${username})`;
|
||||
const displayName = currentUser.full_name || currentUser.username;
|
||||
document.getElementById('userDisplay').textContent = `${displayName} (${currentUser.username})`;
|
||||
|
||||
// 관리자인 경우 메뉴 표시
|
||||
if (username === 'hyungi' || currentUser.role === 'admin') {
|
||||
if (currentUser.role === 'admin') {
|
||||
document.getElementById('dailyWorkBtn').style.display = '';
|
||||
document.getElementById('listBtn').style.display = '';
|
||||
document.getElementById('summaryBtn').style.display = '';
|
||||
document.getElementById('projectBtn').style.display = '';
|
||||
|
||||
@@ -135,6 +135,7 @@ const IssuesAPI = {
|
||||
const dataToSend = {
|
||||
category: issueData.category,
|
||||
description: issueData.description,
|
||||
project_id: issueData.project_id,
|
||||
photo: issueData.photos && issueData.photos.length > 0 ? issueData.photos[0] : null,
|
||||
photo2: issueData.photos && issueData.photos.length > 1 ? issueData.photos[1] : null
|
||||
};
|
||||
|
||||
68
frontend/static/js/auth-common.js
Normal file
68
frontend/static/js/auth-common.js
Normal file
@@ -0,0 +1,68 @@
|
||||
// 공통 인증 및 네비게이션 관리
|
||||
class AuthCommon {
|
||||
static init(currentPage = '') {
|
||||
// 토큰 기반 사용자 정보 확인
|
||||
const user = TokenManager.getUser();
|
||||
if (!user) {
|
||||
window.location.href = 'index.html';
|
||||
return null;
|
||||
}
|
||||
|
||||
// 전역 currentUser 설정
|
||||
window.currentUser = user;
|
||||
|
||||
// 헤더 생성 (페이지별로 다른 active 상태)
|
||||
CommonHeader.init(currentPage);
|
||||
|
||||
// 사용자 정보 표시
|
||||
this.updateUserDisplay(user);
|
||||
|
||||
// 네비게이션 권한 업데이트
|
||||
this.updateNavigation(user);
|
||||
|
||||
return user;
|
||||
}
|
||||
|
||||
static updateUserDisplay(user) {
|
||||
const userDisplayElement = document.getElementById('userDisplay');
|
||||
if (userDisplayElement) {
|
||||
const displayName = user.full_name || user.username;
|
||||
userDisplayElement.textContent = `${displayName} (${user.username})`;
|
||||
}
|
||||
}
|
||||
|
||||
static updateNavigation(user) {
|
||||
const isAdmin = user.role === 'admin';
|
||||
|
||||
// 관리자 전용 메뉴들
|
||||
const adminMenus = [
|
||||
'dailyWorkBtn',
|
||||
'listBtn',
|
||||
'summaryBtn',
|
||||
'projectBtn',
|
||||
'adminBtn'
|
||||
];
|
||||
|
||||
adminMenus.forEach(menuId => {
|
||||
const element = document.getElementById(menuId);
|
||||
if (element) {
|
||||
element.style.display = isAdmin ? '' : 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static logout() {
|
||||
AuthAPI.logout();
|
||||
}
|
||||
}
|
||||
|
||||
// 전역 함수들
|
||||
function logout() {
|
||||
AuthCommon.logout();
|
||||
}
|
||||
|
||||
function showSection(sectionName) {
|
||||
if (typeof window.showSection === 'function') {
|
||||
window.showSection(sectionName);
|
||||
}
|
||||
}
|
||||
106
frontend/static/js/common-header.js
Normal file
106
frontend/static/js/common-header.js
Normal file
@@ -0,0 +1,106 @@
|
||||
// 공통 헤더 생성 및 관리
|
||||
class CommonHeader {
|
||||
static create(currentPage = '') {
|
||||
return `
|
||||
<!-- 헤더 -->
|
||||
<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="AuthCommon.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 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>
|
||||
${this.getNavButton('index.html', 'mainBtn', 'fas fa-camera-retro', '부적합 등록', currentPage === 'main')}
|
||||
${this.getNavButton('issue-view.html', 'issueViewBtn', 'fas fa-search', '부적합 조회', currentPage === 'issue-view')}
|
||||
${this.getNavButtonInternal('list', 'listBtn', 'fas fa-list', '목록 관리', currentPage === 'list')}
|
||||
${this.getNavButtonInternal('summary', 'summaryBtn', 'fas fa-chart-bar', '보고서', currentPage === 'summary')}
|
||||
<a href="project-management.html" class="nav-link" style="display:none;" id="projectBtn">
|
||||
<i class="fas fa-folder-open mr-2"></i>프로젝트 관리
|
||||
</a>
|
||||
<a href="admin.html" class="nav-link" style="display:none;" id="adminBtn">
|
||||
<i class="fas fa-users-cog mr-2"></i>관리
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
`;
|
||||
}
|
||||
|
||||
static getNavButton(href, id, iconClass, text, isActive = false) {
|
||||
const activeClass = isActive ? ' active' : '';
|
||||
return `<a href="${href}" class="nav-link${activeClass}" id="${id}">
|
||||
<i class="${iconClass} mr-2"></i>${text}
|
||||
</a>`;
|
||||
}
|
||||
|
||||
static getNavButtonInternal(section, id, iconClass, text, isActive = false) {
|
||||
const activeClass = isActive ? ' active' : '';
|
||||
if (section === 'list' || section === 'summary') {
|
||||
return `<button class="nav-link${activeClass}" onclick="showSection('${section}')" style="display:none;" id="${id}">
|
||||
<i class="${iconClass} mr-2"></i>${text}
|
||||
</button>`;
|
||||
}
|
||||
return `<a href="index.html#${section}" class="nav-link${activeClass}" style="display:none;" id="${id}">
|
||||
<i class="${iconClass} mr-2"></i>${text}
|
||||
</a>`;
|
||||
}
|
||||
|
||||
static init(currentPage = '') {
|
||||
// 헤더 HTML 삽입
|
||||
const headerContainer = document.getElementById('header-container');
|
||||
if (headerContainer) {
|
||||
headerContainer.innerHTML = this.create();
|
||||
}
|
||||
|
||||
// 현재 페이지 활성화
|
||||
this.setActivePage(currentPage);
|
||||
}
|
||||
|
||||
static setActivePage(currentPage) {
|
||||
// 모든 nav-link에서 active 클래스 제거
|
||||
document.querySelectorAll('.nav-link').forEach(link => {
|
||||
link.classList.remove('active');
|
||||
});
|
||||
|
||||
// 현재 페이지에 active 클래스 추가
|
||||
const activeElement = document.getElementById(currentPage);
|
||||
if (activeElement) {
|
||||
activeElement.classList.add('active');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 관리자 버튼 클릭 처리 (전역 함수)
|
||||
function handleAdminClick() {
|
||||
if (window.currentUser && window.currentUser.role === 'admin') {
|
||||
window.location.href = 'admin.html';
|
||||
} else {
|
||||
// 비밀번호 변경 모달 표시
|
||||
if (typeof showPasswordChangeModal === 'function') {
|
||||
showPasswordChangeModal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 섹션 전환 (메인 페이지용)
|
||||
function showSection(sectionName) {
|
||||
if (typeof window.showSection === 'function') {
|
||||
window.showSection(sectionName);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user