diff --git a/src/App.jsx b/src/App.jsx index c7e052a..e23bee4 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -100,6 +100,9 @@ const App = () => { const [showDXFilters, setShowDXFilters] = useState(false); const [showPSKFilters, setShowPSKFilters] = useState(false); const [weatherExpanded, setWeatherExpanded] = useState(false); + const [tempUnit, setTempUnit] = useState(() => { + try { return localStorage.getItem('openhamclock_tempUnit') || 'F'; } catch { return 'F'; } + }); const [isFullscreen, setIsFullscreen] = useState(false); // Map layer visibility @@ -209,7 +212,7 @@ const App = () => { const propagation = usePropagation(config.location, dxLocation); const mySpots = useMySpots(config.callsign); const satellites = useSatellites(config.location); - const localWeather = useLocalWeather(config.location); + const localWeather = useLocalWeather(config.location, tempUnit); const pskReporter = usePSKReporter(config.callsign, { minutes: 15, enabled: config.callsign !== 'N0CALL' }); const wsjtx = useWSJTX(); @@ -615,29 +618,60 @@ const App = () => { {/* Local Weather — compact by default, click to expand */} - {localWeather.data && ( + {localWeather.data && (() => { + const w = localWeather.data; + const deg = `°${w.tempUnit || tempUnit}`; + const wind = w.windUnit || 'mph'; + const vis = w.visUnit || 'mi'; + return (
{/* Compact summary row — always visible */} -
setWeatherExpanded(!weatherExpanded)} - style={{ - display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer', - userSelect: 'none', padding: '2px 0', - }} - > - {localWeather.data.icon} - - {localWeather.data.temp}°F - - {localWeather.data.description} - - 💨{localWeather.data.windSpeed} - - +
+
setWeatherExpanded(!weatherExpanded)} + style={{ + display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer', + userSelect: 'none', flex: 1, minWidth: 0, + }} + > + {w.icon} + + {w.temp}{deg} + + {w.description} + + 💨{w.windSpeed} + + +
+ {/* F/C toggle */} +
{/* Expanded details */} @@ -645,14 +679,14 @@ const App = () => {
{/* Feels like + hi/lo */}
- {localWeather.data.feelsLike !== localWeather.data.temp && ( - Feels like {localWeather.data.feelsLike}°F + {w.feelsLike !== w.temp && ( + Feels like {w.feelsLike}{deg} )} - {localWeather.data.todayHigh != null && ( + {w.todayHigh != null && ( - ▲{localWeather.data.todayHigh}° + ▲{w.todayHigh}° {' '} - ▼{localWeather.data.todayLow}° + ▼{w.todayLow}° )}
@@ -667,50 +701,50 @@ const App = () => { }}>
💨 Wind - {localWeather.data.windDir} {localWeather.data.windSpeed} mph + {w.windDir} {w.windSpeed} {wind}
💧 Humidity - {localWeather.data.humidity}% + {w.humidity}%
- {localWeather.data.windGusts > localWeather.data.windSpeed + 5 && ( + {w.windGusts > w.windSpeed + 5 && (
🌬️ Gusts - {localWeather.data.windGusts} mph + {w.windGusts} {wind}
)}
🌡️ Dew Pt - {localWeather.data.dewPoint}°F + {w.dewPoint}{deg}
- {localWeather.data.pressure && ( + {w.pressure && (
🔵 Pressure - {localWeather.data.pressure} hPa + {w.pressure} hPa
)}
☁️ Clouds - {localWeather.data.cloudCover}% + {w.cloudCover}%
- {localWeather.data.visibility && ( + {w.visibility && (
👁️ Vis - {localWeather.data.visibility} mi + {w.visibility} {vis}
)} - {localWeather.data.uvIndex > 0 && ( + {w.uvIndex > 0 && (
☀️ UV - = 8 ? '#ef4444' : localWeather.data.uvIndex >= 6 ? '#f97316' : localWeather.data.uvIndex >= 3 ? '#eab308' : 'var(--text-secondary)' }}> - {localWeather.data.uvIndex.toFixed(1)} + = 8 ? '#ef4444' : w.uvIndex >= 6 ? '#f97316' : w.uvIndex >= 3 ? '#eab308' : 'var(--text-secondary)' }}> + {w.uvIndex.toFixed(1)}
)}
{/* 3-Day Forecast */} - {localWeather.data.daily?.length > 0 && ( + {w.daily?.length > 0 && (
{ }}>
FORECAST
- {localWeather.data.daily.map((day, i) => ( + {w.daily.map((day, i) => (
{
)}
- )} + ); + })()}
{/* DX Location */} diff --git a/src/hooks/useLocalWeather.js b/src/hooks/useLocalWeather.js index 3798097..d0afd86 100644 --- a/src/hooks/useLocalWeather.js +++ b/src/hooks/useLocalWeather.js @@ -43,7 +43,7 @@ function windDirection(deg) { return dirs[Math.round(deg / 22.5) % 16]; } -export const useLocalWeather = (location) => { +export const useLocalWeather = (location, tempUnit = 'F') => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); @@ -52,15 +52,16 @@ export const useLocalWeather = (location) => { const fetchWeather = async () => { try { + const isMetric = tempUnit === 'C'; const params = [ `latitude=${location.lat}`, `longitude=${location.lon}`, 'current=temperature_2m,relative_humidity_2m,apparent_temperature,weather_code,cloud_cover,pressure_msl,wind_speed_10m,wind_direction_10m,wind_gusts_10m,precipitation,uv_index,visibility,dew_point_2m,is_day', 'daily=temperature_2m_max,temperature_2m_min,precipitation_sum,precipitation_probability_max,weather_code,sunrise,sunset,uv_index_max,wind_speed_10m_max', 'hourly=temperature_2m,precipitation_probability,weather_code', - 'temperature_unit=fahrenheit', - 'wind_speed_unit=mph', - 'precipitation_unit=inch', + `temperature_unit=${isMetric ? 'celsius' : 'fahrenheit'}`, + `wind_speed_unit=${isMetric ? 'kmh' : 'mph'}`, + `precipitation_unit=${isMetric ? 'mm' : 'inch'}`, 'timezone=auto', 'forecast_days=3', 'forecast_hours=24', @@ -128,7 +129,11 @@ export const useLocalWeather = (location) => { windGusts: Math.round(current.wind_gusts_10m || 0), precipitation: current.precipitation || 0, uvIndex: current.uv_index || 0, - visibility: current.visibility ? (current.visibility / 1609.34).toFixed(1) : null, // meters to miles + visibility: current.visibility + ? isMetric + ? (current.visibility / 1000).toFixed(1) // meters to km + : (current.visibility / 1609.34).toFixed(1) // meters to miles + : null, isDay: current.is_day === 1, weatherCode: code, // Today's highs/lows @@ -139,6 +144,10 @@ export const useLocalWeather = (location) => { daily: dailyForecast, // Timezone timezone: result.timezone || '', + // Units + tempUnit: isMetric ? 'C' : 'F', + windUnit: isMetric ? 'km/h' : 'mph', + visUnit: isMetric ? 'km' : 'mi', }); } catch (err) { console.error('Weather error:', err); @@ -150,7 +159,7 @@ export const useLocalWeather = (location) => { fetchWeather(); const interval = setInterval(fetchWeather, 15 * 60 * 1000); // 15 minutes return () => clearInterval(interval); - }, [location?.lat, location?.lon]); + }, [location?.lat, location?.lon, tempUnit]); return { data, loading }; };