refactor(frontend): 대시보드 섹션 로더 로직 개선
- admin.html의 잘못된 스크립트 태그 제거 - load-sections.js 리팩토링으로 코드 중복 제거 및 성능 최적화 - Promise.all과 DOMParser를 활용하여 화면 깜빡임 없이 동적 컨텐츠 로드
This commit is contained in:
@@ -1,187 +1,104 @@
|
|||||||
// ✅ /js/load-sections.js - 확장 가능한 구조 (개선됨)
|
// /js/load-sections.js
|
||||||
import { API, getAuthHeaders } from '/js/api-config.js';
|
import { getUser } from './auth.js';
|
||||||
|
import { apiGet } from './api-helper.js';
|
||||||
|
|
||||||
// 역할별 섹션 매핑 (쉽게 추가/수정 가능)
|
// 역할에 따라 불러올 섹션 HTML 파일을 매핑합니다.
|
||||||
const SECTION_MAP = {
|
const SECTION_MAP = {
|
||||||
'admin': '/components/sections/admin-sections.html',
|
admin: '/components/sections/admin-sections.html',
|
||||||
'system': '/components/sections/admin-sections.html',
|
system: '/components/sections/admin-sections.html', // system도 admin과 동일한 섹션을 사용
|
||||||
'leader': '/components/sections/leader-sections.html',
|
leader: '/components/sections/leader-sections.html',
|
||||||
'group_leader': '/components/sections/leader-sections.html',
|
user: '/components/sections/user-sections.html',
|
||||||
'support': '/components/sections/support-sections.html',
|
default: '/components/sections/user-sections.html', // 역할이 없는 경우 기본값
|
||||||
'support_team': '/components/sections/support-sections.html',
|
|
||||||
'user': '/components/sections/user-sections.html',
|
|
||||||
'worker': '/components/sections/user-sections.html'
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 공통 섹션 (모든 사용자에게 표시)
|
/**
|
||||||
const COMMON_SECTIONS = '/components/sections/common-sections.html';
|
* API를 통해 대시보드 통계 데이터를 가져옵니다.
|
||||||
|
* @returns {Promise<object|null>} 통계 데이터 또는 에러 시 null
|
||||||
async function loadSections() {
|
*/
|
||||||
|
async function fetchDashboardStats() {
|
||||||
try {
|
try {
|
||||||
console.log('🔄 섹션 로딩 시작');
|
const today = new Date().toISOString().split('T')[0];
|
||||||
|
// 실제 백엔드 엔드포인트는 /api/dashboard/stats 와 같은 형태로 구현될 수 있습니다.
|
||||||
// 사용자 정보 확인
|
const stats = await apiGet(`/workreports?start=${today}&end=${today}`);
|
||||||
const token = localStorage.getItem('token');
|
// 필요한 데이터 형태로 가공 (예시)
|
||||||
if (!token) {
|
return {
|
||||||
console.log('❌ 토큰 없음, 로그인 페이지로 이동');
|
today_reports_count: stats.length,
|
||||||
window.location.href = '/index.html';
|
today_workers_count: new Set(stats.map(d => d.worker_id)).size,
|
||||||
return;
|
};
|
||||||
}
|
} catch (error) {
|
||||||
|
console.error('대시보드 통계 데이터 로드 실패:', error);
|
||||||
let userInfo = { role: 'user', access_level: 'worker' };
|
return null;
|
||||||
try {
|
|
||||||
const payload = JSON.parse(atob(token.split('.')[1]));
|
|
||||||
userInfo = {
|
|
||||||
role: payload.role || 'user',
|
|
||||||
access_level: payload.access_level || 'worker'
|
|
||||||
};
|
|
||||||
console.log('👤 사용자 정보:', userInfo);
|
|
||||||
} catch (err) {
|
|
||||||
console.warn('⚠️ JWT 파싱 실패:', err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ 컨테이너 찾기 - 더 안전한 방식
|
|
||||||
const possibleContainers = [
|
|
||||||
'#sections-container',
|
|
||||||
'#admin-sections',
|
|
||||||
'#user-sections',
|
|
||||||
'main[id$="-sections"]',
|
|
||||||
'#content-container main'
|
|
||||||
];
|
|
||||||
|
|
||||||
let container = null;
|
|
||||||
for (const selector of possibleContainers) {
|
|
||||||
container = document.querySelector(selector);
|
|
||||||
if (container) {
|
|
||||||
console.log(`✅ 컨테이너 발견: ${selector}`);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!container) {
|
|
||||||
console.error('❌ 섹션 컨테이너를 찾을 수 없습니다');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
container.innerHTML = '<div class="loading">콘텐츠를 불러오는 중...</div>';
|
|
||||||
|
|
||||||
// 역할별 섹션 파일 결정 (수정된 버전)
|
|
||||||
console.log('🔍 사용자 정보 디버깅:');
|
|
||||||
console.log('- userInfo.role:', userInfo.role);
|
|
||||||
console.log('- userInfo.access_level:', userInfo.access_level);
|
|
||||||
|
|
||||||
// role이 없으므로 access_level을 우선 사용
|
|
||||||
const effectiveRole = userInfo.access_level || userInfo.role || 'user';
|
|
||||||
const sectionFile = SECTION_MAP[effectiveRole] || SECTION_MAP['user'];
|
|
||||||
|
|
||||||
console.log(`📄 실제 사용될 역할: ${effectiveRole}`);
|
|
||||||
console.log(`📄 로딩할 섹션 파일: ${sectionFile}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. 공통 섹션 로드 (있을 경우)
|
|
||||||
let commonHtml = '';
|
|
||||||
try {
|
|
||||||
console.log('📄 공통 섹션 로딩 시도');
|
|
||||||
const commonRes = await fetch(COMMON_SECTIONS);
|
|
||||||
if (commonRes.ok) {
|
|
||||||
commonHtml = await commonRes.text();
|
|
||||||
console.log('✅ 공통 섹션 로딩 성공');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.log('ℹ️ 공통 섹션 없음 (정상)');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 역할별 섹션 로드
|
|
||||||
console.log('📄 역할별 섹션 로딩 시도');
|
|
||||||
const res = await fetch(sectionFile);
|
|
||||||
if (!res.ok) {
|
|
||||||
throw new Error(`HTTP ${res.status}: 섹션 파일을 찾을 수 없습니다 (${sectionFile})`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const roleHtml = await res.text();
|
|
||||||
console.log('✅ 역할별 섹션 로딩 성공');
|
|
||||||
|
|
||||||
// 3. 조합하여 표시
|
|
||||||
container.innerHTML = commonHtml + roleHtml;
|
|
||||||
console.log('✅ 섹션 HTML 렌더링 완료');
|
|
||||||
|
|
||||||
// 4. 추가 데이터 로드 (필요시)
|
|
||||||
await loadDynamicData(userInfo);
|
|
||||||
console.log('✅ 섹션 로딩 완료');
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
console.error('❌ 섹션 로드 실패:', err);
|
|
||||||
container.innerHTML = `
|
|
||||||
<div class="error-state">
|
|
||||||
<h3>❌ 콘텐츠를 불러올 수 없습니다</h3>
|
|
||||||
<p>오류: ${err.message}</p>
|
|
||||||
<p>잠시 후 다시 시도해주세요.</p>
|
|
||||||
<button onclick="location.reload()" class="btn btn-primary">🔄 새로고침</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (err) {
|
|
||||||
console.error('🔴 섹션 로딩 실패:', err);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 동적 데이터 로드 (예: 대시보드 통계)
|
/**
|
||||||
async function loadDynamicData(userInfo) {
|
* 가상 DOM에 통계 데이터를 채워 넣습니다.
|
||||||
console.log('📊 동적 데이터 로딩 시작');
|
* @param {Document} doc - 파싱된 HTML 문서 객체
|
||||||
|
* @param {object} stats - 통계 데이터
|
||||||
|
*/
|
||||||
|
function populateStatsData(doc, stats) {
|
||||||
|
if (!stats) return;
|
||||||
|
|
||||||
|
const todayStatsEl = doc.getElementById('today-stats');
|
||||||
|
if (todayStatsEl) {
|
||||||
|
todayStatsEl.innerHTML = `
|
||||||
|
<p>📝 오늘 등록된 작업: ${stats.today_reports_count}건</p>
|
||||||
|
<p>👥 참여 작업자: ${stats.today_workers_count}명</p>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 메인 로직: 페이지에 역할별 섹션을 로드하고 내용을 채웁니다.
|
||||||
|
*/
|
||||||
|
async function initializeSections() {
|
||||||
|
const mainContainer = document.querySelector('main[id$="-sections"]');
|
||||||
|
if (!mainContainer) {
|
||||||
|
console.error('섹션을 담을 메인 컨테이너를 찾을 수 없습니다.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mainContainer.innerHTML = '<div class="loading">콘텐츠를 불러오는 중...</div>';
|
||||||
|
|
||||||
|
const currentUser = getUser();
|
||||||
|
if (!currentUser) {
|
||||||
|
mainContainer.innerHTML = '<div class="error-state">사용자 정보를 찾을 수 없습니다.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 오늘의 작업 현황
|
const sectionFile = SECTION_MAP[currentUser.role] || SECTION_MAP.default;
|
||||||
const todayStats = document.getElementById('today-stats');
|
|
||||||
if (todayStats) {
|
try {
|
||||||
try {
|
// 1. 역할에 맞는 HTML 템플릿과 동적 데이터를 동시에 로드 (Promise.all 활용)
|
||||||
const today = new Date().toISOString().split('T')[0];
|
const [htmlResponse, statsData] = await Promise.all([
|
||||||
const res = await fetch(`${API}/workreports?start=${today}&end=${today}`, {
|
fetch(sectionFile),
|
||||||
headers: getAuthHeaders()
|
fetchDashboardStats()
|
||||||
});
|
]);
|
||||||
if (res.ok) {
|
|
||||||
const data = await res.json();
|
if (!htmlResponse.ok) {
|
||||||
todayStats.innerHTML = `
|
throw new Error(`섹션 파일(${sectionFile})을 불러오는 데 실패했습니다.`);
|
||||||
<p>📝 오늘 등록된 작업: ${data.length}건</p>
|
|
||||||
<p>👥 참여 작업자: ${new Set(data.map(d => d.worker_id)).size}명</p>
|
|
||||||
`;
|
|
||||||
console.log('✅ 오늘 통계 로딩 완료');
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('❌ 통계 로드 실패:', e);
|
|
||||||
if (todayStats) {
|
|
||||||
todayStats.innerHTML = '<p>⚠️ 통계를 불러올 수 없습니다</p>';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
const htmlText = await htmlResponse.text();
|
||||||
|
|
||||||
// 빠른 링크 활성화
|
// 2. 텍스트를 가상 DOM으로 파싱
|
||||||
initializeQuickLinks(userInfo);
|
const parser = new DOMParser();
|
||||||
|
const doc = parser.parseFromString(htmlText, 'text/html');
|
||||||
|
|
||||||
|
// 3. (필요 시) 역할 기반으로 가상 DOM 필터링 - 현재는 파일 자체가 역할별로 나뉘어 불필요
|
||||||
|
// filterByRole(doc, currentUser.role);
|
||||||
|
|
||||||
|
// 4. 가상 DOM에 동적 데이터 채우기
|
||||||
|
populateStatsData(doc, statsData);
|
||||||
|
|
||||||
|
// 5. 모든 수정이 완료된 HTML을 실제 DOM에 한 번에 삽입
|
||||||
|
mainContainer.innerHTML = doc.body.innerHTML;
|
||||||
|
|
||||||
|
console.log(`✅ ${currentUser.role} 역할의 섹션 로딩 완료.`);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('섹션 로딩 중 오류 발생:', error);
|
||||||
|
mainContainer.innerHTML = `<div class="error-state">콘텐츠 로딩에 실패했습니다: ${error.message}</div>`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 권한별 빠른 링크 표시/숨김
|
// DOM이 로드되면 섹션 초기화를 시작합니다.
|
||||||
function initializeQuickLinks(userInfo) {
|
document.addEventListener('DOMContentLoaded', initializeSections);
|
||||||
console.log('🔗 빠른 링크 초기화');
|
|
||||||
|
|
||||||
// 권한에 따라 특정 링크 숨기기
|
|
||||||
if (userInfo.role !== 'admin' && userInfo.access_level !== 'admin') {
|
|
||||||
document.querySelectorAll('.admin-only').forEach(el => {
|
|
||||||
el.style.display = 'none';
|
|
||||||
console.log('🔒 관리자 전용 링크 숨김');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userInfo.access_level !== 'group_leader') {
|
|
||||||
document.querySelectorAll('.leader-only').forEach(el => {
|
|
||||||
el.style.display = 'none';
|
|
||||||
console.log('🔒 그룹장 전용 링크 숨김');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('✅ 빠른 링크 초기화 완료');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 페이지 로드 시 실행
|
|
||||||
document.addEventListener('DOMContentLoaded', loadSections);
|
|
||||||
|
|
||||||
// 수동 새로고침 함수 (다른 곳에서 호출 가능)
|
|
||||||
window.refreshSections = loadSections;
|
|
||||||
@@ -29,7 +29,6 @@
|
|||||||
<script type="module" src="/js/load-sidebar.js"></script>
|
<script type="module" src="/js/load-sidebar.js"></script>
|
||||||
<script type="module" src="/js/load-sections.js"></script>
|
<script type="module" src="/js/load-sections.js"></script>
|
||||||
<!-- ✅ admin.js는 다른 모듈들이 로딩된 후 실행되도록 순서 조정 -->
|
<!-- ✅ admin.js는 다른 모듈들이 로딩된 후 실행되도록 순서 조정 -->
|
||||||
<script type="module" src="/components/sections/admin-sections.html"></script>
|
|
||||||
<script type="module" src="/js/admin.js"></script>
|
<script type="module" src="/js/admin.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user