diff --git a/frontend/index.html b/frontend/index.html index b92b03d..4958206 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -8,7 +8,7 @@ - S&P 배치 관리 + S&P Data Platform
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 7efdcfd..96c6356 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,11 +1,12 @@ import { lazy, Suspense } from 'react'; -import { BrowserRouter, Routes, Route } from 'react-router-dom'; +import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom'; import { ToastProvider, useToastContext } from './contexts/ToastContext'; import { ThemeProvider } from './contexts/ThemeContext'; import Navbar from './components/Navbar'; import ToastContainer from './components/Toast'; import LoadingSpinner from './components/LoadingSpinner'; +const MainMenu = lazy(() => import('./pages/MainMenu')); const Dashboard = lazy(() => import('./pages/Dashboard')); const Jobs = lazy(() => import('./pages/Jobs')); const Executions = lazy(() => import('./pages/Executions')); @@ -20,14 +21,17 @@ const RiskComplianceHistory = lazy(() => import('./pages/RiskComplianceHistory') function AppLayout() { const { toasts, removeToast } = useToastContext(); + const location = useLocation(); + const isMainMenu = location.pathname === '/'; return (
-
+
}> - } /> + } /> + } /> } /> } /> } /> diff --git a/frontend/src/components/Navbar.tsx b/frontend/src/components/Navbar.tsx index 142689f..adf72d7 100644 --- a/frontend/src/components/Navbar.tsx +++ b/frontend/src/components/Navbar.tsx @@ -1,58 +1,107 @@ import { Link, useLocation } from 'react-router-dom'; import { useThemeContext } from '../contexts/ThemeContext'; -const navItems = [ - { path: '/', label: '대시보드', icon: '📊' }, - { path: '/executions', label: '실행 이력', icon: '📋' }, - { path: '/recollects', label: '재수집 이력', icon: '🔄' }, - { path: '/jobs', label: '작업', icon: '⚙️' }, - { path: '/schedules', label: '스케줄', icon: '🕐' }, - { path: '/schedule-timeline', label: '타임라인', icon: '📅' }, - { path: '/bypass-config', label: 'Bypass API', icon: '🔗' }, - { path: '/screening-guide', label: 'Screening Guide', icon: '⚖️' }, - { path: '/risk-compliance-history', label: 'Change History', icon: '📜' }, +interface NavSection { + key: string; + title: string; + paths: string[]; + items: { path: string; label: string; icon: string }[]; +} + +const sections: NavSection[] = [ + { + 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: '📅' }, + ], + }, + { + key: 'bypass', + title: 'S&P Bypass', + paths: ['/bypass-config'], + items: [ + { path: '/bypass-config', label: 'Bypass API', icon: '🔗' }, + ], + }, + { + 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: '📜' }, + ], + }, ]; -export default function Navbar() { - const location = useLocation(); - const { theme, toggle } = useThemeContext(); - - const isActive = (path: string) => { - if (path === '/') return location.pathname === '/'; - return location.pathname.startsWith(path); - }; - - return ( - - ); +function getCurrentSection(pathname: string): NavSection | null { + for (const section of sections) { + if (section.paths.some((p) => pathname === p || pathname.startsWith(p + '/'))) { + return section; + } + } + return null; +} + +export default function Navbar() { + const location = useLocation(); + 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'; + return location.pathname === path || location.pathname.startsWith(path + '/'); + }; + + return ( + + ); } diff --git a/frontend/src/pages/MainMenu.tsx b/frontend/src/pages/MainMenu.tsx new file mode 100644 index 0000000..60a1723 --- /dev/null +++ b/frontend/src/pages/MainMenu.tsx @@ -0,0 +1,69 @@ +import { Link } from 'react-router-dom'; +import { useThemeContext } from '../contexts/ThemeContext'; + +const sections = [ + { + title: 'S&P Collector', + description: 'S&P 배치 수집 관리', + detail: '대시보드, 실행 이력, 재수집 이력, 작업 관리, 스케줄, 타임라인', + path: '/dashboard', + icon: '🔄', + iconClass: 'gc-card-icon', + menuCount: 6, + }, + { + title: 'S&P Bypass', + description: 'S&P Bypass API 관리', + detail: 'API 등록, 코드 생성 관리, 테스트', + path: '/bypass-config', + icon: '🔗', + iconClass: 'gc-card-icon gc-card-icon-guide', + menuCount: 1, + }, + { + title: 'S&P Risk & Compliance', + description: 'S&P 위험 지표 및 규정 준수', + detail: '위험 지표 및 규정 준수 가이드, 변경 이력 조회', + path: '/screening-guide', + icon: '⚖️', + iconClass: 'gc-card-icon gc-card-icon-nexus', + menuCount: 2, + }, +]; + +export default function MainMenu() { + const { theme, toggle } = useThemeContext(); + + return ( +
+ {/* 헤더 */} +
+

S&P Data Platform

+

해양 데이터 통합 관리 플랫폼

+
+ + {/* 섹션 카드 */} +
+ {sections.map((section) => ( + +
+ {section.icon} +
+

{section.title}

+

{section.description}
{section.detail}

+ + ))} +
+ + {/* 테마 토글 */} + +
+ ); +} diff --git a/frontend/src/theme/base.css b/frontend/src/theme/base.css index 803906a..7148406 100644 --- a/frontend/src/theme/base.css +++ b/frontend/src/theme/base.css @@ -23,3 +23,76 @@ body { ::-webkit-scrollbar-thumb:hover { background: var(--wing-accent); } + +/* Main Menu Cards */ +.gc-cards { + padding: 2rem 0; + display: flex; + justify-content: center; + align-items: stretch; + gap: 2rem; + width: 80%; + margin: 0 auto; +} + +.gc-cards > * { + flex: 1 1 0; +} + +.gc-card { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding: 2.5rem 2rem; + border: 1px solid var(--wing-border); + border-radius: 12px; + background: var(--wing-surface); + text-decoration: none !important; + color: inherit !important; + transition: all 0.2s ease; + height: 100%; +} + +.gc-card:hover { + border-color: #4183c4; + box-shadow: 0 4px 16px rgba(65, 131, 196, 0.15); + transform: translateY(-2px); +} + +.gc-card-icon { + color: #4183c4; + margin-bottom: 1rem; +} + +.gc-card-icon-guide { + color: #21ba45; +} + +.gc-card-icon-nexus { + color: #f2711c; +} + +.gc-card h3 { + font-size: 1.3rem; + margin-bottom: 0.5rem; + color: var(--wing-text); +} + +.gc-card p { + font-size: 0.95rem; + color: var(--wing-muted); + line-height: 1.5; + margin-bottom: 1rem; +} + +.gc-card-link { + font-size: 0.9rem; + color: #4183c4; + font-weight: 600; + margin-top: auto; +} + +.gc-card:hover .gc-card-link { + text-decoration: underline; +} diff --git a/src/main/java/com/snp/batch/global/controller/WebViewController.java b/src/main/java/com/snp/batch/global/controller/WebViewController.java index cfb5928..bf2206b 100644 --- a/src/main/java/com/snp/batch/global/controller/WebViewController.java +++ b/src/main/java/com/snp/batch/global/controller/WebViewController.java @@ -12,11 +12,11 @@ import org.springframework.web.bind.annotation.GetMapping; @Controller public class WebViewController { - @GetMapping({"/", "/jobs", "/executions", "/executions/{id:\\d+}", + @GetMapping({"/", "/dashboard", "/jobs", "/executions", "/executions/{id:\\d+}", "/recollects", "/recollects/{id:\\d+}", "/schedules", "/schedule-timeline", "/monitoring", "/bypass-config", "/screening-guide", "/risk-compliance-history", - "/jobs/**", "/executions/**", "/recollects/**", + "/dashboard/**", "/jobs/**", "/executions/**", "/recollects/**", "/schedules/**", "/schedule-timeline/**", "/monitoring/**", "/bypass-config/**", "/screening-guide/**", "/risk-compliance-history/**"}) public String forward() {