import {useEffect, useState} from 'react'; export interface WeatherData { temperature: number; condition: string; recommendation: string; observedAt?: string; isFallback?: boolean; cached?: boolean; stale?: boolean; } /** Open-Meteo weathercode → 한국어 상태 + 안내 문구. 코드표는 WMO 4677 기준. */ function describe(code: number): {condition: string; recommendation: string} { if (code === 0) { return { condition: '맑음', recommendation: '청명한 하늘 아래 해안도로 드라이브와 산책로를 추천합니다.', }; } if (code >= 1 && code <= 3) { return { condition: '구름많음', recommendation: '부드러운 햇살과 구름이 어우러져 야외에서 노을을 감상하기 좋습니다.', }; } if (code >= 50 && code <= 69) { return { condition: '비', recommendation: '빗소리를 들으며 실내 공간에서 따뜻하게 머무르기 좋은 날입니다.', }; } if (code >= 70 && code <= 79) { return { condition: '눈', recommendation: '설경을 바라보며 따뜻한 차 한 잔과 함께 실내에서 쉬어가기 좋습니다.', }; } return { condition: '흐림', recommendation: '운치 있는 산책 후 실내에서 티타임을 가져보세요.', }; } const FALLBACK: WeatherData = { temperature: 24.5, condition: '맑음', recommendation: '맑고 선선한 바람이 부는, 산책하기 좋은 날씨입니다.', isFallback: true, }; /** * 실시간 날씨. Open-Meteo 는 API 키가 필요 없다 — 좌표만 있으면 된다. * * ★ 실패해도 사용자에게 에러를 보이지 않는다. 날씨는 부가 정보라 * 못 받아오면 조용히 폴백값을 쓴다(isFallback 으로 구분 가능). */ export function useWeather(regionCode?: string, lat?: number, lon?: number): WeatherData { const [weather, setWeather] = useState(FALLBACK); useEffect(() => { if (!regionCode || lat == null || lon == null) return; let alive = true; const controller = new AbortController(); async function fetchWeather() { try { const baseUrl = import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:9800'; const query = new URLSearchParams({ region_code: regionCode!, latitude: String(lat), longitude: String(lon), }); const res = await fetch(`${baseUrl}/v1/local/weather?${query}`, {signal: controller.signal}); if (!res.ok) return; const data = await res.json(); const current = data?.weather; if (!data?.result?.success || !current || !alive) return; const {condition, recommendation} = describe(Number(current.weather_code)); setWeather({ temperature: Math.round(Number(current.temperature) * 10) / 10, condition, recommendation, observedAt: current.observed_at, cached: Boolean(data.cached), stale: Boolean(data.stale), }); } catch { // 폴백 유지 — 화면에 에러를 띄우지 않는다. } } void fetchWeather(); const timer = setInterval(() => void fetchWeather(), 10 * 60 * 1000); return () => { alive = false; controller.abort(); clearInterval(timer); }; }, [regionCode, lat, lon]); return weather; }