wing-ops/frontend/src/common/hooks/useSubMenu.ts
htlee a61864646f refactor(frontend): 공통 모듈 common/ 분리 + OpenLayers 제거 + path alias 설정
- OpenLayers(ol) 패키지 제거 (미사용, import 0건)
- common/ 디렉토리 생성: components, hooks, services, store, types, utils
- 17개 공통 파일을 common/으로 이동 (git mv, blame 이력 보존)
- MainTab 타입을 App.tsx에서 common/types/navigation.ts로 분리
- tsconfig path alias (@common/*, @tabs/*) + vite resolve.alias 설정
- 42개 import 경로를 @common/ alias 또는 상대경로로 수정

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

140 lines
4.7 KiB
TypeScript
Executable File

import { useState, useEffect } from 'react'
import type { MainTab } from '../types/navigation'
interface SubMenuItem {
id: string
label: string
icon: string
}
// 메인 탭별 서브 메뉴 설정
const subMenuConfigs: Record<MainTab, SubMenuItem[] | null> = {
hns: [
{ id: 'analysis', label: '대기확산 분석', icon: '🧪' },
{ id: 'list', label: '분석 목록', icon: '📋' },
{ id: 'scenario', label: '시나리오 관리', icon: '📊' },
{ id: 'manual', label: 'HNS 대응매뉴얼', icon: '📖' },
{ id: 'theory', label: '확산모델 이론', icon: '📐' },
{ id: 'substance', label: 'HNS 물질정보', icon: '🧬' }
],
prediction: [
{ id: 'analysis', label: '유출유 확산분석', icon: '🔬' },
{ id: 'list', label: '분석 목록', icon: '📋' },
{ id: 'theory', label: '유출유확산모델 이론', icon: '📐' },
{ id: 'boom-theory', label: '오일펜스 배치 알고리즘 이론', icon: '🛡️' }
],
rescue: [
{ id: 'rescue', label: '긴급구난예측', icon: '🚨' },
{ id: 'list', label: '긴급구난 목록', icon: '📋' },
{ id: 'scenario', label: '시나리오 관리', icon: '📊' },
{ id: 'theory', label: '긴급구난모델 이론', icon: '📚' }
],
reports: [
{ id: 'report-list', label: '보고서 목록', icon: '📋' },
{ id: 'template', label: '표준보고서 템플릿', icon: '📝' },
{ id: 'generate', label: '보고서 생성', icon: '🔄' }
],
aerial: [
{ id: 'media', label: '영상사진관리', icon: '📷' },
{ id: 'analysis', label: '유출유면적분석', icon: '🧩' },
{ id: 'realtime', label: '실시간드론', icon: '🛸' },
{ id: 'sensor', label: '오염/선박3D분석', icon: '🔍' },
{ id: 'satellite', label: '위성요청', icon: '🛰' },
{ id: 'cctv', label: 'CCTV 조회', icon: '📹' },
{ id: 'theory', label: '항공탐색 이론', icon: '📐' }
],
assets: null,
scat: null,
incidents: null,
board: [
{ id: 'all', label: '전체', icon: '📋' },
{ id: 'notice', label: '공지사항', icon: '📢' },
{ id: 'data', label: '자료실', icon: '📂' },
{ id: 'qna', label: 'Q&A', icon: '❓' },
{ id: 'manual', label: '해경매뉴얼', icon: '📘' }
],
weather: null,
admin: [
{ id: 'users', label: '사용자 관리', icon: '👥' },
{ id: 'permissions', label: '사용자 권한 관리', icon: '🔐' },
{ id: 'menus', label: '메뉴 관리', icon: '📑' },
{ id: 'settings', label: '시스템 설정', icon: '⚙️' }
]
}
// 전역 상태 관리 (간단한 방식)
const subMenuState: Record<MainTab, string> = {
hns: 'analysis',
prediction: 'analysis',
rescue: 'rescue',
reports: 'report-list',
aerial: 'media',
assets: '',
scat: '',
incidents: '',
board: 'all',
weather: '',
admin: 'users'
}
const listeners: Set<() => void> = new Set()
function setSubTab(mainTab: MainTab, subTab: string) {
subMenuState[mainTab] = subTab
listeners.forEach(listener => listener())
}
function subscribe(listener: () => void) {
listeners.add(listener)
return () => { listeners.delete(listener) }
}
export function useSubMenu(mainTab: MainTab) {
const [activeSubTab, setActiveSubTabLocal] = useState(subMenuState[mainTab])
useEffect(() => {
const unsubscribe = subscribe(() => {
setActiveSubTabLocal(subMenuState[mainTab])
})
return unsubscribe
}, [mainTab])
const setActiveSubTab = (subTab: string) => {
setSubTab(mainTab, subTab)
}
return {
activeSubTab,
setActiveSubTab,
subMenuConfig: subMenuConfigs[mainTab]
}
}
// ─── 글로벌 메인탭 전환 (크로스 뷰 네비게이션) ─────────────
type MainTabListener = (tab: MainTab) => void
let mainTabListener: MainTabListener | null = null
/** App.tsx에서 호출하여 글로벌 탭 전환 리스너 등록 */
export function registerMainTabSwitcher(fn: MainTabListener) {
mainTabListener = fn
}
/** 어느 컴포넌트에서든 메인탭 + 서브탭을 한번에 전환 */
export function navigateToTab(mainTab: MainTab, subTab?: string) {
if (subTab) setSubTab(mainTab, subTab)
if (mainTabListener) mainTabListener(mainTab)
}
// ─── 보고서 생성 카테고리 힌트 ──────────────────────────
/** 보고서 생성 탭으로 이동 시 초기 카테고리 (0=유출유, 1=HNS, 2=긴급구난) */
let _reportGenCategory: number | null = null
export function setReportGenCategory(cat: number | null) { _reportGenCategory = cat }
export function consumeReportGenCategory(): number | null {
const v = _reportGenCategory
_reportGenCategory = null
return v
}
export { subMenuState }