Tabelle hinzugefügt
This commit is contained in:
39
api/main.py
39
api/main.py
@@ -383,8 +383,6 @@ async def get_daily_aggregated_data(
|
|||||||
conn = get_db_connection()
|
conn = get_db_connection()
|
||||||
try:
|
try:
|
||||||
with conn.cursor() as cursor:
|
with conn.cursor() as cursor:
|
||||||
# Bei 365 Tagen: alle verfügbaren Daten zurückgeben
|
|
||||||
if days >= 365:
|
|
||||||
cursor.execute("""
|
cursor.execute("""
|
||||||
SELECT
|
SELECT
|
||||||
date_trunc('day', datetime) as datetime,
|
date_trunc('day', datetime) as datetime,
|
||||||
@@ -392,26 +390,15 @@ async def get_daily_aggregated_data(
|
|||||||
MIN(temperature)::float as min_temperature,
|
MIN(temperature)::float as min_temperature,
|
||||||
MAX(temperature)::float as max_temperature,
|
MAX(temperature)::float as max_temperature,
|
||||||
ROUND(AVG(humidity))::int as humidity,
|
ROUND(AVG(humidity))::int as humidity,
|
||||||
|
MIN(humidity)::int as min_humidity,
|
||||||
|
MAX(humidity)::int as max_humidity,
|
||||||
AVG(pressure)::float as pressure,
|
AVG(pressure)::float as pressure,
|
||||||
|
MIN(pressure)::float as min_pressure,
|
||||||
|
MAX(pressure)::float as max_pressure,
|
||||||
AVG(wind_speed * 1.60934)::float as wind_speed,
|
AVG(wind_speed * 1.60934)::float as wind_speed,
|
||||||
MAX(wind_gust * 1.60934)::float as wind_gust,
|
MAX(wind_gust * 1.60934)::float as wind_gust,
|
||||||
AVG(wind_dir)::float as wind_dir
|
AVG(wind_dir)::float as wind_dir,
|
||||||
FROM weather_data
|
SUM(rain)::float as total_rain
|
||||||
GROUP BY date_trunc('day', datetime)
|
|
||||||
ORDER BY datetime ASC
|
|
||||||
""")
|
|
||||||
else:
|
|
||||||
cursor.execute("""
|
|
||||||
SELECT
|
|
||||||
date_trunc('day', datetime) as datetime,
|
|
||||||
AVG(temperature)::float as temperature,
|
|
||||||
MIN(temperature)::float as min_temperature,
|
|
||||||
MAX(temperature)::float as max_temperature,
|
|
||||||
ROUND(AVG(humidity))::int as humidity,
|
|
||||||
AVG(pressure)::float as pressure,
|
|
||||||
AVG(wind_speed * 1.60934)::float as wind_speed,
|
|
||||||
MAX(wind_gust * 1.60934)::float as wind_gust,
|
|
||||||
AVG(wind_dir)::float as wind_dir
|
|
||||||
FROM weather_data
|
FROM weather_data
|
||||||
WHERE datetime >= NOW() - make_interval(days => %s)
|
WHERE datetime >= NOW() - make_interval(days => %s)
|
||||||
GROUP BY date_trunc('day', datetime)
|
GROUP BY date_trunc('day', datetime)
|
||||||
@@ -439,10 +426,15 @@ async def get_daily_with_minmax_data(
|
|||||||
MIN(temperature)::float as min_temperature,
|
MIN(temperature)::float as min_temperature,
|
||||||
MAX(temperature)::float as max_temperature,
|
MAX(temperature)::float as max_temperature,
|
||||||
ROUND(AVG(humidity))::int as humidity,
|
ROUND(AVG(humidity))::int as humidity,
|
||||||
|
MIN(humidity)::int as min_humidity,
|
||||||
|
MAX(humidity)::int as max_humidity,
|
||||||
AVG(pressure)::float as pressure,
|
AVG(pressure)::float as pressure,
|
||||||
|
MIN(pressure)::float as min_pressure,
|
||||||
|
MAX(pressure)::float as max_pressure,
|
||||||
AVG(wind_speed * 1.60934)::float as wind_speed,
|
AVG(wind_speed * 1.60934)::float as wind_speed,
|
||||||
MAX(wind_gust * 1.60934)::float as wind_gust,
|
MAX(wind_gust * 1.60934)::float as wind_gust,
|
||||||
AVG(wind_dir)::float as wind_dir
|
AVG(wind_dir)::float as wind_dir,
|
||||||
|
SUM(rain)::float as total_rain
|
||||||
FROM weather_data
|
FROM weather_data
|
||||||
WHERE datetime >= NOW() - make_interval(days => %s)
|
WHERE datetime >= NOW() - make_interval(days => %s)
|
||||||
GROUP BY date_trunc('day', datetime)
|
GROUP BY date_trunc('day', datetime)
|
||||||
@@ -566,10 +558,15 @@ async def get_daily_aggregated_range(
|
|||||||
MIN(temperature)::float as min_temperature,
|
MIN(temperature)::float as min_temperature,
|
||||||
MAX(temperature)::float as max_temperature,
|
MAX(temperature)::float as max_temperature,
|
||||||
ROUND(AVG(humidity))::int as humidity,
|
ROUND(AVG(humidity))::int as humidity,
|
||||||
|
MIN(humidity)::int as min_humidity,
|
||||||
|
MAX(humidity)::int as max_humidity,
|
||||||
AVG(pressure)::float as pressure,
|
AVG(pressure)::float as pressure,
|
||||||
|
MIN(pressure)::float as min_pressure,
|
||||||
|
MAX(pressure)::float as max_pressure,
|
||||||
AVG(wind_speed * 1.60934)::float as wind_speed,
|
AVG(wind_speed * 1.60934)::float as wind_speed,
|
||||||
MAX(wind_gust * 1.60934)::float as wind_gust,
|
MAX(wind_gust * 1.60934)::float as wind_gust,
|
||||||
AVG(wind_dir)::float as wind_dir
|
AVG(wind_dir)::float as wind_dir,
|
||||||
|
SUM(rain)::float as total_rain
|
||||||
FROM weather_data
|
FROM weather_data
|
||||||
WHERE datetime BETWEEN %s AND %s
|
WHERE datetime BETWEEN %s AND %s
|
||||||
GROUP BY date_trunc('day', datetime)
|
GROUP BY date_trunc('day', datetime)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ function App() {
|
|||||||
const [error, setError] = useState(null)
|
const [error, setError] = useState(null)
|
||||||
const [lastUpdate, setLastUpdate] = useState(null)
|
const [lastUpdate, setLastUpdate] = useState(null)
|
||||||
const [timeRange, setTimeRange] = useState('24h') // '24h', '7d', '30d', '365d', oder {type: 'custom', start, end, days}
|
const [timeRange, setTimeRange] = useState('24h') // '24h', '7d', '30d', '365d', oder {type: 'custom', start, end, days}
|
||||||
|
const [showTable, setShowTable] = useState(false)
|
||||||
|
|
||||||
// Handler für Zeitbereich-Änderungen
|
// Handler für Zeitbereich-Änderungen
|
||||||
const handleTimeRangeChange = (range, customParams) => {
|
const handleTimeRangeChange = (range, customParams) => {
|
||||||
@@ -186,6 +187,8 @@ function App() {
|
|||||||
rainData={rainData}
|
rainData={rainData}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
onTimeRangeChange={handleTimeRangeChange}
|
onTimeRangeChange={handleTimeRangeChange}
|
||||||
|
showTable={showTable}
|
||||||
|
onToggleTable={() => setShowTable(v => !v)}
|
||||||
/>
|
/>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -378,3 +378,155 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Tabelle & Druck ──────────────────────────────────────── */
|
||||||
|
|
||||||
|
.table-toggle-btn {
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
background: white;
|
||||||
|
border: 2px solid #0066cc;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #0066cc;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-toggle-btn:hover {
|
||||||
|
background: #e6f0fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-toggle-btn.active {
|
||||||
|
background: #0066cc;
|
||||||
|
border-color: #0066cc;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-view {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-bottom: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-print {
|
||||||
|
padding: 0.45rem 1.2rem;
|
||||||
|
background: white;
|
||||||
|
border: 2px solid #555;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-print:hover {
|
||||||
|
background: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-table th,
|
||||||
|
.weather-table td {
|
||||||
|
border-left: 1px solid rgba(255,255,255,0.3);
|
||||||
|
border-right: 1px solid rgba(255,255,255,0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-table td {
|
||||||
|
border-left: 1px solid #ddd;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-table th {
|
||||||
|
background: #0066cc;
|
||||||
|
color: white;
|
||||||
|
padding: 0.6rem 0.5rem;
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-table thead tr:nth-child(2) th {
|
||||||
|
background: #3388dd;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
padding: 0.3rem 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-table thead tr:first-child th:first-child {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-table td {
|
||||||
|
padding: 0.45rem 0.5rem;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-table td:first-child {
|
||||||
|
text-align: left;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-table tbody tr:nth-child(even) {
|
||||||
|
background: #f5f8fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-table tbody tr:hover {
|
||||||
|
background: #e6f0fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Drucken */
|
||||||
|
@media print {
|
||||||
|
.no-print,
|
||||||
|
.time-range-nav,
|
||||||
|
.time-range-label,
|
||||||
|
.dashboard-footer,
|
||||||
|
.modal-overlay {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dashboard {
|
||||||
|
max-width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-table {
|
||||||
|
box-shadow: none;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-table th {
|
||||||
|
background: #333 !important;
|
||||||
|
color: white !important;
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
|
||||||
|
.weather-table tbody tr:nth-child(even) {
|
||||||
|
background: #f0f0f0 !important;
|
||||||
|
-webkit-print-color-adjust: exact;
|
||||||
|
print-color-adjust: exact;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ Highcharts.setOptions({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '24h', onTimeRangeChange }) => {
|
const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '24h', onTimeRangeChange, showTable = false, onToggleTable }) => {
|
||||||
// State für benutzerdefinierten Zeitbereich
|
// State für benutzerdefinierten Zeitbereich
|
||||||
const [showCustomRangeModal, setShowCustomRangeModal] = useState(false)
|
const [showCustomRangeModal, setShowCustomRangeModal] = useState(false)
|
||||||
const [customStartDate, setCustomStartDate] = useState('')
|
const [customStartDate, setCustomStartDate] = useState('')
|
||||||
@@ -147,6 +147,22 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
|
|||||||
}
|
}
|
||||||
}, [timeRange])
|
}, [timeRange])
|
||||||
|
|
||||||
|
// Spezieller Suffix für Regen
|
||||||
|
const rainSuffix = useMemo(() => {
|
||||||
|
if (typeof timeRange === 'object' && timeRange.type === 'custom') {
|
||||||
|
const days = timeRange.days || 1
|
||||||
|
return days >= 7 ? ' (pro Tag)' : ''
|
||||||
|
}
|
||||||
|
switch (timeRange) {
|
||||||
|
case '7d':
|
||||||
|
case '30d':
|
||||||
|
case '365d':
|
||||||
|
return ' (pro Tag)'
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}, [timeRange])
|
||||||
|
|
||||||
// Spezieller Suffix für Temperatur bei 30d/365d
|
// Spezieller Suffix für Temperatur bei 30d/365d
|
||||||
const temperatureSuffix = useMemo(() => {
|
const temperatureSuffix = useMemo(() => {
|
||||||
// Custom range: basierend auf days
|
// Custom range: basierend auf days
|
||||||
@@ -552,6 +568,7 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
|
|||||||
fillColor: 'rgba(54, 162, 235, 0.3)',
|
fillColor: 'rgba(54, 162, 235, 0.3)',
|
||||||
type: 'area',
|
type: 'area',
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
valueDecimals: 1,
|
||||||
valueSuffix: ' mm'
|
valueSuffix: ' mm'
|
||||||
}
|
}
|
||||||
}, {
|
}, {
|
||||||
@@ -573,6 +590,7 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
|
|||||||
color: 'rgb(54, 162, 235)',
|
color: 'rgb(54, 162, 235)',
|
||||||
type: 'column',
|
type: 'column',
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
valueDecimals: 1,
|
||||||
valueSuffix: ' mm'
|
valueSuffix: ' mm'
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
@@ -585,6 +603,22 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
|
|||||||
color: 'rgb(54, 162, 235)',
|
color: 'rgb(54, 162, 235)',
|
||||||
type: 'column',
|
type: 'column',
|
||||||
tooltip: {
|
tooltip: {
|
||||||
|
valueDecimals: 1,
|
||||||
|
valueSuffix: ' mm'
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
} else if (typeof timeRange === 'object' && timeRange.type === 'custom') {
|
||||||
|
// Custom range: tägliche Summen aus sortedData (total_rain ist im daily-aggregated-range enthalten)
|
||||||
|
yAxisTitle = 'Regen (mm pro Tag)'
|
||||||
|
series = [{
|
||||||
|
name: 'Regen',
|
||||||
|
data: sortedData
|
||||||
|
.filter(item => item.total_rain != null && item.total_rain > 0)
|
||||||
|
.map(item => [new Date(item.datetime).getTime(), item.total_rain]),
|
||||||
|
color: 'rgb(54, 162, 235)',
|
||||||
|
type: 'column',
|
||||||
|
tooltip: {
|
||||||
|
valueDecimals: 1,
|
||||||
valueSuffix: ' mm'
|
valueSuffix: ' mm'
|
||||||
}
|
}
|
||||||
}]
|
}]
|
||||||
@@ -793,6 +827,77 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
|
|||||||
}
|
}
|
||||||
}, [sortedData, timeRange])
|
}, [sortedData, timeRange])
|
||||||
|
|
||||||
|
// Regen-Lookup: date-string → total_rain
|
||||||
|
const rainByDate = useMemo(() => {
|
||||||
|
const map = {}
|
||||||
|
rainData.forEach(item => {
|
||||||
|
const key = item.date
|
||||||
|
? item.date.split('T')[0]
|
||||||
|
: item.week_start
|
||||||
|
? item.week_start.split('T')[0]
|
||||||
|
: null
|
||||||
|
if (key) map[key] = item.total_rain
|
||||||
|
})
|
||||||
|
return map
|
||||||
|
}, [rainData])
|
||||||
|
|
||||||
|
// Tabellen-Daten: ein Eintrag pro Tag
|
||||||
|
const tableData = useMemo(() => {
|
||||||
|
const isCustomRange = typeof timeRange === 'object' && timeRange.type === 'custom'
|
||||||
|
const customDays = isCustomRange ? (timeRange.days || 1) : 0
|
||||||
|
const isDailyAggregated =
|
||||||
|
['7d', '30d', '365d'].includes(timeRange) || (isCustomRange && customDays >= 7)
|
||||||
|
|
||||||
|
if (isDailyAggregated) {
|
||||||
|
return sortedData.map(item => {
|
||||||
|
const dateKey = item.datetime.split('T')[0]
|
||||||
|
return {
|
||||||
|
date: format(new Date(item.datetime), 'dd.MM.yyyy', { locale: de }),
|
||||||
|
tempMin: item.min_temperature ?? null,
|
||||||
|
tempMax: item.max_temperature ?? null,
|
||||||
|
humMin: item.min_humidity ?? null,
|
||||||
|
humMax: item.max_humidity ?? null,
|
||||||
|
pressMin: item.min_pressure ?? null,
|
||||||
|
pressMax: item.max_pressure ?? null,
|
||||||
|
rain: item.total_rain ?? rainByDate[dateKey] ?? null,
|
||||||
|
windMax: item.wind_gust ?? null,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Stundenwerte → pro Tag aggregieren
|
||||||
|
const byDay = {}
|
||||||
|
sortedData.forEach(item => {
|
||||||
|
const d = new Date(item.datetime)
|
||||||
|
const dateKey = format(d, 'yyyy-MM-dd')
|
||||||
|
const dateLabel = format(d, 'dd.MM.yyyy', { locale: de })
|
||||||
|
if (!byDay[dateKey]) {
|
||||||
|
byDay[dateKey] = { date: dateLabel, temps: [], hums: [], pressures: [], rains: [], windGusts: [] }
|
||||||
|
}
|
||||||
|
if (item.temperature != null) byDay[dateKey].temps.push(item.temperature)
|
||||||
|
if (item.humidity != null) byDay[dateKey].hums.push(item.humidity)
|
||||||
|
if (item.pressure != null) byDay[dateKey].pressures.push(item.pressure)
|
||||||
|
if (item.rain != null) byDay[dateKey].rains.push(item.rain)
|
||||||
|
if (item.wind_gust != null) byDay[dateKey].windGusts.push(item.wind_gust)
|
||||||
|
})
|
||||||
|
const startKey = isCustomRange ? timeRange.start.split('T')[0] : null
|
||||||
|
const endKey = isCustomRange ? timeRange.end.split('T')[0] : null
|
||||||
|
return Object.entries(byDay)
|
||||||
|
.sort(([a], [b]) => a.localeCompare(b))
|
||||||
|
.filter(([k]) => !startKey || (k >= startKey && k <= endKey))
|
||||||
|
.map(([, d]) => ({
|
||||||
|
date: d.date,
|
||||||
|
tempMin: d.temps.length ? Math.min(...d.temps) : null,
|
||||||
|
tempMax: d.temps.length ? Math.max(...d.temps) : null,
|
||||||
|
humMin: d.hums.length ? Math.round(Math.min(...d.hums)) : null,
|
||||||
|
humMax: d.hums.length ? Math.round(Math.max(...d.hums)) : null,
|
||||||
|
pressMin: d.pressures.length ? Math.min(...d.pressures) : null,
|
||||||
|
pressMax: d.pressures.length ? Math.max(...d.pressures) : null,
|
||||||
|
rain: d.rains.length ? Math.max(...d.rains) : null,
|
||||||
|
windMax: d.windGusts.length ? Math.max(...d.windGusts) : null,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}, [sortedData, rainByDate, timeRange])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dashboard">
|
<div className="dashboard">
|
||||||
{/* Navigation für Zeitraum-Auswahl */}
|
{/* Navigation für Zeitraum-Auswahl */}
|
||||||
@@ -832,6 +937,12 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
|
|||||||
<span className="time-range-full">Bereich</span>
|
<span className="time-range-full">Bereich</span>
|
||||||
<span className="time-range-short">Bereich</span>
|
<span className="time-range-short">Bereich</span>
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
className={`table-toggle-btn${showTable ? ' active' : ''}`}
|
||||||
|
onClick={onToggleTable}
|
||||||
|
>
|
||||||
|
<span>{showTable ? 'Grafik' : 'Tabelle'}</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Zeitraum-Beschreibung */}
|
{/* Zeitraum-Beschreibung */}
|
||||||
@@ -839,7 +950,49 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
|
|||||||
{timeRangeLabel}
|
{timeRangeLabel}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Charts Grid */}
|
{/* Charts Grid / Tabellenansicht */}
|
||||||
|
{showTable ? (
|
||||||
|
<div className="table-view">
|
||||||
|
<div className="table-actions no-print">
|
||||||
|
<button className="btn-print" onClick={() => window.print()}>🖨️ Drucken</button>
|
||||||
|
</div>
|
||||||
|
<table className="weather-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th rowSpan={2}>Datum</th>
|
||||||
|
<th colSpan={2}>Temperatur<br/>°C</th>
|
||||||
|
<th colSpan={2}>Feuchte<br/>%</th>
|
||||||
|
<th colSpan={2}>Luftdruck<br/>hPa</th>
|
||||||
|
<th rowSpan={2}>Regen<br/>mm</th>
|
||||||
|
<th rowSpan={2}>Wind-V<br/>max km/h</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>min</th>
|
||||||
|
<th>max</th>
|
||||||
|
<th>min</th>
|
||||||
|
<th>max</th>
|
||||||
|
<th>min</th>
|
||||||
|
<th>max</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{tableData.map((row, i) => (
|
||||||
|
<tr key={i}>
|
||||||
|
<td>{row.date}</td>
|
||||||
|
<td>{row.tempMin != null ? row.tempMin.toFixed(1) : '-'}</td>
|
||||||
|
<td>{row.tempMax != null ? row.tempMax.toFixed(1) : '-'}</td>
|
||||||
|
<td>{row.humMin != null ? row.humMin : '-'}</td>
|
||||||
|
<td>{row.humMax != null ? row.humMax : '-'}</td>
|
||||||
|
<td>{row.pressMin != null ? row.pressMin.toFixed(0) : '-'}</td>
|
||||||
|
<td>{row.pressMax != null ? row.pressMax.toFixed(0) : '-'}</td>
|
||||||
|
<td>{row.rain != null ? row.rain.toFixed(1) : '0'}</td>
|
||||||
|
<td>{row.windMax != null ? row.windMax.toFixed(1) : '-'}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
<div className="charts-grid">
|
<div className="charts-grid">
|
||||||
<div className="chart-item">
|
<div className="chart-item">
|
||||||
<div className="current-value">Aktuell: {current.temperature?.toFixed(1) || '-'}°C</div>
|
<div className="current-value">Aktuell: {current.temperature?.toFixed(1) || '-'}°C</div>
|
||||||
@@ -883,7 +1036,7 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
|
|||||||
<div className="chart-item">
|
<div className="chart-item">
|
||||||
<div className="current-value">Aktuell: {current.rain?.toFixed(1) || '-'} mm</div>
|
<div className="current-value">Aktuell: {current.rain?.toFixed(1) || '-'} mm</div>
|
||||||
<div className="chart-container">
|
<div className="chart-container">
|
||||||
<h3><span>🌧️ Regen{aggregationSuffix}</span><span className="unit">[mm]</span></h3>
|
<h3><span>🌧️ Regen{rainSuffix}</span><span className="unit">[mm]</span></h3>
|
||||||
<div className="chart-wrapper">
|
<div className="chart-wrapper">
|
||||||
<HighchartsReact highcharts={Highcharts} options={rainOptions} />
|
<HighchartsReact highcharts={Highcharts} options={rainOptions} />
|
||||||
</div>
|
</div>
|
||||||
@@ -916,6 +1069,7 @@ const WeatherDashboard = ({ data, currentData = [], rainData = [], timeRange = '
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
)} {/* end showTable ternary */}
|
||||||
|
|
||||||
{/* Modal für benutzerdefinierten Zeitbereich */}
|
{/* Modal für benutzerdefinierten Zeitbereich */}
|
||||||
{showCustomRangeModal && (
|
{showCustomRangeModal && (
|
||||||
|
|||||||
Reference in New Issue
Block a user