diff --git a/frontend/src/views/BriefingView.vue b/frontend/src/views/BriefingView.vue index 27f5fec..31560a3 100644 --- a/frontend/src/views/BriefingView.vue +++ b/frontend/src/views/BriefingView.vue @@ -92,6 +92,17 @@ async function loadWeather() { } catch { /* silent */ } } +const refreshingWeather = ref(false) +async function refreshWeather() { + refreshingWeather.value = true + try { + const data = await apiPost<{ locations: WeatherData[]; temp_unit: string }>('/api/briefing/weather/refresh', {}) + weatherData.value = data.locations ?? [] + tempUnit.value = data.temp_unit ?? 'C' + } catch { /* silent */ } + finally { refreshingWeather.value = false } +} + // News panel (right column) const newsItems = ref([]) @@ -279,14 +290,23 @@ onMounted(async () => {
-
+
+
+ +
+ class="weather-refresh-btn" + :class="{ spinning: refreshingWeather }" + :disabled="refreshingWeather" + @click="refreshWeather" + title="Refresh weather" + >↻
{ margin-bottom: 0; } +.weather-section-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 0.5rem; +} + +.weather-refresh-btn { + background: none; + border: 1px solid var(--color-border); + border-radius: 6px; + color: var(--color-text-muted); + font-size: 1rem; + cursor: pointer; + padding: 0.2rem 0.45rem; + line-height: 1; + transition: all 0.15s; +} +.weather-refresh-btn:hover { + border-color: var(--color-primary); + color: var(--color-primary); +} +.weather-refresh-btn:disabled { opacity: 0.5; cursor: not-allowed; } +.weather-refresh-btn.spinning { + animation: spin 0.8s linear infinite; +} +@keyframes spin { to { transform: rotate(360deg); } } + .weather-tabs { display: flex; gap: 0.25rem; - margin-bottom: 0.5rem; } .weather-tab { diff --git a/src/fabledassistant/routes/briefing.py b/src/fabledassistant/routes/briefing.py index 0cb56c9..d70c63b 100644 --- a/src/fabledassistant/routes/briefing.py +++ b/src/fabledassistant/routes/briefing.py @@ -167,6 +167,7 @@ async def get_weather(): return jsonify({"locations": cards, "temp_unit": temp_unit}) + @briefing_bp.route("/weather/current", methods=["GET"]) @_REQUIRE async def get_current_weather(): @@ -225,11 +226,14 @@ async def refresh_weather(): raw = await get_setting(g.user.id, "briefing_config", "{}") try: config = json.loads(raw) if isinstance(raw, str) else {} + temp_unit = config.get("temp_unit", "C") + if temp_unit not in ("C", "F"): + temp_unit = "C" except Exception: config = {} + temp_unit = "C" locations = config.get("locations", {}) - refreshed = [] for key, loc in locations.items(): if not loc.get("lat") or not loc.get("lon"): continue @@ -241,11 +245,15 @@ async def refresh_weather(): lat=loc["lat"], lon=loc["lon"], ) - refreshed.append(key) except Exception: logger.warning("Failed to refresh weather for %s", key, exc_info=True) - return jsonify({"refreshed": refreshed}) + rows = await weather_svc.get_cached_weather_rows(g.user.id) + cards = [ + card for row in rows + if (card := weather_svc.parse_weather_card_data(row, temp_unit)) is not None + ] + return jsonify({"locations": cards, "temp_unit": temp_unit}) # ── Briefing Conversations ─────────────────────────────────────────────────────