weather icons

This commit is contained in:
Jake Kasper
2025-09-02 08:37:05 -05:00
parent 7897c2be58
commit 8c56914afe
3 changed files with 46 additions and 10 deletions

View File

@@ -35,7 +35,13 @@ app.use(helmet({
scriptSrc: ["'self'", "'unsafe-inline'", "https://maps.googleapis.com"],
styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"],
fontSrc: ["'self'", "https://fonts.gstatic.com"],
imgSrc: ["'self'", "data:", "https://maps.googleapis.com", "https://maps.gstatic.com"],
imgSrc: [
"'self'",
"data:",
"https://maps.googleapis.com",
"https://maps.gstatic.com",
"https://openweathermap.org"
],
connectSrc: ["'self'", "https://api.openweathermap.org"]
}
}

View File

@@ -220,6 +220,7 @@ router.get('/:propertyId/forecast', async (req, res, next) => {
windSpeed: [],
precipitation: 0,
conditions: [],
icons: [],
timestamps: []
};
}
@@ -229,6 +230,7 @@ router.get('/:propertyId/forecast', async (req, res, next) => {
dailyForecast[date].windSpeed.push(item.wind?.speed || 0);
dailyForecast[date].precipitation += item.rain?.['3h'] || 0;
dailyForecast[date].conditions.push(item.weather[0].description);
dailyForecast[date].icons.push(item.weather[0].icon);
dailyForecast[date].timestamps.push(new Date(item.dt * 1000));
});
@@ -246,6 +248,7 @@ router.get('/:propertyId/forecast', async (req, res, next) => {
maxWindSpeed: Math.round(Math.max(...windSpeeds)),
totalPrecipitation: Math.round(day.precipitation * 100) / 100,
conditions: day.conditions[0], // Use first condition of the day
icon: day.icons[0],
timestamps: day.timestamps
};
}).slice(0, 5); // Limit to 5 days

View File

@@ -3,7 +3,7 @@ import { propertiesAPI, weatherAPI } from '../../services/api';
const Weather = () => {
const [properties, setProperties] = useState([]);
const [weatherMap, setWeatherMap] = useState({}); // propertyId -> weather
const [weatherMap, setWeatherMap] = useState({}); // propertyId -> { weather, forecast }
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
@@ -20,14 +20,26 @@ const Weather = () => {
.filter(p => p.latitude && p.longitude)
.map(p => p.id);
const results = await Promise.allSettled(ids.map(id => weatherAPI.getCurrent(id)));
// Current weather
const currentResults = await Promise.allSettled(ids.map(id => weatherAPI.getCurrent(id)));
const map = {};
results.forEach((r, idx) => {
currentResults.forEach((r, idx) => {
const id = ids[idx];
if (r.status === 'fulfilled') {
map[id] = r.value.data.data.weather;
map[id] = { weather: r.value.data.data.weather };
}
});
// 5-day forecast
const forecastResults = await Promise.allSettled(ids.map(id => weatherAPI.getForecast(id)));
forecastResults.forEach((r, idx) => {
const id = ids[idx];
if (r.status === 'fulfilled') {
map[id] = map[id] || {};
map[id].forecast = r.value.data.data.forecast;
}
});
setWeatherMap(map);
setError(null);
} catch (e) {
@@ -58,20 +70,35 @@ const Weather = () => {
</div>
{!p.latitude || !p.longitude ? (
<div className="text-sm text-gray-600">No coordinates set</div>
) : weatherMap[p.id] ? (
) : weatherMap[p.id]?.weather ? (
<div className="flex items-center">
<img alt="icon" src={`https://openweathermap.org/img/wn/${weatherMap[p.id].current.icon}@2x.png`} />
<img alt="" width="50" height="50" src={`https://openweathermap.org/img/wn/${weatherMap[p.id].weather.current.icon}@2x.png`} />
<div className="ml-3">
<div className="text-xl font-bold">{weatherMap[p.id].current.temperature}°F</div>
<div className="text-sm text-gray-700">{weatherMap[p.id].current.conditions}</div>
<div className="text-xl font-bold">{weatherMap[p.id].weather.current.temperature}°F</div>
<div className="text-sm text-gray-700">{weatherMap[p.id].weather.current.conditions}</div>
<div className="text-xs text-gray-500 mt-1">
Humidity {weatherMap[p.id].current.humidity}% Wind {Math.round(weatherMap[p.id].current.windSpeed)} mph
Humidity {weatherMap[p.id].weather.current.humidity}% Wind {Math.round(weatherMap[p.id].weather.current.windSpeed)} mph
</div>
</div>
</div>
) : (
<div className="text-sm text-gray-600">Loading weather</div>
)}
{/* Forecast */}
{weatherMap[p.id]?.forecast?.daily && (
<div className="mt-3 grid grid-cols-5 gap-2 text-center">
{weatherMap[p.id].forecast.daily.map((d, i) => (
<div key={i} className="text-xs text-gray-700">
<div className="font-medium">
{new Date(d.date).toLocaleDateString(undefined, { weekday: 'short' })}
</div>
<img alt="" className="mx-auto" width="40" height="40" src={`https://openweathermap.org/img/wn/${d.icon}.png`} />
<div>{d.temperatureHigh}° / {d.temperatureLow}°</div>
</div>
))}
</div>
)}
</div>
))}
</div>