weather_app/src/lib/stores/notifications.svelte.js

154 lines
4.9 KiB
JavaScript

/**
* Weather alert / notification store using Svelte 5 runes.
* Monitors forecast data and generates alert banners.
* Dismissed alerts persist in IndexedDB and only reset when the forecast date changes.
*/
import { isSevereWeather } from '../api/weather-codes.js'
import { app } from './app.svelte.js'
import { saveSettings } from '../storage/db.js'
import { parseLocalDate } from '../dates.js'
export class NotificationStore {
/** @type {Array<{ id: string, type: string, severity: 'info'|'warning'|'danger', message: string, icon: string }>} */
alerts = $state([])
/** @type {Set<string>} */
dismissedIds = $state(new Set())
/** Track which forecast day last generated alerts — reset dismissals when the day changes */
lastForecastDate = null
/** Whether dismissals have been loaded from DB */
_dismissalsLoaded = false
/**
* Check forecast data and generate appropriate alerts.
*/
analyze() {
const data = app.forecastData
const settings = app.settings
if (!data || !settings.alertsEnabled) {
this.alerts = []
return
}
// Load persisted dismissals from DB on first analysis
if (!this._dismissalsLoaded) {
this._dismissalsLoaded = true
this._loadDismissed()
}
// Track the forecast date. If it changed, reset dismissals.
const forecastDate = data.daily?.time?.[0] || null
if (forecastDate && this.lastForecastDate && forecastDate !== this.lastForecastDate) {
this.resetDismissed()
}
this.lastForecastDate = forecastDate
const t = settings.alertThresholds
const newAlerts = []
function add(id, type, severity, message, icon) {
if (!app.settings.alertsEnabled) return
newAlerts.push({ id, type, severity, message, icon })
}
const current = data.current
const daily = data.daily || {}
// --- Current weather alerts ---
if (t.thunderstorm && isSevereWeather(current.weather_code)) {
add('storm-now', 'storm', 'danger',
'⚡ Thunderstorm active — seek shelter if outdoors', '⛈️')
}
// --- Daily forecast alerts ---
if (daily.time) {
for (let i = 0; i < daily.time.length && i < 3; i++) {
const day = daily.time[i]
const dateLabel = i === 0 ? 'Today' : i === 1 ? 'Tomorrow' : parseLocalDate(day).toLocaleDateString('en-US', { weekday: 'short' })
const precip = daily.precipitation_probability_max?.[i] || 0
if (precip >= t.precip) {
add(`rain-${i}`, 'precip', 'warning',
`🌧️ ${precip}% chance of rain ${dateLabel.toLowerCase()}`, '🌧️')
}
const code = daily.weather_code?.[i]
if (t.thunderstorm && isSevereWeather(code)) {
add(`storm-${i}`, 'storm', 'danger',
`⛈️ Thunderstorms expected ${dateLabel.toLowerCase()}`, '⛈️')
}
const tempHigh = daily.temperature_2m_max?.[i]
if (tempHigh != null && tempHigh > t.tempHigh) {
add(`heat-${i}`, 'heat', 'warning',
`🔥 ${dateLabel} high of ${Math.round(tempHigh)}° — stay hydrated`, '🔥')
}
const tempMin = daily.temperature_2m_min?.[i]
if (tempMin != null && tempMin < t.tempLow) {
add(`cold-${i}`, 'cold', 'warning',
`🥶 ${dateLabel} low of ${Math.round(tempMin)}° — bundle up`, '❄️')
}
const uv = daily.uv_index_max?.[i]
if (uv != null && uv >= t.uvIndex) {
add(`uv-${i}`, 'uv', 'warning',
`☀️ High UV index (${uv}) ${dateLabel.toLowerCase()} — wear sunscreen`, '🧴')
}
const wind = daily.wind_speed_10m_max?.[i]
if (wind != null && wind >= t.windGust) {
add(`wind-${i}`, 'wind', 'warning',
`💨 Strong winds (${Math.round(wind)} ${app.settings.units === 'imperial' ? 'mph' : 'km/h'}) ${dateLabel.toLowerCase()}`, '💨')
}
}
}
this.alerts = newAlerts.filter((a) => !this.dismissedIds.has(a.id))
}
/**
* Dismiss a specific alert. Persists to IndexedDB.
* @param {string} alertId
*/
dismiss(alertId) {
this.dismissedIds.add(alertId)
this.alerts = this.alerts.filter((a) => a.id !== alertId)
this._persistDismissed()
}
/**
* Reset all dismissed alerts (e.g., when forecast date changes).
*/
resetDismissed() {
this.dismissedIds.clear()
this._persistDismissed()
}
/**
* Load dismissed alert IDs from the persisted settings.
*/
_loadDismissed() {
const saved = app.settings.dismissedAlerts || []
this.dismissedIds = new Set(saved)
}
/**
* Save dismissed alert IDs to settings in IndexedDB.
*/
async _persistDismissed() {
const arr = Array.from(this.dismissedIds)
app.settings.dismissedAlerts = arr
try {
await saveSettings(app.settings)
} catch (e) {
console.error('Failed to persist dismissed alerts:', e)
}
}
}
export const notifications = new NotificationStore()