import { useState, useEffect, useRef } from 'react' import WeatherDashboard from './components/WeatherDashboard' import './App.css' // API-Basis-URL: in Dev direkt auf Backend, in Prod ueber Nginx-Proxy const API_BASE = import.meta.env.DEV ? 'http://localhost:8000' : '/api' // 24-Stunden-URL fuer "Aktuell"-Anzeige (auch bei laengeren Zeitraeumen gebraucht) const CURRENT_URL = `${API_BASE}/weather/history?hours=24&limit=5000` // JSON-Fetch-Helfer: liefert {ok, data} oder wirft bei Netzfehler. // Per signal kann der Request abgebrochen werden, wenn timeRange wechselt. async function fetchJson(url, signal) { const res = await fetch(url, { signal }) if (!res.ok) throw new Error(`HTTP ${res.status} bei ${url}`) return res.json() } // Bestimmt die URLs fuer den gewaehlten Zeitbereich. // Returns: { weatherUrl, rainUrl, needsCurrent } function buildUrls(timeRange) { // Custom-Range if (typeof timeRange === 'object' && timeRange.type === 'custom') { const start = encodeURIComponent(timeRange.start) const end = encodeURIComponent(timeRange.end) const days = timeRange.days || 1 const path = days >= 7 ? 'daily-aggregated-range' : 'hourly-aggregated-range' return { weatherUrl: `${API_BASE}/weather/${path}?start=${start}&end=${end}`, rainUrl: days < 7 ? `${API_BASE}/weather/daily-aggregated-range?start=${start}&end=${end}` : null, needsCurrent: true, } } switch (timeRange) { case '24h': return { weatherUrl: `${API_BASE}/weather/history?hours=24&limit=5000`, rainUrl: null, needsCurrent: false, // Hauptdaten SIND die aktuellen 24h-Daten } case '7d': return { weatherUrl: `${API_BASE}/weather/daily-with-minmax?days=7`, rainUrl: `${API_BASE}/weather/rain-daily?days=7`, needsCurrent: true, } case '30d': return { weatherUrl: `${API_BASE}/weather/daily-with-minmax?days=30`, rainUrl: `${API_BASE}/weather/rain-daily?days=30`, needsCurrent: true, } case '365d': return { weatherUrl: `${API_BASE}/weather/daily-aggregated?days=365`, rainUrl: `${API_BASE}/weather/rain-weekly?days=365`, needsCurrent: true, } default: return { weatherUrl: `${API_BASE}/weather/history?hours=24`, rainUrl: null, needsCurrent: false, } } } function App() { const [weatherData, setWeatherData] = useState([]) const [currentWeatherData, setCurrentWeatherData] = useState([]) const [rainData, setRainData] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [lastUpdate, setLastUpdate] = useState(null) const [timeRange, setTimeRange] = useState('24h') const [showTable, setShowTable] = useState(false) // Erster-Lade-Flag: nur beim allerersten Fetch zeigen wir den Spinner. // Bei spaeteren Re-Fetches (Auto-Refresh, Time-Range-Wechsel) bleiben die // alten Daten sichtbar, bis die neuen da sind — flackert weniger. const isInitialLoadRef = useRef(true) const handleTimeRangeChange = (range, customParams) => { if (range === 'custom' && customParams) { const start = new Date(customParams.start) const end = new Date(customParams.end) const days = Math.ceil((end - start) / (1000 * 60 * 60 * 24)) setTimeRange({ type: 'custom', start: customParams.start, end: customParams.end, days }) } else { setTimeRange(range) } } useEffect(() => { // Statische Daten: kein Fetch noetig if (window.__WEATHER_DATA__ && timeRange === '24h') { setWeatherData(window.__WEATHER_DATA__) setCurrentWeatherData(window.__WEATHER_DATA__) setRainData([]) setLastUpdate(new Date()) setLoading(false) return } const controller = new AbortController() const fetchData = async () => { if (isInitialLoadRef.current) setLoading(true) const { weatherUrl, rainUrl, needsCurrent } = buildUrls(timeRange) // Alle drei Requests parallel starten (statt sequentiell wie vorher). // allSettled, damit ein Fehler bei rain/current die Hauptdaten nicht blockiert. const requests = [ fetchJson(weatherUrl, controller.signal), // [0] weather - Pflicht needsCurrent ? fetchJson(CURRENT_URL, controller.signal) : null, // [1] current - optional rainUrl ? fetchJson(rainUrl, controller.signal) : null, // [2] rain - optional ] const results = await Promise.allSettled(requests.map(p => p ?? Promise.resolve(null))) // AbortError ignorieren — passiert, wenn timeRange waehrend des Requests // gewechselt hat. Der nachfolgende Effekt-Lauf macht den richtigen Fetch. const aborted = results.some( r => r.status === 'rejected' && r.reason?.name === 'AbortError' ) if (aborted) return // Hauptdaten-Fehler ist fatal; ohne die zeigen wir nichts an. if (results[0].status === 'rejected') { setError(results[0].reason?.message || 'Unbekannter Fehler') setLoading(false) isInitialLoadRef.current = false return } const weatherResult = results[0].value const currentResult = results[1].status === 'fulfilled' ? results[1].value : null const rainResult = results[2].status === 'fulfilled' ? results[2].value : null setError(null) setWeatherData(weatherResult) // Wenn 24h gewaehlt ist, sind weather und current dieselben Daten setCurrentWeatherData(needsCurrent ? (currentResult ?? []) : weatherResult) setRainData(rainResult ?? []) setLastUpdate(new Date()) setLoading(false) isInitialLoadRef.current = false } fetchData() // Auto-Refresh nur bei 24h, nur wenn keine statischen Daten let interval = null if (!window.__WEATHER_DATA__ && timeRange === '24h') { interval = setInterval(fetchData, 5 * 60 * 1000) } return () => { controller.abort() if (interval) clearInterval(interval) } }, [timeRange]) if (loading) { return (

Lade Wetterdaten...

) } if (error) { return (

Fehler beim Laden der Daten

{error}

) } // Aktuelle Zeit formatieren const now = new Date() const dateStr = now.toLocaleDateString('de-DE', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }) const timeStr = now.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' }) // TODO: Sonnenauf-/untergang und Mondphase berechnen // Aktuell Platzhalter - benoetigt Bibliothek wie 'suncalc' const sunrise = "06:45" const sunset = "18:30" const moonPhase = "abnehmend 50%" return (

Aktuelle Wetterdaten

{dateStr} - {timeStr} Uhr
48.6 N - 9.6 E - 574 m NN
Sonnen-Aufgang: {sunrise} - Untergang: {sunset}    Mond-Phase: {moonPhase}
setShowTable(v => !v)} />
) } export default App