refactor(frontend): 탭 단위 패키지 구조 전환 (tabs/)

- 11개 탭 디렉토리 생성: tabs/{prediction,hns,rescue,weather,incidents,aerial,board,reports,assets,scat,admin}/
- 51개 컴포넌트를 역할 기반(views/, analysis/, layout/) → 탭 기반(tabs/) 구조로 이동
- weather 탭에 전용 hooks/, services/ 포함
- incidents 탭에 전용 services/ 포함
- 공통 지도 컴포넌트(MapView, BacktrackReplay)를 common/components/map/으로 이동
- 각 탭에 index.ts 생성하여 View 컴포넌트 re-export
- App.tsx import를 @tabs/ alias 사용으로 변경
- 전체 import 경로 수정 (탭 내부 상대경로, 외부 @common/ alias)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
htlee 2026-02-28 14:08:34 +09:00
부모 a61864646f
커밋 f099ff29b1
62개의 변경된 파일67개의 추가작업 그리고 57개의 파일을 삭제

파일 보기

@ -6,17 +6,17 @@ import { LoginPage } from '@common/components/auth/LoginPage'
import { registerMainTabSwitcher } from '@common/hooks/useSubMenu' import { registerMainTabSwitcher } from '@common/hooks/useSubMenu'
import { useAuthStore } from '@common/store/authStore' import { useAuthStore } from '@common/store/authStore'
import { useMenuStore } from '@common/store/menuStore' import { useMenuStore } from '@common/store/menuStore'
import { OilSpillView } from './components/views/OilSpillView' import { OilSpillView } from '@tabs/prediction'
import { ReportsView } from './components/views/ReportsView' import { ReportsView } from '@tabs/reports'
import { HNSView } from './components/views/HNSView' import { HNSView } from '@tabs/hns'
import { AerialView } from './components/views/AerialView' import { AerialView } from '@tabs/aerial'
import { AssetsView } from './components/views/AssetsView' import { AssetsView } from '@tabs/assets'
import { BoardView } from './components/views/BoardView' import { BoardView } from '@tabs/board'
import { WeatherView } from './components/views/WeatherView' import { WeatherView } from '@tabs/weather'
import { IncidentsView } from './components/views/IncidentsView' import { IncidentsView } from '@tabs/incidents'
import { AdminView } from './components/views/AdminView' import { AdminView } from '@tabs/admin'
import { PreScatView } from './components/views/PreScatView' import { PreScatView } from '@tabs/scat'
import { RescueView } from './components/views/RescueView' import { RescueView } from '@tabs/rescue'
const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID || '' const GOOGLE_CLIENT_ID = import.meta.env.VITE_GOOGLE_CLIENT_ID || ''

파일 보기

@ -2,9 +2,9 @@ import { useState, useMemo, useEffect } from 'react'
import { MapContainer, TileLayer, Marker, Popup, useMap, useMapEvents, CircleMarker, Circle, Polyline } from 'react-leaflet' import { MapContainer, TileLayer, Marker, Popup, useMap, useMapEvents, CircleMarker, Circle, Polyline } from 'react-leaflet'
import 'leaflet/dist/leaflet.css' import 'leaflet/dist/leaflet.css'
import L from 'leaflet' import L from 'leaflet'
import { layerDatabase } from '../../data/layerDatabase' import { layerDatabase } from '../../../data/layerDatabase'
import { decimalToDMS } from '@common/utils/coordinates' import { decimalToDMS } from '@common/utils/coordinates'
import type { PredictionModel } from '../views/OilSpillView' import type { PredictionModel } from '@tabs/prediction/components/OilSpillView'
import type { BoomLine, BoomLineCoord } from '@common/types/boomLine' import type { BoomLine, BoomLineCoord } from '@common/types/boomLine'
import type { ReplayShip, CollisionEvent } from '@common/types/backtrack' import type { ReplayShip, CollisionEvent } from '@common/types/backtrack'
import { BacktrackReplayOverlay } from './BacktrackReplayOverlay' import { BacktrackReplayOverlay } from './BacktrackReplayOverlay'

파일 보기

@ -0,0 +1 @@
export { AdminView } from './components/AdminView'

파일 보기

@ -1,6 +1,6 @@
import { useState, useRef, useEffect } from 'react' import { useState, useRef, useEffect } from 'react'
import { useSubMenu } from '@common/hooks/useSubMenu' import { useSubMenu } from '@common/hooks/useSubMenu'
import { AerialTheoryView } from '../analysis/AerialTheoryView' import { AerialTheoryView } from './AerialTheoryView'
type AerialTab = 'media' | 'analysis' | 'realtime' | 'sensor' type AerialTab = 'media' | 'analysis' | 'realtime' | 'sensor'

