feat: 프로젝트 관리 및 비밀번호 변경 기능 개선
주요 변경사항: - 비활성화된 프로젝트 관리 기능 추가 * 프로젝트 관리 페이지에 접을 수 있는 비활성 프로젝트 섹션 추가 * 비활성화된 프로젝트 복구 기능 제공 * 업로드 시에는 활성 프로젝트만 표시되도록 API 호출 분리 - 헤더 비밀번호 변경 기능 완전 구현 * CommonHeader.js에 완전한 비밀번호 변경 모달 구현 * ESC 키 지원, 실시간 유효성 검사, 토스트 메시지 추가 * 중복 코드 제거 및 통일된 함수 호출 구조 - 수신함 수정 내용 표시 문제 해결 * description 우선 표시로 최신 수정 내용 반영 * 관리함에서 final_description/final_category 업데이트 로직 추가 - 현황판 날짜 그룹화 개선 * 업로드일 기준에서 관리함 진입일(reviewed_at) 기준으로 변경 * Invalid Date 오류 해결 - 프로젝트 관리 페이지 JavaScript 오류 수정 * 중복 변수 선언 및 함수 참조 오류 해결 * 페이지 초기화 로직 개선 기술적 개선: - API 호출 최적화 (active_only 매개변수 명시적 전달) - 프론트엔드 표시 우선순위 통일 (description || final_description) - 백엔드 final_* 필드 업데이트 로직 추가
This commit is contained in:
@@ -142,6 +142,24 @@
|
||||
<!-- 프로젝트 목록이 여기에 표시됩니다 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 비활성화된 프로젝트 섹션 -->
|
||||
<div class="bg-white rounded-xl shadow-sm p-6 mt-6">
|
||||
<div class="flex justify-between items-center mb-4 cursor-pointer" onclick="toggleInactiveProjects()">
|
||||
<div class="flex items-center space-x-2">
|
||||
<i id="inactiveToggleIcon" class="fas fa-chevron-down transition-transform duration-200"></i>
|
||||
<h2 class="text-lg font-semibold text-gray-600">비활성화된 프로젝트</h2>
|
||||
<span id="inactiveProjectCount" class="bg-gray-100 text-gray-600 px-2 py-1 rounded-full text-sm font-medium">0</span>
|
||||
</div>
|
||||
<div class="text-sm text-gray-500">
|
||||
<i class="fas fa-info-circle mr-1"></i>클릭하여 펼치기/접기
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="inactiveProjectsList" class="space-y-3">
|
||||
<!-- 비활성화된 프로젝트 목록이 여기에 표시됩니다 -->
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- API 스크립트 먼저 로드 (최강 캐시 무력화) -->
|
||||
@@ -170,40 +188,50 @@
|
||||
console.log('🚀 캐시 버스터:', cacheBuster);
|
||||
</script>
|
||||
|
||||
<!-- API 스크립트 동적 로딩 -->
|
||||
<!-- 메인 스크립트 -->
|
||||
<script>
|
||||
// 최강 캐시 무력화로 API 스크립트 로드
|
||||
const timestamp = new Date().getTime();
|
||||
const random1 = Math.random() * 1000000;
|
||||
const random2 = Math.floor(Math.random() * 1000000);
|
||||
const cacheBuster = `${timestamp}-${random1}-${random2}`;
|
||||
|
||||
// 기존 api.js 스크립트 제거
|
||||
const existingScripts = document.querySelectorAll('script[src*="api.js"]');
|
||||
existingScripts.forEach(script => script.remove());
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.src = `/static/js/api.js?v=${timestamp}&cb=${random1}&bust=${random2}&force=${Date.now()}`;
|
||||
script.onload = function() {
|
||||
console.log('✅ API 스크립트 로드 완료 (project-management.html)');
|
||||
console.log('🔍 API_BASE_URL:', typeof API_BASE_URL !== 'undefined' ? API_BASE_URL : 'undefined');
|
||||
// API 로드 후 인증 확인 시작
|
||||
checkAdminAccess();
|
||||
};
|
||||
script.setAttribute('cache-control', 'no-cache, no-store, must-revalidate');
|
||||
script.setAttribute('pragma', 'no-cache');
|
||||
script.setAttribute('expires', '0');
|
||||
script.crossOrigin = 'anonymous';
|
||||
document.head.appendChild(script);
|
||||
|
||||
console.log('📱 프로젝트 관리 - 캐시 버스터:', cacheBuster);
|
||||
// 관리자 권한 확인 함수
|
||||
async function checkAdminAccess() {
|
||||
try {
|
||||
const currentUser = await AuthAPI.getCurrentUser();
|
||||
if (!currentUser || currentUser.role !== 'admin') {
|
||||
alert('관리자만 접근할 수 있습니다.');
|
||||
window.location.href = '/index.html';
|
||||
return;
|
||||
}
|
||||
// 권한 확인 후 페이지 초기화
|
||||
initializeProjectManagement();
|
||||
} catch (error) {
|
||||
console.error('권한 확인 실패:', error);
|
||||
alert('로그인이 필요합니다.');
|
||||
window.location.href = '/index.html';
|
||||
}
|
||||
}
|
||||
|
||||
// 프로젝트 관리 페이지 초기화 함수
|
||||
async function initializeProjectManagement() {
|
||||
try {
|
||||
console.log('🚀 프로젝트 관리 페이지 초기화 시작');
|
||||
|
||||
// 헤더 애니메이션 시작
|
||||
animateHeaderAppearance();
|
||||
|
||||
// 프로젝트 목록 로드
|
||||
await loadProjects();
|
||||
|
||||
console.log('✅ 프로젝트 관리 페이지 초기화 완료');
|
||||
} catch (error) {
|
||||
console.error('❌ 프로젝트 관리 페이지 초기화 실패:', error);
|
||||
alert('페이지 로드 중 오류가 발생했습니다.');
|
||||
}
|
||||
}
|
||||
</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>
|
||||
// 사용자 확인 (관리자만 접근 가능)
|
||||
// 전역 변수
|
||||
let currentUser = null;
|
||||
|
||||
// 애니메이션 함수들
|
||||
@@ -309,7 +337,7 @@
|
||||
console.log('프로젝트 로드 시작 (API)');
|
||||
|
||||
try {
|
||||
// API에서 프로젝트 로드
|
||||
// API에서 모든 프로젝트 로드 (활성/비활성 모두)
|
||||
const apiProjects = await ProjectsAPI.getAll(false);
|
||||
|
||||
// API 데이터를 그대로 사용 (필드명 통일)
|
||||
@@ -368,11 +396,16 @@
|
||||
|
||||
// 프로젝트 목록 표시
|
||||
function displayProjectList() {
|
||||
const container = document.getElementById('projectsList');
|
||||
container.innerHTML = '';
|
||||
const activeContainer = document.getElementById('projectsList');
|
||||
const inactiveContainer = document.getElementById('inactiveProjectsList');
|
||||
const inactiveCount = document.getElementById('inactiveProjectCount');
|
||||
|
||||
activeContainer.innerHTML = '';
|
||||
inactiveContainer.innerHTML = '';
|
||||
|
||||
if (projects.length === 0) {
|
||||
container.innerHTML = '<p class="text-gray-500 text-center py-8">등록된 프로젝트가 없습니다.</p>';
|
||||
activeContainer.innerHTML = '<p class="text-gray-500 text-center py-8">등록된 프로젝트가 없습니다.</p>';
|
||||
inactiveCount.textContent = '0';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -383,15 +416,12 @@
|
||||
console.log('전체 프로젝트:', projects.length, '개');
|
||||
console.log('활성 프로젝트:', activeProjects.length, '개');
|
||||
console.log('비활성 프로젝트:', inactiveProjects.length, '개');
|
||||
console.log('비활성 프로젝트 목록:', inactiveProjects);
|
||||
|
||||
// 비활성 프로젝트 개수 업데이트
|
||||
inactiveCount.textContent = inactiveProjects.length;
|
||||
|
||||
// 활성 프로젝트 표시
|
||||
if (activeProjects.length > 0) {
|
||||
const activeHeader = document.createElement('div');
|
||||
activeHeader.className = 'mb-4';
|
||||
activeHeader.innerHTML = '<h3 class="text-md font-semibold text-green-700"><i class="fas fa-check-circle mr-2"></i>활성 프로젝트 (' + activeProjects.length + '개)</h3>';
|
||||
container.appendChild(activeHeader);
|
||||
|
||||
activeProjects.forEach(project => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'border border-green-200 rounded-lg p-4 hover:shadow-md transition-shadow mb-3 bg-green-50';
|
||||
@@ -421,20 +451,17 @@
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
activeContainer.appendChild(div);
|
||||
});
|
||||
} else {
|
||||
activeContainer.innerHTML = '<p class="text-gray-500 text-center py-8">활성 프로젝트가 없습니다.</p>';
|
||||
}
|
||||
|
||||
// 비활성 프로젝트 표시
|
||||
if (inactiveProjects.length > 0) {
|
||||
const inactiveHeader = document.createElement('div');
|
||||
inactiveHeader.className = 'mb-4 mt-6';
|
||||
inactiveHeader.innerHTML = '<h3 class="text-md font-semibold text-gray-500"><i class="fas fa-pause-circle mr-2"></i>비활성 프로젝트 (' + inactiveProjects.length + '개)</h3>';
|
||||
container.appendChild(inactiveHeader);
|
||||
|
||||
inactiveProjects.forEach(project => {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'border border-gray-200 rounded-lg p-4 hover:shadow-md transition-shadow mb-3 bg-gray-50 opacity-75';
|
||||
div.className = 'border border-gray-200 rounded-lg p-4 hover:shadow-md transition-shadow mb-3 bg-gray-50';
|
||||
div.innerHTML = `
|
||||
<div class="flex justify-between items-start">
|
||||
<div class="flex-1">
|
||||
@@ -458,8 +485,28 @@
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
container.appendChild(div);
|
||||
inactiveContainer.appendChild(div);
|
||||
});
|
||||
} else {
|
||||
inactiveContainer.innerHTML = '<p class="text-gray-500 text-center py-8">비활성화된 프로젝트가 없습니다.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// 비활성 프로젝트 섹션 토글
|
||||
function toggleInactiveProjects() {
|
||||
const inactiveList = document.getElementById('inactiveProjectsList');
|
||||
const toggleIcon = document.getElementById('inactiveToggleIcon');
|
||||
|
||||
if (inactiveList.style.display === 'none') {
|
||||
// 펼치기
|
||||
inactiveList.style.display = 'block';
|
||||
toggleIcon.classList.remove('fa-chevron-right');
|
||||
toggleIcon.classList.add('fa-chevron-down');
|
||||
} else {
|
||||
// 접기
|
||||
inactiveList.style.display = 'none';
|
||||
toggleIcon.classList.remove('fa-chevron-down');
|
||||
toggleIcon.classList.add('fa-chevron-right');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user