wing-ops/frontend/src/App.tsx
htlee fd48e755f2 feat(admin): 메뉴 관리 기능 구현 (DB 단일 소스, 이모지 피커)
- 메뉴 활성/비활성, 순서, 라벨, 아이콘을 DB(AUTH_SETTING)에서 관리
- GET/PUT /api/menus 엔드포인트 추가
- Zustand menuStore로 메뉴 설정 전역 상태 관리
- TopBar: DB 메뉴 설정 기반 동적 탭 렌더링 (ALL_TABS 하드코딩 제거)
- AdminView MenusPanel: API 연동, 이모지 피커(@emoji-mart) 통합
- SETTING_VAL 컬럼 VARCHAR(500) → TEXT 마이그레이션
- dotenv 추가로 .env 파일 자동 로딩
- wing_auth DB 비밀번호 기본값 수정 (JDBC 호환)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-28 02:21:36 +09:00

114 lines
3.5 KiB
TypeScript
Executable File

import { useState, useEffect } from 'react'
import { GoogleOAuthProvider } from '@react-oauth/google'
import { MainLayout } from './components/layout/MainLayout'
import { LoginPage } from './components/auth/LoginPage'
import { registerMainTabSwitcher } from './hooks/useSubMenu'
import { useAuthStore } from './store/authStore'
import { useMenuStore } from './store/menuStore'
import { OilSpillView } from './components/views/OilSpillView'
import { ReportsView } from './components/views/ReportsView'
import { HNSView } from './components/views/HNSView'
import { AerialView } from './components/views/AerialView'
import { AssetsView } from './components/views/AssetsView'
import { BoardView } from './components/views/BoardView'
import { WeatherView } from './components/views/WeatherView'
import { IncidentsView } from './components/views/IncidentsView'
import { AdminView } from './components/views/AdminView'
import { PreScatView } from './components/views/PreScatView'
import { RescueView } from './components/views/RescueView'
export type MainTab = 'prediction' | 'hns' | 'rescue' | 'reports' | 'aerial' | 'assets' | 'scat' | 'incidents' | 'board' | 'weather' | 'admin'
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()
useEffect(() => {
checkSession()
}, [checkSession])
useEffect(() => {
if (isAuthenticated) {
loadMenuConfig()
}
}, [isAuthenticated, loadMenuConfig])
useEffect(() => {
registerMainTabSwitcher(setActiveMainTab)
}, [])
// 세션 확인 중 스플래시
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 <PreScatView />
case 'admin':
return <AdminView />
case 'rescue':
return <RescueView />
default:
return <div className="flex items-center justify-center h-full text-text-3"> ...</div>
}
}
return (
<MainLayout activeMainTab={activeMainTab} onMainTabChange={setActiveMainTab}>
{renderView()}
</MainLayout>
)
}
function AppWithProviders() {
if (!GOOGLE_CLIENT_ID) {
return <App />
}
return (
<GoogleOAuthProvider clientId={GOOGLE_CLIENT_ID}>
<App />
</GoogleOAuthProvider>
)
}
export default AppWithProviders