signal-batch/frontend/src/App.tsx
htlee 8fafaad6c0 feat: Phase 3 — API Explorer 지도 스캐폴딩
- MapLibre GL JS 5 지도 컨테이너 (Light/Dark 테마 자동 전환)
- Sidebar 접기/펼치기 레이아웃 (320px 사이드바 + 전체 높이 지도)
- API 유형 선택 UI (최근 위치 / 해구별 항적 / 선박별 항적)
- gisApi 클라이언트 (V1/V2 REST API 인터페이스)
- 지도 상수 (한반도 중심, 항적 색상, OpenFreeMap 타일)
- i18n 한/영 explorer.* 키 12개 추가
- lazy loading: ApiExplorer 청크 분리 (gzip 278KB)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 19:14:03 +09:00

64 lines
2.5 KiB
TypeScript

import { lazy, Suspense, Component, type ReactNode } from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import { ThemeProvider } from './contexts/ThemeContext.tsx'
import { I18nProvider } from './i18n/I18nContext.tsx'
import AppLayout from './components/layout/AppLayout.tsx'
import LoadingSpinner from './components/common/LoadingSpinner.tsx'
const Dashboard = lazy(() => import('./pages/Dashboard.tsx'))
const JobMonitor = lazy(() => import('./pages/JobMonitor.tsx'))
const DataPipeline = lazy(() => import('./pages/DataPipeline.tsx'))
const AreaStats = lazy(() => import('./pages/AreaStats.tsx'))
const ApiExplorer = lazy(() => import('./pages/ApiExplorer.tsx'))
const BASE_URL = import.meta.env.VITE_BASE_URL || '/signal-batch'
/* React Error Boundary — 페이지 렌더링 에러 시 전체 앱 crash 방지 */
class ErrorBoundary extends Component<{ children: ReactNode }, { error: Error | null }> {
state: { error: Error | null } = { error: null }
static getDerivedStateFromError(error: Error) {
return { error }
}
render() {
if (this.state.error) {
return (
<div className="flex flex-col items-center justify-center gap-4 py-20">
<h2 className="text-xl font-bold text-danger">Rendering Error</h2>
<p className="text-sm text-muted">{this.state.error.message}</p>
<button
className="rounded bg-primary px-4 py-2 text-sm text-white"
onClick={() => this.setState({ error: null })}
>
Retry
</button>
</div>
)
}
return this.props.children
}
}
export default function App() {
return (
<I18nProvider>
<ThemeProvider>
<BrowserRouter basename={BASE_URL}>
<ErrorBoundary>
<Routes>
<Route element={<AppLayout />}>
<Route index element={<Suspense fallback={<LoadingSpinner />}><Dashboard /></Suspense>} />
<Route path="jobs" element={<Suspense fallback={<LoadingSpinner />}><JobMonitor /></Suspense>} />
<Route path="pipeline" element={<Suspense fallback={<LoadingSpinner />}><DataPipeline /></Suspense>} />
<Route path="area-stats" element={<Suspense fallback={<LoadingSpinner />}><AreaStats /></Suspense>} />
<Route path="api-explorer" element={<Suspense fallback={<LoadingSpinner />}><ApiExplorer /></Suspense>} />
</Route>
</Routes>
</ErrorBoundary>
</BrowserRouter>
</ThemeProvider>
</I18nProvider>
)
}