- 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>
163 lines
4.8 KiB
TypeScript
Executable File
163 lines
4.8 KiB
TypeScript
Executable File
import { useState, useEffect } from 'react'
|
|
import {
|
|
getRecentObservation,
|
|
OBS_STATION_CODES,
|
|
} from '../services/khoaApi'
|
|
|
|
interface WeatherStation {
|
|
id: string
|
|
name: string
|
|
location: { lat: number; lon: number }
|
|
}
|
|
|
|
export interface EnrichedWeatherStation extends WeatherStation {
|
|
wind: {
|
|
speed: number
|
|
direction: number
|
|
speed_1k: number
|
|
speed_3k: number
|
|
}
|
|
wave: {
|
|
height: number
|
|
period: number
|
|
}
|
|
temperature: {
|
|
current: number
|
|
feelsLike: number
|
|
}
|
|
pressure: number
|
|
visibility: number
|
|
}
|
|
|
|
/**
|
|
* 기상 데이터 가져오기 훅 (KHOA 조위관측소 API 사용)
|
|
*/
|
|
export function useWeatherData(stations: WeatherStation[]) {
|
|
const [weatherStations, setWeatherStations] = useState<EnrichedWeatherStation[]>([])
|
|
const [loading, setLoading] = useState(true)
|
|
const [error, setError] = useState<string | null>(null)
|
|
const [lastUpdate, setLastUpdate] = useState<Date | null>(null)
|
|
|
|
useEffect(() => {
|
|
let isMounted = true
|
|
|
|
// 관측소별 fallback 데이터 생성
|
|
function generateFallbackStation(station: WeatherStation): EnrichedWeatherStation {
|
|
const seed = station.location.lat * 100 + station.location.lon
|
|
const r = (n: number) => Math.round(n * 10) / 10 // 소수 첫째자리
|
|
const windSpeed = r(6 + (seed % 7))
|
|
const waveHeight = r(0.8 + (seed % 20) / 10)
|
|
const temp = r(5 + (seed % 8))
|
|
const windDir = [0, 45, 90, 135, 180, 225, 270, 315][Math.floor(seed) % 8]
|
|
|
|
return {
|
|
...station,
|
|
wind: {
|
|
speed: windSpeed,
|
|
direction: windDir,
|
|
speed_1k: r(windSpeed * 0.8),
|
|
speed_3k: r(windSpeed * 1.2)
|
|
},
|
|
wave: { height: waveHeight, period: 4 + (Math.floor(seed) % 3) },
|
|
temperature: { current: temp, feelsLike: r(temp - windSpeed * 0.3) },
|
|
pressure: 1010 + (Math.floor(seed) % 12),
|
|
visibility: 12 + (Math.floor(seed) % 10)
|
|
}
|
|
}
|
|
|
|
async function fetchWeatherData() {
|
|
try {
|
|
setLoading(true)
|
|
setError(null)
|
|
|
|
const enrichedStations: EnrichedWeatherStation[] = []
|
|
let apiFailed = false
|
|
|
|
for (const station of stations) {
|
|
if (apiFailed) {
|
|
enrichedStations.push(generateFallbackStation(station))
|
|
continue
|
|
}
|
|
|
|
try {
|
|
const obsCode = OBS_STATION_CODES[station.id]
|
|
if (!obsCode) {
|
|
enrichedStations.push(generateFallbackStation(station))
|
|
continue
|
|
}
|
|
|
|
const obs = await getRecentObservation(obsCode)
|
|
|
|
if (obs) {
|
|
const r = (n: number) => Math.round(n * 10) / 10
|
|
const windSpeed = r(obs.wind_speed ?? 8.5)
|
|
const windDir = obs.wind_dir ?? 315
|
|
const waterTemp = r(obs.water_temp ?? 8.0)
|
|
const airPres = Math.round(obs.air_pres ?? 1016)
|
|
|
|
enrichedStations.push({
|
|
...station,
|
|
wind: {
|
|
speed: windSpeed,
|
|
direction: windDir,
|
|
speed_1k: r(windSpeed * 0.8),
|
|
speed_3k: r(windSpeed * 1.2)
|
|
},
|
|
wave: {
|
|
height: r(1.0 + windSpeed * 0.1),
|
|
period: Math.floor(4 + windSpeed * 0.3)
|
|
},
|
|
temperature: {
|
|
current: waterTemp,
|
|
feelsLike: r((obs.air_temp ?? waterTemp) - windSpeed * 0.3)
|
|
},
|
|
pressure: airPres,
|
|
visibility: airPres > 1010 ? 15 + Math.floor(Math.random() * 5) : 10
|
|
})
|
|
} else {
|
|
enrichedStations.push(generateFallbackStation(station))
|
|
}
|
|
|
|
await new Promise(resolve => setTimeout(resolve, 100))
|
|
|
|
} catch (stationError) {
|
|
if (!apiFailed) {
|
|
console.warn('KHOA API 연결 실패, fallback 데이터를 사용합니다:', stationError)
|
|
apiFailed = true
|
|
}
|
|
enrichedStations.push(generateFallbackStation(station))
|
|
}
|
|
}
|
|
|
|
if (isMounted) {
|
|
setWeatherStations(enrichedStations)
|
|
setLastUpdate(new Date())
|
|
setLoading(false)
|
|
if (apiFailed) {
|
|
setError('KHOA API 연결 실패 — fallback 데이터 사용 중')
|
|
}
|
|
}
|
|
|
|
} catch (err) {
|
|
console.error('기상 데이터 가져오기 오류:', err)
|
|
if (isMounted) {
|
|
setError(err instanceof Error ? err.message : '알 수 없는 오류')
|
|
setLoading(false)
|
|
}
|
|
}
|
|
}
|
|
|
|
fetchWeatherData()
|
|
|
|
// 10분마다 데이터 갱신
|
|
const interval = setInterval(fetchWeatherData, 10 * 60 * 1000)
|
|
|
|
return () => {
|
|
isMounted = false
|
|
clearInterval(interval)
|
|
}
|
|
}, [stations])
|
|
|
|
return { weatherStations, loading, error, lastUpdate }
|
|
}
|