파일 보기

@ -0,0 +1 @@
export { AerialView } from './components/AerialView'

파일 보기

@ -0,0 +1 @@
export { AssetsView } from './components/AssetsView'

파일 보기

@ -1,7 +1,7 @@
import { useState } from 'react' import { useState } from 'react'
import { useSubMenu } from '@common/hooks/useSubMenu' import { useSubMenu } from '@common/hooks/useSubMenu'
import { BoardWriteForm } from '../board/BoardWriteForm' import { BoardWriteForm } from './BoardWriteForm'
import { BoardDetailView } from '../board/BoardDetailView' import { BoardDetailView } from './BoardDetailView'
interface BoardPost { interface BoardPost {
id: number id: number

파일 보기

@ -0,0 +1 @@
export { BoardView } from './components/BoardView'

파일 보기

@ -1,6 +1,6 @@
import React, { useState, useRef, useMemo } from 'react' import React, { useState, useRef, useMemo } from 'react'
import { sanitizeHtml } from '@common/utils/sanitize' import { sanitizeHtml } from '@common/utils/sanitize'
import { HNS_SEARCH_DB, type HNSSearchSubstance } from '../../data/hnsSubstanceSearchData' import { HNS_SEARCH_DB, type HNSSearchSubstance } from '../../../data/hnsSubstanceSearchData'
/* ═══ HNS 물질 데이터베이스 ═══ */ /* ═══ HNS 물질 데이터베이스 ═══ */
interface HNSSubstance { interface HNSSubstance {

파일 보기

@ -1,12 +1,12 @@
import { useState } from 'react' import { useState } from 'react'
import { HNSLeftPanel } from '../layout/HNSLeftPanel' import { HNSLeftPanel } from './HNSLeftPanel'
import { HNSRightPanel } from '../layout/HNSRightPanel' import { HNSRightPanel } from './HNSRightPanel'
import { MapView } from '../map/MapView' import { MapView } from '@common/components/map/MapView'
import { HNSAnalysisListTable } from '../analysis/HNSAnalysisListTable' import { HNSAnalysisListTable } from './HNSAnalysisListTable'
import { HNSTheoryView } from '../analysis/HNSTheoryView' import { HNSTheoryView } from './HNSTheoryView'
import { HNSSubstanceView } from '../analysis/HNSSubstanceView' import { HNSSubstanceView } from './HNSSubstanceView'
import { HNSScenarioView } from '../analysis/HNSScenarioView' import { HNSScenarioView } from './HNSScenarioView'
import { HNSRecalcModal } from '../analysis/HNSRecalcModal' import { HNSRecalcModal } from './HNSRecalcModal'
import { useSubMenu, navigateToTab, setReportGenCategory } from '@common/hooks/useSubMenu' import { useSubMenu, navigateToTab, setReportGenCategory } from '@common/hooks/useSubMenu'
/* ─── HNS 매뉴얼 뷰어 컴포넌트 ─── */ /* ─── HNS 매뉴얼 뷰어 컴포넌트 ─── */

파일 보기

@ -0,0 +1 @@
export { HNSView } from './components/HNSView'

파일 보기

@ -3,9 +3,9 @@ import { MapContainer, TileLayer, CircleMarker, Popup, Marker } from 'react-leaf
import L from 'leaflet' import L from 'leaflet'
import type { LatLngExpression } from 'leaflet' import type { LatLngExpression } from 'leaflet'
import 'leaflet/dist/leaflet.css' import 'leaflet/dist/leaflet.css'
import { IncidentsLeftPanel, type Incident } from '../incidents/IncidentsLeftPanel' import { IncidentsLeftPanel, type Incident } from './IncidentsLeftPanel'
import { IncidentsRightPanel, type ViewMode, type AnalysisSection } from '../incidents/IncidentsRightPanel' import { IncidentsRightPanel, type ViewMode, type AnalysisSection } from './IncidentsRightPanel'
import { mockVessels, VESSEL_LEGEND, type Vessel } from '../../data/vesselMockData' import { mockVessels, VESSEL_LEGEND, type Vessel } from '../../../data/vesselMockData'
// Mock incident data (HTML 참고 6건) // Mock incident data (HTML 참고 6건)
const mockIncidents: Incident[] = [ const mockIncidents: Incident[] = [

파일 보기

@ -0,0 +1 @@
export { IncidentsView } from './components/IncidentsView'

파일 보기

@ -1,16 +1,16 @@
import { useState, useMemo } from 'react' import { useState, useMemo } from 'react'
import { LayerTree } from '@common/components/layer/LayerTree' import { LayerTree } from '@common/components/layer/LayerTree'
import { useLayerTree } from '@common/hooks/useLayers' import { useLayerTree } from '@common/hooks/useLayers'
import { layerData } from '../../data/layerData' import { layerData } from '../../../data/layerData'
import type { LayerNode } from '../../data/layerData' import type { LayerNode } from '../../../data/layerData'
import type { Layer } from '../../data/layerDatabase' import type { Layer } from '../../../data/layerDatabase'
import { decimalToDMS } from '@common/utils/coordinates' import { decimalToDMS } from '@common/utils/coordinates'
import { ComboBox } from '@common/components/ui/ComboBox' import { ComboBox } from '@common/components/ui/ComboBox'
import { ALL_MODELS } from '../views/OilSpillView' import { ALL_MODELS } from './OilSpillView'
import type { PredictionModel } from '../views/OilSpillView' import type { PredictionModel } from './OilSpillView'
import type { BoomLine, BoomLineCoord, AlgorithmSettings, ContainmentResult } from '@common/types/boomLine' import type { BoomLine, BoomLineCoord, AlgorithmSettings, ContainmentResult } from '@common/types/boomLine'
import { generateAIBoomLines, runContainmentAnalysis, computePolylineLength, computeBearing } from '@common/utils/geo' import { generateAIBoomLines, runContainmentAnalysis, computePolylineLength, computeBearing } from '@common/utils/geo'
import type { Analysis } from '../analysis/AnalysisListTable' import type { Analysis } from './AnalysisListTable'
interface LeftPanelProps { interface LeftPanelProps {
selectedAnalysis?: Analysis | null selectedAnalysis?: Analysis | null

파일 보기

@ -1,18 +1,18 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { LeftPanel } from '../layout/LeftPanel' import { LeftPanel } from './LeftPanel'
import { RightPanel } from '../layout/RightPanel' import { RightPanel } from './RightPanel'
import { MapView } from '../map/MapView' import { MapView } from '@common/components/map/MapView'
import { AnalysisListTable, type Analysis } from '../analysis/AnalysisListTable' import { AnalysisListTable, type Analysis } from './AnalysisListTable'
import { OilSpillTheoryView } from '../analysis/OilSpillTheoryView' import { OilSpillTheoryView } from './OilSpillTheoryView'
import { BoomDeploymentTheoryView } from '../analysis/BoomDeploymentTheoryView' import { BoomDeploymentTheoryView } from './BoomDeploymentTheoryView'
import { BacktrackModal } from '../analysis/BacktrackModal' import { BacktrackModal } from './BacktrackModal'
import { RecalcModal } from '../analysis/RecalcModal' import { RecalcModal } from './RecalcModal'
import { BacktrackReplayBar } from '../map/BacktrackReplayBar' import { BacktrackReplayBar } from '@common/components/map/BacktrackReplayBar'
import { useSubMenu, navigateToTab, setReportGenCategory } from '@common/hooks/useSubMenu' import { useSubMenu, navigateToTab, setReportGenCategory } from '@common/hooks/useSubMenu'
import type { BoomLine, AlgorithmSettings, ContainmentResult, BoomLineCoord } from '@common/types/boomLine' import type { BoomLine, AlgorithmSettings, ContainmentResult, BoomLineCoord } from '@common/types/boomLine'
import type { BacktrackPhase, BacktrackVessel } from '@common/types/backtrack' import type { BacktrackPhase, BacktrackVessel } from '@common/types/backtrack'
import { TOTAL_REPLAY_FRAMES } from '@common/types/backtrack' import { TOTAL_REPLAY_FRAMES } from '@common/types/backtrack'
import { MOCK_CONDITIONS, MOCK_VESSELS, MOCK_REPLAY_SHIPS, MOCK_COLLISION } from '../../data/backtrackMockData' import { MOCK_CONDITIONS, MOCK_VESSELS, MOCK_REPLAY_SHIPS, MOCK_COLLISION } from '../../../data/backtrackMockData'
export type PredictionModel = 'KOSPS' | 'POSEIDON' | 'OpenDrift' export type PredictionModel = 'KOSPS' | 'POSEIDON' | 'OpenDrift'
// eslint-disable-next-line react-refresh/only-export-components // eslint-disable-next-line react-refresh/only-export-components

파일 보기

@ -1,5 +1,5 @@
import { useState, useRef, useEffect } from 'react' import { useState, useRef, useEffect } from 'react'
import type { PredictionModel } from '../views/OilSpillView' import type { PredictionModel } from './OilSpillView'
interface RecalcModalProps { interface RecalcModalProps {
isOpen: boolean isOpen: boolean

파일 보기

@ -0,0 +1 @@
export { OilSpillView } from './components/OilSpillView'

파일 보기

@ -8,7 +8,7 @@ import {
type OilSpillReportData, type OilSpillReportData,
type ReportType, type ReportType,
type Jurisdiction, type Jurisdiction,
} from '../reports/OilSpillReportTemplate' } from './OilSpillReportTemplate'
import { sanitizeHtml } from '@common/utils/sanitize' import { sanitizeHtml } from '@common/utils/sanitize'
import { useSubMenu, consumeReportGenCategory } from '@common/hooks/useSubMenu' import { useSubMenu, consumeReportGenCategory } from '@common/hooks/useSubMenu'

파일 보기

@ -0,0 +1 @@
export { ReportsView } from './components/ReportsView'

파일 보기

@ -1,7 +1,7 @@
import { useState, useEffect } from 'react' import { useState, useEffect } from 'react'
import { useSubMenu } from '@common/hooks/useSubMenu' import { useSubMenu } from '@common/hooks/useSubMenu'
import { RescueTheoryView } from '../analysis/RescueTheoryView' import { RescueTheoryView } from './RescueTheoryView'
import { RescueScenarioView } from '../analysis/RescueScenarioView' import { RescueScenarioView } from './RescueScenarioView'
/* ─── Types ─── */ /* ─── Types ─── */
type AccidentType = 'collision' | 'grounding' | 'turning' | 'capsizing' | 'sharpTurn' | 'flooding' | 'sinking' type AccidentType = 'collision' | 'grounding' | 'turning' | 'capsizing' | 'sharpTurn' | 'flooding' | 'sinking'

파일 보기

@ -0,0 +1 @@
export { RescueView } from './components/RescueView'

파일 보기

@ -1,7 +1,7 @@
import { ImageOverlay, useMap } from 'react-leaflet' import { ImageOverlay, useMap } from 'react-leaflet'
import { LatLngBounds } from 'leaflet' import { LatLngBounds } from 'leaflet'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import type { OceanForecastData } from '../../services/khoaApi' import type { OceanForecastData } from '../services/khoaApi'
interface OceanForecastOverlayProps { interface OceanForecastOverlayProps {
forecast: OceanForecastData | null forecast: OceanForecastData | null

파일 보기

@ -2,14 +2,14 @@ import { useState, useEffect } from 'react'
import { MapContainer, TileLayer, useMapEvents } from 'react-leaflet' import { MapContainer, TileLayer, useMapEvents } from 'react-leaflet'
import type { LatLngExpression } from 'leaflet' import type { LatLngExpression } from 'leaflet'
import 'leaflet/dist/leaflet.css' import 'leaflet/dist/leaflet.css'
import { WeatherRightPanel } from '../weather/WeatherRightPanel' import { WeatherRightPanel } from './WeatherRightPanel'
import { WeatherMapOverlay } from '../weather/WeatherMapOverlay' import { WeatherMapOverlay } from './WeatherMapOverlay'
import { OceanForecastOverlay } from '../weather/OceanForecastOverlay' import { OceanForecastOverlay } from './OceanForecastOverlay'
import { OceanCurrentLayer } from '../weather/OceanCurrentLayer' import { OceanCurrentLayer } from './OceanCurrentLayer'
import { WaterTemperatureLayer } from '../weather/WaterTemperatureLayer' import { WaterTemperatureLayer } from './WaterTemperatureLayer'
import { WindParticleLayer } from '../weather/WindParticleLayer' import { WindParticleLayer } from './WindParticleLayer'
import { useWeatherData } from '../../hooks/useWeatherData' import { useWeatherData } from '../hooks/useWeatherData'
import { useOceanForecast } from '../../hooks/useOceanForecast' import { useOceanForecast } from '../hooks/useOceanForecast'
type TimeOffset = '0' | '3' | '6' | '9' type TimeOffset = '0' | '3' | '6' | '9'

파일 보기

@ -0,0 +1 @@
export { WeatherView } from './components/WeatherView'