V 1.2.0 diverse Anpassungen, so dass es ähnlich der alten Anwenung ist. Ist nun mal gut zu benutzen

This commit is contained in:
rxf
2026-03-24 17:18:25 +01:00
parent acd509fef6
commit 267f8198b9
4 changed files with 168 additions and 70 deletions

View File

@@ -1,7 +1,7 @@
{ {
"name": "wetterstation-frontend", "name": "wetterstation-frontend",
"private": true, "private": true,
"version": "1.1.0", "version": "1.2.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -4,6 +4,7 @@ import './App.css'
function App() { function App() {
const [weatherData, setWeatherData] = useState([]) const [weatherData, setWeatherData] = useState([])
const [currentWeatherData, setCurrentWeatherData] = useState([]) // Immer die aktuellen 24h-Werte
const [rainData, setRainData] = useState([]) const [rainData, setRainData] = useState([])
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
const [error, setError] = useState(null) const [error, setError] = useState(null)
@@ -18,6 +19,7 @@ function App() {
// Prüfe ob eingebettete Daten vorhanden sind (statischer Build) // Prüfe ob eingebettete Daten vorhanden sind (statischer Build)
if (window.__WEATHER_DATA__ && timeRange === '24h') { if (window.__WEATHER_DATA__ && timeRange === '24h') {
setWeatherData(window.__WEATHER_DATA__) setWeatherData(window.__WEATHER_DATA__)
setCurrentWeatherData(window.__WEATHER_DATA__)
setRainData([]) setRainData([])
setLastUpdate(new Date()) setLastUpdate(new Date())
setLoading(false) setLoading(false)
@@ -58,6 +60,18 @@ function App() {
const weatherDataResult = await weatherResponse.json() const weatherDataResult = await weatherResponse.json()
setWeatherData(weatherDataResult) setWeatherData(weatherDataResult)
// Immer die aktuellen 24h-Daten für "Aktuell"-Anzeige laden
if (timeRange !== '24h') {
const currentUrl = `${baseUrl}/weather/history?hours=24`
const currentResponse = await fetch(currentUrl)
if (currentResponse.ok) {
const currentDataResult = await currentResponse.json()
setCurrentWeatherData(currentDataResult)
}
} else {
setCurrentWeatherData(weatherDataResult)
}
// Regendaten laden (falls separater Endpunkt) // Regendaten laden (falls separater Endpunkt)
if (rainUrl) { if (rainUrl) {
const rainResponse = await fetch(rainUrl) const rainResponse = await fetch(rainUrl)
@@ -138,6 +152,7 @@ function App() {
<main className="app-main"> <main className="app-main">
<WeatherDashboard <WeatherDashboard
data={weatherData} data={weatherData}
currentData={currentWeatherData}
rainData={rainData} rainData={rainData}
timeRange={timeRange} timeRange={timeRange}
onTimeRangeChange={setTimeRange} onTimeRangeChange={setTimeRange}

View File

@@ -1,6 +1,6 @@
.dashboard { .dashboard {
width: 100%; width: 100%;
/* max-width: 1900px; */ /* max-width: 1900px; */
max-width: 795px; max-width: 795px;
margin: 0 auto; margin: 0 auto;
} }
@@ -51,7 +51,7 @@
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 0.75rem; gap: 0.75rem;
margin-bottom: 1.5rem; margin-bottom: 1rem;
} }
.value-card { .value-card {
@@ -82,17 +82,43 @@
gap: 1rem; gap: 1rem;
} }
.chart-item {
display: flex;
flex-direction: column;
gap: 0.2rem;
}
.chart-container { .chart-container {
background: white; background: white;
padding: 1rem; padding: 0.5rem;
border-radius: 8px; border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
} }
.chart-container h3 { .chart-container h3 {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
color: #333; color: #333;
font-size: 1rem; font-size: 1rem;
background: #e0e0e0;
padding: 0.5rem;
border-radius: 4px;
margin: -0.5rem -0.5rem 0.5rem -0.5rem;
}
.chart-container h3 .unit {
font-weight: normal;
color: #666;
font-size: 0.9rem;
}
.current-value {
text-align: left;
font-size: 0.9rem;
color: #0066cc;
font-weight: 600;
} }
.chart-wrapper { .chart-wrapper {
@@ -102,11 +128,15 @@
} }
.chart-stats { .chart-stats {
margin-top: 0.5rem; /* margin-top: 0.5rem; */
text-align: center; text-align: center;
font-size: 0.85rem; font-size: 0.7rem;
color: #666; color: #666;
font-weight: 500; font-weight: 500;
background: #e0e0e0;
padding: 0.5rem;
border-radius: 4px;
margin: 0.2rem -0.5rem -0.5rem -0.5rem;
} }
.dashboard-footer { .dashboard-footer {
@@ -117,7 +147,7 @@
.footer-divider { .footer-divider {
border: none; border: none;
border-top: 1px solid #ccc; border-top: 1px solid #ccc;
margin: 0 0 1rem 0; margin: 0 0 0.5rem 0;
} }
.footer-credits { .footer-credits {
@@ -155,7 +185,8 @@
.version-line { .version-line {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin-bottom: 15px; margin-bottom: 0.5rem;
font-size: 0.85rem;
} }
.version-short { .version-short {
@@ -164,6 +195,15 @@
.version-full { .version-full {
display: inline; display: inline;
color: #666;
}
.time-range-short {
display: none;
}
.time-range-full {
display: inline;
} }
/* Responsive Design für schmale Bildschirme (Smartphones) */ /* Responsive Design für schmale Bildschirme (Smartphones) */
@@ -185,12 +225,21 @@
font-size: 0.85rem; font-size: 0.85rem;
} }
.time-range-short {
display: inline;
}
.time-range-full {
display: none;
}
.time-range-label { .time-range-label {
font-size: 1rem; font-size: 1rem;
} }
.version-short { .version-short {
display: inline; display: inline;
color: #666;
} }
.version-full { .version-full {

View File

@@ -20,12 +20,17 @@ Highcharts.setOptions({
} }
}) })
const WeatherDashboard = ({ data, rainData = [], timeRange = '24h', onTimeRangeChange }) => { const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '24h', onTimeRangeChange }) => {
// Daten vorbereiten und nach Zeit sortieren (älteste zuerst) // Daten vorbereiten und nach Zeit sortieren (älteste zuerst)
const sortedData = useMemo(() => { const sortedData = useMemo(() => {
return [...data].sort((a, b) => new Date(a.datetime) - new Date(b.datetime)) return [...data].sort((a, b) => new Date(a.datetime) - new Date(b.datetime))
}, [data]) }, [data])
// Aktuelle Werte aus separaten currentData (immer 24h)
const sortedCurrentData = useMemo(() => {
return [...currentData].sort((a, b) => new Date(a.datetime) - new Date(b.datetime))
}, [currentData])
// Zeitraum-Label // Zeitraum-Label
const timeRangeLabel = useMemo(() => { const timeRangeLabel = useMemo(() => {
switch (timeRange) { switch (timeRange) {
@@ -118,7 +123,7 @@ const WeatherDashboard = ({ data, rainData = [], timeRange = '24h', onTimeRangeC
tooltip: { tooltip: {
shared: true, shared: true,
crosshairs: true, crosshairs: true,
xDateFormat: timeRange === '24h' ? '%d.%m.%Y %H:%M' : '%d.%m.%Y' xDateFormat: timeRange === '24h' ? '%d.%m.%Y %H:%M' : (timeRange === '7d' || timeRange === '30d' ? '%d.%m.%Y - %Hh' : '%d.%m.%Y')
}, },
plotOptions: { plotOptions: {
series: { series: {
@@ -160,7 +165,7 @@ const WeatherDashboard = ({ data, rainData = [], timeRange = '24h', onTimeRangeC
...getCommonOptions(), ...getCommonOptions(),
yAxis: { yAxis: {
...getCommonOptions().yAxis, ...getCommonOptions().yAxis,
title: { text: 'Temperatur (°C)' }, title: { text: null },
min: yMin, min: yMin,
max: yMax max: yMax
}, },
@@ -181,6 +186,7 @@ const WeatherDashboard = ({ data, rainData = [], timeRange = '24h', onTimeRangeC
gapSize: 2 * 24 * 3600 * 1000, gapSize: 2 * 24 * 3600 * 1000,
gapUnit: 'value', gapUnit: 'value',
tooltip: { tooltip: {
valueDecimals: 1,
valueSuffix: ' °C' valueSuffix: ' °C'
} }
}] }]
@@ -192,7 +198,7 @@ const WeatherDashboard = ({ data, rainData = [], timeRange = '24h', onTimeRangeC
...getCommonOptions(), ...getCommonOptions(),
yAxis: { yAxis: {
...getCommonOptions().yAxis, ...getCommonOptions().yAxis,
title: { text: 'Feuchte (%)' }, title: { text: null },
min: 40, min: 40,
max: 100 max: 100
}, },
@@ -212,6 +218,7 @@ const WeatherDashboard = ({ data, rainData = [], timeRange = '24h', onTimeRangeC
gapSize: 2 * 24 * 3600 * 1000, gapSize: 2 * 24 * 3600 * 1000,
gapUnit: 'value', gapUnit: 'value',
tooltip: { tooltip: {
valueDecimals: 0,
valueSuffix: ' %' valueSuffix: ' %'
} }
}] }]
@@ -237,7 +244,7 @@ const WeatherDashboard = ({ data, rainData = [], timeRange = '24h', onTimeRangeC
...getCommonOptions(), ...getCommonOptions(),
yAxis: { yAxis: {
...getCommonOptions().yAxis, ...getCommonOptions().yAxis,
title: { text: 'Luftdruck (hPa)' }, title: { text: null },
min: yMin, min: yMin,
max: yMax max: yMax
}, },
@@ -257,6 +264,7 @@ const WeatherDashboard = ({ data, rainData = [], timeRange = '24h', onTimeRangeC
gapSize: 2 * 24 * 3600 * 1000, gapSize: 2 * 24 * 3600 * 1000,
gapUnit: 'value', gapUnit: 'value',
tooltip: { tooltip: {
valueDecimals: 0,
valueSuffix: ' hPa' valueSuffix: ' hPa'
} }
}] }]
@@ -320,7 +328,7 @@ const WeatherDashboard = ({ data, rainData = [], timeRange = '24h', onTimeRangeC
...getCommonOptions(), ...getCommonOptions(),
yAxis: { yAxis: {
...getCommonOptions().yAxis, ...getCommonOptions().yAxis,
title: { text: yAxisTitle } title: { text: null }
}, },
series series
} }
@@ -342,6 +350,7 @@ const WeatherDashboard = ({ data, rainData = [], timeRange = '24h', onTimeRangeC
gapSize: 2 * 24 * 3600 * 1000, gapSize: 2 * 24 * 3600 * 1000,
gapUnit: 'value', gapUnit: 'value',
tooltip: { tooltip: {
valueDecimals: 1,
valueSuffix: ' km/h' valueSuffix: ' km/h'
} }
}] }]
@@ -357,6 +366,7 @@ const WeatherDashboard = ({ data, rainData = [], timeRange = '24h', onTimeRangeC
gapSize: 2 * 24 * 3600 * 1000, gapSize: 2 * 24 * 3600 * 1000,
gapUnit: 'value', gapUnit: 'value',
tooltip: { tooltip: {
valueDecimals: 1,
valueSuffix: ' km/h' valueSuffix: ' km/h'
} }
}, { }, {
@@ -371,6 +381,7 @@ const WeatherDashboard = ({ data, rainData = [], timeRange = '24h', onTimeRangeC
gapSize: 2 * 24 * 3600 * 1000, gapSize: 2 * 24 * 3600 * 1000,
gapUnit: 'value', gapUnit: 'value',
tooltip: { tooltip: {
valueDecimals: 1,
valueSuffix: ' km/h' valueSuffix: ' km/h'
} }
}] }]
@@ -390,12 +401,7 @@ const WeatherDashboard = ({ data, rainData = [], timeRange = '24h', onTimeRangeC
}, },
yAxis: { yAxis: {
...getCommonOptions().yAxis, ...getCommonOptions().yAxis,
title: { title: { text: null }
text: 'Windspeed (km/h)',
style: {
whiteSpace: 'nowrap'
}
}
}, },
series series
} }
@@ -404,6 +410,13 @@ const WeatherDashboard = ({ data, rainData = [], timeRange = '24h', onTimeRangeC
// Windrichtung Chart // Windrichtung Chart
const windDirOptions = useMemo(() => ({ const windDirOptions = useMemo(() => ({
...getCommonOptions(), ...getCommonOptions(),
tooltip: {
formatter: function() {
const dateFormat = timeRange === '24h' ? '%d.%m.%Y %H:%M' : (timeRange === '7d' || timeRange === '30d' ? '%d.%m.%Y - %Hh' : '%d.%m.%Y')
const dateStr = Highcharts.dateFormat(dateFormat, this.x)
return `${dateStr}<br/><span style="color:${this.color}">\u25CF</span> ${this.series.name}: <b>${this.y.toFixed(0)}°</b>`
}
},
plotOptions: { plotOptions: {
scatter: { scatter: {
marker: { marker: {
@@ -420,7 +433,7 @@ const WeatherDashboard = ({ data, rainData = [], timeRange = '24h', onTimeRangeC
}, },
yAxis: { yAxis: {
...getCommonOptions().yAxis, ...getCommonOptions().yAxis,
title: { text: 'Windrichtung' }, title: { text: null },
min: 0, min: 0,
max: 360, max: 360,
tickInterval: 45, tickInterval: 45,
@@ -438,15 +451,12 @@ const WeatherDashboard = ({ data, rainData = [], timeRange = '24h', onTimeRangeC
name: 'Windrichtung', name: 'Windrichtung',
data: sortedData.filter(item => item.wind_dir != null).map(item => [new Date(item.datetime).getTime(), item.wind_dir]), data: sortedData.filter(item => item.wind_dir != null).map(item => [new Date(item.datetime).getTime(), item.wind_dir]),
color: 'rgb(54, 162, 235)', color: 'rgb(54, 162, 235)',
type: 'scatter', type: 'scatter'
tooltip: {
valueSuffix: ' °'
}
}] }]
}), [sortedData]) }), [sortedData, timeRange])
// Aktuellste Werte für Übersicht // Aktuellste Werte für Übersicht (immer aus den 24h-Daten, Fallback auf sortedData)
const current = sortedData[sortedData.length - 1] || {} const current = (sortedCurrentData.length > 0 ? sortedCurrentData[sortedCurrentData.length - 1] : sortedData[sortedData.length - 1]) || {}
// Berechne Min/Max für den gewählten Zeitraum // Berechne Min/Max für den gewählten Zeitraum
const periodStats = useMemo(() => { const periodStats = useMemo(() => {
@@ -512,25 +522,29 @@ const WeatherDashboard = ({ data, rainData = [], timeRange = '24h', onTimeRangeC
className={timeRange === '24h' ? 'active' : ''} className={timeRange === '24h' ? 'active' : ''}
onClick={() => onTimeRangeChange('24h')} onClick={() => onTimeRangeChange('24h')}
> >
24 Stunden <span className="time-range-full">24 Stunden</span>
<span className="time-range-short">24h</span>
</button> </button>
<button <button
className={timeRange === '7d' ? 'active' : ''} className={timeRange === '7d' ? 'active' : ''}
onClick={() => onTimeRangeChange('7d')} onClick={() => onTimeRangeChange('7d')}
> >
7 Tage <span className="time-range-full">7 Tage</span>
<span className="time-range-short">7d</span>
</button> </button>
<button <button
className={timeRange === '30d' ? 'active' : ''} className={timeRange === '30d' ? 'active' : ''}
onClick={() => onTimeRangeChange('30d')} onClick={() => onTimeRangeChange('30d')}
> >
30 Tage <span className="time-range-full">30 Tage</span>
<span className="time-range-short">30d</span>
</button> </button>
<button <button
className={timeRange === '365d' ? 'active' : ''} className={timeRange === '365d' ? 'active' : ''}
onClick={() => onTimeRangeChange('365d')} onClick={() => onTimeRangeChange('365d')}
> >
365 Tage <span className="time-range-full">365 Tage</span>
<span className="time-range-short">365d</span>
</button> </button>
</div> </div>
@@ -541,57 +555,77 @@ const WeatherDashboard = ({ data, rainData = [], timeRange = '24h', onTimeRangeC
{/* Charts Grid */} {/* Charts Grid */}
<div className="charts-grid"> <div className="charts-grid">
<div className="chart-container"> <div className="chart-item">
<h3>🌡 Temperatur{aggregationSuffix} - Aktuell: {current.temperature?.toFixed(1) || '-'}°C</h3> <div className="current-value">Aktuell: {current.temperature?.toFixed(1) || '-'}°C</div>
<div className="chart-wrapper"> <div className="chart-container">
<HighchartsReact highcharts={Highcharts} options={temperatureOptions} /> <h3><span>🌡 Temperatur{aggregationSuffix}</span><span className="unit">[°C]</span></h3>
</div> <div className="chart-wrapper">
<div className="chart-stats"> <HighchartsReact highcharts={Highcharts} options={temperatureOptions} />
Min: {periodStats.minTemp?.toFixed(1) || '-'}°C ({periodStats.minTempTime || '-'}) | Max: {periodStats.maxTemp?.toFixed(1) || '-'}°C ({periodStats.maxTempTime || '-'}) </div>
<div className="chart-stats">
Min: {periodStats.minTemp?.toFixed(1) || '-'}°C ({periodStats.minTempTime || '-'}) | Max: {periodStats.maxTemp?.toFixed(1) || '-'}°C ({periodStats.maxTempTime || '-'})
</div>
</div> </div>
</div> </div>
<div className="chart-container"> <div className="chart-item">
<h3>🌐 Luftdruck{aggregationSuffix} - Aktuell: {current.pressure?.toFixed(1) || '-'} hPa</h3> <div className="current-value">Aktuell: {current.pressure?.toFixed(0) || '-'} hPa</div>
<div className="chart-wrapper"> <div className="chart-container">
<HighchartsReact highcharts={Highcharts} options={pressureOptions} /> <h3><span>🌐 Luftdruck{aggregationSuffix}</span><span className="unit">[hPa]</span></h3>
</div> <div className="chart-wrapper">
<div className="chart-stats"> <HighchartsReact highcharts={Highcharts} options={pressureOptions} />
Min: {periodStats.minPressure?.toFixed(1) || '-'} hPa ({periodStats.minPressureTime || '-'}) | Max: {periodStats.maxPressure?.toFixed(1) || '-'} hPa ({periodStats.maxPressureTime || '-'}) </div>
<div className="chart-stats">
Min: {periodStats.minPressure?.toFixed(0) || '-'} hPa ({periodStats.minPressureTime || '-'}) | Max: {periodStats.maxPressure?.toFixed(0) || '-'} hPa ({periodStats.maxPressureTime || '-'})
</div>
</div> </div>
</div> </div>
<div className="chart-container"> <div className="chart-item">
<h3>💧 Luftfeuchtigkeit{aggregationSuffix} - Aktuell: {current.humidity || '-'}%</h3> <div className="current-value">Aktuell: {current.humidity || '-'}%</div>
<div className="chart-wrapper"> <div className="chart-container">
<HighchartsReact highcharts={Highcharts} options={humidityOptions} /> <h3><span>💧 Luftfeuchtigkeit{aggregationSuffix}</span><span className="unit">[%]</span></h3>
</div> <div className="chart-wrapper">
<div className="chart-stats"> <HighchartsReact highcharts={Highcharts} options={humidityOptions} />
Min: {periodStats.minHumidity || '-'}% ({periodStats.minHumidityTime || '-'}) | Max: {periodStats.maxHumidity || '-'}% ({periodStats.maxHumidityTime || '-'}) </div>
<div className="chart-stats">
Min: {periodStats.minHumidity || '-'}% ({periodStats.minHumidityTime || '-'}) | Max: {periodStats.maxHumidity || '-'}% ({periodStats.maxHumidityTime || '-'})
</div>
</div> </div>
</div> </div>
<div className="chart-container"> <div className="chart-item">
<h3>🌧 Regen{aggregationSuffix} - Aktuell: {current.rain?.toFixed(1) || '-'} mm</h3> <div className="current-value">Aktuell: {current.rain?.toFixed(1) || '-'} mm</div>
<div className="chart-wrapper"> <div className="chart-container">
<HighchartsReact highcharts={Highcharts} options={rainOptions} /> <h3><span>🌧 Regen{aggregationSuffix}</span><span className="unit">[mm]</span></h3>
<div className="chart-wrapper">
<HighchartsReact highcharts={Highcharts} options={rainOptions} />
</div>
<div className="chart-stats"> </div>
</div> </div>
</div> </div>
<div className="chart-container"> <div className="chart-item">
<h3>🧭 Windrichtung{aggregationSuffix} - Aktuell: {current.wind_dir ?? '-'}°</h3> <div className="current-value">Aktuell: {current.wind_dir ?? '-'}°</div>
<div className="chart-wrapper"> <div className="chart-container">
<HighchartsReact highcharts={Highcharts} options={windDirOptions} /> <h3><span>🧭 Windrichtung{aggregationSuffix}</span><span className="unit">[°]</span></h3>
<div className="chart-wrapper">
<HighchartsReact highcharts={Highcharts} options={windDirOptions} />
</div>
<div className="chart-stats"> </div>
</div> </div>
</div> </div>
<div className="chart-container"> <div className="chart-item">
<h3>💨 Windspeed{aggregationSuffix} - Aktuell: {current.wind_speed?.toFixed(1) || '-'} km/h</h3> <div className="current-value">Aktuell: {current.wind_speed?.toFixed(1) || '-'} km/h</div>
<div className="chart-wrapper"> <div className="chart-container">
<HighchartsReact highcharts={Highcharts} options={windSpeedOptions} /> <h3><span>💨 Windspeed{aggregationSuffix}</span><span className="unit">[km/h]</span></h3>
</div> <div className="chart-wrapper">
<div className="chart-stats"> <HighchartsReact highcharts={Highcharts} options={windSpeedOptions} />
Max: {periodStats.maxWindGust?.toFixed(1) || '-'} km/h ({periodStats.maxWindGustTime || '-'}) </div>
<div className="chart-stats">
Max: {periodStats.maxWindGust?.toFixed(1) || '-'} km/h ({periodStats.maxWindGustTime || '-'})
</div>
</div> </div>
</div> </div>
@@ -610,7 +644,7 @@ const WeatherDashboard = ({ data, rainData = [], timeRange = '24h', onTimeRangeC
{' '}{version} {buildDate} {' '}{version} {buildDate}
</div> </div>
</div> </div>
<hr /> <hr className="footer-divider" />
<div className="footer-credits"> <div className="footer-credits">
<div className="footer-left">Daten-Erfassung mit einer Davis VantagePro.</div> <div className="footer-left">Daten-Erfassung mit einer Davis VantagePro.</div>
<div className="footer-right">Grafiken erzeugt mit HighCharts</div> <div className="footer-right">Grafiken erzeugt mit HighCharts</div>