feat(frontend): 공통 UI 로더 및 대시보드 구조 개선
- auth-check, load-navbar, load-sidebar 리팩토링 - auth.js 모듈을 활용하여 코드 중복 제거 및 일관성 확보 - DOMParser를 사용하여 컴포넌트 로딩 시 화면 깜빡임 현상 해결 - user-dashboard에 API 연동을 위한 견고한 기반 코드 마련
This commit is contained in:
@@ -1,79 +1,27 @@
|
||||
// ✅ /js/auth-check.js
|
||||
// 토큰 검증과 권한 체크
|
||||
// /js/auth-check.js
|
||||
import { isLoggedIn, getUser, clearAuthData } from './auth.js';
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
function isValidJWT(token) {
|
||||
return typeof token === 'string' && token.split('.').length === 3;
|
||||
}
|
||||
|
||||
function getPayload(token) {
|
||||
try {
|
||||
return JSON.parse(atob(token.split('.')[1]));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!token || !isValidJWT(token)) {
|
||||
console.log('🚨 토큰이 없거나 유효하지 않음');
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
window.location.href = '/index.html';
|
||||
} else {
|
||||
const user = getPayload(token);
|
||||
const storedUser = JSON.parse(localStorage.getItem('user') || '{}');
|
||||
|
||||
console.log('🔐 JWT 사용자 정보:', user);
|
||||
console.log('💾 저장된 사용자 정보:', storedUser);
|
||||
|
||||
// 사용자 정보 우선순위: localStorage > JWT payload
|
||||
const currentUser = storedUser.access_level ? storedUser : user;
|
||||
|
||||
if (!currentUser || !currentUser.username || !currentUser.access_level) {
|
||||
console.log('🚨 사용자 정보가 유효하지 않음');
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
// 즉시 실행 함수로 스코프를 보호하고 로직을 실행
|
||||
(function() {
|
||||
if (!isLoggedIn()) {
|
||||
console.log('🚨 인증되지 않은 사용자. 로그인 페이지로 이동합니다.');
|
||||
clearAuthData(); // 만약을 위해 한번 더 정리
|
||||
window.location.href = '/index.html';
|
||||
} else {
|
||||
console.log('✅ 인증 성공:', currentUser.username, currentUser.access_level);
|
||||
|
||||
// 사용자 이름 표시
|
||||
const userNameElements = document.querySelectorAll('#user-name, .user-name');
|
||||
userNameElements.forEach(el => {
|
||||
if (el) el.textContent = currentUser.name || currentUser.username;
|
||||
});
|
||||
|
||||
// 🎯 역할별 메뉴 표시/숨김 처리
|
||||
const accessLevel = currentUser.access_level;
|
||||
|
||||
// 관리자 전용 메뉴
|
||||
if (accessLevel !== 'admin' && accessLevel !== 'system') {
|
||||
const adminOnly = document.querySelectorAll('.admin-only, .system-only');
|
||||
adminOnly.forEach(el => el.remove());
|
||||
}
|
||||
|
||||
// 그룹장 전용 메뉴
|
||||
if (accessLevel !== 'group_leader') {
|
||||
const groupLeaderOnly = document.querySelectorAll('.group-leader-only');
|
||||
groupLeaderOnly.forEach(el => el.remove());
|
||||
}
|
||||
|
||||
// 지원팀 전용 메뉴
|
||||
if (accessLevel !== 'support') {
|
||||
const supportOnly = document.querySelectorAll('.support-only');
|
||||
supportOnly.forEach(el => el.remove());
|
||||
}
|
||||
|
||||
// 일반 작업자 전용 메뉴
|
||||
if (accessLevel !== 'worker' && accessLevel !== 'user') {
|
||||
const workerOnly = document.querySelectorAll('.worker-only');
|
||||
workerOnly.forEach(el => el.remove());
|
||||
}
|
||||
|
||||
// 전역 사용자 정보 저장
|
||||
window.currentUser = currentUser;
|
||||
|
||||
console.log('🎭 역할별 메뉴 필터링 완료:', accessLevel);
|
||||
return; // 이후 코드 실행 방지
|
||||
}
|
||||
}
|
||||
|
||||
const currentUser = getUser();
|
||||
|
||||
// 사용자 정보가 유효한지 확인 (토큰은 있지만 유저 정보가 깨졌을 경우)
|
||||
if (!currentUser || !currentUser.username || !currentUser.role) {
|
||||
console.error('🚨 사용자 정보가 유효하지 않습니다. 강제 로그아웃 처리합니다.');
|
||||
clearAuthData();
|
||||
window.location.href = '/index.html';
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(`✅ ${currentUser.username}(${currentUser.role})님 인증 성공.`);
|
||||
|
||||
// 역할 기반 메뉴 제어 로직은 각 컴포넌트 로더(load-navbar.js 등)로 이전함.
|
||||
// 전역 변수 할당(window.currentUser) 제거.
|
||||
})();
|
||||
@@ -1,252 +1,144 @@
|
||||
// js/load-navbar.js
|
||||
// 네비게이션바 로드 및 프로필 드롭다운 기능 구현
|
||||
import { getUser, clearAuthData } from './auth.js';
|
||||
|
||||
// 역할 이름을 한글로 변환하는 맵
|
||||
const ROLE_NAMES = {
|
||||
admin: '관리자',
|
||||
system: '시스템 관리자',
|
||||
leader: '그룹장',
|
||||
user: '작업자',
|
||||
support: '지원팀',
|
||||
default: '사용자',
|
||||
};
|
||||
|
||||
/**
|
||||
* 사용자 역할에 따라 메뉴 항목을 필터링합니다.
|
||||
* @param {Document} doc - 파싱된 HTML 문서 객체
|
||||
* @param {string} userRole - 현재 사용자의 역할
|
||||
*/
|
||||
function filterMenuByRole(doc, userRole) {
|
||||
const selectors = [
|
||||
{ role: 'admin', selector: '.admin-only' },
|
||||
{ role: 'system', selector: '.system-only' },
|
||||
{ role: 'leader', selector: '.leader-only' },
|
||||
];
|
||||
|
||||
selectors.forEach(({ role, selector }) => {
|
||||
// 사용자가 해당 역할을 가지고 있지 않으면 메뉴 항목을 제거
|
||||
if (userRole !== role) {
|
||||
doc.querySelectorAll(selector).forEach(el => el.remove());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 네비게이션 바에 사용자 정보를 채웁니다.
|
||||
* @param {Document} doc - 파싱된 HTML 문서 객체
|
||||
* @param {object} user - 현재 사용자 객체
|
||||
*/
|
||||
function populateUserInfo(doc, user) {
|
||||
const displayName = user.name || user.username;
|
||||
const roleName = ROLE_NAMES[user.role] || ROLE_NAMES.default;
|
||||
|
||||
// 상단 바 사용자 이름
|
||||
const userNameEl = doc.getElementById('user-name');
|
||||
if (userNameEl) userNameEl.textContent = displayName;
|
||||
|
||||
// 상단 바 사용자 역할
|
||||
const userRoleEl = doc.getElementById('user-role');
|
||||
if (userRoleEl) userRoleEl.textContent = roleName;
|
||||
|
||||
// 드롭다운 메뉴 사용자 이름
|
||||
const dropdownNameEl = doc.getElementById('dropdown-user-fullname');
|
||||
if (dropdownNameEl) dropdownNameEl.textContent = displayName;
|
||||
|
||||
// 드롭다운 메뉴 사용자 아이디
|
||||
const dropdownIdEl = doc.getElementById('dropdown-user-id');
|
||||
if (dropdownIdEl) dropdownIdEl.textContent = `@${user.username}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 네비게이션 바와 관련된 모든 이벤트를 설정합니다.
|
||||
*/
|
||||
function setupNavbarEvents() {
|
||||
const userInfoDropdown = document.getElementById('user-info-dropdown');
|
||||
const profileDropdownMenu = document.getElementById('profile-dropdown-menu');
|
||||
|
||||
// 드롭다운 토글
|
||||
if (userInfoDropdown && profileDropdownMenu) {
|
||||
userInfoDropdown.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
profileDropdownMenu.classList.toggle('show');
|
||||
userInfoDropdown.classList.toggle('active');
|
||||
});
|
||||
}
|
||||
|
||||
// 로그아웃 버튼
|
||||
const logoutButton = document.getElementById('dropdown-logout');
|
||||
if (logoutButton) {
|
||||
logoutButton.addEventListener('click', () => {
|
||||
if (confirm('로그아웃 하시겠습니까?')) {
|
||||
clearAuthData();
|
||||
window.location.href = '/index.html';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 외부 클릭 시 드롭다운 닫기
|
||||
document.addEventListener('click', (e) => {
|
||||
if (profileDropdownMenu && !userInfoDropdown.contains(e.target) && !profileDropdownMenu.contains(e.target)) {
|
||||
profileDropdownMenu.classList.remove('show');
|
||||
userInfoDropdown.classList.remove('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 시간을 업데이트하는 함수
|
||||
*/
|
||||
function updateTime() {
|
||||
const timeElement = document.getElementById('current-time');
|
||||
if (timeElement) {
|
||||
const now = new Date();
|
||||
timeElement.textContent = now.toLocaleTimeString('ko-KR', { hour12: false });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 메인 로직: DOMContentLoaded 시 실행
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const navbarContainer = document.getElementById('navbar-container');
|
||||
if (!navbarContainer) return;
|
||||
|
||||
const currentUser = getUser();
|
||||
if (!currentUser) return; // 사용자가 없으면 아무 작업도 하지 않음
|
||||
|
||||
try {
|
||||
// navbar.html 파일 로드
|
||||
const res = await fetch('/components/navbar.html');
|
||||
const html = await res.text();
|
||||
|
||||
// navbar 컨테이너 찾기
|
||||
const container = document.getElementById('navbar-container') || document.getElementById('navbar-placeholder');
|
||||
if (!container) {
|
||||
console.error('네비게이션 컨테이너를 찾을 수 없습니다');
|
||||
return;
|
||||
}
|
||||
|
||||
// HTML 삽입
|
||||
container.innerHTML = html;
|
||||
const response = await fetch('/components/navbar.html');
|
||||
const htmlText = await response.text();
|
||||
|
||||
// 토큰 확인
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) return;
|
||||
// 1. 텍스트를 가상 DOM으로 파싱
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(htmlText, 'text/html');
|
||||
|
||||
// 역할 매핑 테이블
|
||||
const roleMap = {
|
||||
worker: '작업자',
|
||||
group_leader: '그룹장',
|
||||
groupleader: '그룹장',
|
||||
leader: '리더',
|
||||
supervisor: '감독자',
|
||||
team_leader: '팀장',
|
||||
support_team: '지원팀',
|
||||
support: '지원팀',
|
||||
admin_ceo: '업무관리자',
|
||||
admin_plant: '시스템관리자',
|
||||
admin: '관리자',
|
||||
administrator: '관리자',
|
||||
system: '시스템관리자'
|
||||
};
|
||||
// 2. DOM에 삽입하기 *전*에 내용 수정
|
||||
filterMenuByRole(doc, currentUser.role);
|
||||
populateUserInfo(doc, currentUser);
|
||||
|
||||
// JWT 토큰 파싱
|
||||
let payload;
|
||||
try {
|
||||
payload = JSON.parse(atob(token.split('.')[1]));
|
||||
} catch (err) {
|
||||
console.warn('JWT 파싱 실패:', err);
|
||||
return;
|
||||
}
|
||||
// 3. 수정 완료된 HTML을 실제 DOM에 삽입 (깜빡임 방지)
|
||||
navbarContainer.innerHTML = doc.body.innerHTML;
|
||||
|
||||
// 저장된 사용자 정보 확인
|
||||
const storedUser = JSON.parse(localStorage.getItem('user') || '{}');
|
||||
const currentUser = storedUser.access_level ? storedUser : payload;
|
||||
// 4. DOM에 삽입된 후에 이벤트 리스너 설정
|
||||
setupNavbarEvents();
|
||||
|
||||
// ✅ 사용자 정보 표시
|
||||
const nameEl = document.getElementById('user-name');
|
||||
if (nameEl) {
|
||||
nameEl.textContent = currentUser.name || currentUser.username || '사용자';
|
||||
}
|
||||
|
||||
const roleEl = document.getElementById('user-role');
|
||||
if (roleEl) {
|
||||
const accessLevel = (currentUser.access_level || '').toLowerCase();
|
||||
const roleName = roleMap[accessLevel] || '사용자';
|
||||
roleEl.textContent = roleName;
|
||||
}
|
||||
|
||||
// ✅ 드롭다운 헤더 사용자 정보
|
||||
const dropdownFullname = document.getElementById('dropdown-user-fullname');
|
||||
if (dropdownFullname) {
|
||||
dropdownFullname.textContent = currentUser.name || currentUser.username || '사용자';
|
||||
}
|
||||
|
||||
const dropdownUserId = document.getElementById('dropdown-user-id');
|
||||
if (dropdownUserId) {
|
||||
dropdownUserId.textContent = `@${currentUser.username || 'user'}`;
|
||||
}
|
||||
|
||||
// ✅ 현재 시간 업데이트 시작
|
||||
// 5. 실시간 시간 업데이트 시작
|
||||
updateTime();
|
||||
setInterval(updateTime, 1000);
|
||||
|
||||
// ✅ 프로필 드롭다운 이벤트 설정
|
||||
const userInfoDropdown = document.getElementById('user-info-dropdown');
|
||||
const profileDropdownMenu = document.getElementById('profile-dropdown-menu');
|
||||
|
||||
if (userInfoDropdown && profileDropdownMenu) {
|
||||
// 드롭다운 토글
|
||||
userInfoDropdown.addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
const isOpen = profileDropdownMenu.classList.contains('show');
|
||||
|
||||
if (isOpen) {
|
||||
closeProfileDropdown();
|
||||
} else {
|
||||
openProfileDropdown();
|
||||
}
|
||||
});
|
||||
console.log('✅ 네비게이션 바 로딩 완료');
|
||||
|
||||
// 드롭다운 외부 클릭 시 닫기
|
||||
document.addEventListener('click', function(e) {
|
||||
if (!userInfoDropdown.contains(e.target) && !profileDropdownMenu.contains(e.target)) {
|
||||
closeProfileDropdown();
|
||||
}
|
||||
});
|
||||
|
||||
// ESC 키로 닫기
|
||||
document.addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Escape') {
|
||||
closeProfileDropdown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ 대시보드 버튼 이벤트
|
||||
const dashboardBtn = document.querySelector('.dashboard-btn');
|
||||
if (dashboardBtn) {
|
||||
dashboardBtn.addEventListener('click', function() {
|
||||
navigateToDashboard();
|
||||
});
|
||||
}
|
||||
|
||||
// ✅ 드롭다운 로그아웃 버튼
|
||||
const dropdownLogout = document.getElementById('dropdown-logout');
|
||||
if (dropdownLogout) {
|
||||
dropdownLogout.addEventListener('click', function() {
|
||||
logout();
|
||||
});
|
||||
}
|
||||
|
||||
console.log('✅ 네비게이션 바 로딩 및 이벤트 설정 완료:', {
|
||||
name: currentUser.name || currentUser.username,
|
||||
role: currentUser.access_level,
|
||||
dashboardBtn: !!dashboardBtn,
|
||||
profileDropdown: !!userInfoDropdown
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error('🔴 네비게이션 바 로딩 실패:', err);
|
||||
} catch (error) {
|
||||
console.error('🔴 네비게이션 바 로딩 중 오류 발생:', error);
|
||||
navbarContainer.innerHTML = '<p>네비게이션 바를 불러오는 데 실패했습니다.</p>';
|
||||
}
|
||||
});
|
||||
|
||||
// ✅ 프로필 드롭다운 열기
|
||||
function openProfileDropdown() {
|
||||
const userInfo = document.getElementById('user-info-dropdown');
|
||||
const dropdown = document.getElementById('profile-dropdown-menu');
|
||||
|
||||
if (userInfo && dropdown) {
|
||||
userInfo.classList.add('active');
|
||||
dropdown.classList.add('show');
|
||||
console.log('📂 프로필 드롭다운 열림');
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 프로필 드롭다운 닫기
|
||||
function closeProfileDropdown() {
|
||||
const userInfo = document.getElementById('user-info-dropdown');
|
||||
const dropdown = document.getElementById('profile-dropdown-menu');
|
||||
|
||||
if (userInfo && dropdown) {
|
||||
userInfo.classList.remove('active');
|
||||
dropdown.classList.remove('show');
|
||||
console.log('📁 프로필 드롭다운 닫힘');
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 시간 업데이트 함수
|
||||
function updateTime() {
|
||||
const now = new Date();
|
||||
const timeString = now.toLocaleTimeString('ko-KR', {
|
||||
hour12: false,
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit'
|
||||
});
|
||||
const timeElement = document.getElementById('current-time');
|
||||
if (timeElement) {
|
||||
timeElement.textContent = timeString;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ 역할별 대시보드 네비게이션
|
||||
function navigateToDashboard() {
|
||||
console.log('🏠 대시보드 버튼 클릭됨');
|
||||
|
||||
const user = JSON.parse(localStorage.getItem('user') || '{}');
|
||||
const accessLevel = (user.access_level || '').toLowerCase().trim();
|
||||
|
||||
console.log('👤 현재 사용자:', user);
|
||||
console.log('🔑 access_level:', accessLevel);
|
||||
|
||||
// 그룹장/리더 관련 키워드들
|
||||
const leaderKeywords = [
|
||||
'group_leader', 'groupleader', 'group-leader',
|
||||
'leader', 'supervisor', 'team_leader', 'teamleader',
|
||||
'그룹장', '팀장', '현장책임자'
|
||||
];
|
||||
|
||||
// 관리자 관련 키워드들
|
||||
const adminKeywords = [
|
||||
'admin', 'administrator', 'system',
|
||||
'관리자', '시스템관리자'
|
||||
];
|
||||
|
||||
// 지원팀 관련 키워드들
|
||||
const supportKeywords = [
|
||||
'support', 'support_team', 'supportteam',
|
||||
'지원팀', '지원'
|
||||
];
|
||||
|
||||
let targetUrl = '/pages/dashboard/user.html';
|
||||
|
||||
// 키워드 매칭
|
||||
if (leaderKeywords.some(keyword => accessLevel.includes(keyword.toLowerCase()))) {
|
||||
targetUrl = '/pages/dashboard/group-leader.html';
|
||||
console.log('✅ 그룹장 페이지로 이동');
|
||||
} else if (adminKeywords.some(keyword => accessLevel.includes(keyword.toLowerCase()))) {
|
||||
targetUrl = '/pages/dashboard/admin.html';
|
||||
console.log('✅ 관리자 페이지로 이동');
|
||||
} else if (supportKeywords.some(keyword => accessLevel.includes(keyword.toLowerCase()))) {
|
||||
targetUrl = '/pages/dashboard/support.html';
|
||||
console.log('✅ 지원팀 페이지로 이동');
|
||||
} else {
|
||||
console.log('✅ 일반 사용자 페이지로 이동');
|
||||
}
|
||||
|
||||
console.log('🎯 이동할 URL:', targetUrl);
|
||||
window.location.href = targetUrl;
|
||||
}
|
||||
|
||||
// ✅ 로그아웃 함수
|
||||
function logout() {
|
||||
console.log('🚪 로그아웃 버튼 클릭됨');
|
||||
|
||||
if (confirm('로그아웃 하시겠습니까?')) {
|
||||
console.log('✅ 로그아웃 확인됨');
|
||||
|
||||
// 로컬 스토리지 정리
|
||||
localStorage.removeItem('token');
|
||||
localStorage.removeItem('user');
|
||||
|
||||
console.log('🗑️ 로컬 스토리지 정리 완료');
|
||||
|
||||
// 부드러운 전환 효과
|
||||
document.body.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
console.log('🏠 로그인 페이지로 이동');
|
||||
window.location.href = '/index.html';
|
||||
}, 300);
|
||||
} else {
|
||||
console.log('❌ 로그아웃 취소됨');
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1,46 +1,67 @@
|
||||
// ✅ /js/load-sidebar.js (access_level 기반 메뉴 필터링)
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
try {
|
||||
// 1) 사이드바 HTML 로딩
|
||||
const res = await fetch('/components/sidebar.html');
|
||||
const html = await res.text();
|
||||
document.getElementById('sidebar-container').innerHTML = html;
|
||||
// /js/load-sidebar.js
|
||||
import { getUser } from './auth.js';
|
||||
|
||||
// 2) 토큰 존재 확인
|
||||
const token = localStorage.getItem('token');
|
||||
if (!token) return;
|
||||
/**
|
||||
* 사용자 역할에 따라 사이드바 메뉴 항목을 필터링합니다.
|
||||
* @param {Document} doc - 파싱된 HTML 문서 객체
|
||||
* @param {string} userRole - 현재 사용자의 역할
|
||||
*/
|
||||
function filterSidebarByRole(doc, userRole) {
|
||||
// 'system' 역할은 모든 메뉴를 볼 수 있으므로 필터링하지 않음
|
||||
if (userRole === 'system') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 역할과 그에 해당하는 클래스 선택자 매핑
|
||||
const roleClassMap = {
|
||||
admin: '.admin-only',
|
||||
leader: '.leader-only',
|
||||
user: '.user-only', // 또는 'worker-only' 등, sidebar.html에 정의된 클래스에 맞춰야 함
|
||||
support: '.support-only'
|
||||
};
|
||||
|
||||
// 3) JWT 파싱해서 access_level 추출
|
||||
let access;
|
||||
try {
|
||||
const payload = JSON.parse(atob(token.split('.')[1]));
|
||||
access = payload.access_level;
|
||||
} catch (err) {
|
||||
console.warn('JWT 파싱 실패:', err);
|
||||
return;
|
||||
// 모든 역할 기반 선택자를 가져옴
|
||||
const allRoleSelectors = Object.values(roleClassMap).join(', ');
|
||||
const allRoleElements = doc.querySelectorAll(allRoleSelectors);
|
||||
|
||||
allRoleElements.forEach(el => {
|
||||
// 요소가 현재 사용자 역할에 해당하는 클래스를 가지고 있는지 확인
|
||||
const userRoleSelector = roleClassMap[userRole];
|
||||
if (!userRoleSelector || !el.matches(userRoleSelector)) {
|
||||
el.remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 4) 시스템 계정은 전부 유지
|
||||
if (access === 'system') return;
|
||||
|
||||
// 5) 클래스 이름 목록
|
||||
const classMap = [
|
||||
'worker-only',
|
||||
'group-leader-only',
|
||||
'support-only',
|
||||
'admin-only',
|
||||
'system-only'
|
||||
];
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const sidebarContainer = document.getElementById('sidebar-container');
|
||||
if (!sidebarContainer) return;
|
||||
|
||||
// 6) 본인 권한에 해당하지 않는 요소 제거
|
||||
classMap.forEach(cls => {
|
||||
const required = cls.replace('-only', '').replace('-', '_'); // 'group-leader-only' → 'group_leader'
|
||||
if (access !== required) {
|
||||
document.querySelectorAll(`.${cls}`).forEach(el => el.remove());
|
||||
}
|
||||
});
|
||||
const currentUser = getUser();
|
||||
if (!currentUser) return; // 비로그인 상태면 사이드바를 로드하지 않음
|
||||
|
||||
} catch (err) {
|
||||
console.error('🔴 사이드바 로딩 실패:', err);
|
||||
try {
|
||||
const response = await fetch('/components/sidebar.html');
|
||||
if (!response.ok) {
|
||||
throw new Error(`사이드바 파일을 불러올 수 없습니다: ${response.statusText}`);
|
||||
}
|
||||
const htmlText = await response.text();
|
||||
|
||||
// 1. 텍스트를 가상 DOM으로 파싱
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(htmlText, 'text/html');
|
||||
|
||||
// 2. DOM에 삽입하기 *전*에 역할에 따라 메뉴 필터링
|
||||
filterSidebarByRole(doc, currentUser.role);
|
||||
|
||||
// 3. 수정 완료된 HTML을 실제 DOM에 삽입
|
||||
sidebarContainer.innerHTML = doc.body.innerHTML;
|
||||
|
||||
console.log('✅ 사이드바 로딩 및 필터링 완료');
|
||||
|
||||
} catch (error) {
|
||||
console.error('🔴 사이드바 로딩 실패:', error);
|
||||
sidebarContainer.innerHTML = '<p>메뉴 로딩 실패</p>';
|
||||
}
|
||||
});
|
||||
@@ -1,31 +1,93 @@
|
||||
import { API, getAuthHeaders } from '/js/api-config.js';
|
||||
// /js/user-dashboard.js
|
||||
import { getUser } from './auth.js';
|
||||
import { apiGet } from './api-helper.js'; // 개선된 api-helper를 사용합니다.
|
||||
|
||||
// 오늘 일정 로드
|
||||
/**
|
||||
* API를 호출하여 오늘의 작업 일정을 불러와 화면에 표시합니다.
|
||||
*/
|
||||
async function loadTodaySchedule() {
|
||||
// 구현 필요
|
||||
document.getElementById('today-schedule').innerHTML =
|
||||
'<p>오늘의 작업 일정이 여기에 표시됩니다.</p>';
|
||||
}
|
||||
const scheduleContainer = document.getElementById('today-schedule');
|
||||
scheduleContainer.innerHTML = '<p>📅 오늘의 작업 일정을 불러오는 중...</p>';
|
||||
|
||||
// 작업 통계 로드
|
||||
async function loadWorkStats() {
|
||||
// 구현 필요
|
||||
document.getElementById('work-stats').innerHTML =
|
||||
'<p>이번 달 작업 시간: 160시간</p>';
|
||||
}
|
||||
|
||||
// 환영 메시지 개인화
|
||||
function personalizeWelcome() {
|
||||
const user = window.currentUser;
|
||||
if (user) {
|
||||
document.getElementById('welcome-message').textContent =
|
||||
`${user.name}님, 환영합니다!`;
|
||||
try {
|
||||
// 예시: /api/dashboard/today-schedule 엔드포인트에서 데이터를 가져옵니다.
|
||||
// 실제 엔드포인트는 백엔드 구현에 따라 달라질 수 있습니다.
|
||||
const scheduleData = await apiGet('/dashboard/today-schedule');
|
||||
|
||||
if (scheduleData && scheduleData.length > 0) {
|
||||
const scheduleHtml = scheduleData.map(item => `
|
||||
<div class="schedule-item">
|
||||
<span class="time">${item.time}</span>
|
||||
<span class="task">${item.task_name}</span>
|
||||
<span class="status ${item.status}">${item.status_kor}</span>
|
||||
</div>
|
||||
`).join('');
|
||||
scheduleContainer.innerHTML = scheduleHtml;
|
||||
} else {
|
||||
scheduleContainer.innerHTML = '<p>오늘 예정된 작업이 없습니다.</p>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('오늘의 작업 일정 로드 실패:', error);
|
||||
scheduleContainer.innerHTML = '<p class="error">일정 정보를 불러오는 데 실패했습니다.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
// 초기화
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
/**
|
||||
* API를 호출하여 현재 사용자의 작업 통계를 불러와 화면에 표시합니다.
|
||||
*/
|
||||
async function loadWorkStats() {
|
||||
const statsContainer = document.getElementById('work-stats');
|
||||
statsContainer.innerHTML = '<p>📈 내 작업 현황을 불러오는 중...</p>';
|
||||
|
||||
try {
|
||||
// 예시: /api/dashboard/my-stats 엔드포인트에서 데이터를 가져옵니다.
|
||||
const statsData = await apiGet('/dashboard/my-stats');
|
||||
|
||||
if (statsData) {
|
||||
const statsHtml = `
|
||||
<div class="stat-item">
|
||||
<span>이번 주 작업 시간:</span>
|
||||
<strong>${statsData.weekly_hours || 0} 시간</strong>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span>이번 달 작업 시간:</span>
|
||||
<strong>${statsData.monthly_hours || 0} 시간</strong>
|
||||
</div>
|
||||
<div class="stat-item">
|
||||
<span>완료한 작업 수:</span>
|
||||
<strong>${statsData.completed_tasks || 0} 건</strong>
|
||||
</div>
|
||||
`;
|
||||
statsContainer.innerHTML = statsHtml;
|
||||
} else {
|
||||
statsContainer.innerHTML = '<p>표시할 통계 정보가 없습니다.</p>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('작업 통계 로드 실패:', error);
|
||||
statsContainer.innerHTML = '<p class="error">통계 정보를 불러오는 데 실패했습니다.</p>';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 환영 메시지를 사용자 이름으로 개인화합니다.
|
||||
*/
|
||||
function personalizeWelcome() {
|
||||
// 전역 변수 대신 auth.js 모듈을 통해 사용자 정보를 가져옵니다.
|
||||
const user = getUser();
|
||||
if (user) {
|
||||
const welcomeEl = document.getElementById('welcome-message');
|
||||
if (welcomeEl) {
|
||||
welcomeEl.textContent = `${user.name || user.username}님, 환영합니다! 오늘 하루도 안전하게 작업하세요.`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 페이지 초기화 함수
|
||||
function initializeDashboard() {
|
||||
personalizeWelcome();
|
||||
loadTodaySchedule();
|
||||
loadWorkStats();
|
||||
});
|
||||
}
|
||||
|
||||
// DOM이 로드되면 대시보드 초기화를 시작합니다.
|
||||
document.addEventListener('DOMContentLoaded', initializeDashboard);
|
||||
Reference in New Issue
Block a user