Files
ev-charger-as/frontend/static/js/auth.js
byun d75bd5f358 UI 개선 — 모바일 사이드바 하단에 로그아웃 버튼 추가
모바일에서 nav-user가 숨겨져 로그아웃 불가한 문제 수정.
renderNav에서 사이드바 하단에 사용자명·역할 표시와 로그아웃 링크를 자동 주입.
모든 역할(관리자·정비사·제조사·옵저버) 공통 적용.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 16:01:51 +09:00

106 lines
4.1 KiB
JavaScript

const Auth = (() => {
const KEY_TOKEN = 'ev_token';
const KEY_ROLE = 'ev_role';
const KEY_NAME = 'ev_name';
const KEY_ID = 'ev_uid';
function save(token, role, name, id) {
localStorage.setItem(KEY_TOKEN, token);
localStorage.setItem(KEY_ROLE, role);
localStorage.setItem(KEY_NAME, name);
localStorage.setItem(KEY_ID, id);
}
function token() { return localStorage.getItem(KEY_TOKEN); }
function role() { return localStorage.getItem(KEY_ROLE); }
function name() { return localStorage.getItem(KEY_NAME); }
function uid() { return localStorage.getItem(KEY_ID); }
function logout() {
[KEY_TOKEN, KEY_ROLE, KEY_NAME, KEY_ID].forEach(k => localStorage.removeItem(k));
location.href = '/pages/login.html';
}
function require(allowedRoles) {
if (!token()) { logout(); return false; }
if (allowedRoles && !allowedRoles.includes(role())) {
alert('접근 권한이 없습니다.');
history.back();
return false;
}
return true;
}
function renderNav(el) {
if (!el) return;
el.innerHTML = `
<button class="nav-hamburger" onclick="Auth.toggleMobileNav()" aria-label="메뉴">☰</button>
<span class="nav-user">
<span>${name()} <small style="color:var(--accent)">[${role()}]</small></span>
<a onclick="Auth.logout()">로그아웃</a>
</span>`;
// 모바일 오버레이 삽입 (중복 방지)
if (!document.getElementById('mobileNavOverlay')) {
const ov = document.createElement('div');
ov.id = 'mobileNavOverlay';
ov.className = 'mobile-nav-overlay';
ov.addEventListener('click', closeMobileNav);
document.body.appendChild(ov);
}
// 사이드바 링크 클릭 시 드로어 닫기 + 하단 로그아웃 주입 (모바일용)
setTimeout(() => {
document.querySelectorAll('.sidebar a').forEach(a => {
a.addEventListener('click', closeMobileNav);
});
const sidebar = document.querySelector('.sidebar');
if (sidebar && !sidebar.querySelector('.sidebar-user-footer')) {
const footer = document.createElement('div');
footer.className = 'sidebar-user-footer';
footer.innerHTML = `
<div style="border-top:1px solid rgba(255,255,255,.12);margin:12px 0 4px;"></div>
<div style="padding:6px 20px;font-size:11px;color:rgba(255,255,255,.4);">${name()} <span style="color:var(--accent)">[${role()}]</span></div>
<a onclick="Auth.logout()" style="cursor:pointer;color:rgba(255,255,255,.6);">🚪 로그아웃</a>`;
sidebar.appendChild(footer);
}
}, 0);
}
function toggleMobileNav() {
const sidebar = document.querySelector('.sidebar');
const overlay = document.getElementById('mobileNavOverlay');
if (!sidebar) return;
const opening = !sidebar.classList.contains('mobile-open');
sidebar.classList.toggle('mobile-open', opening);
overlay?.classList.toggle('show', opening);
}
function closeMobileNav() {
document.querySelector('.sidebar')?.classList.remove('mobile-open');
document.getElementById('mobileNavOverlay')?.classList.remove('show');
}
function statusBadge(status) {
const map = {
pending_approval: '승인대기', pending: '접수', in_progress: '처리중',
done: '완료', waiting: '부품대기', revisit: '재방문', closed: '상황종료',
registered: '등록', reviewing: '검토중', developing: '개발중',
deployed: '배포완료', observer: '옵저버',
};
return `<span class="badge s-${status}">${map[status] || status}</span>`;
}
function costStatusBadge(s) {
const map = { pending:'미처리', billed:'청구완료', waived:'면제', settled:'정산완료' };
return `<span class="badge s-cost-${s}">${map[s] || s}</span>`;
}
function fmtDt(dt) {
if (!dt) return '-';
return new Date(dt).toLocaleString('ko-KR', {year:'numeric',month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'});
}
return { save, token, role, name, uid, logout, require, renderNav,
toggleMobileNav, closeMobileNav, statusBadge, costStatusBadge, fmtDt };
})();