Grafik 2 spaltig auch bein iFrame
Übertragen nach sternwarte funktioniert
This commit is contained in:
59
frontend/src/App-static.jsx
Normal file
59
frontend/src/App-static.jsx
Normal file
@@ -0,0 +1,59 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import WeatherDashboard from './components/WeatherDashboard'
|
||||
import './App.css'
|
||||
|
||||
function App() {
|
||||
const [weatherData, setWeatherData] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
const [lastUpdate, setLastUpdate] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
// Prüfe ob eingebettete Daten vorhanden sind
|
||||
if (window.__WEATHER_DATA__) {
|
||||
setWeatherData(window.__WEATHER_DATA__)
|
||||
setLastUpdate(new Date())
|
||||
setLoading(false)
|
||||
} else {
|
||||
setError('Keine Wetterdaten verfügbar')
|
||||
setLoading(false)
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="loading-container">
|
||||
<div className="loading-spinner"></div>
|
||||
<p>Lade Wetterdaten...</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="error-container">
|
||||
<h2>Fehler beim Laden der Daten</h2>
|
||||
<p>{error}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<header className="app-header">
|
||||
<h1>🌤️ Wetterstation</h1>
|
||||
{lastUpdate && (
|
||||
<p className="last-update">
|
||||
Letzte Aktualisierung: {lastUpdate.toLocaleTimeString('de-DE')}
|
||||
</p>
|
||||
)}
|
||||
</header>
|
||||
|
||||
<main className="app-main">
|
||||
<WeatherDashboard data={weatherData} />
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
@@ -8,63 +8,16 @@ function App() {
|
||||
const [error, setError] = useState(null)
|
||||
const [lastUpdate, setLastUpdate] = useState(null)
|
||||
|
||||
const fetchWeatherData = async () => {
|
||||
try {
|
||||
const apiUrl = import.meta.env.VITE_API_URL || '/api'
|
||||
const response = await fetch(`${apiUrl}/weather/history?hours=24`)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Fehler beim Laden der Daten')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
setWeatherData(data)
|
||||
useEffect(() => {
|
||||
// Prüfe ob eingebettete Daten vorhanden sind
|
||||
if (window.__WEATHER_DATA__) {
|
||||
setWeatherData(window.__WEATHER_DATA__)
|
||||
setLastUpdate(new Date())
|
||||
setError(null)
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
console.error('Fehler beim Laden der Wetterdaten:', err)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
} else {
|
||||
setError('Keine Wetterdaten verfügbar')
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchWeatherData()
|
||||
|
||||
// Berechne Zeit bis zum nächsten 5-Min-Schritt + 1 Minute
|
||||
const scheduleNextRefresh = () => {
|
||||
const now = new Date()
|
||||
const minutes = now.getMinutes()
|
||||
const seconds = now.getSeconds()
|
||||
const milliseconds = now.getMilliseconds()
|
||||
|
||||
// Nächster 5-Minuten-Schritt
|
||||
const nextFiveMinStep = Math.ceil(minutes / 5) * 5
|
||||
// Plus 1 Minute
|
||||
const targetMinute = (nextFiveMinStep + 1) % 60
|
||||
|
||||
let targetTime = new Date(now)
|
||||
targetTime.setMinutes(targetMinute, 0, 0)
|
||||
|
||||
// Wenn die Zielzeit in der Vergangenheit liegt, füge eine Stunde hinzu
|
||||
if (targetTime <= now) {
|
||||
targetTime.setHours(targetTime.getHours() + 1)
|
||||
}
|
||||
|
||||
const timeUntilRefresh = targetTime - now
|
||||
|
||||
console.log(`Nächster Refresh: ${targetTime.toLocaleTimeString('de-DE')} (in ${Math.round(timeUntilRefresh / 1000)}s)`)
|
||||
|
||||
return setTimeout(() => {
|
||||
fetchWeatherData()
|
||||
scheduleNextRefresh()
|
||||
}, timeUntilRefresh)
|
||||
}
|
||||
|
||||
const timeout = scheduleNextRefresh()
|
||||
|
||||
return () => clearTimeout(timeout)
|
||||
}, [])
|
||||
|
||||
if (loading) {
|
||||
@@ -81,7 +34,6 @@ function App() {
|
||||
<div className="error-container">
|
||||
<h2>Fehler beim Laden der Daten</h2>
|
||||
<p>{error}</p>
|
||||
<button onClick={fetchWeatherData}>Erneut versuchen</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
107
frontend/src/App.jsx_org
Normal file
107
frontend/src/App.jsx_org
Normal file
@@ -0,0 +1,107 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import WeatherDashboard from './components/WeatherDashboard'
|
||||
import './App.css'
|
||||
|
||||
function App() {
|
||||
const [weatherData, setWeatherData] = useState([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState(null)
|
||||
const [lastUpdate, setLastUpdate] = useState(null)
|
||||
|
||||
const fetchWeatherData = async () => {
|
||||
try {
|
||||
const apiUrl = import.meta.env.VITE_API_URL || '/api'
|
||||
const response = await fetch(`${apiUrl}/weather/history?hours=24`)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Fehler beim Laden der Daten')
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
setWeatherData(data)
|
||||
setLastUpdate(new Date())
|
||||
setError(null)
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
console.error('Fehler beim Laden der Wetterdaten:', err)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchWeatherData()
|
||||
|
||||
// Berechne Zeit bis zum nächsten 5-Min-Schritt + 1 Minute
|
||||
const scheduleNextRefresh = () => {
|
||||
const now = new Date()
|
||||
const minutes = now.getMinutes()
|
||||
const seconds = now.getSeconds()
|
||||
const milliseconds = now.getMilliseconds()
|
||||
|
||||
// Nächster 5-Minuten-Schritt
|
||||
const nextFiveMinStep = Math.ceil(minutes / 5) * 5
|
||||
// Plus 1 Minute
|
||||
const targetMinute = (nextFiveMinStep + 1) % 60
|
||||
|
||||
let targetTime = new Date(now)
|
||||
targetTime.setMinutes(targetMinute, 0, 0)
|
||||
|
||||
// Wenn die Zielzeit in der Vergangenheit liegt, füge eine Stunde hinzu
|
||||
if (targetTime <= now) {
|
||||
targetTime.setHours(targetTime.getHours() + 1)
|
||||
}
|
||||
|
||||
const timeUntilRefresh = targetTime - now
|
||||
|
||||
console.log(`Nächster Refresh: ${targetTime.toLocaleTimeString('de-DE')} (in ${Math.round(timeUntilRefresh / 1000)}s)`)
|
||||
|
||||
return setTimeout(() => {
|
||||
fetchWeatherData()
|
||||
scheduleNextRefresh()
|
||||
}, timeUntilRefresh)
|
||||
}
|
||||
|
||||
const timeout = scheduleNextRefresh()
|
||||
|
||||
return () => clearTimeout(timeout)
|
||||
}, [])
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="loading-container">
|
||||
<div className="loading-spinner"></div>
|
||||
<p>Lade Wetterdaten...</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="error-container">
|
||||
<h2>Fehler beim Laden der Daten</h2>
|
||||
<p>{error}</p>
|
||||
<button onClick={fetchWeatherData}>Erneut versuchen</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
<header className="app-header">
|
||||
<h1>🌤️ Wetterstation</h1>
|
||||
{lastUpdate && (
|
||||
<p className="last-update">
|
||||
Letzte Aktualisierung: {lastUpdate.toLocaleTimeString('de-DE')}
|
||||
</p>
|
||||
)}
|
||||
</header>
|
||||
|
||||
<main className="app-main">
|
||||
<WeatherDashboard data={weatherData} />
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
@@ -1,18 +1,19 @@
|
||||
.dashboard {
|
||||
width: 100%;
|
||||
max-width: 795px;
|
||||
}
|
||||
|
||||
.current-values {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.value-card {
|
||||
background: white;
|
||||
padding: 1.5rem;
|
||||
border-radius: 12px;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -20,13 +21,13 @@
|
||||
}
|
||||
|
||||
.value-label {
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.8rem;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.value-number {
|
||||
font-size: 2rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
@@ -34,47 +35,23 @@
|
||||
.charts-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1.5rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
background: white;
|
||||
padding: 1.5rem;
|
||||
border-radius: 12px;
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.chart-container h3 {
|
||||
margin-bottom: 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
color: #333;
|
||||
font-size: 1.2rem;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
height: 300px;
|
||||
height: 250px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.charts-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.chart-container.chart-full {
|
||||
grid-column: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.current-values {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.value-number {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.chart-wrapper {
|
||||
height: 250px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ const WeatherDashboard = ({ data }) => {
|
||||
data: sortedData.map(item => item.temperature),
|
||||
borderColor: 'rgb(255, 99, 132)',
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.1)',
|
||||
fill: true,
|
||||
fill: 'start',
|
||||
tension: 0.4,
|
||||
}
|
||||
]
|
||||
@@ -254,9 +254,11 @@ const WeatherDashboard = ({ data }) => {
|
||||
label: 'Windrichtung (°)',
|
||||
data: sortedData.map(item => item.wind_dir),
|
||||
borderColor: 'rgb(255, 205, 86)',
|
||||
backgroundColor: 'rgba(255, 205, 86, 0.1)',
|
||||
fill: true,
|
||||
tension: 0,
|
||||
backgroundColor: 'rgb(255, 205, 86)',
|
||||
pointRadius: 4,
|
||||
pointHoverRadius: 6,
|
||||
showLine: false,
|
||||
fill: false,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user