- react-router-dom 도입, /design 경로에 디자인 토큰/컴포넌트 카탈로그 페이지 추가 - SCAT 지도에서 하드코딩된 제주 해안선 좌표 제거, 인접 구간 기반 동적 방향 계산으로 전환 - @/ path alias 추가, SVG 아이콘 에셋 추가
136 lines
4.0 KiB
TypeScript
Executable File
136 lines
4.0 KiB
TypeScript
Executable File
import { useState, useEffect } from 'react'
|
|
import { Routes, Route } from 'react-router-dom'
|
|
import { GoogleOAuthProvider } from '@react-oauth/google'
|
|
import type { MainTab } from '@common/types/navigation'
|
|
import { MainLayout } from '@common/components/layout/MainLayout'
|
|
import { LoginPage } from '@common/components/auth/LoginPage'
|
|
import { registerMainTabSwitcher } from '@common/hooks/useSubMenu'
|
|
import { useAuthStore } from '@common/store/authStore'
|
|
import { useMenuStore } from '@common/store/menuStore'
|
|
import { useMapStore } from '@common/store/mapStore'
|
|
import { API_BASE_URL } from '@common/services/api'
|
|
import { OilSpillView } from '@tabs/prediction'
|
|
import { ReportsView } from '@tabs/reports'
|
|
import { HNSView } from '@tabs/hns'
|
|
import { AerialView } from '@tabs/aerial'
|
|
import { AssetsView } from '@tabs/assets'
|
|
import { BoardView } from '@tabs/board'
|
|
import { WeatherView } from '@tabs/weather'
|
|
import { IncidentsView } from '@tabs/incidents'
|
|
import { AdminView } from '@tabs/admin'
|
|
import { ScatView } from '@tabs/scat'
|
|
import { RescueView } from '@tabs/rescue'
|
|
import { DesignPage } from '@/pages/design/DesignPage'
|
|
|
|
const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID || ''
|
|
|
|
function App() {
|
|
const [activeMainTab, setActiveMainTab] = useState<MainTab>('prediction')
|
|
const { isAuthenticated, isLoading, checkSession } = useAuthStore()
|
|
const { loadMenuConfig } = useMenuStore()
|
|
const { loadMapTypes } = useMapStore()
|
|
|
|
useEffect(() => {
|
|
checkSession()
|
|
}, [checkSession])
|
|
|
|
useEffect(() => {
|
|
if (isAuthenticated) {
|
|
loadMenuConfig()
|
|
loadMapTypes()
|
|
}
|
|
}, [isAuthenticated, loadMenuConfig, loadMapTypes])
|
|
|
|
useEffect(() => {
|
|
registerMainTabSwitcher(setActiveMainTab)
|
|
}, [])
|
|
|
|
// 감사 로그: 탭 이동 기록
|
|
useEffect(() => {
|
|
if (!isAuthenticated) return
|
|
const blob = new Blob(
|
|
[JSON.stringify({ action: 'TAB_VIEW', detail: activeMainTab })],
|
|
{ type: 'text/plain' }
|
|
)
|
|
navigator.sendBeacon(`${API_BASE_URL}/audit/log`, blob)
|
|
}, [activeMainTab, isAuthenticated])
|
|
|
|
// 세션 확인 중 스플래시
|
|
if (isLoading) {
|
|
return (
|
|
<div style={{
|
|
width: '100vw', height: '100vh', display: 'flex',
|
|
flexDirection: 'column', alignItems: 'center', justifyContent: 'center',
|
|
background: '#001028', gap: 16,
|
|
}}>
|
|
<img src="/wing_logo_text_white.svg" alt="WING" style={{ height: 28, opacity: 0.8 }} />
|
|
<div style={{
|
|
width: 32, height: 32, border: '3px solid rgba(6,182,212,0.2)',
|
|
borderTop: '3px solid rgba(6,182,212,0.8)', borderRadius: '50%',
|
|
animation: 'loginSpin 0.8s linear infinite',
|
|
}} />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
// 미인증 → 로그인 페이지
|
|
if (!isAuthenticated) {
|
|
return <LoginPage />
|
|
}
|
|
|
|
const renderView = () => {
|
|
switch (activeMainTab) {
|
|
case 'prediction':
|
|
return <OilSpillView />
|
|
case 'reports':
|
|
return <ReportsView />
|
|
case 'hns':
|
|
return <HNSView />
|
|
case 'aerial':
|
|
return <AerialView />
|
|
case 'assets':
|
|
return <AssetsView />
|
|
case 'board':
|
|
return <BoardView />
|
|
case 'weather':
|
|
return <WeatherView />
|
|
case 'incidents':
|
|
return <IncidentsView />
|
|
case 'scat':
|
|
return <ScatView />
|
|
case 'admin':
|
|
return <AdminView />
|
|
case 'rescue':
|
|
return <RescueView />
|
|
case 'monitor':
|
|
return null
|
|
default:
|
|
return <div className="flex items-center justify-center h-full text-text-3">준비 중입니다...</div>
|
|
}
|
|
}
|
|
|
|
return (
|
|
<Routes>
|
|
<Route path="/design" element={<DesignPage />} />
|
|
<Route path="*" element={
|
|
<MainLayout activeMainTab={activeMainTab} onMainTabChange={setActiveMainTab}>
|
|
{renderView()}
|
|
</MainLayout>
|
|
} />
|
|
</Routes>
|
|
)
|
|
}
|
|
|
|
function AppWithProviders() {
|
|
if (!GOOGLE_CLIENT_ID) {
|
|
return <App />
|
|
}
|
|
return (
|
|
<GoogleOAuthProvider clientId={GOOGLE_CLIENT_ID}>
|
|
<App />
|
|
</GoogleOAuthProvider>
|
|
)
|
|
}
|
|
|
|
export default AppWithProviders
|