zioinfo-mail/workspace/zioinfo-web/frontend/src/components/layout/Header.jsx
DESKTOP-TKLFCPR\ython e9f0455c94 feat(zioinfo-web): (주)지오정보기술 홈페이지 Spring Boot+React 구현
Spring Boot 3.2 + React 18 SPA + SQLite
GUARDiA PMS ZIO-WEB-2026 프로젝트 연동
URP 스타일 디자인, GUARDiA 솔루션 상세 소개 (스크린샷 6장)
25개 ChatOps 봇 명령어 카탈로그, Messenger Bot 시나리오 데모
manual/17 개발가이드 생성

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 08:27:40 +09:00

160 lines
5.7 KiB
JavaScript

import React, { useState, useEffect, useCallback } from 'react';
import { Link, useLocation } from 'react-router-dom';
import './Header.css';
const MENU = [
{
id: 'company', label: '회사소개',
children: [
{ label: 'CEO 인사말', path: '/company/greeting' },
{ label: '연혁', path: '/company/history' },
{ label: '조직도', path: '/company/organization' },
{ label: 'CI 소개', path: '/company/ci' },
{ label: '오시는 길', path: '/company/location' },
]
},
{
id: 'solution', label: '솔루션',
children: [
{ label: 'GUARDiA ITSM', path: '/solution/guardia', badge: 'NEW' },
{ label: 'ERP', path: '/solution/erp' },
{ label: 'CRM', path: '/solution/crm' },
{ label: 'BI', path: '/solution/bi' },
]
},
{
id: 'business', label: '사업실적',
children: [
{ label: '구축 레퍼런스', path: '/business/reference' },
{ label: '파트너', path: '/business/partner' },
]
},
{
id: 'support', label: '고객지원',
children: [
{ label: '공지사항', path: '/support/notice' },
{ label: 'FAQ', path: '/support/faq' },
{ label: '카탈로그', path: '/support/catalog' },
{ label: '문의하기', path: '/support/contact' },
]
},
{
id: 'recruit', label: '채용',
children: [
{ label: '채용공고', path: '/recruit/jobs' },
{ label: '복리후생', path: '/recruit/welfare' },
{ label: '지원하기', path: '/recruit/apply' },
]
},
{
id: 'news', label: '뉴스',
children: [
{ label: '뉴스룸', path: '/news/newsroom' },
{ label: '기술 블로그', path: '/news/blog' },
]
},
];
export default function Header() {
const [scrolled, setScrolled] = useState(false);
const [activeMenu, setActiveMenu] = useState(null);
const [mobileOpen, setMobileOpen] = useState(false);
const location = useLocation();
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 60);
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
useEffect(() => {
setMobileOpen(false);
setActiveMenu(null);
}, [location]);
const isActive = (menu) =>
menu.children?.some(c => location.pathname.startsWith(c.path));
return (
<>
{/* 접근성 스킵 링크 */}
<a href="#main-content" className="skip-link">본문 바로가기</a>
<header className={`header ${scrolled ? 'scrolled' : ''} ${mobileOpen ? 'mobile-open' : ''}`}
role="banner">
<div className="header-inner container">
{/* 로고 */}
<Link to="/" className="logo" aria-label="(주)지오정보기술 홈으로">
<img src="/logo.png" alt="(주)지오정보기술 로고" height="40"
onError={e => { e.target.style.display='none'; e.target.nextSibling.style.display='flex'; }} />
<span className="logo-text" style={{display:'none'}}>
<strong>Zio</strong>Info
</span>
</Link>
{/* 데스크톱 메뉴 */}
<nav className="nav-desktop" role="navigation" aria-label="주요 메뉴">
{MENU.map(menu => (
<div key={menu.id}
className={`nav-item ${isActive(menu) ? 'active' : ''}`}
onMouseEnter={() => setActiveMenu(menu.id)}
onMouseLeave={() => setActiveMenu(null)}>
<button className="nav-trigger" aria-haspopup="true"
aria-expanded={activeMenu === menu.id}>
{menu.label}
</button>
{activeMenu === menu.id && (
<div className="dropdown" role="menu">
{menu.children.map(child => (
<Link key={child.path} to={child.path}
className={`dropdown-item ${location.pathname === child.path ? 'current' : ''}`}
role="menuitem">
{child.label}
{child.badge && <span className="badge badge-new">{child.badge}</span>}
</Link>
))}
</div>
)}
</div>
))}
</nav>
{/* 문의 버튼 */}
<Link to="/support/contact" className="btn btn-primary btn-sm header-cta">
문의하기
</Link>
{/* 햄버거 (모바일) */}
<button className="hamburger" aria-label="모바일 메뉴"
aria-expanded={mobileOpen}
onClick={() => setMobileOpen(v => !v)}>
<span/><span/><span/>
</button>
</div>
{/* 모바일 메뉴 */}
{mobileOpen && (
<nav className="nav-mobile" role="navigation" aria-label="모바일 메뉴">
{MENU.map(menu => (
<details key={menu.id} className="mobile-group">
<summary className="mobile-group-header">{menu.label}</summary>
<div className="mobile-children">
{menu.children.map(child => (
<Link key={child.path} to={child.path} className="mobile-child">
{child.label}
{child.badge && <span className="badge badge-new">{child.badge}</span>}
</Link>
))}
</div>
</details>
))}
<Link to="/support/contact" className="btn btn-primary" style={{margin:'16px'}}>
문의하기
</Link>
</nav>
)}
</header>
</>
);
}