wing-ops/frontend/src/tabs/weather/hooks/useWeatherData.ts
htlee f099ff29b1 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>
2026-02-28 14:08:34 +09:00

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 }
}