diff --git a/docs/RELEASE-NOTES.md b/docs/RELEASE-NOTES.md index 6817940..c9dee1d 100644 --- a/docs/RELEASE-NOTES.md +++ b/docs/RELEASE-NOTES.md @@ -4,6 +4,12 @@ ## [Unreleased] +### 추가 +- 공통 UI 피드백 반영 (#121) + - 2단 탭 네비게이션 (섹션 탭 + 서브 탭) + - 섹션 간 직접 이동 + - 메인화면 카드 높이 동기화 (CSS Grid) + ## [2026-03-31] ### 추가 diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 96c6356..416167e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -25,26 +25,40 @@ function AppLayout() { const isMainMenu = location.pathname === '/'; return ( -
-
- - }> - - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - -
+
+ {/* 메인 화면: 전체화면, 섹션 페이지: 탭 + 스크롤 콘텐츠 */} + {isMainMenu ? ( +
+ }> + + } /> + + +
+ ) : ( + <> +
+ +
+
+ }> + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + +
+ + )}
); diff --git a/frontend/src/components/Navbar.tsx b/frontend/src/components/Navbar.tsx index adf72d7..564edcf 100644 --- a/frontend/src/components/Navbar.tsx +++ b/frontend/src/components/Navbar.tsx @@ -1,49 +1,75 @@ -import { Link, useLocation } from 'react-router-dom'; +import { Link, useLocation, useNavigate } from 'react-router-dom'; import { useThemeContext } from '../contexts/ThemeContext'; -interface NavSection { - key: string; - title: string; - paths: string[]; - items: { path: string; label: string; icon: string }[]; +interface MenuItem { + id: string; + label: string; + path: string; } -const sections: NavSection[] = [ +interface MenuSection { + id: string; + label: string; + shortLabel: string; + icon: React.ReactNode; + defaultPath: string; + children: MenuItem[]; +} + +const MENU_STRUCTURE: MenuSection[] = [ { - key: 'collector', - title: 'S&P Collector', - paths: ['/dashboard', '/jobs', '/executions', '/recollects', '/schedules', '/schedule-timeline'], - items: [ - { path: '/dashboard', label: '대시보드', icon: '📊' }, - { path: '/executions', label: '실행 이력', icon: '📋' }, - { path: '/recollects', label: '재수집 이력', icon: '🔄' }, - { path: '/jobs', label: '작업', icon: '⚙️' }, - { path: '/schedules', label: '스케줄', icon: '🕐' }, - { path: '/schedule-timeline', label: '타임라인', icon: '📅' }, + id: 'collector', + label: 'S&P Collector', + shortLabel: 'Collector', + icon: ( + + + + ), + defaultPath: '/dashboard', + children: [ + { id: 'dashboard', label: '대시보드', path: '/dashboard' }, + { id: 'executions', label: '실행 이력', path: '/executions' }, + { id: 'recollects', label: '재수집 이력', path: '/recollects' }, + { id: 'jobs', label: '작업 관리', path: '/jobs' }, + { id: 'schedules', label: '스케줄', path: '/schedules' }, + { id: 'timeline', label: '타임라인', path: '/schedule-timeline' }, ], }, { - key: 'bypass', - title: 'S&P Bypass', - paths: ['/bypass-config'], - items: [ - { path: '/bypass-config', label: 'Bypass API', icon: '🔗' }, + id: 'bypass', + label: 'S&P Bypass', + shortLabel: 'Bypass', + icon: ( + + + + ), + defaultPath: '/bypass-config', + children: [ + { id: 'bypass-config', label: 'Bypass API', path: '/bypass-config' }, ], }, { - key: 'risk', - title: 'S&P Risk & Compliance', - paths: ['/screening-guide', '/risk-compliance-history'], - items: [ - { path: '/screening-guide', label: 'Screening Guide', icon: '⚖️' }, - { path: '/risk-compliance-history', label: 'Change History', icon: '📜' }, + id: 'risk', + label: 'S&P Risk & Compliance', + shortLabel: 'Risk & Compliance', + icon: ( + + + + ), + defaultPath: '/screening-guide', + children: [ + { id: 'screening-guide', label: 'Screening Guide', path: '/screening-guide' }, + { id: 'risk-compliance-history', label: 'Change History', path: '/risk-compliance-history' }, ], }, ]; -function getCurrentSection(pathname: string): NavSection | null { - for (const section of sections) { - if (section.paths.some((p) => pathname === p || pathname.startsWith(p + '/'))) { +function getCurrentSection(pathname: string): MenuSection | null { + for (const section of MENU_STRUCTURE) { + if (section.children.some((c) => pathname === c.path || pathname.startsWith(c.path + '/'))) { return section; } } @@ -52,56 +78,75 @@ function getCurrentSection(pathname: string): NavSection | null { export default function Navbar() { const location = useLocation(); + const navigate = useNavigate(); const { theme, toggle } = useThemeContext(); const currentSection = getCurrentSection(location.pathname); - // 메인 화면에서는 Navbar 숨김 + // 메인 화면에서는 숨김 if (!currentSection) return null; - const isActive = (path: string) => { - if (path === '/dashboard') return location.pathname === '/dashboard'; + const isActivePath = (path: string) => { return location.pathname === path || location.pathname.startsWith(path + '/'); }; return ( - +
); } diff --git a/frontend/src/theme/base.css b/frontend/src/theme/base.css index 7148406..de7d284 100644 --- a/frontend/src/theme/base.css +++ b/frontend/src/theme/base.css @@ -27,16 +27,19 @@ body { /* Main Menu Cards */ .gc-cards { padding: 2rem 0; - display: flex; - justify-content: center; - align-items: stretch; + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-auto-rows: 1fr; gap: 2rem; width: 80%; margin: 0 auto; } -.gc-cards > * { - flex: 1 1 0; +@media (max-width: 768px) { + .gc-cards { + grid-template-columns: 1fr; + width: 90%; + } } .gc-card {