zioinfo-mail/workspace/zioinfo-web/frontend/src/pages/admin/AdminLayout.jsx
DESKTOP-TKLFCPR\ython 8d1168f50c feat(history): company history DB management + admin CRUD
- CompanyHistory JPA entity (tb_company_history)
- CompanyHistoryRepository
- GET /api/history: DB-based grouped history (year + items[])
- Admin CRUD: GET/POST/PUT/DELETE /api/admin/history
- DataInitializer: 35 history items seeded from 2000 to 2026
- Company.jsx: useHistory() hook -> API fetch with fallback
- AdminHistory.jsx: year-grouped timeline CRUD UI
- AdminLayout: 회사 연혁 menu added

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 17:52:02 +09:00

112 lines
3.7 KiB
JavaScript

import { useEffect, useState } from 'react';
import { NavLink, Outlet, useNavigate, useLocation } from 'react-router-dom';
import './admin.css';
const NAV = [
{ section: '메인' },
{ path: '/admin/dashboard', icon: '📊', label: '대시보드' },
{ section: '콘텐츠 관리' },
{ path: '/admin/news', icon: '📰', label: '뉴스/공지사항' },
{ path: '/admin/history', icon: '📅', label: '회사 연혁' },
{ path: '/admin/recruit', icon: '👥', label: '채용공고' },
{ section: '고객 관리' },
{ path: '/admin/inquiries', icon: '📩', label: '문의 관리', badgeKey: 'pendingInquiries' },
{ path: '/admin/members', icon: '👤', label: '회원 관리' },
{ section: '시스템' },
{ path: '/admin/settings', icon: '⚙️', label: '설정' },
];
export default function AdminLayout() {
const navigate = useNavigate();
const location = useLocation();
const [user, setUser] = useState(null);
const [pageTitle, setPageTitle] = useState('대시보드');
const [badges, setBadges] = useState({});
useEffect(() => {
const token = localStorage.getItem('admin_token');
if (!token) { navigate('/admin/login'); return; }
const userData = JSON.parse(localStorage.getItem('admin_user') || '{}');
setUser(userData);
fetchBadges(token);
}, [navigate]);
useEffect(() => {
const map = {
'/admin/dashboard': '대시보드',
'/admin/news': '뉴스/공지사항 관리',
'/admin/inquiries': '문의 관리',
'/admin/recruit': '채용공고 관리',
'/admin/members': '회원 관리',
'/admin/settings': '설정',
};
setPageTitle(map[location.pathname] || '관리자');
}, [location.pathname]);
const fetchBadges = async (token) => {
try {
const res = await fetch('/api/admin/dashboard', {
headers: { Authorization: `Bearer ${token}` },
});
if (res.ok) {
const d = await res.json();
setBadges({ pendingInquiries: d.pendingInquiries || 0 });
}
} catch {}
};
const logout = () => {
localStorage.removeItem('admin_token');
localStorage.removeItem('admin_user');
navigate('/admin/login');
};
if (!user) return null;
return (
<div className="admin-wrap">
<aside className="admin-sidebar">
<div className="admin-sidebar-logo">
<h2>ZioInfo Admin</h2>
<span>()지오정보기술 관리자</span>
</div>
<nav className="admin-nav">
{NAV.map((item, i) =>
item.section ? (
<div key={i} className="admin-nav-section">{item.section}</div>
) : (
<NavLink key={item.path} to={item.path}
className={({ isActive }) => isActive ? 'active' : ''}>
<span className="nav-icon">{item.icon}</span>
{item.label}
{item.badgeKey && badges[item.badgeKey] > 0 && (
<span className="admin-nav-badge">{badges[item.badgeKey]}</span>
)}
</NavLink>
)
)}
</nav>
<div className="admin-sidebar-footer">
<button onClick={logout}>🚪 로그아웃</button>
</div>
</aside>
<main className="admin-main">
<div className="admin-topbar">
<h1>{pageTitle}</h1>
<div className="admin-topbar-right">
<span className="admin-user-badge">👤 {user.displayName || user.username}</span>
<a href="/" target="_blank" rel="noreferrer"
style={{ fontSize: 12, color: '#64748b', textDecoration: 'none' }}>
🌐 홈페이지 보기
</a>
</div>
</div>
<div className="admin-content">
<Outlet />
</div>
</main>
</div>
);
}